Compare commits

..

10 commits

Author SHA1 Message Date
0d98ae8ab1 class statement 2024-11-06 00:09:50 +02:00
c1549bbc6d fmt 2024-11-02 18:23:58 +02:00
1af4030dc1 scope and binding 2024-10-14 22:53:26 +03:00
a24586e601 lambda 2024-10-12 14:48:27 +03:00
1117f2c104 closures 2024-10-12 00:09:25 +03:00
1fdd522c8f user defined funcs 2024-10-11 17:01:12 +03:00
ce0492c489 fun declaration 2024-10-09 23:36:18 +03:00
a6e0673c3b functions 2024-10-09 19:57:52 +03:00
e8aeb6d5c5 for loop 2024-10-08 20:32:13 +03:00
69042ccf99 break 2024-10-07 21:47:55 +03:00
20 changed files with 1071 additions and 152 deletions

View file

@ -1,5 +1,6 @@
{
"cSpell.words": [
"arity",
"glox",
"PAREN",
"stmts"

View file

@ -71,6 +71,43 @@ func (as *AstStringer) visitLogical(l *Logical) any {
return nil
}
func (as *AstStringer) visitCall(c *Call) any {
as.str.WriteString("(call ")
c.callee.accept(as)
if len(c.args) != 0 {
as.str.WriteString(" ")
}
for i, arg := range c.args {
arg.accept(as)
if i < len(c.args)-1 {
as.str.WriteString(" ")
}
}
as.str.WriteString(")")
return nil
}
func (as *AstStringer) visitLambda(l *Lambda) any {
as.str.WriteString("(lambda ")
if len(l.args) != 0 {
as.str.WriteString("(")
for i, arg := range l.args {
as.str.WriteString(arg.lexeme)
if i < len(l.args)-1 {
as.str.WriteString(" ")
}
}
as.str.WriteString(")")
}
for _, stmt := range l.body {
stmt.accept(as)
}
as.str.WriteString(")")
return nil
}
func (as *AstStringer) visitPrintStmt(p *PrintStmt) {
as.str.WriteString("(print ")
p.val.accept(as)
@ -104,7 +141,7 @@ func (as *AstStringer) visitBlockStmt(b *BlockStmt) {
func (as *AstStringer) visitIfStmt(i *IfStmt) {
as.str.WriteString("(if ")
i.expr.accept(as)
i.cond.accept(as)
as.str.WriteString(" ")
i.then.accept(as)
if i.or != nil {
@ -125,3 +162,35 @@ func (as *AstStringer) visitWhileStmt(w *WhileStmt) {
w.body.accept(as)
as.str.WriteString(")")
}
func (as *AstStringer) visitBreakStmt(b *BreakStmt) {
as.str.WriteString("(break)")
}
func (as *AstStringer) visitFunStmt(f *FunStmt) {
as.str.WriteString(fmt.Sprintf("(fun %s ", f.name.lexeme))
if len(f.args) != 0 {
as.str.WriteString("(")
for i, arg := range f.args {
as.str.WriteString(arg.lexeme)
if i < len(f.args)-1 {
as.str.WriteString(" ")
}
}
as.str.WriteString(")")
}
for _, stmt := range f.body {
stmt.accept(as)
}
as.str.WriteString(")")
}
func (as *AstStringer) visitReturnStmt(r *ReturnStmt) {
as.str.WriteString("(return ")
r.value.accept(as)
as.str.WriteString(")")
}
func (as *AstStringer) visitClassStmt(c *ClassStmt) {
fmt.Printf("(class %s)", c.name.lexeme)
}

46
callable.go Normal file
View file

@ -0,0 +1,46 @@
package main
type Callable interface {
arity() int
call(i *Interpreter, args ...any) (ret any)
}
type Function struct {
name Token
args []Token
body []Stmt
closure *Environment
}
func (f *Function) call(i *Interpreter, args ...any) (ret any) {
defer func() {
if err := recover(); err != nil {
re, ok := err.(Return)
if !ok {
panic(err)
}
ret = re.val
}
}()
env := newEnvironment(f.closure)
for idx, arg := range f.args {
env.define(arg.lexeme, args[idx])
}
i.executeBlock(f.body, env)
return nil
}
func (f *Function) arity() int {
return len(f.args)
}
func newFunction(name Token, args []Token, body []Stmt, env *Environment) Callable {
return &Function{name, args, body, env}
}

9
class.go Normal file
View file

@ -0,0 +1,9 @@
package main
type Class struct {
name string
}
func (c *Class) String() string {
return c.name
}

57
env.go
View file

@ -1,12 +1,14 @@
package main
import "fmt"
type Environment struct {
values map[string]any
parent *Environment
values map[string]any
enclosing *Environment
}
func newEnvironment(parent *Environment) *Environment {
return &Environment{values: map[string]any{}, parent: parent}
func newEnvironment(enclosing *Environment) *Environment {
return &Environment{map[string]any{}, enclosing}
}
func (env *Environment) get(key string) any {
@ -14,8 +16,8 @@ func (env *Environment) get(key string) any {
return found
}
if env.parent != nil {
return env.parent.get(key)
if env.enclosing != nil {
return env.enclosing.get(key)
}
return nil
@ -23,20 +25,39 @@ func (env *Environment) get(key string) any {
func (env *Environment) exists(key string) bool {
_, ok := env.values[key]
if !ok && env.parent != nil {
return env.parent.exists(key)
}
return ok
}
func (env *Environment) set(key string, val any) {
if env.parent != nil && env.parent.exists(key) {
env.parent.set(key, val)
}
func (env *Environment) define(key string, val any) {
env.values[key] = val
}
func (env *Environment) assign(key Token, val any) *RuntimeError {
if env.exists(key.lexeme) {
env.values[key.lexeme] = val
return nil
}
if env.enclosing == nil {
return &RuntimeError{key, fmt.Sprintf("Can't assign: undefined variable '%s'.", key.lexeme)}
}
return env.enclosing.assign(key, val)
}
func (env *Environment) getAt(distance int, key string) any {
return env.ancestor(distance).get(key)
}
func (env *Environment) assignAt(distance int, key Token, val any) {
env.ancestor(distance).values[key.lexeme] = val
}
func (env *Environment) ancestor(distance int) *Environment {
parent := env
for i := 0; i < distance; i++ {
parent = parent.enclosing
}
return parent
}

24
expr.go
View file

@ -1,7 +1,9 @@
package main
type ExprVisitor interface {
visitCall(c *Call) any
visitUnary(u *Unary) any
visitLambda(l *Lambda) any
visitBinary(b *Binary) any
visitLiteral(l *Literal) any
visitGrouping(g *Grouping) any
@ -49,9 +51,23 @@ type Logical struct {
right Expr
}
type Call struct {
callee Expr
paren Token
args []Expr
}
type Lambda struct {
name Token
args []Token
body []Stmt
}
func (c *Call) expr() {}
func (u *Unary) expr() {}
func (a *Assign) expr() {}
func (b *Binary) expr() {}
func (l *Lambda) expr() {}
func (l *Literal) expr() {}
func (g *Grouping) expr() {}
func (v *Variable) expr() {}
@ -84,3 +100,11 @@ func (a *Assign) accept(v ExprVisitor) any {
func (l *Logical) accept(v ExprVisitor) any {
return v.visitLogical(l)
}
func (c *Call) accept(v ExprVisitor) any {
return v.visitCall(c)
}
func (l *Lambda) accept(v ExprVisitor) any {
return v.visitLambda(l)
}

View file

@ -1,63 +0,0 @@
package main
import (
"fmt"
"strings"
)
type ExprToRPN struct {
str strings.Builder
}
func (as ExprToRPN) String(expr Expr) string {
if expr == nil {
return ""
}
expr.accept(&as)
return as.str.String()
}
func (as *ExprToRPN) visitBinary(b *Binary) any {
b.left.accept(as)
as.str.WriteString(" ")
b.right.accept(as)
as.str.WriteString(" ")
as.str.WriteString(b.op.lexeme)
return nil
}
func (as *ExprToRPN) visitLiteral(l *Literal) any {
as.str.WriteString(fmt.Sprintf("%v", l.value))
return nil
}
func (as *ExprToRPN) visitGrouping(g *Grouping) any {
g.expression.accept(as)
as.str.WriteString(" group")
return nil
}
func (as *ExprToRPN) visitUnary(u *Unary) any {
u.right.accept(as)
as.str.WriteString(fmt.Sprintf(" %s", u.op.lexeme))
return nil
}
func (as *ExprToRPN) visitVariable(va *Variable) any {
as.str.WriteString(va.name.lexeme)
return nil
}
func (as *ExprToRPN) visitAssignment(a *Assign) any {
as.str.WriteString(fmt.Sprintf("%v %s =", a.value, a.variable.lexeme))
return nil
}
func (as *ExprToRPN) visitLogical(lo *Logical) any {
lo.left.accept(as)
lo.right.accept(as)
as.str.WriteString(" or")
return nil
}

17
globals.go Normal file
View file

@ -0,0 +1,17 @@
package main
import "time"
type ClockFun struct{}
func (cf *ClockFun) call(i *Interpreter, args ...any) any {
return time.Now().Unix()
}
func (cf *ClockFun) arity() int {
return 0
}
func defineGlobals(env *Environment) {
env.define("clock", &ClockFun{})
}

39
glox.go
View file

@ -2,6 +2,7 @@ package main
import (
"bufio"
"fmt"
"log"
"os"
)
@ -27,12 +28,22 @@ func (gl *Glox) runPrompt() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanLines)
doRun := func(line []byte) {
defer func() {
if err := recover(); err != nil {
log.Println(err)
}
}()
gl.run(line)
}
for {
print("> ")
if !scanner.Scan() {
break
}
gl.run(scanner.Bytes())
doRun(scanner.Bytes())
}
}
@ -47,11 +58,29 @@ func (gl *Glox) runFile(path string) {
}
func (gl *Glox) run(source []byte) {
tokens, _ := newScanner(source).scan()
tokens, err := newScanner(source).scan()
stmts, _ := newParser(tokens).parse()
if err != nil {
panic(err)
}
// fmt.Println(AstStringer{stmts: stmts})
stmts, parseErrs := newParser(tokens).parse()
gl.Interpreter.interpret(stmts)
if parseErrs != nil {
panic(parseErrs)
}
fmt.Println(AstStringer{stmts: stmts})
resolveErrs := newResolver(gl.Interpreter).resolveStmts(stmts...)
if resolveErrs != nil {
panic(resolveErrs)
}
interpreterErrs := gl.Interpreter.interpret(stmts)
if interpreterErrs != nil {
panic(interpreterErrs)
}
}

View file

@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"log"
"reflect"
@ -8,8 +9,11 @@ import (
)
type Interpreter struct {
env *Environment
errors []error
env *Environment
globals *Environment
locals map[Expr]int
errors []error
brk bool
}
type RuntimeError struct {
@ -17,15 +21,30 @@ type RuntimeError struct {
msg string
}
type Return struct {
val any
}
func (re *RuntimeError) Error() string {
return fmt.Sprintf("RuntimeError [%d][%s] Error: %s", re.token.line, re.token.typ, re.msg)
}
func newInterpreter() *Interpreter {
return &Interpreter{env: newEnvironment(nil)}
globals := newEnvironment(nil)
defineGlobals(globals)
return &Interpreter{
env: globals,
globals: globals,
locals: map[Expr]int{},
errors: []error{},
brk: false,
}
}
func (i *Interpreter) interpret(stmts []Stmt) []error {
func (i *Interpreter) interpret(stmts []Stmt) error {
defer i.recover()
i.errors = []error{}
@ -34,7 +53,7 @@ func (i *Interpreter) interpret(stmts []Stmt) []error {
stmt.accept(i)
}
return i.errors
return errors.Join(i.errors...)
}
func (i *Interpreter) recover() {
@ -119,25 +138,22 @@ func (i *Interpreter) visitUnary(u *Unary) any {
}
func (i *Interpreter) visitVariable(v *Variable) any {
if !i.env.exists(v.name.lexeme) {
i.panic(&RuntimeError{v.name, fmt.Sprintf("Can't assign: undefined variable '%s'.", v.name.lexeme)})
}
val := i.env.get(v.name.lexeme)
return val
return i.lookUpVariable(v.name, v)
}
func (i *Interpreter) visitAssignment(a *Assign) any {
val := i.evaluate(a.value)
distance, isLocal := i.locals[a]
if !i.env.exists(a.variable.lexeme) {
i.panic(&RuntimeError{a.variable, fmt.Sprintf("Can't assign: undefined variable '%s'.", a.variable.lexeme)})
if isLocal {
i.env.assignAt(distance, a.variable, val)
return val
}
val := i.evaluate(a.value)
i.env.set(a.variable.lexeme, val)
err := i.globals.assign(a.variable, val)
if err != nil {
i.panic(err)
}
return val
}
@ -155,6 +171,60 @@ func (i *Interpreter) visitLogical(lo *Logical) any {
return i.evaluate(lo.right)
}
func (i *Interpreter) visitCall(c *Call) any {
callee := i.evaluate(c.callee)
args := []any{}
for _, arg := range c.args {
args = append(args, i.evaluate(arg))
}
callable, ok := callee.(Callable)
if !ok {
i.panic(&RuntimeError{c.paren, "Can only call function and classes."})
}
if callable.arity() != len(args) {
i.panic(&RuntimeError{
c.paren,
fmt.Sprintf(
"Expected %d arguments but got %d",
callable.arity(),
len(args),
),
})
}
return callable.call(i, args...)
}
func (i *Interpreter) visitFunStmt(f *FunStmt) {
i.env.define(f.name.lexeme, newFunction(f.name, f.args, f.body, i.env))
}
func (i *Interpreter) visitClassStmt(c *ClassStmt) {
i.env.define(c.name.lexeme, nil)
class := &Class{c.name.lexeme}
i.env.assign(c.name, class)
}
func (i *Interpreter) visitLambda(l *Lambda) any {
return newFunction(l.name, l.args, l.body, i.env)
}
func (i *Interpreter) visitReturnStmt(r *ReturnStmt) {
var value any
if r.value != nil {
value = i.evaluate(r.value)
}
panic(Return{value})
}
func (i *Interpreter) visitPrintStmt(p *PrintStmt) {
fmt.Printf("%v\n", i.evaluate(p.val))
}
@ -171,23 +241,41 @@ func (i *Interpreter) visitVarStmt(v *VarStmt) {
val = i.evaluate(v.initializer)
}
i.env.set(v.name.lexeme, val)
i.env.define(v.name.lexeme, val)
}
func (i *Interpreter) visitBlockStmt(b *BlockStmt) {
i.executeBlock(b.stmts, newEnvironment(i.env))
}
func (i *Interpreter) executeBlock(stmts []Stmt, current *Environment) {
parentEnv := i.env
i.env = newEnvironment(parentEnv)
i.env = current
// need to restore environment after
// panic(Return) in visitReturnStmt
defer func() {
i.env = parentEnv
}()
for _, stmt := range stmts {
if i.brk {
break
}
for _, stmt := range b.stmts {
stmt.accept(i)
}
i.env = parentEnv
}
func (i *Interpreter) visitBreakStmt(b *BreakStmt) {
i.brk = true
}
func (i *Interpreter) visitIfStmt(iff *IfStmt) {
if isTruthy(i.evaluate(iff.expr)) {
if isTruthy(i.evaluate(iff.cond)) {
iff.then.accept(i)
} else if iff.or != nil {
@ -203,9 +291,11 @@ func (i *Interpreter) visitEnvStmt(e *EnvStmt) {
for walker != nil {
flatten = slices.Insert(flatten, 0, walker)
walker = walker.parent
walker = walker.enclosing
}
fmt.Printf("globals: %+v\n", *i.globals)
for ident, e := range flatten {
fmt.Printf("%*s", ident, "")
fmt.Printf("%+v\n", *e)
@ -214,10 +304,30 @@ func (i *Interpreter) visitEnvStmt(e *EnvStmt) {
func (i *Interpreter) visitWhileStmt(w *WhileStmt) {
for isTruthy(i.evaluate(w.cond)) {
if i.brk {
i.brk = false
break
}
w.body.accept(i)
}
}
func (i *Interpreter) resolve(expr Expr, depth int) {
i.locals[expr] = depth
}
func (i *Interpreter) lookUpVariable(name Token, expr Expr) any {
distance, isLocal := i.locals[expr]
if !isLocal {
return i.globals.get(name.lexeme)
}
return i.env.getAt(distance, name.lexeme)
}
func (i *Interpreter) panic(re *RuntimeError) {
i.errors = append(i.errors, re)
log.Println(re)

244
parser.go
View file

@ -1,8 +1,8 @@
package main
import (
"errors"
"fmt"
"log"
)
type Parser struct {
@ -17,7 +17,12 @@ type ParseError struct {
}
func (pe *ParseError) Error() string {
return fmt.Sprintf("ParseError [%d][%s]: %s", pe.token.line, pe.token.typ, pe.message)
return fmt.Sprintf(
"ParseError [%d][%s]: %s",
pe.token.line,
pe.token.typ,
pe.message,
)
}
func newParser(tokens []Token) *Parser {
@ -28,7 +33,7 @@ func newParser(tokens []Token) *Parser {
}
// program -> declaration* EOF
func (p *Parser) parse() ([]Stmt, []error) {
func (p *Parser) parse() ([]Stmt, error) {
defer p.recover()
stmts := []Stmt{}
@ -40,21 +45,31 @@ func (p *Parser) parse() ([]Stmt, []error) {
}
}
return stmts, p.errors
return stmts, errors.Join(p.errors...)
}
// declaration -> varDecl | statement
// declaration ->
// varDecl | funDecl | classDecl | statement
func (p *Parser) declaration() Stmt {
defer p.synchronize()
if p.match(VAR) {
return p.varDecl()
}
if p.match(FUN) {
return p.function("function")
}
if p.match(CLASS) {
return p.classDecl()
}
return p.statement()
}
// varDecl -> "var" IDENTIFIER ("=" expression)? ";"
func (p *Parser) varDecl() Stmt {
name := p.consume(IDENTIFIER, "expect identifier for variable")
name := p.consume(IDENTIFIER, "Expect identifier for variable")
var initializer Expr = nil
if p.match(EQUAL) {
@ -66,12 +81,59 @@ func (p *Parser) varDecl() Stmt {
return &VarStmt{name, initializer}
}
// classDecl -> "class" IDENTIFIER "{" function* "}"
func (p *Parser) classDecl() Stmt {
name := p.consume(IDENTIFIER, "Expect identifier for variable")
p.consume(LEFT_BRACE, "Expect '{' after class identifier")
methods := []FunStmt{}
for !p.isAtEnd() && !p.check(RIGHT_BRACE) {
methods = append(methods, *p.function("method"))
}
p.consume(RIGHT_BRACE, "Expect '}' after class definition")
return &ClassStmt{name, methods}
}
// funDecl -> "fun" function
// function -> IDENTIFIER "(" parameters? ")" blockStmt
// parameters -> IDENTIFIER ( "," IDENTIFIER )*
func (p *Parser) function(kind string) *FunStmt {
name := p.consume(IDENTIFIER, fmt.Sprintf("Expect %s name.", kind))
p.consume(LEFT_PAREN, fmt.Sprintf("Expect '(' after %s name.", kind))
args := []Token{}
for !p.check(RIGHT_PAREN) {
args = append(
args,
p.consume(
IDENTIFIER,
fmt.Sprintf("Expect %s argument.", kind),
),
)
if p.check(COMMA) {
p.advance()
}
}
p.consume(RIGHT_PAREN, fmt.Sprintf("Expect ')' after %s name.", kind))
p.consume(LEFT_BRACE, fmt.Sprintf("Expect '{' after %s arguments.", kind))
body := p.block()
return &FunStmt{name, args, body}
}
// statement -> exprStmt
//
// | whileStmt
// | forStmt
// | printStmt
// | blockStmt
// | breakStmt
// | ifStmt
// | returnStmt
// | env
func (p *Parser) statement() Stmt {
if p.match(PRINT) {
@ -94,6 +156,18 @@ func (p *Parser) statement() Stmt {
return p.whileStmt()
}
if p.match(FOR) {
return p.forStmt()
}
if p.match(BREAK) {
return p.breakStmt()
}
if p.match(RETURN) {
return p.returnStmt()
}
return p.exprStmt()
}
@ -121,8 +195,7 @@ func (p *Parser) printStmt() Stmt {
return &PrintStmt{expr}
}
// blockStmt -> "{" statement* "}"
func (p *Parser) blockStmt() Stmt {
func (p *Parser) block() []Stmt {
stmts := []Stmt{}
for !p.check(RIGHT_BRACE) && !p.isAtEnd() {
@ -131,7 +204,18 @@ func (p *Parser) blockStmt() Stmt {
p.consume(RIGHT_BRACE, "Unclosed block: Expected '}'.")
return &BlockStmt{stmts}
return stmts
}
// blockStmt -> "{" statement* "}"
func (p *Parser) blockStmt() *BlockStmt {
return &BlockStmt{p.block()}
}
// breakStmt -> break ";"
func (p *Parser) breakStmt() Stmt {
p.consume(SEMICOLON, "Expect ';' after break.")
return &BreakStmt{}
}
// if -> "if" "(" expression ")" statement ("else" statement)?
@ -160,12 +244,73 @@ func (p *Parser) whileStmt() Stmt {
return &WhileStmt{cond, body}
}
// for -> "for" ( "(" ( varDecl | exprStmt | ";" ) expression? ";" expression ")" )? statement
func (p *Parser) forStmt() Stmt {
if p.check(LEFT_BRACE) {
return &WhileStmt{&Literal{true}, p.statement()}
}
p.consume(LEFT_PAREN, "Expect '(' after 'for'.")
var init Stmt
if p.match(SEMICOLON) {
init = nil
} else if p.match(VAR) {
init = p.varDecl()
} else {
init = p.exprStmt()
}
var cond Expr
if !p.check(SEMICOLON) {
cond = p.expression()
}
p.consume(SEMICOLON, "Expect ';' after for loop condition;")
var incr Expr
if !p.check(RIGHT_PAREN) {
incr = p.expression()
}
p.consume(RIGHT_PAREN, "Expect ')' after for clauses;")
var body = p.statement()
if incr != nil {
body = &BlockStmt{[]Stmt{body, &ExprStmt{incr}}}
}
if cond == nil {
cond = &Literal{true}
}
body = &WhileStmt{cond, body}
if init != nil {
body = &BlockStmt{[]Stmt{init, body}}
}
return body
}
// env -> "env" ";"
func (p *Parser) envStmt() Stmt {
p.consume(SEMICOLON, "Expect ';' after 'env'.")
return &EnvStmt{}
}
// return -> "return" expression ";"
func (p *Parser) returnStmt() Stmt {
ret := p.expression()
p.consume(SEMICOLON, "Expect ';' after return;")
return &ReturnStmt{ret}
}
// expression -> assignment
func (p *Parser) expression() Expr {
return p.assignment()
@ -276,10 +421,52 @@ func (p *Parser) unary() Expr {
return &Unary{op, right}
}
return p.primary()
return p.call()
}
// primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER
// call -> primary ( "(" arguments? ")" )*
func (p *Parser) call() Expr {
expr := p.primary()
for {
if p.match(LEFT_PAREN) {
expr = p.arguments(expr)
} else {
break
}
}
return expr
}
// arguments -> expression ( "," expression )*
func (p *Parser) arguments(callee Expr) Expr {
arguments := []Expr{}
if !p.check(RIGHT_PAREN) {
for {
arguments = append(arguments, p.expression())
if !p.match(COMMA) {
break
}
}
}
paren := p.consume(RIGHT_PAREN, "Expect ')' after arguments.")
return &Call{callee, paren, arguments}
}
// primary -> IDENTIFIER
//
// | NUMBER
// | STRING
// | "true"
// | "false"
// | "nil"
// | "(" expression ")"
// | lambda
func (p *Parser) primary() Expr {
switch {
case p.match(FALSE):
@ -290,6 +477,10 @@ func (p *Parser) primary() Expr {
return &Literal{nil}
}
if p.match(FUN) {
return p.lambda()
}
if p.match(NUMBER, STRING) {
return &Literal{p.previous().literal}
}
@ -304,11 +495,39 @@ func (p *Parser) primary() Expr {
return &Grouping{expr}
}
// p.panic(&ParseError{p.peek(), "Expect expression"})
p.panic(&ParseError{p.peek(), "Expect expression"})
return nil
}
func (p *Parser) lambda() Expr {
name := p.previous()
p.consume(LEFT_PAREN, "Expect '(' before lambda arguments.")
args := []Token{}
for !p.check(RIGHT_PAREN) {
args = append(
args,
p.consume(
IDENTIFIER,
"Expect lambda argument.",
),
)
if p.check(COMMA) {
p.advance()
}
}
p.consume(RIGHT_PAREN, "Expect ')' after lambda arguments.")
p.consume(LEFT_BRACE, "Expect '{' before lambda body.")
body := p.block()
return &Lambda{name, args, body}
}
func (p *Parser) previous() Token {
return p.tokens[p.current-1]
}
@ -391,7 +610,6 @@ func (p *Parser) recover() {
func (p *Parser) panic(pe *ParseError) {
p.errors = append(p.errors, pe)
log.Println(pe)
panic(pe)
}

193
resolver.go Normal file
View file

@ -0,0 +1,193 @@
package main
type Scope map[string]bool
type Resolver struct {
interpreter *Interpreter
scopes Stack[Scope]
}
type ResolveError struct {
msg string
}
func (r *ResolveError) Error() string {
return r.msg
}
func newResolver(i *Interpreter) *Resolver {
return &Resolver{i, NewStack[Scope]()}
}
func (r *Resolver) resolveStmts(stmts ...Stmt) error {
for _, stmt := range stmts {
stmt.accept(r)
}
return nil
}
func (r *Resolver) resolveExprs(exprs ...Expr) error {
for _, expr := range exprs {
expr.accept(r)
}
return nil
}
func (r *Resolver) beginScope() {
r.scopes.Push(map[string]bool{})
}
func (r *Resolver) endScope() {
r.scopes.Pop()
}
func (r *Resolver) declare(token Token) {
if !r.scopes.Empty() {
r.scopes.Peek()[token.lexeme] = false
}
}
func (r *Resolver) define(token Token) {
if !r.scopes.Empty() {
r.scopes.Peek()[token.lexeme] = true
}
}
func (r *Resolver) visitBlockStmt(b *BlockStmt) {
r.beginScope()
r.resolveStmts(b.stmts...)
r.endScope()
}
func (r *Resolver) visitVarStmt(v *VarStmt) {
r.declare(v.name)
if v.initializer != nil {
r.resolveExprs(v.initializer)
}
r.define(v.name)
}
func (r *Resolver) visitVariable(v *Variable) any {
if !r.scopes.Empty() {
defined, declared := r.scopes.Peek()[v.name.lexeme]
if declared && !defined {
panic(&ResolveError{"Can't read local variable in its own initializer."})
}
}
r.resolveLocal(v, v.name)
return nil
}
func (r *Resolver) visitAssignment(a *Assign) any {
r.resolveExprs(a.value)
r.resolveLocal(a, a.variable)
return nil
}
func (r *Resolver) resolveLocal(expr Expr, name Token) {
for i := r.scopes.Size() - 1; i >= 0; i-- {
if _, exists := r.scopes.At(i)[name.lexeme]; exists {
r.interpreter.resolve(expr, r.scopes.Size()-1-i)
return
}
}
}
func (r *Resolver) visitFunStmt(fun *FunStmt) {
r.declare(fun.name)
r.define(fun.name)
r.resolveFun(fun)
}
func (r *Resolver) resolveFun(fun *FunStmt) {
r.beginScope()
for _, arg := range fun.args {
r.declare(arg)
r.define(arg)
}
r.resolveStmts(fun.body...)
r.endScope()
}
func (r *Resolver) visitExprStmt(es *ExprStmt) {
r.resolveExprs(es.expr)
}
func (r *Resolver) visitBreakStmt(b *BreakStmt) {}
func (r *Resolver) visitEnvStmt(b *EnvStmt) {}
func (r *Resolver) visitIfStmt(ifs *IfStmt) {
r.resolveExprs(ifs.cond)
r.resolveStmts(ifs.then)
if ifs.or != nil {
r.resolveStmts(ifs.or)
}
}
func (r *Resolver) visitPrintStmt(p *PrintStmt) {
r.resolveExprs(p.val)
}
func (r *Resolver) visitReturnStmt(ret *ReturnStmt) {
if ret.value != nil {
r.resolveExprs(ret.value)
}
}
func (r *Resolver) visitWhileStmt(w *WhileStmt) {
r.resolveExprs(w.cond)
r.resolveStmts(w.body)
}
func (r *Resolver) visitBinary(b *Binary) any {
r.resolveExprs(b.left)
r.resolveExprs(b.right)
return nil
}
func (r *Resolver) visitCall(c *Call) any {
r.resolveExprs(c.callee)
for _, arg := range c.args {
r.resolveExprs(arg)
}
return nil
}
func (r *Resolver) visitGrouping(g *Grouping) any {
r.resolveExprs(g.expression)
return nil
}
func (r *Resolver) visitLambda(l *Lambda) any {
r.beginScope()
for _, arg := range l.args {
r.declare(arg)
r.define(arg)
}
r.resolveStmts(l.body...)
r.endScope()
return nil
}
func (r *Resolver) visitLiteral(l *Literal) any {
return nil
}
func (r *Resolver) visitLogical(l *Logical) any {
r.resolveExprs(l.left)
r.resolveExprs(l.right)
return nil
}
func (r *Resolver) visitUnary(u *Unary) any {
r.resolveExprs(u.right)
return nil
}
func (r *Resolver) visitClassStmt(c *ClassStmt) {
r.declare(c.name)
r.define(c.name)
}

View file

@ -43,6 +43,7 @@ const (
// keywords
AND
BREAK
CLASS
ENV
ELSE
@ -65,6 +66,7 @@ const (
var keywords = map[string]TokenType{
"and": AND,
"break": BREAK,
"class": CLASS,
"else": ELSE,
"false": FALSE,

47
stack.go Normal file
View file

@ -0,0 +1,47 @@
package main
type Stack[Item any] interface {
Push(Item)
Pop() Item
Peek() Item
At(int) Item
Size() int
Empty() bool
}
type node[Item any] struct {
item Item
next *node[Item]
}
type stack[OfType any] []OfType
func NewStack[OfType any]() Stack[OfType] {
return &stack[OfType]{}
}
func (s *stack[Item]) Push(item Item) {
*s = append(*s, item)
}
func (s *stack[Item]) Pop() Item {
last := s.Peek()
*s = (*s)[:len(*s)-1]
return last
}
func (s *stack[Item]) At(idx int) Item {
return (*s)[idx]
}
func (s *stack[Item]) Peek() Item {
return (*s)[len(*s)-1]
}
func (s *stack[_]) Size() int {
return len(*s)
}
func (s *stack[_]) Empty() bool {
return s.Size() == 0
}

59
stmt.go
View file

@ -3,11 +3,15 @@ package main
type StmtVisitor interface {
visitIfStmt(i *IfStmt)
visitVarStmt(v *VarStmt)
visitEnvStmt(e *EnvStmt)
visitFunStmt(f *FunStmt)
visitExprStmt(es *ExprStmt)
visitPrintStmt(p *PrintStmt)
visitBlockStmt(b *BlockStmt)
visitEnvStmt(e *EnvStmt)
visitWhileStmt(w *WhileStmt)
visitBreakStmt(b *BreakStmt)
visitReturnStmt(r *ReturnStmt)
visitClassStmt(c *ClassStmt)
}
type Stmt interface {
@ -36,7 +40,7 @@ type EnvStmt struct{}
type IfStmt struct {
name Token
expr Expr
cond Expr
then Stmt
or Stmt
}
@ -46,13 +50,34 @@ type WhileStmt struct {
body Stmt
}
func (i *IfStmt) stmt() {}
func (e *EnvStmt) stmt() {}
func (vs *VarStmt) stmt() {}
func (es *ExprStmt) stmt() {}
func (p *PrintStmt) stmt() {}
func (b *BlockStmt) stmt() {}
func (w *WhileStmt) stmt() {}
type ClassStmt struct {
name Token
methods []FunStmt
}
type BreakStmt struct{}
type FunStmt struct {
name Token
args []Token
body []Stmt
}
type ReturnStmt struct {
value Expr
}
func (i *IfStmt) stmt() {}
func (f *FunStmt) stmt() {}
func (e *EnvStmt) stmt() {}
func (vs *VarStmt) stmt() {}
func (es *ExprStmt) stmt() {}
func (p *PrintStmt) stmt() {}
func (b *BlockStmt) stmt() {}
func (w *WhileStmt) stmt() {}
func (b *BreakStmt) stmt() {}
func (r *ReturnStmt) stmt() {}
func (c *ClassStmt) stmt() {}
func (p *PrintStmt) accept(v StmtVisitor) {
v.visitPrintStmt(p)
@ -81,3 +106,19 @@ func (e *EnvStmt) accept(v StmtVisitor) {
func (w *WhileStmt) accept(v StmtVisitor) {
v.visitWhileStmt(w)
}
func (b *BreakStmt) accept(v StmtVisitor) {
v.visitBreakStmt(b)
}
func (f *FunStmt) accept(v StmtVisitor) {
v.visitFunStmt(f)
}
func (r *ReturnStmt) accept(v StmtVisitor) {
v.visitReturnStmt(r)
}
func (c *ClassStmt) accept(v StmtVisitor) {
v.visitClassStmt(c)
}

36
tests/for.lox Normal file
View file

@ -0,0 +1,36 @@
print "full form -------------------------";
for (var i = 0; i < 100; i = i + 20) print i;
print "without init ----------------------";
var n = 100;
for (;n > 0; n = n - 20) print n;
print "only cond -----------------------";
var i = 1;
for (;i < 100;) {
print i;
i = i + 10;
}
print "inf ---------------------------";
var b = 0;
for {
print b;
b = b + 5;
if (b > 10) break;
print "after break";
}
print "fibonachi ------------------------";
var temp;
var first = 0;
for (var second = 1; first < 10000; second = temp + second) {
print first;
temp = first;
first = second;
}

71
tests/functions.lox Normal file
View file

@ -0,0 +1,71 @@
print "native function";
print clock();
fun count(n) {
print n;
if (n > 1) count(n - 1);
print n;
}
count(10);
fun hi(name, surname) {
print "hello, " + name + " " + surname + "!";
}
hi("John", "Doe");
fun re(turn) {
print "before return";
return turn;
print "should not be printed";
}
print re("turn");
fun sum(start, end) {
if (start == end) return start;
return start + sum(start + 1, end);
}
print sum(1, 3);
fun fib(n) {
if (n <= 1) return n;
return fib(n - 2) + fib(n - 1);
}
for (var i = 1; i <= 10; i = i + 1) {
print fib(i);
}
fun makeCounter() {
var i = 0;
fun incr() {
i = i + 1;
print i;
}
return incr;
}
var counter = makeCounter();
counter();
counter();
fun thrice(fn) {
for (var i = 1; i <= 3; i = i + 1) {
fn(i);
}
}
thrice(fun (a) { print a; });
print fun () { return "hello, "; }() + "world";

View file

@ -0,0 +1,12 @@
var a = "global";
{
fun showA() {
print a;
}
showA();
var a = "inner v2";
showA();
}

35
tests/while.lox Normal file
View file

@ -0,0 +1,35 @@
var iterator = 100;
{
while (iterator > 0) {
{
print iterator;
iterator = iterator - 2;
}
}
}
{
while (iterator < 100) {
{
{
print iterator;
iterator = iterator + 2;
if (iterator > 50) break;
print "shoud not be printed after 50";
}
print "OUTER also shoud not be printed after 50";
}
}
}
while (iterator < 100) iterator = iterator + 2;
print iterator;

View file

@ -31,28 +31,29 @@ func _() {
_ = x[STRING-20]
_ = x[NUMBER-21]
_ = x[AND-22]
_ = x[CLASS-23]
_ = x[ENV-24]
_ = x[ELSE-25]
_ = x[FALSE-26]
_ = x[FUN-27]
_ = x[FOR-28]
_ = x[IF-29]
_ = x[NIL-30]
_ = x[OR-31]
_ = x[PRINT-32]
_ = x[RETURN-33]
_ = x[SUPER-34]
_ = x[THIS-35]
_ = x[TRUE-36]
_ = x[VAR-37]
_ = x[WHILE-38]
_ = x[EOF-39]
_ = x[BREAK-23]
_ = x[CLASS-24]
_ = x[ENV-25]
_ = x[ELSE-26]
_ = x[FALSE-27]
_ = x[FUN-28]
_ = x[FOR-29]
_ = x[IF-30]
_ = x[NIL-31]
_ = x[OR-32]
_ = x[PRINT-33]
_ = x[RETURN-34]
_ = x[SUPER-35]
_ = x[THIS-36]
_ = x[TRUE-37]
_ = x[VAR-38]
_ = x[WHILE-39]
_ = x[EOF-40]
}
const _TokenType_name = "LEFT_PARENRIGHT_PARENLEFT_BRACERIGHT_BRACECOMMADOTMINUSPLUSSEMICOLONSLASHSTARBANGBANG_EQUALEQUALEQUAL_EQUALGREATERGREATER_EQUALLESSLESS_EQUALIDENTIFIERSTRINGNUMBERANDCLASSENVELSEFALSEFUNFORIFNILORPRINTRETURNSUPERTHISTRUEVARWHILEEOF"
const _TokenType_name = "LEFT_PARENRIGHT_PARENLEFT_BRACERIGHT_BRACECOMMADOTMINUSPLUSSEMICOLONSLASHSTARBANGBANG_EQUALEQUALEQUAL_EQUALGREATERGREATER_EQUALLESSLESS_EQUALIDENTIFIERSTRINGNUMBERANDBREAKCLASSENVELSEFALSEFUNFORIFNILORPRINTRETURNSUPERTHISTRUEVARWHILEEOF"
var _TokenType_index = [...]uint8{0, 10, 21, 31, 42, 47, 50, 55, 59, 68, 73, 77, 81, 91, 96, 107, 114, 127, 131, 141, 151, 157, 163, 166, 171, 174, 178, 183, 186, 189, 191, 194, 196, 201, 207, 212, 216, 220, 223, 228, 231}
var _TokenType_index = [...]uint8{0, 10, 21, 31, 42, 47, 50, 55, 59, 68, 73, 77, 81, 91, 96, 107, 114, 127, 131, 141, 151, 157, 163, 166, 171, 176, 179, 183, 188, 191, 194, 196, 199, 201, 206, 212, 217, 221, 225, 228, 233, 236}
func (i TokenType) String() string {
if i < 0 || i >= TokenType(len(_TokenType_index)-1) {