feat: copied code over

This commit is contained in:
2026-01-19 19:32:08 -05:00
parent 5f2dcc9394
commit 091bc70172
18 changed files with 1102 additions and 6 deletions

View File

@@ -0,0 +1,87 @@
package lambda
import (
"git.maximhutz.com/max/lambda/pkg/repr"
"git.maximhutz.com/max/lambda/pkg/set"
)
// Expression is the interface for all lambda calculus expression types.
// It embeds the general expr.Expression interface for cross-mode compatibility.
type Expression interface {
repr.Repr
// Substitute replaces all free occurrences of the target variable with the
// replacement expression. Alpha-renaming is performed automatically to
// avoid variable capture.
Substitute(target string, replacement Expression) Expression
// GetFree returns the set of all free variable names in the expression.
// This function does not mutate the input expression.
// The returned set is newly allocated and can be modified by the caller.
GetFree() set.Set[string]
// Rename replaces all occurrences of the target variable name with the new name.
Rename(target string, newName string) Expression
// IsFree returns true if the variable name n occurs free in the expression.
// This function does not mutate the input expression.
IsFree(n string) bool
}
var (
_ Expression = Abstraction{}
_ Expression = Application{}
_ Expression = Variable{}
)
/** ------------------------------------------------------------------------- */
type Abstraction struct {
parameter string
body Expression
}
func (a Abstraction) Parameter() string {
return a.parameter
}
func (a Abstraction) Body() Expression {
return a.body
}
func NewAbstraction(parameter string, body Expression) Abstraction {
return Abstraction{parameter, body}
}
/** ------------------------------------------------------------------------- */
type Application struct {
abstraction Expression
argument Expression
}
func (a Application) Abstraction() Expression {
return a.abstraction
}
func (a Application) Argument() Expression {
return a.argument
}
func NewApplication(abstraction Expression, argument Expression) Application {
return Application{abstraction, argument}
}
/** ------------------------------------------------------------------------- */
type Variable struct {
name string
}
func (v Variable) Name() string {
return v.name
}
func NewVariable(name string) Variable {
return Variable{name}
}

View File

@@ -0,0 +1,19 @@
package lambda
import (
"strconv"
"git.maximhutz.com/max/lambda/pkg/set"
)
// GenerateFreshName generates a variable name that is not in the used set.
// This function does not mutate the used set.
func GenerateFreshName(used set.Set[string]) string {
for i := uint64(0); ; i++ {
attempt := "_" + string(strconv.AppendUint(nil, i, 10))
if !used.Has(attempt) {
return attempt
}
}
}

View File

@@ -0,0 +1,19 @@
package lambda
import "git.maximhutz.com/max/lambda/pkg/set"
func (e Variable) GetFree() set.Set[string] {
return set.New(e.Name())
}
func (e Abstraction) GetFree() set.Set[string] {
vars := e.Body().GetFree()
vars.Remove(e.Parameter())
return vars
}
func (e Application) GetFree() set.Set[string] {
vars := e.Abstraction().GetFree()
vars.Merge(e.Argument().GetFree())
return vars
}

View File

@@ -0,0 +1,12 @@
package lambda
func (e Variable) IsFree(n string) bool {
return e.Name() == n
}
func (e Abstraction) IsFree(n string) bool {
return e.Parameter() != n && e.Body().IsFree(n)
}
func (e Application) IsFree(n string) bool {
return e.Abstraction().IsFree(n) || e.Argument().IsFree(n)
}

10
pkg/repr/lambda/parse.go Normal file
View File

@@ -0,0 +1,10 @@
package lambda
import "fmt"
// Application -> (x x)
// Abstraction -> \x.y
// Variable -> x
func Parse(code string) (Expression, error) {
return nil, fmt.Errorf("not implemented")
}

28
pkg/repr/lambda/rename.go Normal file
View File

