Skip to content

Commit

Permalink
add control flow
Browse files Browse the repository at this point in the history
  • Loading branch information
useEffects committed Jul 27, 2024
1 parent b57edf1 commit 606fb47
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 6 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Golang implementation of a basic tree walk interpreter
Golang implementation of a basic tree walk interpreter.

Find the binaries for windows, linux and macos under the releases section.

Expand All @@ -8,4 +8,5 @@ https://craftinginterpreters.com/
- [x] Representing Code
- [x] Parsing Expressions
- [x] Evaluating Expressions
- [x] Statements and State
- [x] Statements and State
- [x] Control Flow
10 changes: 10 additions & 0 deletions pkg/ast/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,14 @@ type AssignExpr struct {

func (a *AssignExpr) Accept(v ExprVisitor) interface{} {
return v.VisitAssignExpr(a)
}

type LogicalExpr struct {
Left Expr
Operator *scanner.Token
Right Expr
}

func (l *LogicalExpr) Accept(v ExprVisitor) interface{} {
return v.VisitLogicalExpr(l)
}
138 changes: 136 additions & 2 deletions pkg/ast/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,22 @@ func (p *parser) statement() Stmt {
return p.printStatement()
}

if p.match(scanner.IF) {
return p.ifStatement()
}

if p.match(scanner.FOR) {
return p.forStatement()
}

if p.match(scanner.WHILE) {
return p.whileStatement()
}

if p.match(scanner.LEFT_BRACE) {
return &BlockStmt{p.block()}
}

return p.exprStatement()
}

Expand All @@ -73,6 +89,100 @@ func (p *parser) printStatement() Stmt {
return &PrintStmt{expr}
}

func (p *parser) ifStatement() Stmt {
if !p.match(scanner.LEFT_PAREN) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected '(' after if"))
}

condition := p.expression()
if !p.match(scanner.RIGHT_PAREN) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected ')' after conditional expression"))
}

thenBranch := p.statement()
var elseBranch Stmt
if p.match(scanner.ELSE) {
elseBranch = p.statement()
}

return &IfStmt{condition, thenBranch, elseBranch}
}

func (p *parser) forStatement() Stmt {
if !p.match(scanner.LEFT_PAREN) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected '(' after for"))
}

var initializer Stmt
if p.match(scanner.SEMICOLON) {
initializer = nil
} else if p.match(scanner.VAR) {
initializer = p.varDeclaration()
} else {
initializer = p.exprStatement()
}

var condition Expr
if p.tokens[p.current].TokenType != scanner.EOF && p.tokens[p.current].TokenType != scanner.SEMICOLON {
condition = p.expression()
}
if !p.match(scanner.SEMICOLON) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected ';' after conditional expression"))
}

var increment Expr
if p.tokens[p.current].TokenType != scanner.EOF && p.tokens[p.current].TokenType != scanner.RIGHT_PAREN {
increment = p.expression()
}
if !p.match(scanner.RIGHT_PAREN) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected ')' after for clause"))
}

body := p.statement()
if increment != nil {
body = &BlockStmt{[]Stmt{body, &ExprStmt{increment}}}
}

if condition == nil {
condition = &LiteralExpr{true}
}

body = &WhileStmt{condition, body}

if initializer != nil {
body = &BlockStmt{[]Stmt{initializer, body}}
}

return body
}

func (p *parser) whileStatement() Stmt {
if !p.match(scanner.LEFT_PAREN) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected '(' after while"))
}

condition := p.expression()
if !p.match(scanner.RIGHT_PAREN) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected ')' after conditional expression"))
}

return &WhileStmt{condition, p.statement()}
}

func (p *parser) block() []Stmt {
stmts := []Stmt{}

for p.tokens[p.current].TokenType != scanner.EOF && p.tokens[p.current].TokenType != scanner.RIGHT_BRACE {
stmts = append(stmts, p.declaration())
}

if !p.match(scanner.RIGHT_BRACE) {
panic(fault.NewFault(p.tokens[p.current].Line, "expected '}' after block"))
}

return stmts
}

