diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5b19889 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +glox \ No newline at end of file diff --git a/ast_string.go b/ast_string.go index bb115e8..71a0479 100644 --- a/ast_string.go +++ b/ast_string.go @@ -6,12 +6,13 @@ import ( ) type AstStringer struct { - str strings.Builder + str strings.Builder + stmts []Stmt } -func (as AstStringer) String(stmts []Stmt) string { +func (as AstStringer) String() string { - for _, stmt := range stmts { + for _, stmt := range as.stmts { stmt.accept(&as) } @@ -80,3 +81,14 @@ func (as *AstStringer) visitVarStmt(vs *VarStmt) { as.str.WriteString(fmt.Sprintf("(var %v)", vs.name.literal)) } } + +func (as *AstStringer) visitBlockStmt(b *BlockStmt) { + as.str.WriteString("(block ") + + for _, stmt := range b.stmts { + stmt.accept(as) + } + + as.str.WriteString(")") + +} diff --git a/env.go b/env.go new file mode 100644 index 0000000..38d9b98 --- /dev/null +++ b/env.go @@ -0,0 +1,37 @@ +package main + +type Environment struct { + values map[string]any + parent *Environment +} + +func newEnvironment(parent *Environment) *Environment { + return &Environment{values: map[string]any{}, parent: parent} +} + +func (env *Environment) get(key string) any { + if found, ok := env.values[key]; ok { + return found + } + + if env.parent != nil { + return env.parent.get(key) + } + + return nil +} + +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) { + env.values[key] = val +} diff --git a/glox b/glox deleted file mode 100755 index 8b9205c..0000000 Binary files a/glox and /dev/null differ diff --git a/glox.go b/glox.go index a24620d..e25252f 100644 --- a/glox.go +++ b/glox.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "fmt" "log" "os" ) @@ -32,7 +33,7 @@ func (gl *Glox) runPrompt() { if !scanner.Scan() { break } - gl.run(scanner.Bytes(), true) + gl.run(scanner.Bytes()) } } @@ -43,32 +44,15 @@ func (gl *Glox) runFile(path string) { log.Fatal(err) } - runErrors := gl.run(file, false) - - if len(runErrors) != 0 { - for _, e := range runErrors { - log.Print(e) - } - - os.Exit(1) - } - + gl.run(file) } -func (gl *Glox) run(source []byte, interactive bool) []error { - tokens, err := newScanner(source).scan() +func (gl *Glox) run(source []byte) { + tokens, _ := newScanner(source).scan() - if err != nil { - return []error{err} - } + stmts, _ := newParser(tokens).parse() - stmts, parseErrs := newParser(tokens).parse() + fmt.Println(AstStringer{stmts: stmts}) - if len(parseErrs) != 0 && !interactive { - return parseErrs - } - - println(AstStringer{}.String(stmts)) - - return gl.Interpreter.interpret(stmts) + gl.Interpreter.interpret(stmts) } diff --git a/interpreter.go b/interpreter.go index 53807f7..653c95f 100644 --- a/interpreter.go +++ b/interpreter.go @@ -7,7 +7,7 @@ import ( ) type Interpreter struct { - env map[string]any + env *Environment errors []error } @@ -21,7 +21,7 @@ func (re *RuntimeError) Error() string { } func newInterpreter() *Interpreter { - return &Interpreter{env: make(map[string]any)} + return &Interpreter{env: newEnvironment(nil)} } func (i *Interpreter) interpret(stmts []Stmt) []error { @@ -118,25 +118,30 @@ func (i *Interpreter) visitUnary(u *Unary) any { } func (i *Interpreter) visitVariable(v *Variable) any { - if found, ok := i.env[v.name.lexeme]; ok { - return found + + val := i.env.get(v.name.lexeme) + + if val == nil { + i.panic(&RuntimeError{v.name, fmt.Sprintf("Can't evaluate: Undefined variable '%s'.", v.name.lexeme)}) + return nil } - i.panic(&RuntimeError{v.name, fmt.Sprintf("Undefined variable '%s'.", v.name.lexeme)}) - - return nil + return val } func (i *Interpreter) visitAssignment(a *Assign) any { - if _, ok := i.env[a.variable.lexeme]; ok { - val := i.evaluate(a.value) - i.env[a.variable.lexeme] = val - return val + + if !i.env.exists(a.variable.lexeme) { + i.panic(&RuntimeError{a.variable, fmt.Sprintf("Can't assign: undefined variable '%s'.", a.variable.lexeme)}) + + return nil } - i.panic(&RuntimeError{a.variable, fmt.Sprintf("Undefined variable '%s'.", a.variable.lexeme)}) + val := i.evaluate(a.value) - return nil + i.env.set(a.variable.lexeme, val) + + return val } func (i *Interpreter) visitPrintStmt(p *PrintStmt) { @@ -155,7 +160,19 @@ func (i *Interpreter) visitVarStmt(v *VarStmt) { val = i.evaluate(v.initializer) } - i.env[v.name.lexeme] = val + i.env.set(v.name.lexeme, val) +} + +func (i *Interpreter) visitBlockStmt(b *BlockStmt) { + + parentEnv := i.env + i.env = newEnvironment(parentEnv) + + for _, stmt := range b.stmts { + stmt.accept(i) + } + + i.env = parentEnv } func (i *Interpreter) panic(re *RuntimeError) { diff --git a/parser.go b/parser.go index aa7e956..d985909 100644 --- a/parser.go +++ b/parser.go @@ -66,11 +66,16 @@ func (p *Parser) varDecl() Stmt { return &VarStmt{name, initializer} } -// statement -> exprStmt | printStmt +// statement -> exprStmt | printStmt | block func (p *Parser) statement() Stmt { if p.match(PRINT) { return p.printStmt() } + + if p.match(LEFT_BRACE) { + return p.block() + } + return p.exprStmt() } @@ -88,6 +93,19 @@ func (p *Parser) printStmt() Stmt { return &PrintStmt{expr} } +// block -> "{" statement* "}" +func (p *Parser) block() Stmt { + + stmts := []Stmt{} + for !p.check(RIGHT_BRACE) { + stmts = append(stmts, p.declaration()) + } + + p.consume(RIGHT_BRACE, "Unclosed block: Expected '}'.") + + return &BlockStmt{stmts} +} + // expression -> assignment func (p *Parser) expression() Expr { return p.assignment() @@ -199,7 +217,7 @@ func (p *Parser) primary() Expr { return &Grouping{expr} } - p.panic(&ParseError{p.peek(), "Expect expression"}) + // p.panic(&ParseError{p.peek(), "Expect expression"}) return nil } diff --git a/stmt.go b/stmt.go index 63de63e..428f043 100644 --- a/stmt.go +++ b/stmt.go @@ -1,9 +1,10 @@ package main type StmtVisitor interface { - visitPrintStmt(p *PrintStmt) - visitExprStmt(es *ExprStmt) visitVarStmt(v *VarStmt) + visitExprStmt(es *ExprStmt) + visitPrintStmt(p *PrintStmt) + visitBlockStmt(b *BlockStmt) } type Stmt interface { @@ -24,9 +25,14 @@ type VarStmt struct { initializer Expr } -func (p *PrintStmt) stmt() {} -func (es *ExprStmt) stmt() {} +type BlockStmt struct { + stmts []Stmt +} + func (vs *VarStmt) stmt() {} +func (es *ExprStmt) stmt() {} +func (p *PrintStmt) stmt() {} +func (b *BlockStmt) stmt() {} func (p *PrintStmt) accept(v StmtVisitor) { v.visitPrintStmt(p) @@ -39,3 +45,7 @@ func (se *ExprStmt) accept(v StmtVisitor) { func (vs *VarStmt) accept(v StmtVisitor) { v.visitVarStmt(vs) } + +func (b *BlockStmt) accept(v StmtVisitor) { + v.visitBlockStmt(b) +} diff --git a/tests/blocks.lox b/tests/blocks.lox new file mode 100644 index 0000000..824db36 --- /dev/null +++ b/tests/blocks.lox @@ -0,0 +1,22 @@ +var a = "global a"; +var b = "global b"; +var c = "global c"; + +{ + var a = "outer a"; + var b = "outer b"; + { + var a = "inner a"; + print a; + print b; + print c; + } + + print a; + print b; + print c; +} + +print a; +print b; +print c;