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
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
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
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)
@values = {}
@parent = parent

View File

@@ -20,3 +20,13 @@ end
class ExecError < LoxError
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
def initialize()
@env = Environment.new
@env = Environment.global
@scanner = Scanner.new
@parser = Parser.new
end
@@ -14,7 +14,7 @@ class Executor
ast = @parser.parse_on(tokens)
ast.each { | stmt |
@env = stmt.eval(@env)
stmt.eval(@env)
}
end

View File

@@ -24,6 +24,30 @@ def check_same_type_op(operator, *operands)
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
def eval(env)
@@ -115,17 +139,21 @@ Logical = Struct.new(:left, :operator, :right) do
right.eval(env)
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
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
@@ -136,16 +164,16 @@ Variable = Struct.new(:name, :initializer) do
end
env.define(name.lexeme, value)
env
end
end
Block = Struct.new(:stmts) do
def eval(env)
child = Environment.new(env)
ret = nil
stmts.each{ | stmt |
stmt.eval(child)
ret = stmt.eval(child)
}
env
ret
end
end
If = Struct.new(:cond, :thn, :els) do
@@ -155,14 +183,31 @@ If = Struct.new(:cond, :thn, :els) do
elsif els != nil
els.eval(env)
end
env
end
end
While = Struct.new(:cond, :body) do
def eval(env)
ret = nil
while truthy? cond.eval(env)
body.eval(env)
ret = body.eval(env)
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

View File

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

View File

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

View File

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