From b1fef85d602a08a6b6530ecd8183cf3137931767 Mon Sep 17 00:00:00 2001 From: "M.V. Hutz" Date: Wed, 11 Feb 2026 20:04:07 -0500 Subject: [PATCH] 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 --- pkg/iterator/iterator.go | 24 +++------ pkg/lambda/parse.go | 77 +++++++++++++-------------- pkg/saccharine/parse.go | 110 ++++++++++++++++++--------------------- pkg/token/parse.go | 21 ++++---- pkg/token/scan.go | 18 +++---- 5 files changed, 114 insertions(+), 136 deletions(-) diff --git a/pkg/iterator/iterator.go b/pkg/iterator/iterator.go index 6fd1241..52cb373 100644 --- a/pkg/iterator/iterator.go +++ b/pkg/iterator/iterator.go @@ -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 diff --git a/pkg/lambda/parse.go b/pkg/lambda/parse.go index b5859fa..2883d10 100644 --- a/pkg/lambda/parse.go +++ b/pkg/lambda/parse.go @@ -18,54 +18,49 @@ 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 { - return nil, fmt.Errorf("no param (col %d): %w", i.Index(), err) - } else if _, err := token.ParseRawToken(i, tokenDot); err != nil { - return nil, fmt.Errorf("no dot (col %d): %w", i.Index(), err) - } else if body, err := parseExpression(i); err != nil { - return nil, err - } else { - return Abstraction{Parameter: param.Value, Body: body}, nil - } - }) + 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 { + return nil, fmt.Errorf("no param (col %d): %w", i.Index(), err) + } else if _, err := token.ParseRawToken(i, tokenDot); err != nil { + return nil, fmt.Errorf("no dot (col %d): %w", i.Index(), err) + } else if body, err := parseExpression(i); err != nil { + return nil, err + } 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 { - return nil, fmt.Errorf("expected function expression: %w", err) - } else if argument, err := parseExpression(i); err != nil { - return nil, fmt.Errorf("expected argument expression: %w", err) - } else if _, err := token.ParseRawToken(i, tokenCloseParen); err != nil { - return nil, fmt.Errorf("no closing paren (col %d): %w", i.Index(), err) - } else { - return Application{Abstraction: abstraction, Argument: argument}, nil - } - }) + 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 { + return nil, fmt.Errorf("expected function expression: %w", err) + } else if argument, err := parseExpression(i); err != nil { + return nil, fmt.Errorf("expected argument expression: %w", err) + } else if _, err := token.ParseRawToken(i, tokenCloseParen); err != nil { + return nil, fmt.Errorf("no closing paren (col %d): %w", i.Index(), err) + } 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 { - case tokenOpenParen: - return parseApplication(i) - case tokenSlash: - return parseAbstraction(i) - case tokenAtom: - return parseVariable(i) - default: - return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column) - } - }) + switch peek.Type { + case tokenOpenParen: + return parseApplication(i) + case tokenSlash: + return parseAbstraction(i) + case tokenAtom: + return parseVariable(i) + 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. diff --git a/pkg/saccharine/parse.go b/pkg/saccharine/parse.go index 8620ba9..f2546b6 100644 --- a/pkg/saccharine/parse.go +++ b/pkg/saccharine/parse.go @@ -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) - } + if ignoreSoftBreaks { + passSoftBreaks(i) + } - return token.ParseRawToken(i, expected) - }) + return token.ParseRawToken(i, expected) } func parseString(i *tokenIterator) (string, error) { @@ -47,33 +45,29 @@ 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 { - return nil, err - } else if _, err = parseToken(i, TokenDot, true); err != nil { - return nil, fmt.Errorf("no function dot (col %d): %w", i.MustGet().Column, err) - } else if body, err := parseExpression(i); err != nil { - return nil, err - } else { - return &Abstraction{Parameters: parameters, Body: body}, nil - } - }) + 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 { + return nil, err + } else if _, err = parseToken(i, TokenDot, true); err != nil { + return nil, fmt.Errorf("no function dot (col %d): %w", i.MustGet().Column, err) + } else if body, err := parseExpression(i); err != nil { + return nil, err + } 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 { - return nil, err - } else if _, err := parseToken(i, TokenCloseParen, true); err != nil { - return nil, fmt.Errorf("no closing brackets (col %d): %w", i.MustGet().Column, err) - } else { - return &Application{Abstraction: expressions[0], Arguments: expressions[1:]}, nil - } - }) + 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 { + return nil, err + } else if _, err := parseToken(i, TokenCloseParen, true); err != nil { + return nil, fmt.Errorf("no closing brackets (col %d): %w", i.MustGet().Column, err) + } else { + return &Application{Abstraction: expressions[0], Arguments: expressions[1:]}, nil + } } func parseAtom(i *tokenIterator) (*Atom, error) { @@ -133,36 +127,36 @@ 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) + passSoftBreaks(i) - switch peek := i.MustGet(); peek.Type { - case TokenOpenParen: - return parseApplication(i) - case TokenSlash: - return parseAbstraction(i) - case TokenAtom: - return parseAtom(i) - case TokenOpenBrace: - return parseClause(i, true) - default: - return nil, fmt.Errorf("expected expression, got '%v' (col %d)", peek.Value, peek.Column) - } - }) + if i.Done() { + return nil, fmt.Errorf("unexpected end of input") + } + + switch peek := i.MustGet(); peek.Type { + case TokenOpenParen: + return parseApplication(i) + case TokenSlash: + return parseAbstraction(i) + case TokenAtom: + return parseAtom(i) + case TokenOpenBrace: + return parseClause(i, true) + 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 { - return nil, err - } else if body, err := parseExpression(i); err != nil { - return nil, err - } else { - return &LetStatement{Name: parameters[0], Parameters: parameters[1:], Body: body}, nil - } - }) + if parameters, err := token.ParseList(i, parseString, 1); err != nil { + return nil, err + } else if _, err := parseToken(i, TokenAssign, true); err != nil { + return nil, err + } else if body, err := parseExpression(i); err != nil { + return nil, err + } 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) diff --git a/pkg/token/parse.go b/pkg/token/parse.go index f5f0386..5d39207 100644 --- a/pkg/token/parse.go +++ b/pkg/token/parse.go @@ -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 { - 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 - } - }) + tok, err := i.Get() + if err != nil { + return nil, err + } + 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 diff --git a/pkg/token/scan.go b/pkg/token/scan.go index 2457eb8..bf5fa63 100644 --- a/pkg/token/scan.go +++ b/pkg/token/scan.go @@ -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 { - return r, err - } else if !expected(r) { - return r, fmt.Errorf("got unexpected rune %v'", r) - } else { - return r, nil - } - }) + r, err := i.Get() + if err != nil { + return r, err + } + 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