refactor: simplify iterator.Try and remove unnecessary backtracking

Simplify Try to save/restore the index directly instead of
copying and syncing the entire iterator. Remove the now-unused
Copy and Sync methods.

Rewrite ScanRune and ParseRawToken as peek-then-advance so they
no longer need Try at all. Remove redundant Try wrappers from
parse functions that are already disambiguated by their callers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 20:04:07 -05:00
parent da3da70855
commit b1fef85d60
5 changed files with 114 additions and 136 deletions

View File

@@ -19,18 +19,6 @@ func (i Iterator[T]) Index() int {
return i.index 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. // Get returns the datum at the current position of the iterator.
func (i Iterator[T]) Get() (T, error) { func (i Iterator[T]) Get() (T, error) {
var null T 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 // Try attempts to perform an operation using the iterator. If the operation
// succeeds, the iterator is updated. If the operation fails, the iterator is // succeeds, the iterator keeps its new position. If the operation fails, the
// rolled back, and an error is returned. // 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) { 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) out, err := fn(i)
if err == nil { if err != nil {
i.Sync(i2) i.index = saved
} }
return out, err return out, err

View File

@@ -18,7 +18,6 @@ func parseVariable(i *tokenIterator) (Expression, error) {
} }
func parseAbstraction(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 { if _, err := token.ParseRawToken(i, tokenSlash); err != nil {
return nil, fmt.Errorf("no backslash (col %d): %w", i.Index(), err) return nil, fmt.Errorf("no backslash (col %d): %w", i.Index(), err)
} else if param, err := token.ParseRawToken(i, tokenAtom); err != nil { } else if param, err := token.ParseRawToken(i, tokenAtom); err != nil {
@@ -30,11 +29,9 @@ func parseAbstraction(i *tokenIterator) (Expression, error) {
} else { } else {
return Abstraction{Parameter: param.Value, Body: body}, nil return Abstraction{Parameter: param.Value, Body: body}, nil
} }
})
} }
func parseApplication(i *tokenIterator) (Expression, error) { func parseApplication(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) {
if _, err := token.ParseRawToken(i, tokenOpenParen); err != nil { if _, err := token.ParseRawToken(i, tokenOpenParen); err != nil {
return nil, fmt.Errorf("no opening paren (col %d): %w", i.Index(), err) return nil, fmt.Errorf("no opening paren (col %d): %w", i.Index(), err)
} else if abstraction, err := parseExpression(i); err != nil { } else if abstraction, err := parseExpression(i); err != nil {
@@ -46,16 +43,15 @@ func parseApplication(i *tokenIterator) (Expression, error) {
} else { } else {
return Application{Abstraction: abstraction, Argument: argument}, nil return Application{Abstraction: abstraction, Argument: argument}, nil
} }
})
} }
func parseExpression(i *tokenIterator) (Expression, error) { func parseExpression(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) { peek, err := i.Get()
if i.Done() { if err != nil {
return nil, fmt.Errorf("unexpected end of input") return nil, err
} }
switch peek := i.MustGet(); peek.Type { switch peek.Type {
case tokenOpenParen: case tokenOpenParen:
return parseApplication(i) return parseApplication(i)
case tokenSlash: case tokenSlash:
@@ -65,7 +61,6 @@ func parseExpression(i *tokenIterator) (Expression, error) {
default: default:
return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column) return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column)
} }
})
} }
// parse converts a token slice into a lambda calculus expression. // 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) { func parseToken(i *tokenIterator, expected TokenType, ignoreSoftBreaks bool) (*Token, error) {
return iterator.Try(i, func(i *tokenIterator) (*Token, error) {
if ignoreSoftBreaks { if ignoreSoftBreaks {
passSoftBreaks(i) passSoftBreaks(i)
} }
return token.ParseRawToken(i, expected) return token.ParseRawToken(i, expected)
})
} }
func parseString(i *tokenIterator) (string, error) { func parseString(i *tokenIterator) (string, error) {
@@ -47,7 +45,6 @@ func parseBreak(i *tokenIterator) (*Token, error) {
} }
func parseAbstraction(i *tokenIterator) (*Abstraction, error) { func parseAbstraction(i *tokenIterator) (*Abstraction, error) {
return iterator.Try(i, func(i *tokenIterator) (*Abstraction, error) {
if _, err := parseToken(i, TokenSlash, true); err != nil { if _, err := parseToken(i, TokenSlash, true); err != nil {
return nil, fmt.Errorf("no function slash (col %d): %w", i.MustGet().Column, err) 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 { } else if parameters, err := token.ParseList(i, parseString, 0); err != nil {
@@ -59,11 +56,9 @@ func parseAbstraction(i *tokenIterator) (*Abstraction, error) {
} else { } else {
return &Abstraction{Parameters: parameters, Body: body}, nil return &Abstraction{Parameters: parameters, Body: body}, nil
} }
})
} }
func parseApplication(i *tokenIterator) (*Application, error) { func parseApplication(i *tokenIterator) (*Application, error) {
return iterator.Try(i, func(i *tokenIterator) (*Application, error) {
if _, err := parseToken(i, TokenOpenParen, true); err != nil { if _, err := parseToken(i, TokenOpenParen, true); err != nil {
return nil, fmt.Errorf("no openning brackets (col %d): %w", i.MustGet().Column, err) 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 { } else if expressions, err := token.ParseList(i, parseExpression, 1); err != nil {
@@ -73,7 +68,6 @@ func parseApplication(i *tokenIterator) (*Application, error) {
} else { } else {
return &Application{Abstraction: expressions[0], Arguments: expressions[1:]}, nil return &Application{Abstraction: expressions[0], Arguments: expressions[1:]}, nil
} }
})
} }
func parseAtom(i *tokenIterator) (*Atom, error) { func parseAtom(i *tokenIterator) (*Atom, error) {
@@ -133,9 +127,12 @@ func parseClause(i *tokenIterator, braces bool) (*Clause, error) {
} }
func parseExpression(i *tokenIterator) (Expression, error) { func parseExpression(i *tokenIterator) (Expression, error) {
return iterator.Try(i, func(i *tokenIterator) (Expression, error) {
passSoftBreaks(i) passSoftBreaks(i)
if i.Done() {
return nil, fmt.Errorf("unexpected end of input")
}
switch peek := i.MustGet(); peek.Type { switch peek := i.MustGet(); peek.Type {
case TokenOpenParen: case TokenOpenParen:
return parseApplication(i) return parseApplication(i)
@@ -148,11 +145,9 @@ func parseExpression(i *tokenIterator) (Expression, error) {
default: default:
return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column) return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column)
} }
})
} }
func parseLet(i *tokenIterator) (*LetStatement, error) { 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 { if parameters, err := token.ParseList(i, parseString, 1); err != nil {
return nil, err return nil, err
} else if _, err := parseToken(i, TokenAssign, true); err != nil { } else if _, err := parseToken(i, TokenAssign, true); err != nil {
@@ -162,7 +157,6 @@ func parseLet(i *tokenIterator) (*LetStatement, error) {
} else { } else {
return &LetStatement{Name: parameters[0], Parameters: parameters[1:], Body: body}, nil return &LetStatement{Name: parameters[0], Parameters: parameters[1:], Body: body}, nil
} }
})
} }
func parseDeclare(i *tokenIterator) (*DeclareStatement, error) { func parseDeclare(i *tokenIterator) (*DeclareStatement, error) {
@@ -174,9 +168,9 @@ func parseDeclare(i *tokenIterator) (*DeclareStatement, error) {
} }
func parseStatement(i *tokenIterator) (Statement, 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 return let, nil
} else if declare, declErr := parseDeclare(i); declErr == nil { } else if declare, declErr := iterator.Try(i, parseDeclare); declErr == nil {
return declare, nil return declare, nil
} else { } else {
return nil, errors.Join(letErr, declErr) 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 // ParseRawToken consumes the next token from the iterator if its type matches
// the expected type. // 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) { 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) { tok, err := i.Get()
if tok, err := i.Next(); err != nil { if err != nil {
return nil, err 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 // ParseList repeatedly applies a parse function, collecting results into a

View File

@@ -17,15 +17,15 @@ func IsVariable(r rune) bool {
// predicate. // predicate.
// Returns an error if the iterator is exhausted or the rune does not match. // 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) { func ScanRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, error) {
return iterator.Try(i, func(i *iterator.Iterator[rune]) (rune, error) { r, err := i.Get()
if r, err := i.Next(); err != nil { if err != nil {
return r, err 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 // ScanCharacter consumes the next rune from the iterator if it matches the