done with 11

This commit is contained in:
2018-07-25 15:32:37 +02:00
parent 13106d1788
commit 5adcf93d74
12 changed files with 252 additions and 53 deletions

View File

@@ -7,4 +7,7 @@ because I want to learn it.
It deviates a little from the “canonical” version of Lox. I dont use code It deviates a little from the “canonical” version of Lox. I dont use code
generation for the expressions, and I dont use the visitor pattern. `var` and generation for the expressions, and I dont use the visitor pattern. `var` and
`fun` are `let` and `fn`, respectively. And we dont need parentheses around `fun` are `let` and `fn`, respectively. And we dont need parentheses around
branching conditions, instead we require the bodies to be blocks. branching conditions, instead we require the bodies to be blocks. We also have
closures and anonymous functions (and literals for them), and implicit returns.
If you wonder what that looks like, you can look at the [examples](/examples).

10
examples/counter.lox Normal file
View File

@@ -0,0 +1,10 @@
fn makeCounter() {
let i = 0;
fn count() {
i = i + 1;
}
}
let counter = makeCounter();
print(counter()); // "1".
print(counter()); // "2".

8
examples/fib.lox Normal file
View File

@@ -0,0 +1,8 @@
fn fibonacci(n) {
if n <= 1 { return n; }
return fibonacci(n - 2) + fibonacci(n - 1);
}
for let i = 0; i < 25; i = i + 1 {
print(fibonacci(i));
}

9
examples/thrice.lox Normal file
View File

@@ -0,0 +1,9 @@
fn thrice(f) {
for let i = 1; i <= 3; i = i + 1 {
f(i);
}
}
thrice(fn (a) {
print(a);
});

27
rlox/call.rb Normal file
View File

@@ -0,0 +1,27 @@
class Callable
def initialize(name, arity, &block)
@name = name
@arity = arity
@call = block
end
def to_s()
"#{@name}:#{@arity}"
end
def name()
@name
end
def arity()
@arity
end
def call(args)
begin
@call.call(args)
rescue ReturnError => e
e.value
end
end
end

View File

@@ -1,4 +1,14 @@
require './rlox/call'
class Environment class Environment
def self.global()
env = self.new()
env.define("print", Callable.new("print", 1) { | args | puts args[0] })
env.define("clock", Callable.new("clock", 0) { | args | Time.now.to_i })
env
end
def initialize(parent=nil) def initialize(parent=nil)
@values = {} @values = {}
@parent = parent @parent = parent

View File

@@ -20,3 +20,13 @@ end
class ExecError < LoxError class ExecError < LoxError
end end
class ReturnError < StandardError
def initialize(value)
@value = value
end
def value()
@value
end
end

View File

@@ -4,7 +4,7 @@ require './rlox/scan'
class Executor class Executor
def initialize() def initialize()
@env = Environment.new @env = Environment.global
@scanner = Scanner.new @scanner = Scanner.new
@parser = Parser.new @parser = Parser.new
end end
@@ -14,7 +14,7 @@ class Executor
ast = @parser.parse_on(tokens) ast = @parser.parse_on(tokens)
ast.each { | stmt | ast.each { | stmt |
@env = stmt.eval(@env) stmt.eval(@env)
} }
end end

View File

