From 946ab8d8f4523a697059c44c840ca3500ee23e28 Mon Sep 17 00:00:00 2001 From: hellerve Date: Tue, 24 Jul 2018 16:38:20 +0200 Subject: [PATCH] chapter 7 --- main.rb | 1 - rlox/executor.rb | 10 +-- rlox/expression.rb | 84 ++++++++++++++++++++++ rlox/parse.rb | 174 +++++++++++++++++++++++++++++++++++++++++++++ rlox/scan.rb | 8 +-- 5 files changed, 268 insertions(+), 9 deletions(-) create mode 100644 rlox/parse.rb diff --git a/main.rb b/main.rb index 6f07e66..b9d0042 100644 --- a/main.rb +++ b/main.rb @@ -2,7 +2,6 @@ require './rlox/prompt' require './rlox/file' -require './rlox/expression' case ARGV.length when 0 then prompt diff --git a/rlox/executor.rb b/rlox/executor.rb index 14b4201..b426735 100644 --- a/rlox/executor.rb +++ b/rlox/executor.rb @@ -1,11 +1,13 @@ require './rlox/scan' +require './rlox/parse' class Executor def run(source) - scan = Scanner.new source + scanner = Scanner.new source + tokens = scanner.scan + parser = Parser.new tokens + ast = parser.parse - scan.scan.each{ | token | - puts token - } + puts ast.eval end end diff --git a/rlox/expression.rb b/rlox/expression.rb index e05ac98..3f1cb22 100644 --- a/rlox/expression.rb +++ b/rlox/expression.rb @@ -10,23 +10,107 @@ def parenthesize(name, *exprs) res end +def truthy(obj) + if obj == nil + false + elsif obj.is_a? Boolean + obj + else + true + end +end + +def check_number_op(operator, *operands) + operands.each{ | operand | + if not operand.is_a? Numeric + raise ExecError.new(operator.line, + "Operand to '#{operator.lexeme}' must be a number.") + end + } +end + +def check_same_type_op(operator, *operands) + fst = operands[0] + operands.each{ | operand | + if not operand.is_a? fst.class + raise ExecError.new(operator.line, + "Operand to '#{operator.lexeme}' must be a #{fst.class}.") + end + } +end + Binary = Struct.new(:left, :operator, :right) do def dbg() parenthesize(operator.lexeme, left, right) end + + def eval() + l = left.eval + r = right.eval + + case operator.type + when :minus then + check_number_op(operator, l, r) + l - r + when :plus then + check_same_type_op(operator, l, r) + l + r + when :slash then + check_number_op(operator, l, r) + l / r + when :star then + check_number_op(operator, l, r) + l * r + when :gt then + check_number_op(operator, l, r) + l > r + when :geq then + check_number_op(operator, l, r) + l >= r + when :lt then + check_number_op(operator, l, r) + l < r + when :leq then + check_number_op(operator, l, r) + l <= r + when :bang_eq then l != r + when :eq_eq then l == r + else nil + end + end end Grouping = Struct.new(:expression) do def dbg() parenthesize("group", expression) end + + def eval(fn) + expression.eval + end end Literal = Struct.new(:value) do def dbg() value.to_s end + + def eval() + value + end end Unary = Struct.new(:operator, :right) do def dbg() parenthesize(operator.lexeme, right) end + + def eval() + r = right.eval + + case operator.type + when :bang then is_truthy(r) + when :minus then + check_number_op(operator, r) + -r + else nil + end + end end diff --git a/rlox/parse.rb b/rlox/parse.rb new file mode 100644 index 0000000..77443be --- /dev/null +++ b/rlox/parse.rb @@ -0,0 +1,174 @@ +require './rlox/expression' + +class Parser + def initialize(tokens) + @tokens = tokens + @current = 0 + end + + def match(*types) + types.each{ | type | + if check type + advance + return true + end + } + false + end + + def check(type) + if is_at_end + false + else + peek.type == type + end + end + + def advance() + if !is_at_end() + @current += 1 + end + return previous + end + + def is_at_end() + peek.type == :eof + end + + def peek() + @tokens[@current] + end + + def previous() + @tokens[@current-1] + end + + def comma() + expr = equality + + while match(:comma) + operator = previous + right = comparison + expr = Binary.new(expr, operator, right) + end + + expr + end + + def equality() + expr = comparison + + while match(:bang_eq, :eq_eq) + operator = previous + right = comparison + expr = Binary.new(expr, operator, right) + end + + expr + end + + def addition() + expr = multiplication + + while match(:minus, :plus) + operator = previous + right = multiplication + expr = Binary.new(expr, operator, right) + end + + expr + end + + def multiplication() + expr = unary + + while match(:slash, :star) + operator = previous + right = unary + expr = Binary.new(expr, operator, right) + end + + expr + end + + def comparison() + expr = addition + + while match(:gt, :geq, :lt, :geq) + operator = previous + right = addition + expr = Binary.new(expr, operator, right) + end + + expr + end + + def unary() + if match(:bang, :minus) + operator = previous + right = unary + return Unary.new(operator, right) + end + + primary() + end + + def primary() + if match(:false) + Literal.new(false) + elsif match(:true) + Literal.new(true) + elsif match(:nil) + Literal.new(nil) + elsif match(:number, :string) + Literal.new(previous.literal) + elsif match(:left_paren) + expr = expression + consume(:right_paren, "Expect ')' after expression.") + Grouping.new(expr) + else + error(peek, "Expect expression.") + end + end + + def consume(type, msg) + if check(type) + advance + else + error(peek, msg) + end + end + + def error(token, msg) + if token.type == :eof + raise ParseError.new(token.line, msg, " at end") + else + raise ParseError.new(token.line, msg, " at '#{token.lexeme}'") + end + end + + def expression() + comma() + end + + def synchronize() + advance + + while not is_at_end + if previous.type == :semicolon + return + end + + case peek.type + when :class, :fun, :var, :for, :if, :while, :print, :return then + return + end + + advance + end + end + + def parse() + expression + end +end diff --git a/rlox/scan.rb b/rlox/scan.rb index f2f02fc..ea0c3dc 100644 --- a/rlox/scan.rb +++ b/rlox/scan.rb @@ -39,7 +39,7 @@ class Scanner end def add_token(type, literal=nil) - Token.new(type, @source[@start, @current], literal, @line) + Token.new(type, @source[@start, @current-@start], literal, @line) end def match(expected) @@ -81,7 +81,7 @@ class Scanner advance - val = @source[@start+1, @current-1] + val = @source[@start+1, (@current-1)-(@start+1)] add_token(:string, val) end @@ -102,7 +102,7 @@ class Scanner end end - add_token(:number, Float(@source[@start, @current])) + add_token(:number, Float(@source[@start, @current-@start])) end def is_alpha(c) @@ -118,7 +118,7 @@ class Scanner advance end - text = @source[@start, @current] + text = @source[@start, @current-@start] type = :id if KEYWORDS.include?(text)