Created
August 9, 2024 22:36
-
-
Save aziis98/7e22eeba4604de3833f7b0f8443e4828 to your computer and use it in GitHub Desktop.
A small Golang parser with a backtracking helper
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package lang | |
import ( | |
"fmt" | |
"regexp" | |
"strings" | |
"unicode" | |
) | |
type parser struct { | |
source string | |
pos int | |
backtrackPositions []int | |
} | |
func newParser(source string) *parser { | |
return &parser{ | |
source: source, | |
pos: 0, | |
backtrackPositions: []int{}, | |
} | |
} | |
func (p *parser) hasMore() bool { | |
return p.pos < len(p.source) | |
} | |
func (p *parser) expect(prefix string) error { | |
if !p.hasMore() { | |
return fmt.Errorf("expected %q", prefix) | |
} | |
if !strings.HasPrefix(p.source[p.pos:], prefix) { | |
return fmt.Errorf("expected %q", prefix) | |
} | |
p.pos += len(prefix) | |
return nil | |
} | |
func (p *parser) expectRegexp(pattern string) ([]string, error) { | |
if !p.hasMore() { | |
return nil, fmt.Errorf("expected %q", pattern) | |
} | |
re := regexp.MustCompile("^" + pattern) | |
matches := re.FindStringSubmatch(p.source[p.pos:]) | |
if matches == nil { | |
return nil, fmt.Errorf("expected %q", pattern) | |
} | |
p.pos += len(matches[0]) | |
return matches, nil | |
} | |
// skipAllSpaces skips all whitespace characters, even newlines. | |
func (p *parser) skipAllSpaces() { | |
for p.hasMore() && unicode.IsSpace(rune(p.source[p.pos])) { | |
p.pos++ | |
} | |
} | |
// skipInlineSpaces skips only spaces and tabs. | |
func (p *parser) skipInlineSpaces() { | |
// before := p.pos | |
for p.hasMore() && (p.source[p.pos] == ' ' || p.source[p.pos] == '\t') { | |
p.pos++ | |
} | |
// if p.pos == before { | |
// log.Printf("expected inline spaces here %q", p.source[p.pos:]) | |
// } | |
} | |
func (p *parser) push() { | |
p.backtrackPositions = append(p.backtrackPositions, p.pos) | |
} | |
func (p *parser) accept() { | |
if len(p.backtrackPositions) == 0 { | |
panic("no position to pop") | |
} | |
p.backtrackPositions = p.backtrackPositions[:len(p.backtrackPositions)-1] | |
} | |
func (p *parser) backtrack() { | |
if len(p.backtrackPositions) == 0 { | |
panic("no position to backtrack") | |
} | |
p.pos = p.backtrackPositions[len(p.backtrackPositions)-1] | |
p.backtrackPositions = p.backtrackPositions[:len(p.backtrackPositions)-1] | |
} | |
type backtrackingParser interface { | |
push() | |
accept() | |
backtrack() | |
} | |
func backtrackHelper[T any](p backtrackingParser, f func() (T, error)) (T, error) { | |
p.push() | |
out, err := f() | |
if err != nil { | |
p.backtrack() | |
} else { | |
p.accept() | |
} | |
return out, err | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment