feat: better recursive descent
This commit is contained in:
@@ -162,7 +162,7 @@ linters:
|
|||||||
- name: increment-decrement
|
- name: increment-decrement
|
||||||
|
|
||||||
# highlights redundant else-blocks that can be eliminated from the code
|
# highlights redundant else-blocks that can be eliminated from the code
|
||||||
- name: indent-error-flow
|
# - name: indent-error-flow
|
||||||
|
|
||||||
# This rule suggests a shorter way of writing ranges that do not use the second value.
|
# This rule suggests a shorter way of writing ranges that do not use the second value.
|
||||||
- name: range
|
- name: range
|
||||||
@@ -174,7 +174,7 @@ linters:
|
|||||||
- name: redefines-builtin-id
|
- name: redefines-builtin-id
|
||||||
|
|
||||||
# redundant else-blocks that can be eliminated from the code.
|
# redundant else-blocks that can be eliminated from the code.
|
||||||
- name: superfluous-else
|
# - name: superfluous-else
|
||||||
|
|
||||||
# prevent confusing name for variables when using `time` package
|
# prevent confusing name for variables when using `time` package
|
||||||
- name: time-naming
|
- name: time-naming
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -5,4 +5,4 @@ it:
|
|||||||
@ chmod +x ${BINARY_NAME}
|
@ chmod +x ${BINARY_NAME}
|
||||||
|
|
||||||
ex: it
|
ex: it
|
||||||
@ ./lambda.exe - < ./samples/simple.txt
|
@ ./lambda.exe -v - < ./samples/simple.txt
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
# lambda
|
# lambda
|
||||||
|
|
||||||
Making a lambda calculus interpreter in Go.
|
Making a lambda calculus interpreter in Go.
|
||||||
|
|
||||||
|
## Things to talk about
|
||||||
|
|
||||||
|
- Exhaustive sum types.
|
||||||
|
- Recursive descent and left-recursion.
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ func main() {
|
|||||||
logger.Info("Parsed tokens.", "tokens", tokens)
|
logger.Info("Parsed tokens.", "tokens", tokens)
|
||||||
|
|
||||||
// Turn tokens into syntax tree.
|
// Turn tokens into syntax tree.
|
||||||
expression, err := saccharine.GetTree(tokens)
|
expression, err := saccharine.Parse(tokens)
|
||||||
cli.HandleError(err)
|
cli.HandleError(err)
|
||||||
logger.Info("Parsed syntax tree.", "tree", saccharine.Stringify(expression))
|
logger.Info("Parsed syntax tree.", "tree", expression)
|
||||||
|
|
||||||
// Reduce expression.
|
// Reduce expression.
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import "fmt"
|
|||||||
|
|
||||||
// An iterator over slices.
|
// An iterator over slices.
|
||||||
type Iterator[T any] struct {
|
type Iterator[T any] struct {
|
||||||
data []T
|
items []T
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new iterator, over a set of items.
|
// Create a new iterator, over a set of items.
|
||||||
func New[T any](items []T) *Iterator[T] {
|
func Of[T any](items []T) *Iterator[T] {
|
||||||
return &Iterator[T]{data: items, index: 0}
|
return &Iterator[T]{items: items, index: 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the current position of the iterator.
|
// Returns the current position of the iterator.
|
||||||
@@ -21,50 +21,40 @@ func (i Iterator[T]) Index() int {
|
|||||||
return i.index
|
return i.index
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the iterator has no more items to iterate over.
|
func (i Iterator[T]) Copy() *Iterator[T] {
|
||||||
func (i Iterator[T]) IsDone() bool {
|
return &Iterator[T]{items: i.items, index: i.index}
|
||||||
return i.index == len(i.data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the next item in the slice, if one exists. Returns an error if there
|
func (i *Iterator[T]) Sync(o *Iterator[T]) {
|
||||||
// isn't one.
|
i.index = o.index
|
||||||
func (i Iterator[T]) Peek() (T, error) {
|
}
|
||||||
var null T
|
|
||||||
|
|
||||||
if i.IsDone() {
|
// Create a new iterator, over a set of items.
|
||||||
|
func (i Iterator[T]) Get() (T, error) {
|
||||||
|
var null T
|
||||||
|
if i.Done() {
|
||||||
return null, fmt.Errorf("iterator is exhausted")
|
return null, fmt.Errorf("iterator is exhausted")
|
||||||
}
|
}
|
||||||
|
|
||||||
return i.data[i.index], nil
|
return i.items[i.index], nil
|
||||||
}
|
|
||||||
|
|
||||||
// Moves the iterator pointer to the next item. Returns the current item. Fails
|
|
||||||
// if there are no more items to iterate over.
|
|
||||||
func (i *Iterator[T]) Pop() (T, error) {
|
|
||||||
val, err := i.Peek()
|
|
||||||
if err != nil {
|
|
||||||
return val, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new iterator, over a set of items.
|
||||||
|
func (i *Iterator[T]) Next() (T, error) {
|
||||||
|
item, err := i.Get()
|
||||||
|
if err == nil {
|
||||||
i.index++
|
i.index++
|
||||||
return val, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pop until the clause returns false.
|
return item, err
|
||||||
func (i *Iterator[T]) PopWhile(fn func(T) bool) []T {
|
|
||||||
result := []T{}
|
|
||||||
|
|
||||||
for {
|
|
||||||
popped, err := i.Peek()
|
|
||||||
if err != nil || !fn(popped) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, popped)
|
// Create a new iterator, over a set of items.
|
||||||
if _, err := i.Pop(); err != nil {
|
func (i *Iterator[T]) Back() {
|
||||||
break
|
i.index = max(i.index-1, 0)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
// Returns the current position of the iterator.
|
||||||
|
func (i Iterator[T]) Done() bool {
|
||||||
|
return i.index == len(i.items)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package saccharine
|
|
||||||
|
|
||||||
type Node interface {
|
|
||||||
Accept(Visitor)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Abstraction struct {
|
|
||||||
Parameters []string
|
|
||||||
Body Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type Application struct {
|
|
||||||
Abstraction Node
|
|
||||||
Arguments []Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type Variable struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
func NewAbstraction(parameter []string, body Node) *Abstraction {
|
|
||||||
return &Abstraction{Parameters: parameter, Body: body}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewApplication(abstraction Node, arguments []Node) *Application {
|
|
||||||
return &Application{Abstraction: abstraction, Arguments: arguments}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewVariable(name string) *Variable {
|
|
||||||
return &Variable{Name: name}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
func (a *Abstraction) Accept(x Visitor) { x.VisitAbstraction(a) }
|
|
||||||
func (a *Application) Accept(x Visitor) { x.VisitApplication(a) }
|
|
||||||
func (v *Variable) Accept(x Visitor) { x.VisitVariable(v) }
|
|
||||||
|
|
||||||
/** ------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
type Visitor interface {
|
|
||||||
VisitAbstraction(*Abstraction)
|
|
||||||
VisitApplication(*Application)
|
|
||||||
VisitVariable(*Variable)
|
|
||||||
}
|
|
||||||
41
pkg/saccharine/ast/node.go
Normal file
41
pkg/saccharine/ast/node.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
type Expression interface {
|
||||||
|
IsExpression()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
type Abstraction struct {
|
||||||
|
Parameters []string
|
||||||
|
Body Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
Abstraction Expression
|
||||||
|
Arguments []Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
type Atom struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
func NewAbstraction(parameter []string, body Expression) *Abstraction {
|
||||||
|
return &Abstraction{Parameters: parameter, Body: body}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewApplication(abstraction Expression, arguments []Expression) *Application {
|
||||||
|
return &Application{Abstraction: abstraction, Arguments: arguments}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAtom(name string) *Atom {
|
||||||
|
return &Atom{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
func (a Abstraction) IsExpression() {}
|
||||||
|
func (a Application) IsExpression() {}
|
||||||
|
func (v Atom) IsExpression() {}
|
||||||
22
pkg/saccharine/ast/visit.go
Normal file
22
pkg/saccharine/ast/visit.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Visitor[T any] interface {
|
||||||
|
VisitAtom(*Atom) T
|
||||||
|
VisitAbstraction(*Abstraction) T
|
||||||
|
VisitApplication(*Application) T
|
||||||
|
}
|
||||||
|
|
||||||
|
func Visit[T any](visitor Visitor[T], node Expression) T {
|
||||||
|
switch node := node.(type) {
|
||||||
|
case *Atom:
|
||||||
|
return visitor.VisitAtom(node)
|
||||||
|
case *Abstraction:
|
||||||
|
return visitor.VisitAbstraction(node)
|
||||||
|
case *Application:
|
||||||
|
return visitor.VisitApplication(node)
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown node %t", node))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,80 +1,132 @@
|
|||||||
package saccharine
|
package saccharine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/iterator"
|
"git.maximhutz.com/max/lambda/pkg/iterator"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/saccharine/ast"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/saccharine/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
func isVariableToken(t Token) bool {
|
type TokenIterator = iterator.Iterator[token.Token]
|
||||||
return t.Type == TokenVariable
|
|
||||||
|
func parseToken(i *TokenIterator, expected token.Type) (*token.Token, error) {
|
||||||
|
i2 := i.Copy()
|
||||||
|
|
||||||
|
if tok, err := i2.Next(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if tok.Type != expected {
|
||||||
|
return nil, fmt.Errorf("expected token, got %v'", tok.Value)
|
||||||
|
} else {
|
||||||
|
i.Sync(i2)
|
||||||
|
return &tok, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseExpression(i *iterator.Iterator[Token]) (Node, error) {
|
func parseExpression(i *TokenIterator) (ast.Expression, error) {
|
||||||
token, err := i.Pop()
|
slog.Info("attempt exp", "index", i.Index())
|
||||||
if err != nil {
|
if abs, absErr := parseAbstraction(i); absErr == nil {
|
||||||
return nil, fmt.Errorf("could not get next token: %w", err)
|
slog.Info("got exp")
|
||||||
|
return abs, nil
|
||||||
|
} else if atm, atmErr := parseApplication(i); atmErr == nil {
|
||||||
|
slog.Info("got exp")
|
||||||
|
return atm, nil
|
||||||
|
} else if app, appErr := parseAtom(i); appErr == nil {
|
||||||
|
slog.Info("got exp")
|
||||||
|
return app, nil
|
||||||
|
} else {
|
||||||
|
slog.Info("fail exp")
|
||||||
|
return nil, errors.Join(absErr, appErr, atmErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch token.Type {
|
func parseParameters(i *TokenIterator) ([]string, error) {
|
||||||
case TokenVariable:
|
slog.Info("parse param")
|
||||||
return NewVariable(token.Value), nil
|
i2 := i.Copy()
|
||||||
case TokenDot:
|
|
||||||
return nil, fmt.Errorf("token '.' found without a corresponding slash (column %d)", token.Index)
|
|
||||||
case TokenSlash:
|
|
||||||
tokens := i.PopWhile(isVariableToken)
|
|
||||||
variables := []string{}
|
variables := []string{}
|
||||||
|
|
||||||
for _, token := range tokens {
|
for {
|
||||||
variables = append(variables, token.Value)
|
if tok, err := parseToken(i2, token.Atom); err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
variables = append(variables, tok.Value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dot, dotErr := i.Pop(); dotErr != nil {
|
slog.Info("got exp")
|
||||||
return nil, fmt.Errorf("could not find parameter terminator: %w", dotErr)
|
i.Sync(i2)
|
||||||
} else if dot.Type != TokenDot {
|
return variables, nil
|
||||||
return nil, fmt.Errorf("expected '.', got '%v' (column %d)", dot.Value, dot.Index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body, bodyErr := ParseExpression(i)
|
func parseAbstraction(i *TokenIterator) (*ast.Abstraction, error) {
|
||||||
if bodyErr != nil {
|
slog.Info("attempt abs")
|
||||||
return nil, fmt.Errorf("could not parse function body: %w", bodyErr)
|
i2 := i.Copy()
|
||||||
|
|
||||||
|
if _, err := parseToken(i2, token.Slash); err != nil {
|
||||||
|
slog.Info("fail abs")
|
||||||
|
return nil, err
|
||||||
|
} else if parameters, err := parseParameters(i2); err != nil {
|
||||||
|
slog.Info("fail abs")
|
||||||
|
return nil, err
|
||||||
|
} else if _, err = parseToken(i2, token.Dot); err != nil {
|
||||||
|
slog.Info("fail abs")
|
||||||
|
return nil, err
|
||||||
|
} else if body, err := parseExpression(i2); err != nil {
|
||||||
|
slog.Info("fail abs")
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
slog.Info("got abs")
|
||||||
|
i.Sync(i2)
|
||||||
|
return ast.NewAbstraction(parameters, body), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewAbstraction(variables, body), nil
|
func parseApplication(i *TokenIterator) (*ast.Application, error) {
|
||||||
case TokenOpenParen:
|
slog.Info("attempt app")
|
||||||
fn, fnErr := ParseExpression(i)
|
i2 := i.Copy()
|
||||||
if fnErr != nil {
|
expressions := []ast.Expression{}
|
||||||
return nil, fmt.Errorf("could not parse call function: %w", fnErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []Node{}
|
if _, err := parseToken(i2, token.OpenParen); err != nil {
|
||||||
|
slog.Info("fail app")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
if next, nextErr := i.Peek(); nextErr == nil && next.Type == TokenCloseParen {
|
if exp, err := parseExpression(i2); err != nil {
|
||||||
break
|
break
|
||||||
|
} else {
|
||||||
|
expressions = append(expressions, exp)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arg, argErr := ParseExpression(i)
|
if _, err := parseToken(i2, token.CloseParen); err != nil {
|
||||||
if argErr != nil {
|
slog.Info("fail app")
|
||||||
return nil, fmt.Errorf("could not parse call argument: %w", argErr)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, arg)
|
if len(expressions) == 0 {
|
||||||
|
slog.Info("fail app")
|
||||||
|
return nil, fmt.Errorf("application has no arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
closing, closingErr := i.Pop()
|
slog.Info("got app")
|
||||||
if closingErr != nil {
|
i.Sync(i2)
|
||||||
return nil, fmt.Errorf("could not parse call terminating parenthesis: %w", closingErr)
|
return ast.NewApplication(expressions[0], expressions[1:]), nil
|
||||||
} else if closing.Type != TokenCloseParen {
|
|
||||||
return nil, fmt.Errorf("expected call terminating parenthesis, got '%v' (column %v)", closing.Value, closing.Index)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewApplication(fn, args), nil
|
func parseAtom(i *TokenIterator) (*ast.Atom, error) {
|
||||||
|
slog.Info("attempt atm")
|
||||||
|
if tok, err := parseToken(i, token.Atom); err != nil {
|
||||||
|
slog.Info("fail atm")
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
slog.Info("got atm")
|
||||||
|
return ast.NewAtom(tok.Value), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unexpected token '%v' (column %d)", token.Value, token.Index)
|
func Parse(i *TokenIterator) (ast.Expression, error) {
|
||||||
}
|
return parseExpression(i)
|
||||||
|
|
||||||
func GetTree(tokens []Token) (Node, error) {
|
|
||||||
return ParseExpression(iterator.New(tokens))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,31 @@
|
|||||||
package saccharine
|
package saccharine
|
||||||
|
|
||||||
import "strings"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
type stringifyVisitor struct {
|
"git.maximhutz.com/max/lambda/pkg/saccharine/ast"
|
||||||
builder strings.Builder
|
)
|
||||||
|
|
||||||
|
type stringifyVisitor struct{}
|
||||||
|
|
||||||
|
func (v stringifyVisitor) VisitAtom(n *ast.Atom) string {
|
||||||
|
return n.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitVariable(a *Variable) {
|
func (v stringifyVisitor) VisitAbstraction(n *ast.Abstraction) string {
|
||||||
v.builder.WriteString(a.Name)
|
return "\\" + strings.Join(n.Parameters, " ") + "." + ast.Visit(v, n.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) {
|
func (v stringifyVisitor) VisitApplication(n *ast.Application) string {
|
||||||
v.builder.WriteRune('\\')
|
arguments := []string{ast.Visit(v, n.Abstraction)}
|
||||||
v.builder.WriteString(strings.Join(f.Parameters, " "))
|
|
||||||
v.builder.WriteRune('.')
|
for _, argument := range n.Arguments {
|
||||||
f.Body.Accept(v)
|
arguments = append(arguments, ast.Visit(v, argument))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *stringifyVisitor) VisitApplication(c *Application) {
|
return "(" + strings.Join(arguments, " ") + ")"
|
||||||
v.builder.WriteRune('(')
|
|
||||||
c.Abstraction.Accept(v)
|
|
||||||
|
|
||||||
for _, argument := range c.Arguments {
|
|
||||||
v.builder.WriteRune(' ')
|
|
||||||
argument.Accept(v)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
v.builder.WriteRune(')')
|
func Stringify(n ast.Expression) string {
|
||||||
}
|
return ast.Visit(&stringifyVisitor{}, n)
|
||||||
|
|
||||||
func Stringify(n Node) string {
|
|
||||||
b := &stringifyVisitor{}
|
|
||||||
n.Accept(b)
|
|
||||||
return b.builder.String()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package saccharine
|
|
||||||
|
|
||||||
// All tokens in the pseudo-lambda language.
|
|
||||||
type TokenType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Denotes the '(' token.
|
|
||||||
TokenOpenParen TokenType = iota
|
|
||||||
// Denotes the ')' token.
|
|
||||||
TokenCloseParen
|
|
||||||
// Denotes an alpha-numeric variable.
|
|
||||||
TokenVariable
|
|
||||||
// Denotes the '/' token.
|
|
||||||
TokenSlash
|
|
||||||
// Denotes the '.' token.
|
|
||||||
TokenDot
|
|
||||||
)
|
|
||||||
|
|
||||||
// A representation of a token in source code.
|
|
||||||
type Token struct {
|
|
||||||
// Where the token begins in the source text.
|
|
||||||
Index int
|
|
||||||
// What type the token is.
|
|
||||||
Type TokenType
|
|
||||||
// The value of the token.
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
47
pkg/saccharine/token/token.go
Normal file
47
pkg/saccharine/token/token.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
// All tokens in the pseudo-lambda language.
|
||||||
|
type Type int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Denotes the '(' token.
|
||||||
|
OpenParen Type = iota
|
||||||
|
// Denotes the ')' token.
|
||||||
|
CloseParen
|
||||||
|
// Denotes an alpha-numeric variable.
|
||||||
|
Atom
|
||||||
|
// Denotes the '/' token.
|
||||||
|
Slash
|
||||||
|
// Denotes the '.' token.
|
||||||
|
Dot
|
||||||
|
)
|
||||||
|
|
||||||
|
// A representation of a token in source code.
|
||||||
|
type Token struct {
|
||||||
|
// Where the token begins in the source text.
|
||||||
|
Index int
|
||||||
|
// What type the token is.
|
||||||
|
Type Type
|
||||||
|
// The value of the token.
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOpenParen(index int) *Token {
|
||||||
|
return &Token{Type: OpenParen, Index: index, Value: "("}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCloseParen(index int) *Token {
|
||||||
|
return &Token{Type: CloseParen, Index: index, Value: ")"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDot(index int) *Token {
|
||||||
|
return &Token{Type: Dot, Index: index, Value: "."}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSlash(index int) *Token {
|
||||||
|
return &Token{Type: Slash, Index: index, Value: "\\"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAtom(name string, index int) *Token {
|
||||||
|
return &Token{Type: Atom, Index: index, Value: name}
|
||||||
|
}
|
||||||
@@ -3,9 +3,11 @@ package saccharine
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"git.maximhutz.com/max/lambda/pkg/iterator"
|
"git.maximhutz.com/max/lambda/pkg/iterator"
|
||||||
|
"git.maximhutz.com/max/lambda/pkg/saccharine/token"
|
||||||
)
|
)
|
||||||
|
|
||||||
// isVariables determines whether a rune can be a valid variable.
|
// isVariables determines whether a rune can be a valid variable.
|
||||||
@@ -13,60 +15,77 @@ func isVariable(r rune) bool {
|
|||||||
return unicode.IsLetter(r) || unicode.IsNumber(r)
|
return unicode.IsLetter(r) || unicode.IsNumber(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseRune(i *iterator.Iterator[rune], expected func(rune) bool) (rune, error) {
|
||||||
|
i2 := i.Copy()
|
||||||
|
|
||||||
|
if r, err := i2.Next(); err != nil {
|
||||||
|
return r, err
|
||||||
|
} else if !expected(r) {
|
||||||
|
return r, fmt.Errorf("got unexpected rune %v'", r)
|
||||||
|
} else {
|
||||||
|
i.Sync(i2)
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pulls the next token from an iterator over runes. If it cannot, it will
|
// Pulls the next token from an iterator over runes. If it cannot, it will
|
||||||
// return nil. If an error occurs, it will return that.
|
// return nil. If an error occurs, it will return that.
|
||||||
func getToken(i *iterator.Iterator[rune]) (*Token, error) {
|
func getToken(i *iterator.Iterator[rune]) (*token.Token, error) {
|
||||||
index := i.Index()
|
index := i.Index()
|
||||||
|
|
||||||
if i.IsDone() {
|
if i.Done() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
letter, err := i.Pop()
|
letter, err := i.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot produce next token: %w", err)
|
return nil, fmt.Errorf("cannot produce next token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case letter == '(':
|
case letter == '(':
|
||||||
// The opening deliminator of an application.
|
return token.NewOpenParen(index), nil
|
||||||
return &Token{Type: TokenOpenParen, Index: index, Value: string(letter)}, nil
|
|
||||||
case letter == ')':
|
case letter == ')':
|
||||||
// The terminator of an application.
|
return token.NewCloseParen(index), nil
|
||||||
return &Token{Type: TokenCloseParen, Index: index, Value: string(letter)}, nil
|
|
||||||
case letter == '.':
|
case letter == '.':
|
||||||
// The terminator of the parameters in an abstraction.
|
return token.NewDot(index), nil
|
||||||
return &Token{Type: TokenDot, Index: index, Value: string(letter)}, nil
|
|
||||||
case letter == '\\':
|
case letter == '\\':
|
||||||
// The opening deliminator of an abstraction.
|
return token.NewSlash(index), nil
|
||||||
return &Token{Type: TokenSlash, Index: index, Value: string(letter)}, nil
|
|
||||||
case unicode.IsSpace(letter):
|
case unicode.IsSpace(letter):
|
||||||
// If there is a space character, ignore it.
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case isVariable(letter):
|
case isVariable(letter):
|
||||||
rest := i.PopWhile(isVariable)
|
atom := []rune{letter}
|
||||||
atom := string(append([]rune{letter}, rest...))
|
|
||||||
|
|
||||||
return &Token{Index: index, Type: TokenVariable, Value: atom}, nil
|
for {
|
||||||
|
if r, err := parseRune(i, isVariable); err != nil {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
atom = append(atom, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.NewAtom(string(atom), index), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unknown character '%v'", letter)
|
return nil, fmt.Errorf("unknown character '%v'", letter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parses a list of runes into tokens. All error encountered are returned, as well.
|
// Parses a list of runes into tokens. All error encountered are returned, as well.
|
||||||
func GetTokens(input []rune) ([]Token, error) {
|
func GetTokens(input []rune) (*iterator.Iterator[token.Token], error) {
|
||||||
i := iterator.New(input)
|
i := iterator.Of(input)
|
||||||
tokens := []Token{}
|
tokens := []token.Token{}
|
||||||
errorList := []error{}
|
errorList := []error{}
|
||||||
|
|
||||||
for !i.IsDone() {
|
for !i.Done() {
|
||||||
token, err := getToken(i)
|
token, err := getToken(i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Info("token error", "error", err)
|
||||||
errorList = append(errorList, err)
|
errorList = append(errorList, err)
|
||||||
} else if token != nil {
|
} else if token != nil {
|
||||||
|
slog.Info("token parsed", "token", token)
|
||||||
tokens = append(tokens, *token)
|
tokens = append(tokens, *token)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokens, errors.Join(errorList...)
|
return iterator.Of(tokens), errors.Join(errorList...)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user