Compare commits

..

No commits in common. "894ef20cf5aefb39d914a1fa6d5c046a5d8c8a47" and "e8aeb6d5c5eb7e01a09512233d4f66ec1efb551f" have entirely different histories.

20 changed files with 139 additions and 1086 deletions

View file

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

View file

@ -1,44 +1,3 @@
# GLOX
Lox interpreter written i Go.
This is ongoing project where I try to recreate "JLOX" language in Go instead of Java.
It completly based on instructions from Bob Nystrom's book ["Crafting interpretters"](https://www.craftinginterpreters.com/).
Currently supported:
- REPL
- variables
- functions
- functions as values
- closures
- exceptions
- classes
- scoped blocks
- conditions
- loops
- basic arithmetic
- AST printing
- error recovery
TODO:
- Virtual Machine
- Intermediate representaion
## Run
Simply compile and run binary. By default you will get REPL session.
```
go build
./glox
> print 1 + 1;
```
If you want to run script - just pass script path as an argument:
```
go build
./glox ./tests/functions.lox
```
## Testing
TODO

View file

@ -10,18 +10,6 @@ type AstStringer struct {
stmts []Stmt
}
func (as *AstStringer) visitGet(g *Get) any {
as.str.WriteString(fmt.Sprintf("(get %s)", g.name.lexeme))
return nil
}
func (as *AstStringer) visitSet(s *Set) any {
as.str.WriteString(fmt.Sprintf("(set %s ", s.name.lexeme))
s.obj.accept(as)
as.str.WriteString(")")
return nil
}
func (as AstStringer) String() string {
for _, stmt := range as.stmts {
@ -83,43 +71,6 @@ 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)
@ -153,7 +104,7 @@ func (as *AstStringer) visitBlockStmt(b *BlockStmt) {
func (as *AstStringer) visitIfStmt(i *IfStmt) {
as.str.WriteString("(if ")
i.cond.accept(as)
i.expr.accept(as)
as.str.WriteString(" ")
i.then.accept(as)
if i.or != nil {
@ -178,31 +129,3 @@ func (as *AstStringer) visitWhileStmt(w *WhileStmt) {
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)
}

View file

@ -1,6 +0,0 @@
package main
type Callable interface {
arity() int
call(i *Interpreter, args ...any) (ret any)
}

View file

@ -1,37 +0,0 @@
package main
import "fmt"
type Class struct {
name string
}
type ClassInstance struct {
klass *Class
props map[string]any
}
func (c *ClassInstance) String() string {
return fmt.Sprintf("instance of %s", c.klass.name)
}
func (c *ClassInstance) get(name string) (any, bool) {
val, ok := c.props[name]
return val, ok
}
func (c *ClassInstance) set(name string, val any) {
c.props[name] = val
}
func (c *Class) arity() int {
return 0
}
func (c *Class) call(i *Interpreter, args ...any) (ret any) {
return &ClassInstance{c, map[string]any{}}
}
func (c *Class) String() string {
return c.name
}

55
env.go
View file

@ -1,14 +1,12 @@
package main
import "fmt"
type Environment struct {
values map[string]any
enclosing *Environment
parent *Environment
}
func newEnvironment(enclosing *Environment) *Environment {
return &Environment{map[string]any{}, enclosing}
func newEnvironment(parent *Environment) *Environment {
return &Environment{values: map[string]any{}, parent: parent}
}
func (env *Environment) get(key string) any {
@ -16,8 +14,8 @@ func (env *Environment) get(key string) any {
return found
}
if env.enclosing != nil {
return env.enclosing.get(key)
if env.parent != nil {
return env.parent.get(key)
}
return nil
@ -25,39 +23,20 @@ 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) define(key string, val any) {
func (env *Environment) set(key string, val any) {
if env.parent != nil && env.parent.exists(key) {
env.parent.set(key, val)
}
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
}

47
expr.go
View file

@ -1,17 +1,13 @@
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
visitVariable(v *Variable) any
visitLogical(l *Logical) any
visitAssignment(a *Assign) any
visitGet(g *Get) any
visitSet(s *Set) any
}
type Expr interface {
@ -53,40 +49,13 @@ type Logical struct {
right Expr
}
type Call struct {
callee Expr
paren Token
args []Expr
}
type Lambda struct {
name Token
args []Token
body []Stmt
}
type Get struct {
name Token
obj Expr
}
type Set struct {
name Token
obj Expr
value Expr
}
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() {}
func (l *Logical) expr() {}
func (g *Get) expr() {}
func (s *Set) expr() {}
func (u *Unary) accept(v ExprVisitor) any {
return v.visitUnary(u)
@ -115,19 +84,3 @@ 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)
}
func (g *Get) accept(v ExprVisitor) any {
return v.visitGet(g)
}
func (s *Set) accept(v ExprVisitor) any {
return v.visitSet(s)
}

63
expr_rpn.go Normal file
View file

@ -0,0 +1,63 @@
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
}

View file

@ -1,41 +0,0 @@
package main
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}
}

View file

@ -1,17 +0,0 @@
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{})
}