@@ -0,0 +1,28 @@
package lambda
// Rename replaces all occurrences of the target variable name with the new name.
func (e Variable) Rename(target string, newName string) Expression {
if e.Name() == target {
return NewVariable(newName)
}
return e
}
func (e Abstraction) Rename(target string, newName string) Expression {
newParam := e.Parameter()
if e.Parameter() == target {
newParam = newName
}
newBody := e.Body().Rename(target, newName)
return NewAbstraction(newParam, newBody)
}
func (e Application) Rename(target string, newName string) Expression {
newAbs := e.Abstraction().Rename(target, newName)
newArg := e.Argument().Rename(target, newName)
return NewApplication(newAbs, newArg)
}

View File

@@ -0,0 +1,35 @@
package lambda
func (e Variable) Substitute(target string, replacement Expression) Expression {
if e.Name() == target {
return replacement
}
return e
}
func (e Abstraction) Substitute(target string, replacement Expression) Expression {
if e.Parameter() == target {
return e
}
body := e.Body()
param := e.Parameter()
if replacement.IsFree(param) {
freeVars := replacement.GetFree()
freeVars.Merge(body.GetFree())
freshVar := GenerateFreshName(freeVars)
body = body.Rename(param, freshVar)
param = freshVar
}
newBody := body.Substitute(target, replacement)
return NewAbstraction(param, newBody)
}
func (e Application) Substitute(target string, replacement Expression) Expression {
abs := e.Abstraction().Substitute(target, replacement)
arg := e.Argument().Substitute(target, replacement)
return NewApplication(abs, arg)
}

View File

@@ -1,10 +1,4 @@
package repr
import (
"fmt"
)
type Repr interface {
fmt.Stringer
fmt.Scanner
}

View File

@@ -0,0 +1,59 @@
package saccharine
import (
"git.maximhutz.com/max/lambda/pkg/repr"
)
type Expression interface {
repr.Repr
}
var (
_ Expression = Abstraction{}
_ Expression = Application{}
_ Expression = Variable{}
_ Expression = Clause{}
)
/** ------------------------------------------------------------------------- */
type Abstraction struct {
Parameters []string
Body Expression
}
func NewAbstraction(parameter []string, body Expression) *Abstraction {
return &Abstraction{Parameters: parameter, Body: body}
}
/** ------------------------------------------------------------------------- */
type Application struct {
Abstraction Expression
Arguments []Expression
}
func NewApplication(abstraction Expression, arguments []Expression) *Application {
return &Application{Abstraction: abstraction, Arguments: arguments}
}
/** ------------------------------------------------------------------------- */
type Variable struct {
Name string
}
func NewVariable(name string) *Variable {
return &Variable{Name: name}
}
/** ------------------------------------------------------------------------- */
type Clause struct {
Statements []Statement
Returns Expression
}
func NewClause(statements []Statement, returns Expression) *Clause {
return &Clause{Statements: statements, Returns: returns}
}

130
pkg/repr/saccharine/lex.go Normal file
View File

@@ -0,0 +1,130 @@
package saccharine
import (
"errors"
"fmt"
"unicode"
"git.maximhutz.com/max/lambda/pkg/iterator"
"git.maximhutz.com/max/lambda/pkg/trace"
)
// isVariables determines whether a rune can be a valid variable.
func isVariable(r rune) bool {
return unicode.IsLetter(r) || unicode.IsNumber(r)
}
func scanRune(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
}
}
func scanCharacter(i *iterator.Iterator[rune], expected rune) (rune, error) {
i2 := i.Copy()
if r, err := i2.Next(); err != nil {
return r, err
} else if r != expected {
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 scanToken(i *iterator.Iterator[rune]) (*Token, error) {
index := i.Index()
if i.Done() {
return nil, nil
}
letter, err := i.Next()
if err != nil {
return nil, trace.Wrap(err, "cannot produce next token")
}
switch {
case letter == '(':
return NewOpenParen(index), nil
case letter == ')':
return NewCloseParen(index), nil
case letter == '.':
return NewDot(index), nil
case letter == '\\':
return NewSlash(index), nil
case letter == '\n':
return NewSoftBreak(index), nil
case letter == '{':
return NewOpenBrace(index), nil
case letter == '}':
return NewCloseBrace(index), nil
case letter == ':':
if _, err := scanCharacter(i, '='); err != nil {
return nil, err
} else {
return NewAssign(index), nil
}
case letter == ';':
return NewHardBreak(index), nil
case letter == '#':
// Skip everything until the next newline or EOF.
for !i.Done() {
r, err := i.Next()
if err != nil {
return nil, trace.Wrap(err, "error while parsing comment")
}
if r == '\n' {
// Put the newline back so it can be processed as a soft break.
i.Back()
break
}
}
return nil, nil
case unicode.IsSpace(letter):
return nil, nil
case isVariable(letter):
atom := []rune{letter}
for {
if r, err := scanRune(i, isVariable); err != nil {
break
} else {
atom = append(atom, r)
}
}
return NewAtom(string(atom), index), nil
}
return nil, fmt.Errorf("unknown character '%v'", string(letter))
}
// Parse a string into tokens.
func Scan(input string) ([]Token, error) {
i := iterator.Of([]rune(input))
tokens := []Token{}
errorList := []error{}
for !i.Done() {
token, err := scanToken(i)
if err != nil {
errorList = append(errorList, err)
} else if token != nil {
tokens = append(tokens, *token)
}
}
return tokens, errors.Join(errorList...)
}

View File

@@ -0,0 +1,237 @@
package saccharine
import (
"errors"
"fmt"
"git.maximhutz.com/max/lambda/pkg/iterator"
"git.maximhutz.com/max/lambda/pkg/trace"
)
type TokenIterator = iterator.Iterator[Token]
func parseRawToken(i *TokenIterator, expected Type) (*Token, error) {
return iterator.Do(i, func(i *TokenIterator) (*Token, 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'", Name(expected), tok.Value)
} else {
return &tok, nil
}
})
}
func passSoftBreaks(i *TokenIterator) {
for {
if _, err := parseRawToken(i, TokenSoftBreak); err != nil {
return
}
}
}
func parseToken(i *TokenIterator, expected Type, ignoreSoftBreaks bool) (*Token, error) {
return iterator.Do(i, func(i *TokenIterator) (*Token, error) {
if ignoreSoftBreaks {
passSoftBreaks(i)
}
return parseRawToken(i, expected)
})
}
func parseString(i *TokenIterator) (string, error) {
if tok, err := parseToken(i, TokenAtom, true); err != nil {
return "", trace.Wrap(err, "no variable (col %d)", i.Index())
} else {
return tok.Value, nil
}
}
func parseBreak(i *TokenIterator) (*Token, error) {
if tok, softErr := parseRawToken(i, TokenSoftBreak); softErr == nil {
return tok, nil
} else if tok, hardErr := parseRawToken(i, TokenHardBreak); hardErr == nil {
return tok, nil
} else {
return nil, errors.Join(softErr, hardErr)
}
}
func parseList[U any](i *TokenIterator, fn func(*TokenIterator) (U, error), minimum int) ([]U, error) {
results := []U{}
for {
if u, err := fn(i); err != nil {
if len(results) < minimum {
return nil, trace.Wrap(err, "expected at least '%v' items, got only '%v'", minimum, len(results))
}
return results, nil
} else {
results = append(results, u)
}
}
}
func parseAbstraction(i *TokenIterator) (*Abstraction, error) {
return iterator.Do(i, func(i *TokenIterator) (*Abstraction, error) {
if _, err := parseToken(i, TokenSlash, true); err != nil {
return nil, trace.Wrap(err, "no function slash (col %d)", i.MustGet().Column)
} else if parameters, err := parseList(i, parseString, 0); err != nil {
return nil, err
} else if _, err = parseToken(i, TokenDot, true); err != nil {
return nil, trace.Wrap(err, "no function dot (col %d)", i.MustGet().Column)
} else if body, err := parseExpression(i); err != nil {
return nil, err
} else {
return NewAbstraction(parameters, body), nil
}
})
}
func parseApplication(i *TokenIterator) (*Application, error) {
return iterator.Do(i, func(i *TokenIterator) (*Application, error) {
if _, err := parseToken(i, TokenOpenParen, true); err != nil {
return nil, trace.Wrap(err, "no openning brackets (col %d)", i.MustGet().Column)
} else if expressions, err := parseList(i, parseExpression, 1); err != nil {
return nil, err
} else if _, err := parseToken(i, TokenCloseParen, true); err != nil {
return nil, trace.Wrap(err, "no closing brackets (col %d)", i.MustGet().Column)
} else {
return NewApplication(expressions[0], expressions[1:]), nil
}
})
}
func parseAtom(i *TokenIterator) (*Variable, error) {
if tok, err := parseToken(i, TokenAtom, true); err != nil {
return nil, trace.Wrap(err, "no variable (col %d)", i.Index())
} else {
return NewVariable(tok.Value), nil
}
}
func parseStatements(i *TokenIterator) ([]Statement, error) {
statements := []Statement{}
//nolint:errcheck
parseList(i, parseBreak, 0)
for {
if statement, err := parseStatement(i); err != nil {
break
} else if _, err := parseList(i, parseBreak, 1); err != nil && !i.Done() {
break
} else {
statements = append(statements, statement)
}
}
return statements, nil
}
func parseClause(i *TokenIterator, braces bool) (*Clause, error) {
if braces {
if _, err := parseToken(i, TokenOpenBrace, true); err != nil {
return nil, err
}
}
var stmts []Statement
var last *DeclareStatement
var err error
var ok bool
if stmts, err = parseStatements(i); err != nil {
return nil, err
} else if len(stmts) == 0 {
return nil, fmt.Errorf("no statements in clause")
} else if last, ok = stmts[len(stmts)-1].(*DeclareStatement); !ok {
return nil, fmt.Errorf("this clause contains no final return value (col %d)", i.MustGet().Column)
}
if braces {
if _, err := parseToken(i, TokenCloseBrace, true); err != nil {
return nil, err
}
}
return NewClause(stmts[:len(stmts)-1], last.Value), nil
}
func parseExpression(i *TokenIterator) (Expression, error) {
return iterator.Do(i, func(i *TokenIterator) (Expression, error) {
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)
}
})
}
func parseLet(i *TokenIterator) (*LetStatement, error) {
return iterator.Do(i, func(i *TokenIterator) (*LetStatement, error) {
if parameters, err := 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 NewLet(parameters[0], parameters[1:], body), nil
}
})
}
func parseDeclare(i *TokenIterator) (*DeclareStatement, error) {
if value, err := parseExpression(i); err != nil {
return nil, err
} else {
return NewDeclare(value), nil
}
}
func parseStatement(i *TokenIterator) (Statement, error) {
if let, letErr := parseLet(i); letErr == nil {
return let, nil
} else if declare, declErr := parseDeclare(i); declErr == nil {
return declare, nil
} else {
return nil, errors.Join(letErr, declErr)
}
}
// Given a list of tokens, attempt to parse it into an syntax tree.
func parse(tokens []Token) (Expression, error) {
i := iterator.Of(tokens)
exp, err := parseClause(i, false)
if err != nil {
return nil, err
}
if !i.Done() {
return nil, fmt.Errorf("expected EOF, found more code (col %d)", i.MustGet().Column)
}
return exp, nil
}
// Convert a piece of valid saccharine code into an expression.
func Parse(code string) (Expression, error) {
tokens, err := Scan(code)
if err != nil {
return nil, err
}
return parse(tokens)
}