@@ -24,6 +24,30 @@ def check_same_type_op(operator, *operands)
end end
} }
end end
Call = Struct.new(:callee, :paren, :arguments) do
def eval(env)
to_call = callee.eval(env)
al = arguments.length
if not to_call.class.method_defined? :call
raise ExecError.new(paren.line, "Can only call functions and classes.")
end
if al != to_call.arity
raise ExecError.new(
paren.line,
"#{to_call.name} expected #{to_call.arity} arguments but got #{al}."
)
end
args = []
arguments.each{ | arg |
args << arg.eval(env)
}
to_call.call(args)
end
end
Assign = Struct.new(:name, :value) do Assign = Struct.new(:name, :value) do
def eval(env) def eval(env)
@@ -115,17 +139,21 @@ Logical = Struct.new(:left, :operator, :right) do
right.eval(env) right.eval(env)
end end
end end
Fn = Struct.new(:params, :body) do
def eval(env)
Callable.new("fn", params.length) { | args |
nenv = Environment.new(env)
for i in 0..params.length-1
nenv.define(params[i].lexeme, args[i])
end
body.eval(nenv)
}
end
end
Expression = Struct.new(:expression) do Expression = Struct.new(:expression) do
def eval(env) def eval(env)
expression.eval(env) expression.eval(env)
env
end
end
Print = Struct.new(:print) do
def eval(env)
puts print.eval(env)
env
end end
end end
Variable = Struct.new(:name, :initializer) do Variable = Struct.new(:name, :initializer) do
@@ -136,16 +164,16 @@ Variable = Struct.new(:name, :initializer) do
end end
env.define(name.lexeme, value) env.define(name.lexeme, value)
env
end end
end end
Block = Struct.new(:stmts) do Block = Struct.new(:stmts) do
def eval(env) def eval(env)
child = Environment.new(env) child = Environment.new(env)
ret = nil
stmts.each{ | stmt | stmts.each{ | stmt |
stmt.eval(child) ret = stmt.eval(child)
} }
env ret
end end
end end
If = Struct.new(:cond, :thn, :els) do If = Struct.new(:cond, :thn, :els) do
@@ -155,14 +183,31 @@ If = Struct.new(:cond, :thn, :els) do
elsif els != nil elsif els != nil
els.eval(env) els.eval(env)
end end
env
end end
end end
While = Struct.new(:cond, :body) do While = Struct.new(:cond, :body) do
def eval(env) def eval(env)
ret = nil
while truthy? cond.eval(env) while truthy? cond.eval(env)
body.eval(env) ret = body.eval(env)
end end
env ret
end
end
FnDef = Struct.new(:name, :params, :body) do
def eval(env)
env.define(name.lexeme, Callable.new(name.lexeme, params.length) {
| args |
nenv = Environment.new(env)
for i in 0..params.length-1
nenv.define(params[i].lexeme, args[i])
end
body.eval(nenv)
})
end
end
Return = Struct.new(:value) do
def eval(env)
raise ReturnError.new(value == nil ? nil : value.eval(env))
end end
end end

View File

