diff --git a/ast_string.go b/ast_string.go index 71a0479..5f3b976 100644 --- a/ast_string.go +++ b/ast_string.go @@ -62,6 +62,24 @@ func (as *AstStringer) visitAssignment(a *Assign) any { return nil } +func (as *AstStringer) visitLogicalOr(l *LogicalOr) any { + as.str.WriteString("(or ") + l.left.accept(as) + as.str.WriteString(" ") + l.right.accept(as) + as.str.WriteString(")") + return nil +} + +func (as *AstStringer) visitLogicalAnd(l *LogicalAnd) any { + as.str.WriteString("(and ") + l.left.accept(as) + as.str.WriteString(" ") + l.right.accept(as) + as.str.WriteString(")") + return nil +} + func (as *AstStringer) visitPrintStmt(p *PrintStmt) { as.str.WriteString("(print ") p.val.accept(as) @@ -92,3 +110,19 @@ func (as *AstStringer) visitBlockStmt(b *BlockStmt) { as.str.WriteString(")") } + +func (as *AstStringer) visitIfStmt(i *IfStmt) { + as.str.WriteString("(if ") + i.expr.accept(as) + as.str.WriteString(" ") + i.then.accept(as) + if i.or != nil { + as.str.WriteString(" ") + i.or.accept(as) + } + as.str.WriteString(")") +} + +func (as *AstStringer) visitEnvStmt(e *EnvStmt) { + as.str.WriteString("(env)") +} diff --git a/expr.go b/expr.go index 730b558..0f05096 100644 --- a/expr.go +++ b/expr.go @@ -6,7 +6,9 @@ type ExprVisitor interface { visitLiteral(l *Literal) any visitGrouping(g *Grouping) any visitVariable(v *Variable) any + visitLogicalOr(l *LogicalOr) any visitAssignment(a *Assign) any + visitLogicalAnd(l *LogicalAnd) any } type Expr interface { @@ -42,12 +44,26 @@ type Assign struct { value Expr } -func (u *Unary) expr() {} -func (b *Binary) expr() {} -func (l *Literal) expr() {} -func (g *Grouping) expr() {} -func (v *Variable) expr() {} -func (a *Assign) expr() {} +type LogicalOr struct { + left Expr + or Token + right Expr +} + +type LogicalAnd struct { + left Expr + and Token + right Expr +} + +func (u *Unary) expr() {} +func (a *Assign) expr() {} +func (b *Binary) expr() {} +func (l *Literal) expr() {} +func (g *Grouping) expr() {} +func (v *Variable) expr() {} +func (l *LogicalOr) expr() {} +func (l *LogicalAnd) expr() {} func (u *Unary) accept(v ExprVisitor) any { return v.visitUnary(u) @@ -72,3 +88,11 @@ func (va *Variable) accept(v ExprVisitor) any { func (a *Assign) accept(v ExprVisitor) any { return v.visitAssignment(a) } + +func (l *LogicalOr) accept(v ExprVisitor) any { + return v.visitLogicalOr(l) +} + +func (l *LogicalAnd) accept(v ExprVisitor) any { + return v.visitLogicalAnd(l) +} diff --git a/expr_rpn.go b/expr_rpn.go index 53b67bc..3f3be95 100644 --- a/expr_rpn.go +++ b/expr_rpn.go @@ -54,3 +54,17 @@ func (as *ExprToRPN) visitAssignment(a *Assign) any { as.str.WriteString(fmt.Sprintf("%v %s =", a.value, a.variable.lexeme)) return nil } + +func (as *ExprToRPN) visitLogicalOr(lo *LogicalOr) any { + lo.left.accept(as) + lo.right.accept(as) + as.str.WriteString(" or") + return nil +} + +func (as *ExprToRPN) visitLogicalAnd(la *LogicalAnd) any { + la.left.accept(as) + la.right.accept(as) + as.str.WriteString(" and") + return nil +} diff --git a/interpreter.go b/interpreter.go index 653c95f..24ddef4 100644 --- a/interpreter.go +++ b/interpreter.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "reflect" + "slices" ) type Interpreter struct { @@ -119,13 +120,11 @@ func (i *Interpreter) visitUnary(u *Unary) any { func (i *Interpreter) visitVariable(v *Variable) any { - 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 + 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 } @@ -133,8 +132,6 @@ 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)}) - - return nil } val := i.evaluate(a.value) @@ -144,6 +141,14 @@ func (i *Interpreter) visitAssignment(a *Assign) any { return val } +func (i *Interpreter) visitLogicalOr(lo *LogicalOr) any { + return isTruthy(i.evaluate(lo.left)) || isTruthy(i.evaluate(lo.right)) +} + +func (i *Interpreter) visitLogicalAnd(la *LogicalAnd) any { + return isTruthy(i.evaluate(la.left)) && isTruthy(i.evaluate(la.right)) +} + func (i *Interpreter) visitPrintStmt(p *PrintStmt) { fmt.Printf("%v\n", i.evaluate(p.val)) } @@ -175,6 +180,33 @@ func (i *Interpreter) visitBlockStmt(b *BlockStmt) { i.env = parentEnv } +func (i *Interpreter) visitIfStmt(iff *IfStmt) { + if isTruthy(i.evaluate(iff.expr)) { + iff.then.accept(i) + + } else if iff.or != nil { + iff.or.accept(i) + } +} + +func (i *Interpreter) visitEnvStmt(e *EnvStmt) { + + walker := i.env + + flatten := []*Environment{} + + for walker != nil { + flatten = slices.Insert(flatten, 0, walker) + walker = walker.parent + } + + for ident, e := range flatten { + fmt.Printf("%*s", ident, "") + fmt.Printf("%+v\n", *e) + } + +} + func (i *Interpreter) panic(re *RuntimeError) { i.errors = append(i.errors, re) log.Println(re) @@ -198,6 +230,11 @@ func (i *Interpreter) checkIfFloats(op Token, a any, b any) { } func isFloats(a any, b any) bool { + + if a == nil || b == nil { + return false + } + ltype := reflect.TypeOf(a) rtype := reflect.TypeOf(b) @@ -205,6 +242,11 @@ func isFloats(a any, b any) bool { } func isStrings(a any, b any) bool { + + if a == nil || b == nil { + return false + } + ltype := reflect.TypeOf(a) rtype := reflect.TypeOf(b) diff --git a/parser.go b/parser.go index d985909..9129804 100644 --- a/parser.go +++ b/parser.go @@ -66,7 +66,7 @@ func (p *Parser) varDecl() Stmt { return &VarStmt{name, initializer} } -// statement -> exprStmt | printStmt | block +// statement -> exprStmt | printStmt | block | ifStmt | env func (p *Parser) statement() Stmt { if p.match(PRINT) { return p.printStmt() @@ -76,6 +76,14 @@ func (p *Parser) statement() Stmt { return p.block() } + if p.match(IF) { + return p.ifStmt() + } + + if p.match(ENV) { + return p.envStmt() + } + return p.exprStmt() } @@ -83,12 +91,22 @@ func (p *Parser) statement() Stmt { func (p *Parser) exprStmt() Stmt { expr := p.expression() p.consume(SEMICOLON, "Expect ';' after expression.") + + if expr == nil { + return nil + } + return &ExprStmt{expr} } // printStmt -> "print" expression ";" func (p *Parser) printStmt() Stmt { expr := p.expression() + + if expr == nil { + p.panic(&ParseError{p.previous(), "Expect expression after 'print'"}) + } + p.consume(SEMICOLON, "Expect ';' after expression.") return &PrintStmt{expr} } @@ -97,7 +115,7 @@ func (p *Parser) printStmt() Stmt { func (p *Parser) block() Stmt { stmts := []Stmt{} - for !p.check(RIGHT_BRACE) { + for !p.check(RIGHT_BRACE) && !p.isAtEnd() { stmts = append(stmts, p.declaration()) } @@ -106,14 +124,36 @@ func (p *Parser) block() Stmt { return &BlockStmt{stmts} } +// if -> "if" "(" expression ")" statement ("else" statement)? +func (p *Parser) ifStmt() Stmt { + name := p.previous() + p.consume(LEFT_PAREN, "Expect '(' after 'if'.") + expr := p.expression() + p.consume(RIGHT_PAREN, "Expect ')' after 'if' condition.") + then := p.statement() + + var or Stmt = nil + if p.match(ELSE) { + or = p.statement() + } + + return &IfStmt{name, expr, then, or} +} + +// env -> "env" ";" +func (p *Parser) envStmt() Stmt { + p.consume(SEMICOLON, "Expect ';' after 'env'.") + return &EnvStmt{} +} + // expression -> assignment func (p *Parser) expression() Expr { return p.assignment() } -// assignment -> IDENTIFIER "=" assignment | equality +// assignment -> IDENTIFIER "=" assignment | logicalOr func (p *Parser) assignment() Expr { - expr := p.equality() + expr := p.logicalOr() if p.match(EQUAL) { eq := p.previous() @@ -129,6 +169,34 @@ func (p *Parser) assignment() Expr { return expr } +// logicalOr -> logicalAnd ( "or" logicalAnd )* +func (p *Parser) logicalOr() Expr { + left := p.logicalAnd() + + for p.match(OR) { + or := p.previous() + right := p.logicalAnd() + + left = &LogicalOr{left, or, right} + } + + return left +} + +// logicalAnd -> equality ( "and" equality )* +func (p *Parser) logicalAnd() Expr { + left := p.equality() + + for p.match(AND) { + or := p.previous() + right := p.equality() + + left = &LogicalAnd{left, or, right} + } + + return left +} + // equality -> comparison ( ( "==" | "!=" ) comparison )* func (p *Parser) equality() Expr { expr := p.comparison() @@ -289,7 +357,7 @@ func (p *Parser) synchronize() { } switch p.peek().typ { - case CLASS, FOR, FUN, IF, PRINT, RETURN, VAR, WHILE: + case CLASS, FOR, FUN, IF, PRINT, RETURN, VAR, WHILE, ENV: return } diff --git a/scanner.go b/scanner.go index a933d4b..698a6de 100644 --- a/scanner.go +++ b/scanner.go @@ -44,6 +44,7 @@ const ( // keywords AND CLASS + ENV ELSE FALSE FUN @@ -79,6 +80,7 @@ var keywords = map[string]TokenType{ "true": TRUE, "var": VAR, "while": WHILE, + "env": ENV, } type Token struct { diff --git a/stmt.go b/stmt.go index 428f043..cd6efb2 100644 --- a/stmt.go +++ b/stmt.go @@ -1,10 +1,12 @@ package main type StmtVisitor interface { + visitIfStmt(i *IfStmt) visitVarStmt(v *VarStmt) visitExprStmt(es *ExprStmt) visitPrintStmt(p *PrintStmt) visitBlockStmt(b *BlockStmt) + visitEnvStmt(e *EnvStmt) } type Stmt interface { @@ -29,10 +31,21 @@ type BlockStmt struct { stmts []Stmt } +type EnvStmt struct{} + +type IfStmt struct { + name Token + expr Expr + then Stmt + or Stmt +} + +func (i *IfStmt) stmt() {} func (vs *VarStmt) stmt() {} func (es *ExprStmt) stmt() {} func (p *PrintStmt) stmt() {} func (b *BlockStmt) stmt() {} +func (e *EnvStmt) stmt() {} func (p *PrintStmt) accept(v StmtVisitor) { v.visitPrintStmt(p) @@ -49,3 +62,11 @@ func (vs *VarStmt) accept(v StmtVisitor) { func (b *BlockStmt) accept(v StmtVisitor) { v.visitBlockStmt(b) } + +func (i *IfStmt) accept(v StmtVisitor) { + v.visitIfStmt(i) +} + +func (e *EnvStmt) accept(v StmtVisitor) { + v.visitEnvStmt(e) +} diff --git a/tests/env.lox b/tests/env.lox new file mode 100644 index 0000000..48532ae --- /dev/null +++ b/tests/env.lox @@ -0,0 +1,9 @@ +var a = 1; +{ + var b = 2; + { + var c =3; + env; + } + env; +} diff --git a/tokentype_string.go b/tokentype_string.go index d4cec0d..130f89a 100644 --- a/tokentype_string.go +++ b/tokentype_string.go @@ -32,26 +32,27 @@ func _() { _ = x[NUMBER-21] _ = x[AND-22] _ = x[CLASS-23] - _ = x[ELSE-24] - _ = x[FALSE-25] - _ = x[FUN-26] - _ = x[FOR-27] - _ = x[IF-28] - _ = x[NIL-29] - _ = x[OR-30] - _ = x[PRINT-31] - _ = x[RETURN-32] - _ = x[SUPER-33] - _ = x[THIS-34] - _ = x[TRUE-35] - _ = x[VAR-36] - _ = x[WHILE-37] - _ = x[EOF-38] + _ = 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] } -const _TokenType_name = "LEFT_PARENRIGHT_PARENLEFT_BRACERIGHT_BRACECOMMADOTMINUSPLUSSEMICOLONSLASHSTARBANGBANG_EQUALEQUALEQUAL_EQUALGREATERGREATER_EQUALLESSLESS_EQUALIDENTIFIERSTRINGNUMBERANDCLASSELSEFALSEFUNFORIFNILORPRINTRETURNSUPERTHISTRUEVARWHILEEOF" +const _TokenType_name = "LEFT_PARENRIGHT_PARENLEFT_BRACERIGHT_BRACECOMMADOTMINUSPLUSSEMICOLONSLASHSTARBANGBANG_EQUALEQUALEQUAL_EQUALGREATERGREATER_EQUALLESSLESS_EQUALIDENTIFIERSTRINGNUMBERANDCLASSENVELSEFALSEFUNFORIFNILORPRINTRETURNSUPERTHISTRUEVARWHILEEOF" -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, 175, 180, 183, 186, 188, 191, 193, 198, 204, 209, 213, 217, 220, 225, 228} +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} func (i TokenType) String() string { if i < 0 || i >= TokenType(len(_TokenType_index)-1) {