Compare commits

..

No commits in common. 'main' and 'private' have entirely different histories.

@ -1,7 +1,6 @@
# Crafting Interpreters # Crafting Interpreters
- [Crafting Interpreters](https://craftinginterpreters.com/) - [Crafting Interpreters](https://craftinginterpreters.com/)
- [Source repo](https://github.com/munificent/craftinginterpreters)
## Installing Lox ## Installing Lox

@ -18,7 +18,6 @@ module Lox
def scan(src) = @scanner.scan(src) def scan(src) = @scanner.scan(src)
def parse(tokens) = Parser.new(tokens).parse! def parse(tokens) = Parser.new(tokens).parse!
def resolve(stmts) = Resolver.new(@interpreter).resolve(*stmts)
def interpret(stmts) = @interpreter.interpret(stmts) def interpret(stmts) = @interpreter.interpret(stmts)
end end

@ -40,28 +40,12 @@ module Lox
parenthesize(call.callee.accept(self), *call.args) parenthesize(call.callee.accept(self), *call.args)
end end
def visit_return(stmt)
exprs = stmt.value ? [stmt.value] : []
parenthesize("return", *exprs)
end
def visit_while(stmt)
parenthesize("while", stmt.cond, stmt.body)
end
def visit_class(stmt)
exprs = [stmt.superclass, *stmt.methods].compact
parenthesize("class #{stmt.name.lexeme}", *exprs)
end
def visit_function(stmt)
parenthesize("function #{stmt.name.lexeme}", *stmt.body)
end
private private
def parenthesize(name, *exprs) def parenthesize(name, *exprs)
"(#{[name, *exprs.map { _1.accept(self) }].join(" ")})" inside = [name]
inside.concat(exprs.map {|expr| expr.accept(self) })
"(#{inside.join(" ")})"
end end
end end
end end

@ -2,8 +2,6 @@ require_relative "error"
module Lox module Lox
class Environment class Environment
attr_reader :values, :enclosing
def initialize(enclosing = nil) def initialize(enclosing = nil)
@enclosing = enclosing @enclosing = enclosing
@values = {} @values = {}
@ -13,12 +11,6 @@ module Lox
@values[name] = value @values[name] = value
end end
def ancestor(distance)
env = self
distance.times { env = env.enclosing }
env
end
def get(token) def get(token)
name = token.lexeme name = token.lexeme
@ -31,10 +23,6 @@ module Lox
end end
end end
def get_at(distance, name)
ancestor(distance).values.fetch(name)
end
def assign(name, value) def assign(name, value)
lexeme = name.lexeme lexeme = name.lexeme
@ -46,9 +34,5 @@ module Lox
raise RuntimeError.new(name, "Undefined variable '#{lexeme}'.") raise RuntimeError.new(name, "Undefined variable '#{lexeme}'.")
end end
end end
def assign_at(distance, name, value)
ancestor(distance).values[name.lexeme] = value
end
end end
end end

@ -1,11 +1,5 @@
module Lox module Lox
class Error < StandardError class Error < StandardError
attr_reader :token, :message
def initialize(token, message)
@token, @message = token, message
end
end end
class ParseError < Error class ParseError < Error
@ -14,12 +8,16 @@ module Lox
error = "Error" error = "Error"
error << " at #{where}" unless where.empty? error << " at #{where}" unless where.empty?
super("[line #{token.line}] #{error}: #{message}")
super(token, "[line #{token.line}] #{error}: #{message}")
end end
end end
RuntimeError = Class.new(Error) class RuntimeError < Error
ResolverError = Class.new(Error) attr_reader :token
def initialize(token, message)
@token = token
super(message)
end
end
end end

@ -13,13 +13,9 @@ module Lox
expr :Assign, :name, :value expr :Assign, :name, :value
expr :Binary, :left, :op, :right expr :Binary, :left, :op, :right
expr :Call, :callee, :paren, :args expr :Call, :callee, :paren, :args
expr :Get, :object, :name
expr :Grouping, :expr expr :Grouping, :expr
expr :Literal, :value expr :Literal, :value
expr :Logical, :left, :op, :right expr :Logical, :left, :op, :right
expr :Set, :object, :name, :value
expr :Super, :keyword, :method
expr :This, :keyword
expr :Unary, :op, :right expr :Unary, :op, :right
expr :Variable, :name expr :Variable, :name
end end

@ -1,34 +0,0 @@
require_relative "environment"
module Lox
class Function
def initialize(decl, closure, is_initializer)
@decl, @closure, @is_initializer = decl, closure, is_initializer
end
def bind(instance)
env = Environment.new(@closure)
env.define("this", instance)
Function.new(@decl, env, @is_initializer)
end
def arity = @decl.params.size
def call(interpreter, args)
env = Environment.new(@closure)
@decl.params.map(&:lexeme).zip(args).each do |name, value|
env.define(name, value)
end
return_value = catch(:return) {
interpreter.execute_block(@decl.body, env)
}
return @closure.get_at(0, "this") if @is_initializer
return_value
end
def to_s = "<fn #{@decl.name.lexeme}>"
end
end

@ -1,28 +0,0 @@
require_relative "error"
module Lox
class Instance
def initialize(klass)
@klass = klass
@fields = {}
end
def get(name)
return @fields.fetch(name.lexeme) if @fields.has_key?(name.lexeme)
method = @klass.find_method(name.lexeme)
return method.bind(self) unless method.nil?
raise RuntimeError.new(name, "Undefined property '#{name.lexeme}'.")
end
def set(name, value)
@fields[name.lexeme] = value
end
def to_s = "#{@klass.name} instance"
end
end

@ -1,27 +1,10 @@
require_relative "environment" require_relative "environment"
require_relative "function"
require_relative "lox_class"
module Lox module Lox
class Interpreter class Interpreter
attr_reader :globals
def initialize(env=Environment.new) def initialize(env=Environment.new)
@globals = env @env = env
@env = @globals
@locals = {}
# We do **NOT** want struct equality for getting the depth of a local
# since we can have multiple locals referencing the same variable in
# different scopes on a single line.
@locals.compare_by_identity
@globals.define("clock", Class.new {
def arity = 0
def call(*) = Time.now.to_f
def to_s = "<native fn>"
})
end end
# The book does printing and error catching here, but # The book does printing and error catching here, but
@ -35,42 +18,11 @@ module Lox
def evaluate(expr) = expr.accept(self) def evaluate(expr) = expr.accept(self)
def execute(stmt) = stmt.accept(self) def execute(stmt) = stmt.accept(self)
def resolve(expr, depth)
@locals[expr] = depth
end
def visit_block(stmt) def visit_block(stmt)
execute_block(stmt.stmts, Environment.new(@env)) execute_block(stmt.stmts, Environment.new(@env))
nil nil
end end
def visit_class(stmt)
superclass = if stmt.superclass
superclass = evaluate(stmt.superclass)
raise RuntimeError.new(stmt.superclass.name, "Superclass must be a class.") unless superclass.is_a?(LoxClass)
superclass
else
nil
end
@env.define(stmt.name.lexeme, nil)
if superclass
@env = Environment.new(@env)
@env.define("super", superclass)
end
methods = stmt.methods.to_h {|method|
[method.name.lexeme, Function.new(method, @env, method.name.lexeme == "init")]
}
klass = LoxClass.new(stmt.name.lexeme, superclass, methods)
@env = @env.enclosing if superclass
@env.assign(stmt.name, klass)
nil
end
def execute_block(stmts, env) def execute_block(stmts, env)
prev_env = @env prev_env = @env
@env = env @env = env
@ -87,12 +39,6 @@ module Lox
nil nil
end end
def visit_function(stmt)
function = Function.new(stmt, @env, false)
@env.define(stmt.name.lexeme, function)
nil
end
def visit_if(stmt) def visit_if(stmt)
if truthy?(evaluate(stmt.cond)) if truthy?(evaluate(stmt.cond))
evaluate(stmt.then) evaluate(stmt.then)
@ -107,12 +53,6 @@ module Lox
nil nil
end end
def visit_return(stmt)
value = stmt.value ? evaluate(stmt.value) : nil
throw(:return, value)
end
def visit_var(stmt) def visit_var(stmt)
value = stmt.initializer&.yield_self { evaluate(_1) } value = stmt.initializer&.yield_self { evaluate(_1) }
@env.define(stmt.name.lexeme, value) @env.define(stmt.name.lexeme, value)
@ -141,28 +81,6 @@ module Lox
evaluate(expr.right) evaluate(expr.right)
end end
def visit_set(expr)
object = evaluate(expr.object)
raise RuntimeError.new(expr.name, "Only instances have fields.") unless object.is_a?(Instance)
value = evaluate(expr.value)
object.set(expr.name, value)
value
end
def visit_super(expr)
distance = @locals.fetch(expr)
superclass = @env.get_at(distance, "super")
object = @env.get_at(distance-1, "this")
method = superclass.find_method(expr.method.lexeme)
raise RuntimeError.new(expr.method, "Undefined property '#{expr.method.lexeme}'.") if method.nil?
method.bind(object)
end
def visit_this(expr) = lookup_var(expr.keyword, expr)
def visit_unary(expr) def visit_unary(expr)
right = evaluate(expr.right) right = evaluate(expr.right)
@ -176,28 +94,12 @@ module Lox
end end
def visit_variable(expr) def visit_variable(expr)
lookup_var(expr.name, expr) @env.get(expr.name)
end
def lookup_var(name, expr)
if @locals.has_key?(expr)
distance = @locals.fetch(expr)
@env.get_at(distance, name.lexeme)
else
@globals.get(name)
end
end end
def visit_assign(expr) def visit_assign(expr)
value = evaluate(expr.value) value = evaluate(expr.value)
@env.assign(expr.name, value)
if @locals.has_key?(expr)
distance = @locals.fetch(expr)
@env.assign_at(distance, expr.name, value)
else
@globals.assign(expr.name, value)
end
value value
end end
@ -248,13 +150,6 @@ module Lox
func.call(self, args) func.call(self, args)
end end
def visit_get(expr)
object = evaluate(expr.object)
raise RuntimeError.new(expr.name, "Only instances have properties.") unless object.is_a?(Instance)
object.get(expr.name)
end
private private
def truthy?(value) = !!value def truthy?(value) = !!value

@ -1,34 +0,0 @@
require_relative "instance"
module Lox
class LoxClass
attr_reader :name
def initialize(name, superclass, methods)
@name, @superclass, @methods = name, superclass, methods
end
def find_method(name)
@methods.fetch(name) { @superclass&.find_method(name) }
end
def to_s = name
def call(interpreter, args)
instance = Instance.new(self)
if init = find_method("init")
init.bind(instance).call(interpreter, args)
end
instance
end
def arity
init = find_method("init")
init ? init.arity : 0
end
end
end

@ -16,43 +16,18 @@ module Lox
statements << declaration statements << declaration
end end
statements statements
rescue ParseError => e rescue ParseError
$stderr.puts e.message
synchronize! synchronize!
end end
private private
def declaration def declaration
return class_decl if match?(:CLASS)
return function("function") if match?(:FUN)
return var_declaration if match?(:VAR) return var_declaration if match?(:VAR)
statement statement
end end
def class_decl
name = consume!(:IDENTIFIER, "Expect class name.")
superclass = if match?(:LESS)
consume!(:IDENTIFIER, "Expect superclass name.")
Expr::Variable.new(prev)
else
nil
end
consume!(:LEFT_BRACE, "Expect '{' before class body.")
methods = []
until check?(:RIGHT_BRACE) || eot?
methods << function("method")
end
consume!(:RIGHT_BRACE, "Expect '}' after class body.")
Stmt::Class.new(name, superclass, methods)
end
def var_declaration def var_declaration
name = consume!(:IDENTIFIER, "Expect variable name.") name = consume!(:IDENTIFIER, "Expect variable name.")
initializer = match?(:EQUAL) ? expression : nil initializer = match?(:EQUAL) ? expression : nil
@ -74,7 +49,6 @@ module Lox
return for_stmt if match?(:FOR) return for_stmt if match?(:FOR)
return if_stmt if match?(:IF) return if_stmt if match?(:IF)
return print if match?(:PRINT) return print if match?(:PRINT)
return return_stmt if match?(:RETURN)
return while_stmt if match?(:WHILE) return while_stmt if match?(:WHILE)
return Stmt::Block.new(block) if match?(:LEFT_BRACE) return Stmt::Block.new(block) if match?(:LEFT_BRACE)
@ -92,10 +66,10 @@ module Lox
expression_stmt expression_stmt
end end
condition = check?(:SEMICOLON) ? Expr::Literal.new(true) : expression condition = !check?(:SEMICOLON) ? expression : Expr::Literal(true)
consume!(:SEMICOLON, "Expect ';' after loop condition.") consume!(:SEMICOLON, "Expect ';' after loop condition.")
increment = check?(:RIGHT_PAREN) ? nil : expression increment = !check?(:RIGHT_PAREN) ? expression : nil
consume!(:RIGHT_PAREN, "Expect ')' after for clauses.") consume!(:RIGHT_PAREN, "Expect ')' after for clauses.")
body = statement body = statement
@ -136,39 +110,12 @@ module Lox
Stmt::Print.new(value) Stmt::Print.new(value)
end end
def return_stmt
keyword = prev
value = check?(:SEMICOLON) ? nil : expression
consume!(:SEMICOLON, "Expect ';' after return value.")
Stmt::Return.new(keyword, value)
end
def expression_stmt def expression_stmt
value = expression value = expression
consume!(:SEMICOLON, "Expect ';' after value.") consume!(:SEMICOLON, "Expect ';' after value.")
Stmt::Expr.new(value) Stmt::Expr.new(value)
end end
def function(kind)
name = consume!(:IDENTIFIER, "Expect #{kind} name.")
consume!(:LEFT_PAREN, "Expect '(' after #{kind} name.")
params = []
unless check?(:RIGHT_PAREN)
loop do
raise ParseError.new(peek, "Can't have more than 255 parameters.") if params.size >= 255
params << consume!(:IDENTIFIER, "Expect parameter name.")
break unless match?(:COMMA)
end
end
consume!(:RIGHT_PAREN, "Expect ')' after parameters.")
consume!(:LEFT_BRACE, "Expect '{' before #{kind} body.")
body = block
Stmt::Function.new(name, params, body)
end
def block def block
statements = [] statements = []
until check?(:RIGHT_BRACE) || eot? until check?(:RIGHT_BRACE) || eot?
@ -181,19 +128,16 @@ module Lox
def assignment def assignment
expr = or_ expr = or_
return expr unless match?(:EQUAL) if match?(:EQUAL)
eq = prev
value = assignment
eq = prev raise ParseError.new(eq, "Invalid assignment target.") unless expr.instance_of?(Expr::Variable)
value = assignment
case expr return Expr::Assign.new(expr.name, value)
when Expr::Variable
Expr::Assign.new(expr.name, value)
when Expr::Get
Expr::Set.new(expr.object, expr.name, value)
else
raise ParseError.new(eq, "Invalid assignment target.")
end end
expr
end end
def or_ def or_
@ -284,9 +228,6 @@ module Lox
loop do loop do
if match?(:LEFT_PAREN) if match?(:LEFT_PAREN)
expr = finish_call(expr) expr = finish_call(expr)
elsif match?(:DOT)
name = consume!(:IDENTIFIER, "Expect property name after '.'.")
expr = Expr::Get.new(expr, name)
else else
break break
end end
@ -316,15 +257,6 @@ module Lox
return Expr::Literal.new(true) if match?(:TRUE) return Expr::Literal.new(true) if match?(:TRUE)
return Expr::Literal.new(nil) if match?(:NIL) return Expr::Literal.new(nil) if match?(:NIL)
return Expr::Literal.new(prev.literal) if match?(:NUMBER, :STRING) return Expr::Literal.new(prev.literal) if match?(:NUMBER, :STRING)
if match?(:SUPER)
keyword = prev
consume!(:DOT, "Expect '.' after 'super'.")
method = consume!(:IDENTIFIER, "Expect superclass method name.")
return Expr::Super.new(keyword, method)
end
return Expr::This.new(prev) if match?(:THIS)
return Expr::Variable.new(prev) if match?(:IDENTIFIER) return Expr::Variable.new(prev) if match?(:IDENTIFIER)
if match?(:LEFT_PAREN) if match?(:LEFT_PAREN)

@ -1,204 +0,0 @@
require_relative "error"
module Lox
class Resolver
def initialize(interpreter)
@interpreter = interpreter
@scopes = []
@current_func = :NONE
@current_class = :NONE
end
def resolve(*resolvees)
resolvees.each do |resolvee|
resolvee.accept(self)
end
nil
end
def visit_block(stmt)
with_scope do
resolve(*stmt.stmts)
end
nil
end
def visit_class(stmt)
with_current_class(:CLASS) do
declare(stmt.name)
define(stmt.name)
with_superclass_scope = if stmt.superclass
raise ResolverError.new(stmt.superclass.name, "A class can't inherit from itself.") if stmt.name.lexeme == stmt.superclass.name.lexeme
@current_class = :SUBCLASS
resolve(stmt.superclass)
->(&block) { with_scope(super: true) { block.call } }
else
->(&block) { block.call }
end
with_superclass_scope.call do
with_scope(this: true) do
stmt.methods.each do |method|
decl = method.name.lexeme == "init" ? :INIT : :METHOD
resolve_function(method, decl)
end
end
end
end
nil
end
def visit_expr(stmt) = resolve(stmt.expr)
def visit_function(stmt)
declare(stmt.name)
define(stmt.name)
resolve_function(stmt, :FUNCTION)
nil
end
def visit_if(stmt)
resolve(stmt.cond, stmt.then)
resolve(stmt.else) if stmt.else
end
def visit_print(stmt) = resolve(stmt.expr)
def visit_return(stmt)
raise ResolverError.new(stmt.keyword, "Can't return from top-level code.") if @current_func == :NONE
return unless stmt.value
raise ResolverError.new(stmt.keyword, "Can't return a value from an initializer.") if @current_func == :INIT
resolve(stmt.value)
end
def visit_var(stmt)
declare(stmt.name)
resolve(stmt.initializer) if stmt.initializer
define(stmt.name)
nil
end
def visit_while(stmt) = resolve(stmt.cond, stmt.body)
def visit_variable(expr)
if !@scopes.empty? && @scopes.last[expr.name.lexeme] == false
raise ResolverError.new(expr.name, "Can't read local variable in its own initializer.")
end
resolve_local(expr, expr.name)
nil
end
def visit_assign(expr)
resolve(expr.value)
resolve_local(expr, expr.name)
nil
end
def visit_binary(expr) = resolve(expr.left, expr.right)
def visit_call(expr) = resolve(expr.callee, *expr.args)
def visit_get(expr)
resolve(expr.object)
nil
end
def visit_grouping(expr) = resolve(expr.expr)
def visit_literal(expr) = nil
def visit_logical(expr) = resolve(expr.left, expr.right)
def visit_set(expr)
resolve(expr.value)
resolve(expr.object)
nil
end
def visit_super(expr)
raise ResolverError.new(expr.keyword, "Can't use 'super' outside of a class.") if @current_class == :NONE
raise ResolverError.new(expr.keyword, "Can't use 'super' in a class with no superclass.") if @current_class != :SUBCLASS
resolve_local(expr, expr.keyword)
nil
end
def visit_this(expr)
raise ResolverError.new(expr.keyword, "Can't use 'this' outside of a class.") if @current_class == :NONE
resolve_local(expr, expr.keyword)
nil
end
def visit_unary(expr) = resolve(expr.right)
private
def with_scope(scope={})
@scopes.push(scope.transform_keys(&:to_s))
yield
@scopes.pop
end
def declare(name)
scope = @scopes.last
return if scope.nil?
raise ResolverError.new(name, "Already a variable with this name in this scope") if scope.has_key?(name.lexeme)
scope[name.lexeme] = false
end
def define(name)
scope = @scopes.last
return if scope.nil?
scope[name.lexeme] = true
end
def resolve_local(expr, name)
scope_and_depth = @scopes.reverse.each.with_index.find {|scope, depth| scope.has_key?(name.lexeme) }
return unless scope_and_depth
scope, depth = scope_and_depth
@interpreter.resolve(expr, depth)
end
def resolve_function(fn, type)
with_func_type(type) do
with_scope do
fn.params.each do |param|
declare(param)
define(param)
end
resolve(*fn.body)
end
end
end
def with_func_type(type)
enclosing_func = @current_func
@current_func = type
yield
@current_func = enclosing_func
end
def with_current_class(type)
enclosing_class = @current_class
@current_class = type
yield
@current_class = enclosing_class
end
end
end

@ -13,12 +13,9 @@ module Lox
end end
stmt :Block, :stmts stmt :Block, :stmts
stmt :Class, :name, :superclass, :methods
stmt :Expr, :expr stmt :Expr, :expr
stmt :Function, :name, :params, :body
stmt :If, :cond, :then, :else stmt :If, :cond, :then, :else
stmt :Print, :expr stmt :Print, :expr
stmt :Return, :keyword, :value
stmt :Var, :name, :initializer stmt :Var, :name, :initializer
stmt :While, :cond, :body stmt :While, :cond, :body
end end

@ -44,26 +44,4 @@ class TestEnvironment < Lox::Test
assert_equal "qux", enclosed.get(NAME_TOKEN) assert_equal "qux", enclosed.get(NAME_TOKEN)
assert_equal "bar", @env.get(NAME_TOKEN) assert_equal "bar", @env.get(NAME_TOKEN)
end end
def test_get_at
@env.define("name", "foo")
enclosed = Lox::Environment.new(@env)
enclosed.define("name", "bar")
assert_equal "foo", enclosed.get_at(1, "name")
assert_equal "bar", enclosed.get_at(0, "name")
assert_equal "foo", @env.get_at(0, "name")
end
def test_assign_at
enclosed = Lox::Environment.new(@env)
enclosed.assign_at(0, NAME_TOKEN, "foo")
assert_equal "foo", enclosed.get_at(0, "name")
enclosed.assign_at(1, NAME_TOKEN, "bar")
assert_equal "bar", enclosed.get_at(1, "name")
end
end end

@ -2,12 +2,12 @@ require_relative "../test_helper"
require "lox/interpreter" require "lox/interpreter"
require "lox/parser" require "lox/parser"
require "lox/resolver"
require "lox/scanner" require "lox/scanner"
class TestInterpreter < Lox::Test class TestInterpreter < Lox::Test
def setup def setup
@scanner = Lox::Scanner.new @scanner = Lox::Scanner.new
@interpreter = Lox::Interpreter.new
end end
# def test_literal # def test_literal
@ -224,327 +224,6 @@ class TestInterpreter < Lox::Test
SRC SRC
end end
def test_function
assert_interpreted <<~EXPECTED.chomp, <<~SRC
Hi, Dear Reader!
EXPECTED
fun sayHi(first, last) {
print "Hi, " + first + " " + last + "!";
}
sayHi("Dear", "Reader");
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
{
var i = 0; while (i < 3) { { print i; } i = i + 1; }
}
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
2
EXPECTED
for (var i = 0; i < 3; i = i + 1) print i;
SRC
assert_interpreted <<~EXPECTED.chomp, <<~SRC
0
1
1
2
3
5
8
EXPECTED
fun fib(n) {
if (n <= 1) return n;
return fib(n - 2) + fib(n - 1);
}
for (var i = 0; i < 7; i = i + 1) {
print fib(i);
}
SRC
end
def test_local_functions_and_closures
assert_interpreted <<~EXPECTED.chomp, <<~SRC
1
2
EXPECTED
fun makeCounter() {
var i = 0;
fun count() {
i = i + 1;
print i;
}
return count;
}
var counter = makeCounter();
counter(); // "1".
counter(); // "2".
SRC
end
def test_resolver
assert_interpreted <<~EXPECTED.chomp, <<~SRC
1
EXPECTED
var foo = 1;
{
print foo;
var foo = 2;
}
SRC
end
def test_class
assert_interpreted "", <<~SRC
class Breakfast {
cook() {
print "Eggs a-fryin'!";
}
serve(who) {
print "Enjoy your breakfast, " + who + ".";
}
}
SRC
assert_interpreted "DevonshireCream", <<~SRC
class DevonshireCream {
serveOn() {
return "Scones";
}
}
print DevonshireCream; // Prints "DevonshireCream".
SRC
assert_interpreted "Bagel instance", <<~SRC
class Bagel {}
var bagel = Bagel();
print bagel; // Prints "Bagel instance".
SRC
end
def test_set
assert_interpreted "bar", <<~SRC
class Bagel {}
var bagel = Bagel();
bagel.foo = "bar";
print bagel.foo;
SRC
end
def test_class_methods
assert_interpreted "Crunch crunch crunch!", <<~SRC
class Bacon {
eat() {
print "Crunch crunch crunch!";
}
}
Bacon().eat(); // Prints "Crunch crunch crunch!".
SRC
assert_interpreted "The German chocolate cake is delicious!", <<~SRC
class Cake {
taste() {
var adjective = "delicious";
print "The " + this.flavor + " cake is " + adjective + "!";
}
}
var cake = Cake();
cake.flavor = "German chocolate";
cake.taste(); // Prints "The German chocolate cake is delicious!".
SRC
assert_interpreted "Thing instance", <<~SRC
class Thing {
getCallback() {
fun localFunction() {
print this;
}
return localFunction;
}
}
var callback = Thing().getCallback();
callback();
SRC
assert_interpreted "Jane", <<~SRC
class Person {
sayName() {
print this.name;
}
}
var jane = Person();
jane.name = "Jane";
var bill = Person();
bill.name = "Bill";
bill.sayName = jane.sayName;
bill.sayName(); // "Jane"
SRC
end
def test_invalid_this
assert_raises Lox::ResolverError, "Can't use 'this' outside of a class." do
interpret("print this;")
end
assert_raises Lox::ResolverError, "Can't use 'this' outside of a class." do
interpret(<<~SRC)
fun notAMethod() {
print this;
}
SRC
end
end
def test_init
assert_interpreted <<~EXPECTED.chomp, <<~SRC
Foo instance
Foo instance
Foo instance
EXPECTED
class Foo {
init() {
print this;
}
}
var foo = Foo();
print foo.init();
SRC
assert_raises Lox::ResolverError do
interpret(<<~SRC)
class Foo {
init() {
return "something else";
}
}
SRC
end
assert_interpreted "", <<~SRC
class Foo {
init() {
return;
}
}
SRC
end
def test_inheritance
assert_interpreted "", <<~SRC
class Doughnut {}
class BostonCream < Doughnut {}
SRC
assert_raises Lox::ResolverError do
interpret("class Oops < Oops {}")
end
assert_raises Lox::RuntimeError do
interpret(<<~SRC)
var NotAClass = "I am totally not a class";
class Subclass < NotAClass {} // ?!
SRC
end
end
def test_inheriting_methods
assert_interpreted "Fry until golden brown.", <<~SRC
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {}
BostonCream().cook();
SRC
end
def test_calling_superclass_methods
assert_interpreted <<~OUT.chomp, <<~SRC
Fry until golden brown.
Pipe full of custard and coat with chocolate.
OUT
class Doughnut {
cook() {
print "Fry until golden brown.";
}
}
class BostonCream < Doughnut {
cook() {
super.cook();
print "Pipe full of custard and coat with chocolate.";
}
}
BostonCream().cook();
SRC
assert_interpreted "A method", <<~SRC
class A {
method() {
print "A method";
}
}
class B < A {
method() {
print "B method";
}
test() {
super.method();
}
}
class C < B {}
C().test();
SRC
end
def test_invalid_super
assert_raises Lox::ResolverError do
interpret(<<~SRC)
class Eclair {
cook() {
super.cook();
print "Pipe full of crème pâtissière.";
}
}
SRC
end
assert_raises Lox::ResolverError do
interpret("super.notEvenInAClass();")
end
end
private private
def assert_interpreted(expected, src) def assert_interpreted(expected, src)
@ -570,12 +249,7 @@ class TestInterpreter < Lox::Test
def interpret(src) def interpret(src)
with_stdout { with_stdout {
stmts = Lox::Parser.new(@scanner.scan(src)).parse! stmts = Lox::Parser.new(@scanner.scan(src)).parse!
interpreter = Lox::Interpreter.new @interpreter.interpret(stmts)
resolver = Lox::Resolver.new(interpreter);
resolver.resolve(*stmts);
interpreter.interpret(stmts)
} }
end end

@ -81,37 +81,6 @@ class TestParser < Lox::Test
assert_parsed "((var foo) (var bar) (var baz))", :statement, "foo(bar, baz);" assert_parsed "((var foo) (var bar) (var baz))", :statement, "foo(bar, baz);"
end end
def test_return
assert_parsed "(return)", :statement, "return;"
assert_parsed "(return (var foo))", :statement, "return foo;"
end
def test_for
assert_parsed <<~AST.chomp, :statement, "for(var i=0; i<3; i=i+1) print i;"
(block (var i 0.0) (while (< (var i) 3.0) (block (print (var i)) (assign i (+ (var i) 1.0)))))
AST
assert_parsed <<~AST.chomp, :statement, "for(var i=0; ; i=i+1) print i;"
(block (var i 0.0) (while true (block (print (var i)) (assign i (+ (var i) 1.0)))))
AST
end
def test_class
assert_parsed <<~AST.chomp, :declaration, <<~SRC
(class Breakfast (function cook (print Eggs a-fryin'!)) (function serve (print (+ (+ Enjoy your breakfast, (var who)) .))))
AST
class Breakfast {
cook() {
print "Eggs a-fryin'!";
}
serve(who) {
print "Enjoy your breakfast, " + who + ".";
}
}
SRC
end
private private
def assert_parsed(expected, name, src) def assert_parsed(expected, name, src)

@ -1,94 +0,0 @@
require_relative "../test_helper"
require "lox/ast_printer"
require "lox/parser"
require "lox/resolver"
require "lox/scanner"
class TestResolver < Lox::Test
def test_resolver
assert_resolved [
["(var j)", 0],
["(var j)", 2],
["(var j)", 1],
["(assign j (+ (var j) 1.0))", 1],
["(var k)", 0],
["(var k)", 2],
["(var k)", 1],
["(assign k (+ (var k) 1.0))", 1],
], <<~SRC
var i = 0;
while (i < 3) {
print i;
i = i + 1;
}
for(var j=0; j<3; j=j+1) {
print j;
}
{
var k = 0;
while (k < 3) {
{
print k;
}
k = k + 1;
}
}
SRC
end
def test_same_var_name_in_scope
assert_raises Lox::ResolverError do
resolve(<<~SRC)
fun bad() {
var a = "first";
var a = "second";
}
SRC
end
end
def test_returning_from_top_level
assert_raises Lox::ResolverError do
resolve(<<~SRC)
return;
SRC
end
end
private
def assert_resolved(expected, src)
resolves = resolve(src)
ast_printer = Lox::AstPrinter.new
assert_equal expected, resolves.map {|expr, depth|
[ast_printer.print(expr), depth]
}
end
def resolve(src)
stmts = Lox::Parser.new(Lox::Scanner.new.scan(src)).parse!
interpreter = Interpreter.new
Lox::Resolver.new(interpreter).resolve(*stmts)
interpreter.resolves
end
class Interpreter
attr_reader :resolves
def initialize
@resolves = []
end
def resolve(expr, depth)
resolves << [expr, depth]
end
end
end

1
rust/.gitignore vendored

@ -1 +0,0 @@
target

@ -1,6 +0,0 @@
condense_wildcard_suffixes = true
format_strings = true
reorder_impl_items = true
group_imports = "StdExternalCrate"
use_field_init_shorthand = true
wrap_comments = true

364
rust/Cargo.lock generated

@ -1,364 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "backtrace"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "color-eyre"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204"
dependencies = [
"backtrace",
"color-spantrace",
"eyre",
"indenter",
"once_cell",
"owo-colors",
"tracing-error",
]
[[package]]
name = "color-spantrace"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
dependencies = [
"once_cell",
"owo-colors",
"tracing-core",
"tracing-error",
]
[[package]]
name = "eyre"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "gimli"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "indenter"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "lox"
version = "0.1.0"
dependencies = [
"color-eyre",
"thiserror",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34"
dependencies = [
"adler",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "object"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owo-colors"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-error"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
dependencies = [
"tracing",
"tracing-subscriber",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -1,12 +0,0 @@
[package]
name = "lox"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
color-eyre = "0.6.2"
thiserror = "1.0.32"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"

@ -1,3 +0,0 @@
test-watch:
# while true; do fd .*.rs | entr -d cargo test; done
fd .*.rs | entr -d cargo test

@ -1,85 +0,0 @@
use std::fmt;
use crate::value::Value;
#[derive(Debug)]
pub enum OpCode {
Constant(usize),
Add,
Subtract,
Multiply,
Divide,
Negate,
Return,
}
#[derive(Debug, Default)]
pub struct Chunk {
pub code: Vec<OpCode>,
pub lines: Vec<usize>,
pub constants: Vec<Value>,
}
impl Chunk {
pub fn push(&mut self, op_code: OpCode, line: usize) {
self.code.push(op_code);
self.lines.push(line);
}
pub fn push_constant(&mut self, value: Value, line: usize) {
self.constants.push(value);
self.push(OpCode::Constant(self.constants.len() - 1), line)
}
pub fn disassemble(&self, name: &str) {
println!("== {} ==\n{}", name, self);
}
}
impl fmt::Display for Chunk {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in 0..self.code.len() {
writeln!(f, "{}", DisassembledInstruction { i, chunk: self })?;
}
Ok(())
}
}
pub struct DisassembledInstruction<'a> {
i: usize,
chunk: &'a Chunk,
}
impl<'a> DisassembledInstruction<'a> {
pub fn new(i: usize, chunk: &'a Chunk) -> Self {
Self { i, chunk }
}
}
impl fmt::Display for DisassembledInstruction<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:04} ", self.i)?;
let line = self.chunk.lines[self.i];
if self.i > 0 && line != self.chunk.lines[self.i - 1] {
write!(f, " | ")?;
} else {
write!(f, "{:>4} ", line)?;
}
match self.chunk.code[self.i] {
OpCode::Constant(constant) => write!(
f,
"{:<16} {:4} '{}'",
"OP_CONSTANT", constant, self.chunk.constants[constant]
)?,
OpCode::Add => write!(f, "OP_ADD")?,
OpCode::Subtract => write!(f, "OP_SUBTRACT")?,
OpCode::Multiply => write!(f, "OP_MULTIPLY")?,
OpCode::Divide => write!(f, "OP_DIVIDE")?,
OpCode::Negate => write!(f, "OP_NEGATE")?,
OpCode::Return => write!(f, "OP_RETURN")?,
}
Ok(())
}
}

@ -1,19 +0,0 @@
use crate::scanner::Scanner;
use color_eyre::eyre::Result;
pub fn compile(source: &str) -> Result<()> {
let mut scanner = Scanner::new(source);
let mut line = None;
for token in &mut scanner {
if Some(token.line) != line {
print!("{:4} ", token.line);
line = Some(token.line);
} else {
print!(" | ");
}
println!("{:2?} '{}'", token.kind, token.value);
}
Ok(())
}

@ -1,34 +0,0 @@
use std::io;
// use std::process::{ExitCode, Termination};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("compile error")]
Compile,
#[error("runtime error")]
Runtime,
#[error("could not open file {path}")]
ReadFile {
path: String,
#[source]
source: io::Error,
},
#[error("Usage: clox [path]")]
Usage,
}
// Doesn't actually work with eyre... yet? But I'm
// willing to give up "nice" exit status codes for
// eyre's error handling.
// impl Termination for Error {
// fn report(self) -> ExitCode {
// ExitCode::from(match self {
// Error::Compile => 65,
// Error::Runtime => 70,
// Error::ReadFile { path, source } => 74,
// Error::Usage => 64,
// })
// }
// }

@ -1,66 +0,0 @@
use std::io::{self, Write};
use std::{env, fs};
// use chunk::{Chunk, OpCode};
// use vm::VM;
use color_eyre::eyre::{Result, WrapErr};
use error::Error;
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
mod chunk;
mod compiler;
mod error;
mod scanner;
mod value;
mod vm;
fn main() -> Result<()> {
let level = match env::var("DEBUG") {
Ok(_) => Level::DEBUG,
Err(_) => Level::ERROR,
};
let subscriber = FmtSubscriber::builder().with_max_level(level).finish();
tracing::subscriber::set_global_default(subscriber)
.wrap_err("setting default subscriber failed")?;
match &env::args().skip(1).collect::<Vec<_>>()[..] {
[] => repl()?,
[file] => run(file)?,
_ => return Err(Error::Usage.into()),
}
Ok(())
}
fn repl() -> Result<()> {
let stdin = io::stdin(); // We get `Stdin` here.
let mut buffer = String::new();
loop {
print!("> ");
io::stdout().flush()?;
stdin.read_line(&mut buffer)?;
if buffer == "\n" {
return Ok(());
}
interpret(&buffer)?;
}
}
fn interpret(buffer: &str) -> Result<()> {
todo!()
}
fn run(file: &str) -> Result<()> {
let contents = fs::read_to_string(file).map_err(|e| Error::ReadFile {
path: file.to_string(),
source: e,
})?;
interpret(&contents)?;
Ok(())
}

@ -1,312 +0,0 @@
pub struct Scanner<'a> {
source: &'a [u8],
current: usize,
line: usize,
}
fn is_alpha(c: &char) -> bool {
c.is_ascii_alphabetic() || *c == '_'
}
impl<'a> Scanner<'a> {
pub fn new(source: &'a str) -> Self {
Self {
source: source.as_bytes(),
current: 0,
line: 1,
}
}
fn advance(&mut self) -> Option<char> {
self.current += 1;
self.source.get(self.current - 1).map(|&x| x as char)
}
fn advance_if<F: Fn(&char) -> bool>(&mut self, f: F) -> bool {
if matches!(self.peek(), Some(c) if f(&c)) {
self.current += 1;
true
} else {
false
}
}
fn skip_whitespace(&mut self) {
loop {
while self.advance_if(|&c| c == ' ' || c == '\r' || c == '\t') {}
if self.peek() == Some('/') && self.peek_next() == Some('/') {
while self.advance_if(|&c| c != '\n') {}
}
if self.advance_if(|&c| c == '\n') {
self.line += 1;
} else {
return;
}
}
}
fn peek(&self) -> Option<char> {
self.source.get(self.current).map(|&x| x as char)
}
fn peek_next(&self) -> Option<char> {
self.source.get(self.current + 1).map(|&x| x as char)
}
fn string(&mut self) -> Token<'a> {
loop {
match self.advance() {
Some('"') => break,
Some('\n') => {
self.line += 1;
}
Some(_) => {}
None => return Token::error(self, "Unterminated string."),
}
}
Token::new(self, TokenKind::String)
}
fn number(&mut self) -> Token<'a> {
while self.advance_if(char::is_ascii_digit) {}
if self.peek() == Some('.') && matches!(self.peek_next(), Some(c) if c.is_ascii_digit()) {
self.advance();
while self.advance_if(char::is_ascii_digit) {}
}
Token::new(self, TokenKind::Number)
}
fn identifier(&mut self) -> Token<'a> {
while self.advance_if(|c| is_alpha(c) || c.is_ascii_digit()) {}
Token::new(self, self.identifier_type())
}
fn identifier_type(&self) -> TokenKind {
match self.source[0] as char {
'a' if &self.source[1..self.current] == "nd".as_bytes() => TokenKind::And,
'c' if &self.source[1..self.current] == "lass".as_bytes() => TokenKind::Class,
'e' if &self.source[1..self.current] == "lse".as_bytes() => TokenKind::Else,
'i' if &self.source[1..self.current] == "f".as_bytes() => TokenKind::If,
'n' if &self.source[1..self.current] == "il".as_bytes() => TokenKind::Nil,
'o' if &self.source[1..self.current] == "r".as_bytes() => TokenKind::Or,
'p' if &self.source[1..self.current] == "rint".as_bytes() => TokenKind::Print,
'r' if &self.source[1..self.current] == "eturn".as_bytes() => TokenKind::Return,
's' if &self.source[1..self.current] == "uper".as_bytes() => TokenKind::Super,
'v' if &self.source[1..self.current] == "ar".as_bytes() => TokenKind::Var,
'w' if &self.source[1..self.current] == "hile".as_bytes() => TokenKind::While,
'f' if self.current > 1 => match self.source[1] as char {
'a' if &self.source[2..self.current] == "lse".as_bytes() => TokenKind::False,
'o' if &self.source[2..self.current] == "r".as_bytes() => TokenKind::For,
'u' if &self.source[2..self.current] == "n".as_bytes() => TokenKind::Fun,
_ => TokenKind::Identifier,
},
't' if self.current > 1 => match self.source[1] as char {
'h' if &self.source[2..self.current] == "is".as_bytes() => TokenKind::This,
'r' if &self.source[2..self.current] == "ue".as_bytes() => TokenKind::True,
_ => TokenKind::Identifier,
},
_ => TokenKind::Identifier,
}
}
}
impl<'a> Iterator for Scanner<'a> {
type Item = Token<'a>;
fn next(&mut self) -> Option<Self::Item> {
self.skip_whitespace();
self.advance()
.map(|c| match c {
c if is_alpha(&c) => self.identifier(),
c if c.is_ascii_digit() => self.number(),
'(' => Token::new(self, TokenKind::LeftParen),
')' => Token::new(self, TokenKind::RightParen),
'{' => Token::new(self, TokenKind::LeftBrace),
'}' => Token::new(self, TokenKind::RightBrace),
';' => Token::new(self, TokenKind::Semicolon),
',' => Token::new(self, TokenKind::Comma),
'.' => Token::new(self, TokenKind::Dot),
'-' => Token::new(self, TokenKind::Minus),
'+' => Token::new(self, TokenKind::Plus),
'/' => Token::new(self, TokenKind::Slash),
'*' => Token::new(self, TokenKind::Star),
'!' => {
if self.advance_if(|&c| c == '=') {
Token::new(self, TokenKind::BangEqual)
} else {
Token::new(self, TokenKind::Bang)
}
}
'=' => {
if self.advance_if(|&c| c == '=') {
Token::new(self, TokenKind::EqualEqual)
} else {
Token::new(self, TokenKind::Equal)
}
}
'<' => {
if self.advance_if(|&c| c == '=') {
Token::new(self, TokenKind::LessEqual)
} else {
Token::new(self, TokenKind::Less)
}
}
'>' => {
if self.advance_if(|&c| c == '=') {
Token::new(self, TokenKind::GreaterEqual)
} else {
Token::new(self, TokenKind::Greater)
}
}
'"' => self.string(),
_ => Token::error(self, "Unexpected character."),
})
.or({
Some(Token {
kind: TokenKind::Eof,
value: "",
line: self.line,
})
})
}
}
pub struct Token<'a> {
pub kind: TokenKind,
pub value: &'a str,
pub line: usize,
}
impl<'a> Token<'a> {
fn new(scanner: &Scanner<'a>, kind: TokenKind) -> Self {
Token {
kind,
value: std::str::from_utf8(&scanner.source[..scanner.current]).unwrap(),
line: scanner.line,
}
}
fn error(scanner: &Scanner, message: &'static str) -> Self {
Token {
kind: TokenKind::Error,
value: message,
line: scanner.line,
}
}
}
#[derive(Debug, PartialEq)]
pub enum TokenKind {
// Single-character tokens.
LeftParen,
RightParen,
LeftBrace,
RightBrace,
Comma,
Dot,
Minus,
Plus,
Semicolon,
Slash,
Star,
// One or two character tokens.
Bang,
BangEqual,
Equal,
EqualEqual,
Greater,
GreaterEqual,
Less,
LessEqual,
// Literals.
Identifier,
String,
Number,
// Keywords.
And,
Class,
Else,
False,
For,
Fun,
If,
Nil,
Or,
Print,
Return,
Super,
This,
True,
Var,
While,
//
Error,
Eof,
}
#[cfg(test)]
mod tests {
// Note this useful idiom: importing names from outer (for mod tests) scope.
use super::*;
#[test]
fn test_scan() {
assert_source_scan("(", Some(TokenKind::LeftParen));
assert_source_scan("!", Some(TokenKind::Bang));
assert_source_scan("!=", Some(TokenKind::BangEqual));
assert_source_scan("!a", Some(TokenKind::Bang));
assert_source_scan("a", Some(TokenKind::Identifier));
assert_source_scan("_a", Some(TokenKind::Identifier));
assert_source_scan("a1", Some(TokenKind::Identifier));
assert_source_scan("\"foo\"", Some(TokenKind::String));
assert_source_scan("1a", Some(TokenKind::Number));
assert_source_scan("1.1", Some(TokenKind::Number));
assert_source_scan("a", Some(TokenKind::Identifier));
assert_source_scan("an", Some(TokenKind::Identifier));
assert_source_scan("and", Some(TokenKind::And));
assert_source_scan("andy", Some(TokenKind::Identifier));
assert_source_scan("false", Some(TokenKind::False));
assert_source_scan("fa", Some(TokenKind::Identifier));
assert_source_scan("@", Some(TokenKind::Error));
assert_source_scan("", Some(TokenKind::Eof));
}
#[test]
fn test_multi_scan() {
let mut scanner = Scanner::new("()");
assert_scan(&mut scanner, Some(TokenKind::LeftParen));
assert_scan(&mut scanner, Some(TokenKind::RightParen));
assert_scan(&mut scanner, Some(TokenKind::Eof));
}
#[test]
fn test_whitespace() {
assert_source_scan(" foo", Some(TokenKind::Identifier));
assert_source_scan("\tfoo", Some(TokenKind::Identifier));
assert_source_scan("// \n", Some(TokenKind::Eof));
}
fn assert_source_scan(source: &str, kind: Option<TokenKind>) {
let mut scanner = Scanner::new(source);
assert_scan(&mut scanner, kind);
}
fn assert_scan(scanner: &mut Scanner, kind: Option<TokenKind>) {
let token = scanner.next();
assert_eq!(token.map(|x| x.kind), kind);
}
}

