Skip to content

Instantly share code, notes, and snippets.

@aziis98
Created August 9, 2024 22:36
Show Gist options
  • Save aziis98/7e22eeba4604de3833f7b0f8443e4828 to your computer and use it in GitHub Desktop.
Save aziis98/7e22eeba4604de3833f7b0f8443e4828 to your computer and use it in GitHub Desktop.
A small Golang parser with a backtracking helper
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