36
glox.go
View file

@ -28,22 +28,12 @@ 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
}
doRun(scanner.Bytes())
gl.run(scanner.Bytes())
}
}
@ -58,29 +48,11 @@ func (gl *Glox) runFile(path string) {
}
func (gl *Glox) run(source []byte) {
tokens, err := newScanner(source).scan()
tokens, _ := newScanner(source).scan()
if err != nil {
panic(err)
}
stmts, parseErrs := newParser(tokens).parse()
if parseErrs != nil {
panic(parseErrs)
}
stmts, _ := newParser(tokens).parse()
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)
}
gl.Interpreter.interpret(stmts)
}

View file

@ -1,7 +1,6 @@
package main
import (
"errors"
"fmt"
"log"
"reflect"
@ -10,8 +9,6 @@ import (
type Interpreter struct {
env *Environment
globals *Environment
locals map[Expr]int
errors []error
brk bool
}
@ -21,30 +18,15 @@ 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 {
globals := newEnvironment(nil)
defineGlobals(globals)
return &Interpreter{
env: globals,
globals: globals,
locals: map[Expr]int{},
errors: []error{},
brk: false,
}
return &Interpreter{env: newEnvironment(nil), errors: []error{}, brk: false}
}
func (i *Interpreter) interpret(stmts []Stmt) error {
func (i *Interpreter) interpret(stmts []Stmt) []error {
defer i.recover()
i.errors = []error{}
@ -53,7 +35,7 @@ func (i *Interpreter) interpret(stmts []Stmt) error {
stmt.accept(i)
}
return errors.Join(i.errors...)
return i.errors
}
func (i *Interpreter) recover() {
@ -138,22 +120,25 @@ func (i *Interpreter) visitUnary(u *Unary) any {
}
func (i *Interpreter) visitVariable(v *Variable) any {
return i.lookUpVariable(v.name, v)
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
}
func (i *Interpreter) visitAssignment(a *Assign) any {
if !i.env.exists(a.variable.lexeme) {
i.panic(&RuntimeError{a.variable, fmt.Sprintf("Can't assign: undefined variable '%s'.", a.variable.lexeme)})
}
val := i.evaluate(a.value)
distance, isLocal := i.locals[a]
if isLocal {
i.env.assignAt(distance, a.variable, val)
return val
}
i.env.set(a.variable.lexeme, val)
err := i.globals.assign(a.variable, val)
if err != nil {
i.panic(err)
}
return val
}
@ -171,96 +156,6 @@ 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) visitGet(g *Get) any {
object := i.evaluate(g.obj)
instance, ok := object.(*ClassInstance)
if !ok {
i.panic(&RuntimeError{g.name, "Only class instances can have properties"})
}
val, ok := instance.get(g.name.lexeme)
if !ok {
i.panic(&RuntimeError{g.name, fmt.Sprintf("Undefined propery %s", g.name.lexeme)})
}
return val
}
func (i *Interpreter) visitSet(s *Set) any {
object := i.evaluate(s.obj)
instance, ok := object.(*ClassInstance)
if !ok {
i.panic(&RuntimeError{s.name, "Only class instances have fields."})
}
value := i.evaluate(s.value)
instance.set(s.name.lexeme, value)
return value
}
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))
}
@ -277,25 +172,15 @@ func (i *Interpreter) visitVarStmt(v *VarStmt) {
val = i.evaluate(v.initializer)
}
i.env.define(v.name.lexeme, val)
i.env.set(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 = current
i.env = newEnvironment(parentEnv)
// need to restore environment after
// panic(Return) in visitReturnStmt
defer func() {
i.env = parentEnv
}()
for _, stmt := range stmts {
for _, stmt := range b.stmts {
if i.brk {
break
@ -304,6 +189,7 @@ func (i *Interpreter) executeBlock(stmts []Stmt, current *Environment) {
stmt.accept(i)
}
i.env = parentEnv
}
func (i *Interpreter) visitBreakStmt(b *BreakStmt) {
@ -311,7 +197,7 @@ func (i *Interpreter) visitBreakStmt(b *BreakStmt) {
}
func (i *Interpreter) visitIfStmt(iff *IfStmt) {
if isTruthy(i.evaluate(iff.cond)) {
if isTruthy(i.evaluate(iff.expr)) {
iff.then.accept(i)
} else if iff.or != nil {
@ -327,11 +213,9 @@ func (i *Interpreter) visitEnvStmt(e *EnvStmt) {
for walker != nil {
flatten = slices.Insert(flatten, 0, walker)
walker = walker.enclosing
walker = walker.parent
}
fmt.Printf("globals: %+v\n", *i.globals)
for ident, e := range flatten {
fmt.Printf("%*s", ident, "")
fmt.Printf("%+v\n", *e)
@ -350,20 +234,6 @@ func (i *Interpreter) visitWhileStmt(w *WhileStmt) {
}
}
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)

186
parser.go
View file

@ -1,8 +1,8 @@
package main
import (
"errors"
"fmt"
"log"
)
type Parser struct {
@ -17,12 +17,7 @@ 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 {
@ -33,7 +28,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{}
@ -45,86 +40,32 @@ func (p *Parser) parse() ([]Stmt, error) {
}
}
return stmts, errors.Join(p.errors...)
return stmts, p.errors
}
// declaration ->
// varDecl | funDecl | classDecl | statement
// declaration -> varDecl | 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) {
initializer = p.expression()
}
p.consume(SEMICOLON, "Expect ';' after expression in var declaration;")
p.consume(SEMICOLON, "Expect ';' after expression.")
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
@ -133,7 +74,6 @@ func (p *Parser) function(kind string) *FunStmt {
// | blockStmt
// | breakStmt
// | ifStmt
// | returnStmt
// | env
func (p *Parser) statement() Stmt {
if p.match(PRINT) {
@ -164,17 +104,13 @@ func (p *Parser) statement() Stmt {
return p.breakStmt()
}
if p.match(RETURN) {
return p.returnStmt()
}
return p.exprStmt()
}
// exprStmt -> expression ";"
func (p *Parser) exprStmt() Stmt {
expr := p.expression()
p.consume(SEMICOLON, "Expect ';' after statement.")
p.consume(SEMICOLON, "Expect ';' after expression.")
if expr == nil {
return nil
@ -191,11 +127,12 @@ func (p *Parser) printStmt() Stmt {
p.panic(&ParseError{p.previous(), "Expect expression after 'print'"})
}
p.consume(SEMICOLON, "Expect ';' after print expression.")
p.consume(SEMICOLON, "Expect ';' after expression.")
return &PrintStmt{expr}
}
func (p *Parser) block() []Stmt {
// blockStmt -> "{" statement* "}"
func (p *Parser) blockStmt() Stmt {
stmts := []Stmt{}
for !p.check(RIGHT_BRACE) && !p.isAtEnd() {
@ -204,12 +141,7 @@ func (p *Parser) block() []Stmt {
p.consume(RIGHT_BRACE, "Unclosed block: Expected '}'.")
return stmts
}
// blockStmt -> "{" statement* "}"
func (p *Parser) blockStmt() *BlockStmt {
return &BlockStmt{p.block()}
return &BlockStmt{stmts}
}
// breakStmt -> break ";"
@ -304,19 +236,12 @@ func (p *Parser) envStmt() Stmt {
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()
}
// assignment -> (call ".")? IDENTIFIER "=" assignment | or
// assignment -> IDENTIFIER "=" assignment | or
func (p *Parser) assignment() Expr {
expr := p.or()
@ -326,12 +251,11 @@ func (p *Parser) assignment() Expr {
if variable, ok := expr.(*Variable); ok {
return &Assign{variable.name, val}
} else if get, ok := expr.(*Get); ok {
return &Set{get.name, get.obj, val}
}
p.panic(&ParseError{eq, "Invalid assignment target."})
}
return expr
}
@ -422,55 +346,10 @@ func (p *Parser) unary() Expr {
return &Unary{op, right}
}
return p.call()
return p.primary()
}
// call -> primary ( "(" arguments? ")" | "." IDENTIFIER )*
func (p *Parser) call() Expr {
expr := p.primary()
for {
if p.match(LEFT_PAREN) {
expr = p.arguments(expr)
} else if p.match(DOT) {
name := p.consume(IDENTIFIER, "Expect property name after '.'")
expr = &Get{name, 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
// primary -> NUMBER | STRING | "true" | "false" | "nil" | "(" expression ")" | IDENTIFIER
func (p *Parser) primary() Expr {
switch {
case p.match(FALSE):
@ -481,10 +360,6 @@ 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}
}
@ -504,34 +379,6 @@ func (p *Parser) primary() Expr {
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]
}
@ -614,6 +461,7 @@ func (p *Parser) recover() {
func (p *Parser) panic(pe *ParseError) {
p.errors = append(p.errors, pe)
log.Println(pe)
panic(pe)
}

View file

@ -1,13 +0,0 @@
package main
import "testing"
func TestSimpleParser(t *testing.T) {
s := newScanner([]byte("print 1;"))
tokens, _ := s.scan()
p, _ := newParser(tokens).parse()
if p == nil {
t.Fatal("cant parse")
}
}

View file

@ -1,204 +0,0 @@
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)
}
func (r *Resolver) visitGet(g *Get) any {
r.resolveExprs(g.obj)
return nil
}
func (r *Resolver) visitSet(s *Set) any {
r.resolveExprs(s.value)
r.resolveExprs(s.obj)
return nil
}

View file

@ -1,47 +0,0 @@
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
}

37
stmt.go
View file

@ -3,15 +3,12 @@ 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 {
@ -40,7 +37,7 @@ type EnvStmt struct{}
type IfStmt struct {
name Token
cond Expr
expr Expr
then Stmt
or Stmt
}
@ -50,25 +47,9 @@ type WhileStmt struct {
body 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() {}
@ -76,8 +57,6 @@ 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)
@ -110,15 +89,3 @@ func (w *WhileStmt) accept(v StmtVisitor) {
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)
}

View file

@ -1,32 +0,0 @@
class Car {
}
print Car;
var opel = Car();
env;
print opel;
opel.name = "Opel";
class Engine {}
print Engine;
var eng = Engine();
eng.power = "200hp";
opel.engine = eng;
print opel.engine.power;
opel.run = fun () {
}
opel.run();

View file

@ -1,71 +0,0 @@
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

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