chapter 7

This commit is contained in:
2018-07-24 16:38:20 +02:00
parent 6ac116c169
commit 946ab8d8f4
5 changed files with 268 additions and 9 deletions

View File

@@ -2,7 +2,6 @@
require './rlox/prompt'
require './rlox/file'
require './rlox/expression'
case ARGV.length
when 0 then prompt

View File

@@ -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

View File

@@ -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

174
rlox/parse.rb Normal file
View File

@@ -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

View File

@@ -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)