done with 11
This commit is contained in:
@@ -7,4 +7,7 @@ because I want to learn it.
|
||||
It deviates a little from the “canonical” version of Lox. I don’t use code
|
||||
generation for the expressions, and I don’t use the visitor pattern. `var` and
|
||||
`fun` are `let` and `fn`, respectively. And we don’t 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
10
examples/counter.lox
Normal 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
8
examples/fib.lox
Normal 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
9
examples/thrice.lox
Normal 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
27
rlox/call.rb
Normal 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
|
@@ -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
|
||||
|
@@ -20,3 +20,13 @@ end
|
||||
|
||||
class ExecError < LoxError
|
||||
end
|
||||
|
||||
class ReturnError < StandardError
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def value()
|
||||
@value
|
||||
end
|
||||
end
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
144
rlox/parse.rb
144
rlox/parse.rb
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -12,7 +12,6 @@ KEYWORDS = [
|
||||
"if",
|
||||
"nil",
|
||||
"or",
|
||||
"print",
|
||||
"return",
|
||||
"super",
|
||||
"this",
|
||||
|
Reference in New Issue
Block a user