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