From 13106d1788832f8d173d6fe6810b859795f695b5 Mon Sep 17 00:00:00 2001 From: hellerve Date: Wed, 25 Jul 2018 13:18:58 +0200 Subject: [PATCH] done with 9 --- README.md | 5 +++ rlox/environment.rb | 6 ++- rlox/expression.rb | 74 ++++++++++++++++-------------- rlox/parse.rb | 107 +++++++++++++++++++++++++++++++++++++++++--- rlox/prompt.rb | 7 +-- 5 files changed, 156 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index f17193d..ad1e93d 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,8 @@ `rlox` is an unpronouncable variant of the tree-walk interpreter laid out in [Crafting Interpreters](http://craftinginterpreters.com/), written in Ruby, because I want to learn it. + +It deviates a little from the “canonical” version of Lox. I don’t use code +generation for the expressions, and I don’t use the visitor pattern. `var` and +`fun` are `let` and `fn`, respectively. And we don’t need parentheses around +branching conditions, instead we require the bodies to be blocks. diff --git a/rlox/environment.rb b/rlox/environment.rb index 4b7dcd3..f828b7f 100644 --- a/rlox/environment.rb +++ b/rlox/environment.rb @@ -24,11 +24,13 @@ class Environment def assign(name, value) if @values.has_key? name.lexeme - @values.put(name.lexeme, value) + @values[name.lexeme] = value + return end if @parent != nil return @parent.assign(name, value) end - raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.") + raise ExecError.new(name.line, + "Can’t assign undefined variable '#{name.lexeme}'.") end end diff --git a/rlox/expression.rb b/rlox/expression.rb index 355a577..c8af350 100644 --- a/rlox/expression.rb +++ b/rlox/expression.rb @@ -1,20 +1,6 @@ -def parenthesize(name, *exprs) - res = "(#{name}" - - exprs.each { | expr | - res << " " - res << expr.dbg - } - res << ")" - - res -end - -def truthy(obj) - if obj == nil +def truthy?(obj) + if [nil, false, "", 0].include? obj false - elsif obj.is_a? Boolean - obj else true end @@ -47,10 +33,6 @@ Assign = Struct.new(:name, :value) do end end Binary = Struct.new(:left, :operator, :right) do - def dbg() - parenthesize(operator.lexeme, left, right) - end - def eval(env) l = left.eval(env) r = right.eval(env) @@ -82,38 +64,27 @@ Binary = Struct.new(:left, :operator, :right) do l <= r when :bang_eq then l != r when :eq_eq then l == r + when :comma then r else nil end end end Grouping = Struct.new(:expression) do - def dbg() - parenthesize("group", expression) - end - def eval(env) expression.eval(env) end end Literal = Struct.new(:value) do - def dbg() - value.to_s - end - def eval(env) value end end Unary = Struct.new(:operator, :right) do - def dbg() - parenthesize(operator.lexeme, right) - end - def eval(env) r = right.eval(env) case operator.type - when :bang then is_truthy(r) + when :bang then truthy?(r) when :minus then check_number_op(operator, r) -r @@ -126,6 +97,25 @@ Var = Struct.new(:name) do env.get(name) end end +Logical = Struct.new(:left, :operator, :right) do + def eval(env) + l = left.eval(env) + + case operator.type + when :or then + if truthy?(l) + return l + end + when :and then + if not truthy?(l) + return l + end + end + + right.eval(env) + end +end + Expression = Struct.new(:expression) do def eval(env) expression.eval(env) @@ -158,3 +148,21 @@ Block = Struct.new(:stmts) do env end end +If = Struct.new(:cond, :thn, :els) do + def eval(env) + if truthy? cond.eval(env) + thn.eval(env) + elsif els != nil + els.eval(env) + end + env + end +end +While = Struct.new(:cond, :body) do + def eval(env) + while truthy? cond.eval(env) + body.eval(env) + end + env + end +end diff --git a/rlox/parse.rb b/rlox/parse.rb index fee246c..e90c707 100644 --- a/rlox/parse.rb +++ b/rlox/parse.rb @@ -24,7 +24,7 @@ class Parser end def advance() - if !is_at_end() + if !is_at_end @current += 1 end return previous @@ -54,9 +54,33 @@ class Parser expr end - def assignment() + def and_expr() expr = equality + if match(:and) + operator = previous + right = equality + expr = Logical.new(expr, operator, right) + end + + expr + end + + def or_expr() + expr = and_expr + + if match(:or) + operator = previous + right = and_expr + expr = Logical.new(expr, operator, right) + end + + expr + end + + def assignment() + expr = or_expr + if match(:eq) equals = previous value = assignment @@ -126,7 +150,7 @@ class Parser return Unary.new(operator, right) end - primary() + primary end def primary() @@ -166,7 +190,7 @@ class Parser end def expression() - comma() + comma end def synchronize() @@ -210,9 +234,82 @@ class Parser return stmts end + def if_statement() + cond = expression + + consume(:left_brace, "Expect '{' after 'if' condition.") + thn = Block.new(block) + els = nil + + if match(:else) + consume(:left_brace, "Expect '{' after 'else'.") + els = Block.new(block) + end + + If.new(cond, thn, els) + end + + def while_statement() + cond = expression + + consume(:left_brace, "Expect '{' after 'if' condition.") + body = Block.new(block) + + While.new(cond, body) + end + + def for_statement() + init = if match(:semicolon) + nil + elsif match(:var) + var_declaration + else + expression_statement + end + + cond = nil + + if !check(:semicolon) + cond = expression + end + + consume(:semicolon, "Expect ';' after loop condition.") + + inc = nil + + if !check(:semicolon) + inc = expression + end + + consume(:left_brace, "Expect '{' after 'if' condition.") + body = Block.new(block) + + if inc + body = Block.new([body, Expr.new(inc)]) + end + + if not cond + cond = Literal.new(true) + end + + if init + body = Block.new([init, body]) + end + + body = While.new(cond, body) + + body + end + def statement() - if match(:print) + if match(:if) + if_statement + elsif match(:for) + for_statement + elsif match(:print) print_statement + elsif match(:while) + while_statement elsif match(:left_brace) Block.new(block) else diff --git a/rlox/prompt.rb b/rlox/prompt.rb index 840f1e4..d7bdcbe 100644 --- a/rlox/prompt.rb +++ b/rlox/prompt.rb @@ -5,9 +5,9 @@ require './rlox/executor' def prompt() exec = Executor.new - f = File.new("#{Dir.home}/.rlox", File::CREAT|File::RDWR) + f = File.new("#{Dir.home}/.rlox", "a+") File.readlines("#{Dir.home}/.rlox").each { | line | - Readline::HISTORY.push line + Readline::HISTORY.push line.rstrip } begin @@ -16,9 +16,10 @@ def prompt() begin exec.run buf - f.write("#{buf}\n") rescue LoxError => err STDERR.puts err + ensure + f.write("#{buf}\n") end exec.inc_line end