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
|
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
|
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
|
`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
|
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
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
144
rlox/parse.rb
144
rlox/parse.rb
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -12,7 +12,6 @@ KEYWORDS = [
|
|||||||
"if",
|
"if",
|
||||||
"nil",
|
"nil",
|
||||||
"or",
|
"or",
|
||||||
"print",
|
|
||||||
"return",
|
"return",
|
||||||
"super",
|
"super",
|
||||||
"this",
|
"this",
|
||||||
|
Reference in New Issue
Block a user