feat: better recursive descent
This commit is contained in:
@@ -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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"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 {
|
||||
return t.Type == TokenVariable
|
||||
type TokenIterator = iterator.Iterator[token.Token]
|
||||
|
||||
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) {
|
||||
token, err := i.Pop()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get next token: %w", err)
|
||||
func parseExpression(i *TokenIterator) (ast.Expression, error) {
|
||||
slog.Info("attempt exp", "index", i.Index())
|
||||
if abs, absErr := parseAbstraction(i); absErr == nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func parseParameters(i *TokenIterator) ([]string, error) {
|
||||
slog.Info("parse param")
|
||||
i2 := i.Copy()
|
||||
variables := []string{}
|
||||
|
||||
for {
|
||||
if tok, err := parseToken(i2, token.Atom); err != nil {
|
||||
break
|
||||
} else {
|
||||
variables = append(variables, tok.Value)
|
||||
}
|
||||
}
|
||||
|
||||
switch token.Type {
|
||||
case TokenVariable:
|
||||
return NewVariable(token.Value), nil
|
||||
case TokenDot:
|
||||
return nil, fmt.Errorf("token '.' found without a corresponding slash (column %d)", token.Index)
|
||||
case TokenSlash:
|
||||
tokens := i.PopWhile(isVariableToken)
|
||||
variables := []string{}
|
||||
slog.Info("got exp")
|
||||
i.Sync(i2)
|
||||
return variables, nil
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
variables = append(variables, token.Value)
|
||||
}
|
||||
func parseAbstraction(i *TokenIterator) (*ast.Abstraction, error) {
|
||||
slog.Info("attempt abs")
|
||||
i2 := i.Copy()
|
||||
|
||||
if dot, dotErr := i.Pop(); dotErr != nil {
|
||||
return nil, fmt.Errorf("could not find parameter terminator: %w", dotErr)
|
||||
} else if dot.Type != TokenDot {
|
||||
return nil, fmt.Errorf("expected '.', got '%v' (column %d)", dot.Value, dot.Index)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
body, bodyErr := ParseExpression(i)
|
||||
if bodyErr != nil {
|
||||
return nil, fmt.Errorf("could not parse function body: %w", bodyErr)
|
||||
}
|
||||
func parseApplication(i *TokenIterator) (*ast.Application, error) {
|
||||
slog.Info("attempt app")
|
||||
i2 := i.Copy()
|
||||
expressions := []ast.Expression{}
|
||||
|
||||
return NewAbstraction(variables, body), nil
|
||||
case TokenOpenParen:
|
||||
fn, fnErr := ParseExpression(i)
|
||||
if fnErr != nil {
|
||||
return nil, fmt.Errorf("could not parse call function: %w", fnErr)
|
||||
}
|
||||
|
||||
args := []Node{}
|
||||
|
||||
for {
|
||||
if next, nextErr := i.Peek(); nextErr == nil && next.Type == TokenCloseParen {
|
||||
break
|
||||
}
|
||||
|
||||
arg, argErr := ParseExpression(i)
|
||||
if argErr != nil {
|
||||
return nil, fmt.Errorf("could not parse call argument: %w", argErr)
|
||||
}
|
||||
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
closing, closingErr := i.Pop()
|
||||
if closingErr != nil {
|
||||
return nil, fmt.Errorf("could not parse call terminating parenthesis: %w", closingErr)
|
||||
} 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
|
||||
if _, err := parseToken(i2, token.OpenParen); err != nil {
|
||||
slog.Info("fail app")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unexpected token '%v' (column %d)", token.Value, token.Index)
|
||||
for {
|
||||
if exp, err := parseExpression(i2); err != nil {
|
||||
break
|
||||
} else {
|
||||
expressions = append(expressions, exp)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := parseToken(i2, token.CloseParen); err != nil {
|
||||
slog.Info("fail app")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(expressions) == 0 {
|
||||
slog.Info("fail app")
|
||||
return nil, fmt.Errorf("application has no arguments")
|
||||
}
|
||||
|
||||
slog.Info("got app")
|
||||
i.Sync(i2)
|
||||
return ast.NewApplication(expressions[0], expressions[1:]), nil
|
||||
}
|
||||
|
||||
func GetTree(tokens []Token) (Node, error) {
|
||||
return ParseExpression(iterator.New(tokens))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func Parse(i *TokenIterator) (ast.Expression, error) {
|
||||
return parseExpression(i)
|
||||
}
|
||||
|
||||
@@ -1,36 +1,31 @@
|
||||
package saccharine
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
type stringifyVisitor struct {
|
||||
builder strings.Builder
|
||||
"git.maximhutz.com/max/lambda/pkg/saccharine/ast"
|
||||
)
|
||||
|
||||
type stringifyVisitor struct{}
|
||||
|
||||
func (v stringifyVisitor) VisitAtom(n *ast.Atom) string {
|
||||
return n.Name
|
||||
}
|
||||
|
||||
func (v *stringifyVisitor) VisitVariable(a *Variable) {
|
||||
v.builder.WriteString(a.Name)
|
||||
func (v stringifyVisitor) VisitAbstraction(n *ast.Abstraction) string {
|
||||
return "\\" + strings.Join(n.Parameters, " ") + "." + ast.Visit(v, n.Body)
|
||||
}
|
||||
|
||||
func (v *stringifyVisitor) VisitAbstraction(f *Abstraction) {
|
||||
v.builder.WriteRune('\\')
|
||||
v.builder.WriteString(strings.Join(f.Parameters, " "))
|
||||
v.builder.WriteRune('.')
|
||||
f.Body.Accept(v)
|
||||
}
|
||||
func (v stringifyVisitor) VisitApplication(n *ast.Application) string {
|
||||
arguments := []string{ast.Visit(v, n.Abstraction)}
|
||||
|
||||
func (v *stringifyVisitor) VisitApplication(c *Application) {
|
||||
v.builder.WriteRune('(')
|
||||
c.Abstraction.Accept(v)
|
||||
|
||||
for _, argument := range c.Arguments {
|
||||
v.builder.WriteRune(' ')
|
||||
argument.Accept(v)
|
||||
for _, argument := range n.Arguments {
|
||||
arguments = append(arguments, ast.Visit(v, argument))
|
||||
}
|
||||
|
||||
v.builder.WriteRune(')')
|
||||
return "(" + strings.Join(arguments, " ") + ")"
|
||||
}
|
||||
|
||||
func Stringify(n Node) string {
|
||||
b := &stringifyVisitor{}
|
||||
n.Accept(b)
|
||||
return b.builder.String()
|
||||
func Stringify(n ast.Expression) string {
|
||||
return ast.Visit(&stringifyVisitor{}, n)
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"unicode"
|
||||
|
||||
"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.
|
||||
@@ -13,60 +15,77 @@ func isVariable(r rune) bool {
|
||||
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
|
||||
// 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()
|
||||
|
||||
if i.IsDone() {
|
||||
if i.Done() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
letter, err := i.Pop()
|
||||
letter, err := i.Next()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot produce next token: %w", err)
|
||||
}
|
||||
|
||||
switch {
|
||||
case letter == '(':
|
||||
// The opening deliminator of an application.
|
||||
return &Token{Type: TokenOpenParen, Index: index, Value: string(letter)}, nil
|
||||
return token.NewOpenParen(index), nil
|
||||
case letter == ')':
|
||||
// The terminator of an application.
|
||||
return &Token{Type: TokenCloseParen, Index: index, Value: string(letter)}, nil
|
||||
return token.NewCloseParen(index), nil
|
||||
case letter == '.':
|
||||
// The terminator of the parameters in an abstraction.
|
||||
return &Token{Type: TokenDot, Index: index, Value: string(letter)}, nil
|
||||
return token.NewDot(index), nil
|
||||
case letter == '\\':
|
||||
// The opening deliminator of an abstraction.
|
||||
return &Token{Type: TokenSlash, Index: index, Value: string(letter)}, nil
|
||||
return token.NewSlash(index), nil
|
||||
case unicode.IsSpace(letter):
|
||||
// If there is a space character, ignore it.
|
||||
return nil, nil
|
||||
case isVariable(letter):
|
||||
rest := i.PopWhile(isVariable)
|
||||
atom := string(append([]rune{letter}, rest...))
|
||||
atom := []rune{letter}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Parses a list of runes into tokens. All error encountered are returned, as well.
|
||||
func GetTokens(input []rune) ([]Token, error) {
|
||||
i := iterator.New(input)
|
||||
tokens := []Token{}
|
||||
func GetTokens(input []rune) (*iterator.Iterator[token.Token], error) {
|
||||
i := iterator.Of(input)
|
||||
tokens := []token.Token{}
|
||||
errorList := []error{}
|
||||
|
||||
for !i.IsDone() {
|
||||
for !i.Done() {
|
||||
token, err := getToken(i)
|
||||
if err != nil {
|
||||
slog.Info("token error", "error", err)
|
||||
errorList = append(errorList, err)
|
||||
} else if token != nil {
|
||||
slog.Info("token parsed", "token", token)
|
||||
tokens = append(tokens, *token)
|
||||
}
|
||||
}
|
||||
|
||||
return tokens, errors.Join(errorList...)
|
||||
return iterator.Of(tokens), errors.Join(errorList...)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user