Compare commits
No commits in common. 'main' and 'private' have entirely different histories.
@ -1 +0,0 @@
|
|||||||
3.1
|
|
@ -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,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
|
|
@ -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
|
|
@ -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 +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
|
|
@ -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…
Reference in new issue