chapter 7
This commit is contained in:
1
main.rb
1
main.rb
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
require './rlox/prompt'
|
require './rlox/prompt'
|
||||||
require './rlox/file'
|
require './rlox/file'
|
||||||
require './rlox/expression'
|
|
||||||
|
|
||||||
case ARGV.length
|
case ARGV.length
|
||||||
when 0 then prompt
|
when 0 then prompt
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
require './rlox/scan'
|
require './rlox/scan'
|
||||||
|
require './rlox/parse'
|
||||||
|
|
||||||
class Executor
|
class Executor
|
||||||
def run(source)
|
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 ast.eval
|
||||||
puts token
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@@ -10,23 +10,107 @@ def parenthesize(name, *exprs)
|
|||||||
res
|
res
|
||||||
end
|
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
|
Binary = Struct.new(:left, :operator, :right) do
|
||||||
def dbg()
|
def dbg()
|
||||||
parenthesize(operator.lexeme, left, right)
|
parenthesize(operator.lexeme, left, right)
|
||||||
end
|
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
|
end
|
||||||
Grouping = Struct.new(:expression) do
|
Grouping = Struct.new(:expression) do
|
||||||
def dbg()
|
def dbg()
|
||||||
parenthesize("group", expression)
|
parenthesize("group", expression)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def eval(fn)
|
||||||
|
expression.eval
|
||||||
|
end
|
||||||
end
|
end
|
||||||
Literal = Struct.new(:value) do
|
Literal = Struct.new(:value) do
|
||||||
def dbg()
|
def dbg()
|
||||||
value.to_s
|
value.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def eval()
|
||||||
|
value
|
||||||
|
end
|
||||||
end
|
end
|
||||||
Unary = Struct.new(:operator, :right) do
|
Unary = Struct.new(:operator, :right) do
|
||||||
def dbg()
|
def dbg()
|
||||||
parenthesize(operator.lexeme, right)
|
parenthesize(operator.lexeme, right)
|
||||||
end
|
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
|
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
|
end
|
||||||
|
|
||||||
def add_token(type, literal=nil)
|
def add_token(type, literal=nil)
|
||||||
Token.new(type, @source[@start, @current], literal, @line)
|
Token.new(type, @source[@start, @current-@start], literal, @line)
|
||||||
end
|
end
|
||||||
|
|
||||||
def match(expected)
|
def match(expected)
|
||||||
@@ -81,7 +81,7 @@ class Scanner
|
|||||||
|
|
||||||
advance
|
advance
|
||||||
|
|
||||||
val = @source[@start+1, @current-1]
|
val = @source[@start+1, (@current-1)-(@start+1)]
|
||||||
add_token(:string, val)
|
add_token(:string, val)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class Scanner
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
add_token(:number, Float(@source[@start, @current]))
|
add_token(:number, Float(@source[@start, @current-@start]))
|
||||||
end
|
end
|
||||||
|
|
||||||
def is_alpha(c)
|
def is_alpha(c)
|
||||||
@@ -118,7 +118,7 @@ class Scanner
|
|||||||
advance
|
advance
|
||||||
end
|
end
|
||||||
|
|
||||||
text = @source[@start, @current]
|
text = @source[@start, @current-@start]
|
||||||
type = :id
|
type = :id
|
||||||
|
|
||||||
if KEYWORDS.include?(text)
|
if KEYWORDS.include?(text)
|
||||||
|
Reference in New Issue
Block a user