View File

@@ -0,0 +1,31 @@
package saccharine
import (
"git.maximhutz.com/max/lambda/pkg/repr"
)
type Statement interface {
repr.Repr
}
/** ------------------------------------------------------------------------- */
type LetStatement struct {
Name string
Parameters []string
Body Expression
}
func NewLet(name string, parameters []string, body Expression) *LetStatement {
return &LetStatement{Name: name, Parameters: parameters, Body: body}
}
/** ------------------------------------------------------------------------- */
type DeclareStatement struct {
Value Expression
}
func NewDeclare(value Expression) *DeclareStatement {
return &DeclareStatement{Value: value}
}

View File

@@ -0,0 +1,69 @@
package saccharine
import (
"fmt"
"strings"
)
func stringifyAtom(n *Variable) string {
return n.Name
}
func stringifyAbstraction(n *Abstraction) string {
return "\\" + strings.Join(n.Parameters, " ") + "." + Stringify(n.Body)
}
func stringifyApplication(n *Application) string {
arguments := []string{Stringify(n.Abstraction)}
for _, argument := range n.Arguments {
arguments = append(arguments, Stringify(argument))
}
return "(" + strings.Join(arguments, " ") + ")"
}
func stringifyLet(s *LetStatement) string {
return s.Name + " " + strings.Join(s.Parameters, " ") + " := " + Stringify(s.Body)
}
func stringifyDeclare(s *DeclareStatement) string {
return Stringify(s.Value)
}
func stringifyStatement(s Statement) string {
switch s := s.(type) {
case *DeclareStatement:
return stringifyDeclare(s)
case *LetStatement:
return stringifyLet(s)
default:
panic(fmt.Errorf("unknown statement type: %v", s))
}
}
func stringifyClause(n *Clause) string {
stmts := ""
for _, statement := range n.Statements {
stmts += stringifyStatement(statement) + "; "
}
return "{ " + stmts + Stringify(n.Returns) + " }"
}
// Convert an expression back into valid source code.
func Stringify(n Expression) string {
switch n := n.(type) {
case *Variable:
return stringifyAtom(n)
case *Abstraction:
return stringifyAbstraction(n)
case *Application:
return stringifyApplication(n)
case *Clause:
return stringifyClause(n)
default:
panic(fmt.Errorf("unknown expression type: %T", n))
}
}

