chapter 7
This commit is contained in:
1
main.rb
1
main.rb
@@ -2,7 +2,6 @@
|
||||
|
||||
require './rlox/prompt'
|
||||
require './rlox/file'
|
||||
require './rlox/expression'
|
||||
|
||||
case ARGV.length
|
||||
when 0 then prompt
|
||||
|
@@ -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
|
||||
|
@@ -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
174
rlox/parse.rb
Normal 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
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user