Compare commits
77 Commits
@ -0,0 +1 @@
|
||||
3.1
|
@ -0,0 +1,34 @@
|
||||
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
|
@ -0,0 +1,28 @@
|
||||
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
|
@ -0,0 +1,34 @@
|
||||
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
|
@ -0,0 +1,204 @@
|
||||
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
|
@ -0,0 +1,94 @@
|
||||
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
|
@ -0,0 +1 @@
|
||||
target
|
@ -0,0 +1,6 @@
|
||||
condense_wildcard_suffixes = true
|
||||
format_strings = true
|
||||
reorder_impl_items = true
|
||||
group_imports = "StdExternalCrate"
|
||||
use_field_init_shorthand = true
|
||||
wrap_comments = true
|
@ -0,0 +1,364 @@
|
||||
# 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"
|
@ -0,0 +1,12 @@
|
||||
[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"
|
@ -0,0 +1,3 @@
|
||||
test-watch:
|
||||
# while true; do fd .*.rs | entr -d cargo test; done
|
||||
fd .*.rs | entr -d cargo test
|
@ -0,0 +1,85 @@
|
||||
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(())
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
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(())
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
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,
|
||||
// })
|
||||
// }
|
||||
// }
|
@ -0,0 +1,66 @@
|
||||
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(())
|
||||
}
|
@ -0,0 +1,312 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
pub type Value = f64;
|
@ -0,0 +1,75 @@
|
||||
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