func (p *parser) exprStatement() Stmt {
expr := p.expression()

Expand All @@ -88,7 +198,7 @@ func (p *parser) expression() Expr {
}

func (p *parser) assignment() Expr {
expr := p.equality()
expr := p.or()

if p.match(scanner.EQUAL) {
equals := p.tokens[p.current-1]
Expand All @@ -97,13 +207,37 @@ func (p *parser) assignment() Expr {
if variable, ok := expr.(*VariableExpr); ok {
return &AssignExpr{variable.Name, value}
}

fault.NewFault(equals.Line, "invalid assignment target")
}

return expr
}

func (p *parser) or() Expr {
left := p.and()

for p.match(scanner.OR) {
operator := p.tokens[p.current-1]
right := p.and()
left = &LogicalExpr{left, &operator, right}
}

return left
}

func (p *parser) and() Expr {
left := p.equality()

for p.match(scanner.AND) {
operator := p.tokens[p.current-1]
right := p.equality()
left = &LogicalExpr{left, &operator, right}
}

return left
}

func (p *parser) equality() Expr {
left := p.comparison()

Expand Down
27 changes: 27 additions & 0 deletions pkg/ast/stmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,31 @@ type VarStmt struct {

func (v *VarStmt) Accept(v_ StmtVisitor) interface{} {
return v_.VisitVarStmt(v)
}

type BlockStmt struct {
Statements []Stmt
}

func (b *BlockStmt) Accept(v StmtVisitor) interface{} {
return v.VisitBlockStmt(b)
}

type IfStmt struct {
Condition Expr
ThenBranch Stmt
ElseBranch Stmt
}

func (i *IfStmt) Accept(v StmtVisitor) interface{} {
return v.VisitIfStmt(i)
}

type WhileStmt struct {
Condition Expr
Body Stmt
}

func (w *WhileStmt) Accept(v StmtVisitor) interface{} {
return v.VisitWhileStmt(w)
}
4 changes: 4 additions & 0 deletions pkg/ast/visitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ type ExprVisitor interface {
VisitUnaryExpr(u *UnaryExpr) interface{}
VisitVariableExpr(v *VariableExpr) interface{}
VisitAssignExpr(a *AssignExpr) interface{}
VisitLogicalExpr(l *LogicalExpr) interface{}
}

type StmtVisitor interface {
VisitExprStmt(e *ExprStmt) interface{}
VisitPrintStmt(p *PrintStmt) interface{}
VisitVarStmt(p *VarStmt) interface{}
VisitBlockStmt(b *BlockStmt) interface{}
VisitIfStmt(i *IfStmt) interface{}
VisitWhileStmt(w *WhileStmt) interface{}
}
9 changes: 8 additions & 1 deletion pkg/interpreter/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,28 @@ import (
)

type environment struct {
values map[string]interface{}
enclosing *environment
values map[string]interface{}
}

func (e *environment) get(name *scanner.Token) interface{} {
if value, ok := e.values[name.Lexeme]; ok {
return value
}

if e.enclosing != nil {
return e.enclosing.get(name)
}

message := fmt.Sprintf("undefined variable %s", name.Lexeme)
panic(fault.NewFault(name.Line, message))
}

func (e *environment) assign(name *scanner.Token, value interface{}) {
if _, ok := e.values[name.Lexeme]; ok {
e.values[name.Lexeme] = value
} else if e.enclosing != nil {
e.enclosing.assign(name, value)
} else {
message := fmt.Sprintf("undefined variable %s", name.Lexeme)
panic(fault.NewFault(name.Line, message))
Expand Down
58 changes: 57 additions & 1 deletion pkg/interpreter/interpreter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type interpreter struct {
}

func NewInterpreter() *interpreter {
env := &environment{map[string]interface{}{}}
env := &environment{nil, map[string]interface{}{}}
return &interpreter{env}
}

Expand Down Expand Up @@ -64,6 +64,40 @@ func (i *interpreter) VisitVarStmt(v *ast.VarStmt) interface{} {
return nil
}

func (i *interpreter) VisitBlockStmt(b *ast.BlockStmt) interface{} {
prev := i.env

defer func() {
i.env = prev
}()

i.env = &environment{prev, map[string]interface{}{}}
for _, stmt := range b.Statements {
stmt.Accept(i)
}

return nil
}

func (i *interpreter) VisitIfStmt(i_ *ast.IfStmt) interface{} {
value := i_.Condition.Accept(i)
if isTruthy(value) {
i_.ThenBranch.Accept(i)
} else if i_.ElseBranch != nil {
i_.ElseBranch.Accept(i)
}

return nil
}

func (i *interpreter) VisitWhileStmt(w *ast.WhileStmt) interface{} {
for isTruthy(w.Condition.Accept(i)) {
w.Body.Accept(i)
}

return nil
}

func (i *interpreter) VisitBinaryExpr(b *ast.BinaryExpr) interface{} {
left := b.Left.Accept(i)
right := b.Right.Accept(i)
Expand Down Expand Up @@ -156,6 +190,16 @@ func (i *interpreter) VisitAssignExpr(a *ast.AssignExpr) interface{} {
return value
}

func (i *interpreter) VisitLogicalExpr(l *ast.LogicalExpr) interface{} {
left := l.Left.Accept(i)

if (l.Operator.TokenType == scanner.OR && isTruthy(left)) || !isTruthy(left) {
return left
}

return l.Right.Accept(i)
}

func (i *interpreter) checkNumberOperands(operator *scanner.Token, left interface{}, right interface{}) (float64, float64) {
if leftValue, leftOk := left.(float64); leftOk {
if rightValue, rightOk := right.(float64); rightOk {
Expand All @@ -164,4 +208,16 @@ func (i *interpreter) checkNumberOperands(operator *scanner.Token, left interfac
}

panic(fault.NewFault(operator.Line, "operands must be numbers"))
}

func isTruthy(value interface{}) bool {
if value == nil {
return false
}

if boolean, ok := value.(bool); ok {
return boolean
}

return true
}

0 comments on commit 606fb47

Please sign in to comment.