done with 8
This commit is contained in:
2
main.rb
2
main.rb
@@ -8,7 +8,7 @@ case ARGV.length
|
||||
when 1 then
|
||||
begin
|
||||
file ARGV[0]
|
||||
rescue Error => e
|
||||
rescue StandardError => e
|
||||
STDERR.puts e
|
||||
end
|
||||
else
|
||||
|
34
rlox/environment.rb
Normal file
34
rlox/environment.rb
Normal file
@@ -0,0 +1,34 @@
|
||||
class Environment
|
||||
def initialize(parent=nil)
|
||||
@values = {}
|
||||
@parent = parent
|
||||
end
|
||||
|
||||
def to_s()
|
||||
@values.to_s
|
||||
end
|
||||
|
||||
def define(name, value)
|
||||
@values[name] = value
|
||||
end
|
||||
|
||||
def get(name)
|
||||
if @values.has_key? name.lexeme
|
||||
return @values[name.lexeme]
|
||||
end
|
||||
if @parent != nil
|
||||
return @parent.get(name)
|
||||
end
|
||||
raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.")
|
||||
end
|
||||
|
||||
def assign(name, value)
|
||||
if @values.has_key? name.lexeme
|
||||
@values.put(name.lexeme, value)
|
||||
end
|
||||
if @parent != nil
|
||||
return @parent.assign(name, value)
|
||||
end
|
||||
raise ExecError.new(name.line, "Undefined variable '#{name.lexeme}'.")
|
||||
end
|
||||
end
|
@@ -1,13 +1,24 @@
|
||||
require './rlox/scan'
|
||||
require './rlox/environment'
|
||||
require './rlox/parse'
|
||||
require './rlox/scan'
|
||||
|
||||
class Executor
|
||||
def run(source)
|
||||
scanner = Scanner.new source
|
||||
tokens = scanner.scan
|
||||
parser = Parser.new tokens
|
||||
ast = parser.parse
|
||||
def initialize()
|
||||
@env = Environment.new
|
||||
@scanner = Scanner.new
|
||||
@parser = Parser.new
|
||||
end
|
||||
|
||||
puts ast.eval
|
||||
def run(source)
|
||||
tokens = @scanner.scan_on(source)
|
||||
ast = @parser.parse_on(tokens)
|
||||
|
||||
ast.each { | stmt |
|
||||
@env = stmt.eval(@env)
|
||||
}
|
||||
end
|
||||
|
||||
def inc_line()
|
||||
@scanner.inc_line
|
||||
end
|
||||
end
|
||||
|
@@ -39,14 +39,21 @@ def check_same_type_op(operator, *operands)
|
||||
}
|
||||
end
|
||||
|
||||
Assign = Struct.new(:name, :value) do
|
||||
def eval(env)
|
||||
val = value.eval(env)
|
||||
env.assign(name, val)
|
||||
val
|
||||
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
|
||||
def eval(env)
|
||||
l = left.eval(env)
|
||||
r = right.eval(env)
|
||||
|
||||
case operator.type
|
||||
when :minus then
|
||||
@@ -84,8 +91,8 @@ Grouping = Struct.new(:expression) do
|
||||
parenthesize("group", expression)
|
||||
end
|
||||
|
||||
def eval(fn)
|
||||
expression.eval
|
||||
def eval(env)
|
||||
expression.eval(env)
|
||||
end
|
||||
end
|
||||
Literal = Struct.new(:value) do
|
||||
@@ -93,7 +100,7 @@ Literal = Struct.new(:value) do
|
||||
value.to_s
|
||||
end
|
||||
|
||||
def eval()
|
||||
def eval(env)
|
||||
value
|
||||
end
|
||||
end
|
||||
@@ -102,8 +109,8 @@ Unary = Struct.new(:operator, :right) do
|
||||
parenthesize(operator.lexeme, right)
|
||||
end
|
||||
|
||||
def eval()
|
||||
r = right.eval
|
||||
def eval(env)
|
||||
r = right.eval(env)
|
||||
|
||||
case operator.type
|
||||
when :bang then is_truthy(r)
|
||||
@@ -114,3 +121,40 @@ Unary = Struct.new(:operator, :right) do
|
||||
end
|
||||
end
|
||||
end
|
||||
Var = Struct.new(:name) do
|
||||
def eval(env)
|
||||
env.get(name)
|
||||
end
|
||||
end
|
||||
Expression = Struct.new(:expression) do
|
||||
def eval(env)
|
||||
expression.eval(env)
|
||||
env
|
||||
end
|
||||
end
|
||||
Print = Struct.new(:print) do
|
||||
def eval(env)
|
||||
puts print.eval(env)
|
||||
env
|
||||
end
|
||||
end
|
||||
Variable = Struct.new(:name, :initializer) do
|
||||
def eval(env)
|
||||
value = nil
|
||||
if initializer != nil
|
||||
value = initializer.eval(env)
|
||||
end
|
||||
|
||||
env.define(name.lexeme, value)
|
||||
env
|
||||
end
|
||||
end
|
||||
Block = Struct.new(:stmts) do
|
||||
def eval(env)
|
||||
child = Environment.new(env)
|
||||
stmts.each{ | stmt |
|
||||
stmt.eval(child)
|
||||
}
|
||||
env
|
||||
end
|
||||
end
|
||||
|
@@ -1,8 +1,7 @@
|
||||
require './rlox/expression'
|
||||
|
||||
class Parser
|
||||
def initialize(tokens)
|
||||
@tokens = tokens
|
||||
def initialize()
|
||||
@current = 0
|
||||
end
|
||||
|
||||
@@ -44,7 +43,7 @@ class Parser
|
||||
end
|
||||
|
||||
def comma()
|
||||
expr = equality
|
||||
expr = assignment
|
||||
|
||||
while match(:comma)
|
||||
operator = previous
|
||||
@@ -55,6 +54,23 @@ class Parser
|
||||
expr
|
||||
end
|
||||
|
||||
def assignment()
|
||||
expr = equality
|
||||
|
||||
if match(:eq)
|
||||
equals = previous
|
||||
value = assignment
|
||||
if expr.is_a? Var
|
||||
name = expr.name
|
||||
return Assign.new(name, value)
|
||||
end
|
||||
|
||||
error(equals, "Invalid assignment target: '#{expr.dbg}'.")
|
||||
end
|
||||
|
||||
expr
|
||||
end
|
||||
|
||||
def equality()
|
||||
expr = comparison
|
||||
|
||||
@@ -126,6 +142,8 @@ class Parser
|
||||
expr = expression
|
||||
consume(:right_paren, "Expect ')' after expression.")
|
||||
Grouping.new(expr)
|
||||
elsif match(:id)
|
||||
Var.new(previous)
|
||||
else
|
||||
error(peek, "Expect expression.")
|
||||
end
|
||||
@@ -160,7 +178,7 @@ class Parser
|
||||
end
|
||||
|
||||
case peek.type
|
||||
when :class, :fun, :var, :for, :if, :while, :print, :return then
|
||||
when :class, :fun, :let, :for, :if, :while, :print, :return then
|
||||
return
|
||||
end
|
||||
|
||||
@@ -168,7 +186,76 @@ class Parser
|
||||
end
|
||||
end
|
||||
|
||||
def print_statement()
|
||||
value = expression
|
||||
consume(:semicolon, "Expect ';' after value.")
|
||||
Print.new(value)
|
||||
end
|
||||
|
||||
def expression_statement()
|
||||
expr = expression
|
||||
consume(:semicolon, "Expect ';' after expression.")
|
||||
Expression.new(expr)
|
||||
end
|
||||
|
||||
def block()
|
||||
stmts = []
|
||||
|
||||
while not check(:right_brace) and not is_at_end
|
||||
stmts << declaration
|
||||
end
|
||||
|
||||
consume(:right_brace, "Expect '}' after block.")
|
||||
|
||||
return stmts
|
||||
end
|
||||
|
||||
def statement()
|
||||
if match(:print)
|
||||
print_statement
|
||||
elsif match(:left_brace)
|
||||
Block.new(block)
|
||||
else
|
||||
expression_statement
|
||||
end
|
||||
end
|
||||
|
||||
def var_declaration()
|
||||
name = consume(:id, "Expect variable name.")
|
||||
|
||||
initializer = nil
|
||||
if match(:eq)
|
||||
initializer = expression
|
||||
end
|
||||
consume(:semicolon, "Expect ';' after variable declaration.")
|
||||
Variable.new(name, initializer)
|
||||
end
|
||||
|
||||
def declaration()
|
||||
begin
|
||||
if match(:let)
|
||||
return var_declaration
|
||||
end
|
||||
return statement
|
||||
rescue ParseError => e
|
||||
synchronize
|
||||
raise e
|
||||
end
|
||||
end
|
||||
|
||||
def parse()
|
||||
expression
|
||||
statements = []
|
||||
while not is_at_end
|
||||
statements << declaration
|
||||
end
|
||||
|
||||
statements
|
||||
end
|
||||
|
||||
def parse_on(tokens)
|
||||
@current = 0
|
||||
@tokens = tokens
|
||||
|
||||
parse
|
||||
end
|
||||
end
|
||||
|
@@ -4,20 +4,26 @@ require './rlox/executor'
|
||||
|
||||
def prompt()
|
||||
exec = Executor.new
|
||||
|
||||
f = File.new("#{Dir.home}/.rlox", File::CREAT|File::RDWR)
|
||||
File.readlines("#{Dir.home}/.rlox").each { | line |
|
||||
Readline::HISTORY.push line
|
||||
}
|
||||
|
||||
begin
|
||||
while buf = Readline.readline("> ", true)
|
||||
Readline::HISTORY.pop if /^\s*$/ =~ buf
|
||||
|
||||
begin
|
||||
if Readline::HISTORY[Readline::HISTORY.length-2] == buf
|
||||
Readline::HISTORY.pop
|
||||
end
|
||||
rescue IndexError
|
||||
end
|
||||
|
||||
begin
|
||||
exec.run buf
|
||||
f.write("#{buf}\n")
|
||||
rescue LoxError => err
|
||||
STDERR.puts err
|
||||
end
|
||||
exec.inc_line
|
||||
end
|
||||
rescue Interrupt
|
||||
ensure
|
||||
f.close
|
||||
end
|
||||
end
|
||||
|
31
rlox/scan.rb
31
rlox/scan.rb
@@ -22,8 +22,7 @@ KEYWORDS = [
|
||||
]
|
||||
|
||||
class Scanner
|
||||
def initialize(source)
|
||||
@source = source
|
||||
def initialize()
|
||||
@start = 0
|
||||
@current = 0
|
||||
@line = 1
|
||||
@@ -53,7 +52,7 @@ class Scanner
|
||||
|
||||
def peek()
|
||||
if is_at_end
|
||||
'\0'
|
||||
"\0"
|
||||
else
|
||||
@source[@current]
|
||||
end
|
||||
@@ -61,7 +60,7 @@ class Scanner
|
||||
|
||||
def peek_next()
|
||||
if @current + 1 >= @source.length
|
||||
'\0'
|
||||
"\0"
|
||||
else
|
||||
@source[@current+1]
|
||||
end
|
||||
@@ -69,7 +68,7 @@ class Scanner
|
||||
|
||||
def string()
|
||||
while peek != '"' and not is_at_end
|
||||
if peek == '\n'
|
||||
if peek == "\n"
|
||||
@line += 1
|
||||
end
|
||||
advance
|
||||
@@ -148,7 +147,7 @@ class Scanner
|
||||
when '>' then add_token(match('=') ? :geq : :gt)
|
||||
when '/' then
|
||||
if match('/')
|
||||
while peek != '\n' and not is_at_end
|
||||
while peek != "\n" and not is_at_end
|
||||
advance
|
||||
end
|
||||
elsif match('*')
|
||||
@@ -165,8 +164,8 @@ class Scanner
|
||||
else
|
||||
add_token(:slash)
|
||||
end
|
||||
when ' ', '\r', '\t' then nil
|
||||
when '\n' then
|
||||
when ' ', "\r", "\t" then nil
|
||||
when "\n" then
|
||||
@line += 1
|
||||
nil
|
||||
when '"' then string
|
||||
@@ -176,7 +175,9 @@ class Scanner
|
||||
elsif is_alpha(c)
|
||||
identifier
|
||||
else
|
||||
raise ParseError.new(@line, "Unexpected character: #{c}.")
|
||||
raise ParseError.new(
|
||||
@line,
|
||||
"Unexpected character: #{c} (char code: #{c.ord}).")
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -205,4 +206,16 @@ class Scanner
|
||||
|
||||
tokens
|
||||
end
|
||||
|
||||
def scan_on(source)
|
||||
@start = 0
|
||||
@current = 0
|
||||
@source = source
|
||||
|
||||
scan
|
||||
end
|
||||
|
||||
def inc_line()
|
||||
@line += 1
|
||||
end
|
||||
end
|
||||
|
Reference in New Issue
Block a user