@@ -23,6 +23,15 @@ class Parser
end end
end end
def check_next(type)
if is_at_end or peek_next.type == :eof
false
else
peek_next.type == type
end
end
def advance() def advance()
if !is_at_end if !is_at_end
@current += 1 @current += 1
@@ -38,20 +47,12 @@ class Parser
@tokens[@current] @tokens[@current]
end end
def previous() def peek_next()
@tokens[@current-1] @tokens[@current+1]
end end
def comma() def previous()
expr = assignment @tokens[@current-1]
while match(:comma)
operator = previous
right = comparison
expr = Binary.new(expr, operator, right)
end
expr
end end
def and_expr() def and_expr()
@@ -134,7 +135,7 @@ class Parser
def comparison() def comparison()
expr = addition expr = addition
while match(:gt, :geq, :lt, :geq) while match(:gt, :geq, :lt, :leq)
operator = previous operator = previous
right = addition right = addition
expr = Binary.new(expr, operator, right) expr = Binary.new(expr, operator, right)
@@ -150,7 +151,51 @@ class Parser
return Unary.new(operator, right) return Unary.new(operator, right)
end end
primary call
end
def call()
expr = primary
while true
if match(:left_paren)
expr = finish_call(expr)
else
break
end
end
expr
end
def finish_call(callee)
args = []
if not check(:right_paren)
begin
args << expression
end while match(:comma)
end
paren = consume(:right_paren, "Expect ')' after arguments.")
return Call.new(callee, paren, args)
end
def anon_fn()
consume(:left_paren, "Expect '(' after 'fn' keyword'.")
params = []
if not check(:right_paren)
begin
params << consume(:id, "Expect parameter name")
end while match(:comma)
end
consume(:right_paren, "Expect ')' after parameters.")
consume(:left_brace, "Expect '{' before fn body.")
body = Block.new(block)
Fn.new(params, body)
end end
def primary() def primary()
@@ -168,6 +213,8 @@ class Parser
Grouping.new(expr) Grouping.new(expr)
elsif match(:id) elsif match(:id)
Var.new(previous) Var.new(previous)
elsif match(:fn)
anon_fn
else else
error(peek, "Expect expression.") error(peek, "Expect expression.")
end end
@@ -190,7 +237,7 @@ class Parser
end end
def expression() def expression()
comma assignment
end end
def synchronize() def synchronize()
@@ -202,7 +249,7 @@ class Parser
end end
case peek.type case peek.type
when :class, :fun, :let, :for, :if, :while, :print, :return then when :class, :fun, :let, :for, :if, :while, :return then
return return
end end
@@ -210,12 +257,6 @@ class Parser
end end
end end
def print_statement()
value = expression
consume(:semicolon, "Expect ';' after value.")
Print.new(value)
end
def expression_statement() def expression_statement()
expr = expression expr = expression
consume(:semicolon, "Expect ';' after expression.") consume(:semicolon, "Expect ';' after expression.")
@@ -261,7 +302,7 @@ class Parser
def for_statement() def for_statement()
init = if match(:semicolon) init = if match(:semicolon)
nil nil
elsif match(:var) elsif match(:let)
var_declaration var_declaration
else else
expression_statement expression_statement
@@ -284,30 +325,41 @@ class Parser
consume(:left_brace, "Expect '{' after 'if' condition.") consume(:left_brace, "Expect '{' after 'if' condition.")
body = Block.new(block) body = Block.new(block)
if inc if inc != nil
body = Block.new([body, Expr.new(inc)]) body = Block.new([body, Expression.new(inc)])
end end
if not cond if cond == nil
cond = Literal.new(true) cond = Literal.new(true)
end end
if init
body = Block.new([init, body])
end
body = While.new(cond, body) body = While.new(cond, body)
if init != nil
body = Block.new([init, body])
end
body body
end end
def return_statement()
value = nil
if !check(:semicolon)
value = expression
end
consume(:semicolon, "Expect ';' after return value.")
Return.new(value)
end
def statement() def statement()
if match(:if) if match(:if)
if_statement if_statement
elsif match(:for) elsif match(:for)
for_statement for_statement
elsif match(:print) elsif match(:return)
print_statement return_statement
elsif match(:while) elsif match(:while)
while_statement while_statement
elsif match(:left_brace) elsif match(:left_brace)
@@ -328,12 +380,33 @@ class Parser
Variable.new(name, initializer) Variable.new(name, initializer)
end end
def function(kind)
consume(:fn, "Expect 'fn' keyword.")
name = consume(:id, "Expect #{kind} name.")
consume(:left_paren, "Expect '(' after #{kind} name.")
params = []
if not check(:right_paren)
begin
params << consume(:id, "Expect paramenter name")
end while match(:comma)
end
consume(:right_paren, "Expect ')' after parameters.")
consume(:left_brace, "Expect '{' before #{kind} body.")
body = Block.new(block)
FnDef.new(name, params, body)
end
def declaration() def declaration()
begin begin
if match(:let) if check(:fn) and check_next(:id)
return var_declaration function("fn")
elsif match(:let)
var_declaration
else
statement
end end
return statement
rescue ParseError => e rescue ParseError => e
synchronize synchronize
raise e raise e
@@ -344,6 +417,7 @@ class Parser
statements = [] statements = []
while not is_at_end while not is_at_end
statements << declaration statements << declaration
match(:semicolon)
end end
statements statements

View File

@@ -14,8 +14,12 @@ def prompt()
while buf = Readline.readline("> ", true) while buf = Readline.readline("> ", true)
Readline::HISTORY.pop if /^\s*$/ =~ buf Readline::HISTORY.pop if /^\s*$/ =~ buf
if buf[-1] != ';'
buf << ';'
end
begin begin
exec.run buf puts exec.run(buf).to_s
rescue LoxError => err rescue LoxError => err
STDERR.puts err STDERR.puts err
ensure ensure

View File

@@ -12,7 +12,6 @@ KEYWORDS = [
"if", "if",
"nil", "nil",
"or", "or",
"print",
"return", "return",
"super", "super",
"this", "this",