refactor: simplify iterator.Try and remove unnecessary backtracking #47

Merged
mvhutz merged 1 commits from refactor/simplify-iterator-try into main 2026-02-12 01:04:27 +00:00
5 changed files with 114 additions and 136 deletions
Showing only changes of commit b1fef85d60 - Show all commits

View File

@@ -19,18 +19,6 @@ func (i Iterator[T]) Index() int {
return i.index
}
// Copy returns a identical clone of the iterator. The underlying data structure
// is not cloned.
func (i Iterator[T]) Copy() *Iterator[T] {
return &Iterator[T]{items: i.items, index: i.index}
}
// Sync returns the iterator to the position of another. It is assumed that the
// iterators both operate on the same set of data.
func (i *Iterator[T]) Sync(o *Iterator[T]) {
i.index = o.index
}
// Get returns the datum at the current position of the iterator.
func (i Iterator[T]) Get() (T, error) {
var null T
@@ -93,14 +81,14 @@ func (i *Iterator[T]) While(fn func(T) bool) {
}
// Try attempts to perform an operation using the iterator. If the operation
// succeeds, the iterator is updated. If the operation fails, the iterator is
// rolled back, and an error is returned.
// succeeds, the iterator keeps its new position. If the operation fails, the
// iterator is rolled back, and an error is returned.
func Try[T any, U any](i *Iterator[T], fn func(i *Iterator[T]) (U, error)) (U, error) {
i2 := i.Copy()
saved := i.index
out, err := fn(i2)
if err == nil {
i.Sync(i2)
out, err := fn(i)
if err != nil {
i.index = saved
}
return out, err

View File

@@ -18,7 +18,6 @@ func parseVariable(i *tokenIterator) (Expression, error) {
}
func parseAbstraction(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) {
if _, err := token.ParseRawToken(i, tokenSlash); err != nil {
return nil, fmt.Errorf("no backslash (col %d): %w", i.Index(), err)
} else if param, err := token.ParseRawToken(i, tokenAtom); err != nil {
@@ -30,11 +29,9 @@ func parseAbstraction(i *tokenIterator) (Expression, error) {
} else {
return Abstraction{Parameter: param.Value, Body: body}, nil
}
})
}
func parseApplication(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) {
if _, err := token.ParseRawToken(i, tokenOpenParen); err != nil {
return nil, fmt.Errorf("no opening paren (col %d): %w", i.Index(), err)
} else if abstraction, err := parseExpression(i); err != nil {
@@ -46,16 +43,15 @@ func parseApplication(i *tokenIterator) (Expression, error) {
} else {
return Application{Abstraction: abstraction, Argument: argument}, nil
}
})
}
func parseExpression(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) {
if i.Done() {
return nil, fmt.Errorf("unexpected end of input")
peek, err := i.Get()
if err != nil {
return nil, err
}
switch peek := i.MustGet(); peek.Type {
switch peek.Type {
case tokenOpenParen:
return parseApplication(i)
case tokenSlash:
@@ -65,7 +61,6 @@ func parseExpression(i *tokenIterator) (Expression, error) {
default:
return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column)
}
})
}
// parse converts a token slice into a lambda calculus expression.

View File

@@ -19,13 +19,11 @@ func passSoftBreaks(i *tokenIterator) {
}
func parseToken(i *tokenIterator, expected TokenType, ignoreSoftBreaks bool) (*Token, error) {
return iterator.Try(i, func(i *tokenIterator) (*Token, error) {
if ignoreSoftBreaks {
passSoftBreaks(i)
}
return token.ParseRawToken(i, expected)
})
}
func parseString(i *tokenIterator) (string, error) {
@@ -47,7 +45,6 @@ func parseBreak(i *tokenIterator) (*Token, error) {
}
func parseAbstraction(i *tokenIterator) (*Abstraction, error) {
return iterator.Try(i, func(i *tokenIterator) (*Abstraction, error) {
if _, err := parseToken(i, TokenSlash, true); err != nil {
return nil, fmt.Errorf("no function slash (col %d): %w", i.MustGet().Column, err)
} else if parameters, err := token.ParseList(i, parseString, 0); err != nil {
@@ -59,11 +56,9 @@ func parseAbstraction(i *tokenIterator) (*Abstraction, error) {
} else {
return &Abstraction{Parameters: parameters, Body: body}, nil
}
})
}
func parseApplication(i *tokenIterator) (*Application, error) {
return iterator.Try(i, func(i *tokenIterator) (*Application, error) {
if _, err := parseToken(i, TokenOpenParen, true); err != nil {
return nil, fmt.Errorf("no openning brackets (col %d): %w", i.MustGet().Column, err)
} else if expressions, err := token.ParseList(i, parseExpression, 1); err != nil {
@@ -73,7 +68,6 @@ func parseApplication(i *tokenIterator) (*Application, error) {
} else {
return &Application{Abstraction: expressions[0], Arguments: expressions[1:]}, nil
}
})
}
func parseAtom(i *tokenIterator) (*Atom, error) {
@@ -133,9 +127,12 @@ func parseClause(i *tokenIterator, braces bool) (*Clause, error) {
}
func parseExpression(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) {
passSoftBreaks(i)
if i.Done() {
return nil, fmt.Errorf("unexpected end of input")
}
switch peek := i.MustGet(); peek.Type {
case TokenOpenParen:
return parseApplication(i)
@@ -148,11 +145,9 @@ func parseExpression(i *tokenIterator) (Expression, error) {
default:
return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column)
}
})
}
func parseLet(i *tokenIterator) (*LetStatement, error) {
return iterator.Try(i, func(i *tokenIterator) (*LetStatement, error) {
if parameters, err := token.ParseList(i, parseString, 1); err != nil {
return nil, err
} else if _, err := parseToken(i, TokenAssign, true); err != nil {
@@ -162,7 +157,6 @@ func parseLet(i *tokenIterator) (*LetStatement, error) {
} else {
return &LetStatement{Name: parameters[0], Parameters: parameters[1:], Body: body}, nil
}
})
}
func parseDeclare(i *tokenIterator) (*DeclareStatement, error) {
@@ -174,9 +168,9 @@ func parseDeclare(i *tokenIterator) (*DeclareStatement, error) {
}
func parseStatement(i *tokenIterator) (Statement, error) {
if let, letErr := parseLet(i); letErr == nil {
if let, letErr := iterator.Try(i, parseLet); letErr == nil {
return let, nil
} else if declare, declErr := parseDeclare(i); declErr == nil {
} else if declare, declErr := iterator.Try(i, parseDeclare); declErr == nil {
return declare, nil
} else {
return nil, errors.Join(letErr, declErr)

View File

@@ -8,17 +8,18 @@ import (
// ParseRawToken consumes the next token from the iterator if its type matches
// the expected type.
// Uses [iterator.Try] for automatic backtracking on failure.
// Returns an error if the iterator is exhausted or the token type does not
// match.
func ParseRawToken[T Type](i *iterator.Iterator[Token[T]], expected T) (*Token[T], error) {
return iterator.Try(i, func(i *iterator.Iterator[Token[T]]) (*Token[T], error) {
if tok, err := i.Next(); err != nil {
tok, err := i.Get()
if err != nil {
return nil, err
} else if tok.Type != expected {
return nil, fmt.Errorf("expected token %v, got %v'", expected.Name(), tok.Value)
} else {
return &tok, nil
}
})
if tok.Type != expected {
return nil, fmt.Errorf("expected token %v, got %v'", expected.Name(), tok.Value)
}
i.Forward()
return &tok, nil
}
// ParseList repeatedly applies a parse function, collecting results into a

View File

@@ -17,15 +17,15 @@ func IsVariable(r rune) bool {
// predicate.
// Returns an error if the iterator is exhausted or the rune does not match.
func ScanRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, error) {
return iterator.Try(i, func(i *iterator.Iterator[rune]) (rune, error) {
if r, err := i.Next(); err != nil {
r, err := i.Get()
if err != nil {
return r, err
} else if !expected(r) {
return r, fmt.Errorf("got unexpected rune %v'", r)
} else {
return r, nil
}
})
if !expected(r) {
return r, fmt.Errorf("got unexpected rune %v'", r)
}
i.Forward()
return r, nil
}
// ScanCharacter consumes the next rune from the iterator if it matches the