@ -1 +0,0 @@
pub type Value = f64;

@ -1,75 +0,0 @@
use std::ops::{Add, Div, Mul, Sub};
use color_eyre::eyre::Result;
use crate::chunk::{Chunk, DisassembledInstruction, OpCode};
use crate::compiler::compile;
use crate::value::Value;
use tracing::debug;
pub struct VM {
chunk: Chunk,
ip: usize,
stack: [Value; 256],
stack_top: usize,
}
impl VM {
pub fn interpret(source: &str) -> Result<()> {
compile(source);
Ok(())
}
pub fn new(chunk: Chunk) -> Self {
Self {
chunk,
ip: 0,
stack: [0.0; 256],
stack_top: 0,
}
}
pub fn run(&mut self) -> Result<()> {
loop {
debug!("{:?}", &self.stack[0..self.stack_top]);
debug!("{}", DisassembledInstruction::new(self.ip, &self.chunk,));
match &self.chunk.code[self.ip] {
OpCode::Constant(constant) => {
let value = self.chunk.constants[*constant];
self.push(value);
}
OpCode::Add => self.binary_op(Value::add),
OpCode::Subtract => self.binary_op(Value::sub),
OpCode::Multiply => self.binary_op(Value::mul),
OpCode::Divide => self.binary_op(Value::div),
OpCode::Negate => {
let value = self.pop();
self.push(-value);
}
OpCode::Return => {
println!("{}", self.pop());
return Ok(());
}
}
self.ip += 1;
}
}
fn push(&mut self, value: Value) {
self.stack[self.stack_top] = value;
self.stack_top += 1;
}
fn pop(&mut self) -> Value {
self.stack_top -= 1;
self.stack[self.stack_top]
}
fn binary_op<T: Fn(Value, Value) -> Value>(&mut self, op: T) {
let b = self.pop();
let a = self.pop();
self.push(op(a, b));
}
}
Loading…
Cancel
Save