View File

@@ -0,0 +1,91 @@
package saccharine
import "fmt"
// All tokens in the pseudo-lambda language.
type Type int
const (
TokenOpenParen Type = iota // Denotes the '(' token.
TokenCloseParen // Denotes the ')' token.
TokenOpenBrace // Denotes the '{' token.
TokenCloseBrace // Denotes the '}' token.
TokenHardBreak // Denotes the ';' token.
TokenAssign // Denotes the ':=' token.
TokenAtom // Denotes an alpha-numeric variable.
TokenSlash // Denotes the '/' token.
TokenDot // Denotes the '.' token.
TokenSoftBreak // Denotes a new-line.
)
// A representation of a token in source code.
type Token struct {
Column int // Where the token begins in the source text.
Type Type // What type the token is.
Value string // The value of the token.
}
func NewOpenParen(column int) *Token {
return &Token{Type: TokenOpenParen, Column: column, Value: "("}
}
func NewCloseParen(column int) *Token {
return &Token{Type: TokenCloseParen, Column: column, Value: ")"}
}
func NewOpenBrace(column int) *Token {
return &Token{Type: TokenOpenBrace, Column: column, Value: "{"}
}
func NewCloseBrace(column int) *Token {
return &Token{Type: TokenCloseBrace, Column: column, Value: "}"}
}
func NewDot(column int) *Token {
return &Token{Type: TokenDot, Column: column, Value: "."}
}
func NewHardBreak(column int) *Token {
return &Token{Type: TokenHardBreak, Column: column, Value: ";"}
}
func NewAssign(column int) *Token {
return &Token{Type: TokenAssign, Column: column, Value: ":="}
}
func NewSlash(column int) *Token {
return &Token{Type: TokenSlash, Column: column, Value: "\\"}
}
func NewAtom(name string, column int) *Token {
return &Token{Type: TokenAtom, Column: column, Value: name}
}
func NewSoftBreak(column int) *Token {
return &Token{Type: TokenSoftBreak, Column: column, Value: "\\n"}
}
func Name(typ Type) string {
switch typ {
case TokenOpenParen:
return "("
case TokenCloseParen:
return ")"
case TokenSlash:
return "\\"
case TokenDot:
return "."
case TokenAtom:
return "ATOM"
case TokenSoftBreak:
return "\\n"
case TokenHardBreak:
return ";"
default:
panic(fmt.Errorf("unknown token type %v", typ))
}
}
func (t Token) Name() string {
return Name(t.Type)
}