From da141de5e9df26f3017a78dab4fe7f4e49910207 Mon Sep 17 00:00:00 2001 From: hellerve Date: Wed, 25 Jul 2018 18:03:31 +0200 Subject: [PATCH] done with chapter 11 --- rlox/environment.rb | 40 +++++++++ rlox/error.rb | 3 + rlox/executor.rb | 31 ++++++- rlox/expression.rb | 206 +++++++++++++++++++++++++++++++++++++++++--- rlox/parse.rb | 3 +- 5 files changed, 271 insertions(+), 12 deletions(-) diff --git a/rlox/environment.rb b/rlox/environment.rb index b4e191d..aced30a 100644 --- a/rlox/environment.rb +++ b/rlox/environment.rb @@ -22,6 +22,10 @@ class Environment @values[name] = value end + def parent() + @parent + end + def get(name) if @values.has_key? name.lexeme return @values[name.lexeme] @@ -32,6 +36,24 @@ class Environment raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.") end + def get_at(name, distance) + env = self + + distance.times { + env = env.parent + } + env.get(name) + end + + def get_global(name) + env = self + + while env.parent + env = env.parent + end + env.get(name) + end + def assign(name, value) if @values.has_key? name.lexeme @values[name.lexeme] = value @@ -43,4 +65,22 @@ class Environment raise ExecError.new(name.line, "Can’t assign undefined variable '#{name.lexeme}'.") end + + def assign_at(name, value, distance) + env = self + + distance.times { + env = env.parent + } + env.assign(name, value) + end + + def assign_global(name, value) + env = self + + while env.parent + env = env.parent + end + env.assign(name, value) + end end diff --git a/rlox/error.rb b/rlox/error.rb index f920c9a..8a617a3 100644 --- a/rlox/error.rb +++ b/rlox/error.rb @@ -18,6 +18,9 @@ end class ParseError < LoxError end +class VarError < LoxError +end + class ExecError < LoxError end diff --git a/rlox/executor.rb b/rlox/executor.rb index 4b840e6..aca6a87 100644 --- a/rlox/executor.rb +++ b/rlox/executor.rb @@ -2,17 +2,46 @@ require './rlox/environment' require './rlox/parse' require './rlox/scan' +Env = Struct.new(:map, :scopes, :vars, :fn) do + def child() + Env.new(map, scopes, Environment.new(vars), fn) + end + + def lookup(expr, name) + distance = map[expr] + if distance + vars.get_at(name, distance) + else + vars.get_global(name) + end + end +end + class Executor def initialize() - @env = Environment.global + @env = Env.new({}, [], Environment.global, nil) @scanner = Scanner.new @parser = Parser.new end def run(source) + errord = false tokens = @scanner.scan_on(source) ast = @parser.parse_on(tokens) + ast.each { | stmt | + begin + stmt.resolve(@env) + rescue VarError => e + STDERR.puts e + errord = true + end + } + + if errord + return + end + ast.each { | stmt | stmt.eval(@env) } diff --git a/rlox/expression.rb b/rlox/expression.rb index 3a63f15..eaa9a79 100644 --- a/rlox/expression.rb +++ b/rlox/expression.rb @@ -6,6 +6,68 @@ def truthy?(obj) end end +def begin_scope(scopes) + scopes.push({}) +end + +def end_scope(scopes) + last_scope = scopes.pop() + last_scope.each { | key, val | + if val[:used] == 0 + raise VarError.new(val[:line], "Variable #{key} declared but never used.") + end + } +end + +def declare(scopes, name) + if scopes.length == 0 + return + end + + scope = scopes[-1] + + if scope.has_key? name.lexeme + raise VarError.new( + name.line, + "Variable with the name '#{name.lexeme}' already declared in this scope." + ) + end + + scope[name.lexeme] = {:assigned => false, :used => 0, :line => name.line} +end + +def define(scopes, name) + if scopes.length == 0 + return + end + + scope = scopes[-1] + + if not scope.has_key? name.lexeme + raise VarError.new( + name.line, + "Variable '#{name.lexeme}' was assigned, but not declared." + ) + end + + scope[name.lexeme][:assigned] = true + scope[name.lexeme][:used] += 1 +end + +def resolve_local(env, expr, name) + if env.scopes.length == 0 + return + end + + (env.scopes.length-1).downto(0) { | i | + if env.scopes[i].has_key? name.lexeme + env.map[expr] = env.scopes.length - 2 - i + env.scopes[i][name.lexeme][:used] += 1 + return + end + } +end + def check_number_op(operator, *operands) operands.each{ | operand | if not operand.is_a? Numeric @@ -24,7 +86,16 @@ def check_same_type_op(operator, *operands) end } end + Call = Struct.new(:callee, :paren, :arguments) do + def resolve(env) + callee.resolve(env) + + arguments.each{ | arg | + arg.resolve(env) + } + end + def eval(env) to_call = callee.eval(env) al = arguments.length @@ -50,13 +121,28 @@ Call = Struct.new(:callee, :paren, :arguments) do end Assign = Struct.new(:name, :value) do + def resolve(env) + value.resolve(env) + resolve_local(env, self, name) + end + def eval(env) val = value.eval(env) - env.assign(name, val) + distance = env.map[self] + if distance + env.vars.assign_at(name, val, distance) + else + env.vars.assign_global(name, val) + end val end end Binary = Struct.new(:left, :operator, :right) do + def resolve(env) + left.resolve(env) + right.resolve(env) + end + def eval(env) l = left.eval(env) r = right.eval(env) @@ -94,16 +180,27 @@ Binary = Struct.new(:left, :operator, :right) do end end Grouping = Struct.new(:expression) do + def resolve(env) + expression.resolve(env) + end + def eval(env) expression.eval(env) end end Literal = Struct.new(:value) do + def resolve(env) + end + def eval(env) value end end Unary = Struct.new(:operator, :right) do + def resolve(env) + right.resolve(env) + end + def eval(env) r = right.eval(env) @@ -117,11 +214,27 @@ Unary = Struct.new(:operator, :right) do end end Var = Struct.new(:name) do + def resolve(env) + if env.scopes.length > 0 and env.scopes[-1][name.lexeme][:assigned] == false + raise VarError.new( + name.line, + "Cannot read local variable #{name.lexeme} in its own initializer." + ) + end + + resolve_local(env, self, name) + end + def eval(env) - env.get(name) + env.lookup(self, name) end end Logical = Struct.new(:left, :operator, :right) do + def resolve(env) + left.resolve(env) + right.resolve(env) + end + def eval(env) l = left.eval(env) @@ -140,11 +253,23 @@ Logical = Struct.new(:left, :operator, :right) do end end Fn = Struct.new(:params, :body) do + def resolve(env) + begin_scope(env.scopes) + + params.each{ | param | + declare(env.scopes, param) + define(env.scopes, param) + } + + body.resolve(env) + end_scope(env.scopes) + end + def eval(env) Callable.new("fn", params.length) { | args | - nenv = Environment.new(env) + nenv = env.child for i in 0..params.length-1 - nenv.define(params[i].lexeme, args[i]) + nenv.vars.define(params[i].lexeme, args[i]) end body.eval(nenv) } @@ -152,23 +277,43 @@ Fn = Struct.new(:params, :body) do end Expression = Struct.new(:expression) do + def resolve(env) + expression.resolve(env) + end + def eval(env) expression.eval(env) end end Variable = Struct.new(:name, :initializer) do + def resolve(env) + declare(env.scopes, name) + if initializer + initializer.resolve(env) + end + define(env.scopes, name) + end + def eval(env) value = nil if initializer != nil value = initializer.eval(env) end - env.define(name.lexeme, value) + env.vars.define(name.lexeme, value) end end Block = Struct.new(:stmts) do + def resolve(env) + begin_scope(env.scopes) + stmts.each{ | stmt | + stmt.resolve(env) + } + end_scope(env.scopes) + end + def eval(env) - child = Environment.new(env) + child = env.child ret = nil stmts.each{ | stmt | ret = stmt.eval(child) @@ -177,6 +322,14 @@ Block = Struct.new(:stmts) do end end If = Struct.new(:cond, :thn, :els) do + def resolve(env) + cond.resolve(env) + thn.resolve(env) + if els + els.resolve(env) + end + end + def eval(env) if truthy? cond.eval(env) thn.eval(env) @@ -186,6 +339,11 @@ If = Struct.new(:cond, :thn, :els) do end end While = Struct.new(:cond, :body) do + def resolve(env) + cond.resolve(env) + body.resolve(env) + end + def eval(env) ret = nil while truthy? cond.eval(env) @@ -195,18 +353,46 @@ While = Struct.new(:cond, :body) do end end FnDef = Struct.new(:name, :params, :body) do + def resolve(env) + declare(env.scopes, name) + define(env.scopes, name) + + enclosing = env.fn + env.fn = :fn + begin_scope(env.scopes) + + params.each{ | param | + declare(env.scopes, param) + define(env.scopes, param) + } + + body.resolve(env) + end_scope(env.scopes) + env.fn = enclosing + end + def eval(env) - env.define(name.lexeme, Callable.new(name.lexeme, params.length) { + env.vars.define(name.lexeme, Callable.new(name.lexeme, params.length) { | args | - nenv = Environment.new(env) + nenv = env.child for i in 0..params.length-1 - nenv.define(params[i].lexeme, args[i]) + nenv.vars.define(params[i].lexeme, args[i]) end body.eval(nenv) }) end end -Return = Struct.new(:value) do +Return = Struct.new(:keyword, :value) do + def resolve(env) + if not env.fn + raise VarError.new(keyword.line, "Cannot return from top-level code.") + end + + if value + value.resolve(env) + end + end + def eval(env) raise ReturnError.new(value == nil ? nil : value.eval(env)) end diff --git a/rlox/parse.rb b/rlox/parse.rb index a1df791..7f121aa 100644 --- a/rlox/parse.rb +++ b/rlox/parse.rb @@ -343,6 +343,7 @@ class Parser end def return_statement() + keyword = previous value = nil if !check(:semicolon) @@ -350,7 +351,7 @@ class Parser end consume(:semicolon, "Expect ';' after return value.") - Return.new(value) + Return.new(keyword, value) end def statement()