done with 8

This commit is contained in:
2018-07-24 18:34:17 +02:00
parent 946ab8d8f4
commit 856edc4d17
7 changed files with 237 additions and 42 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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