diff --git a/spec/interpreter/callstack_spec.cr b/spec/interpreter/callstack_spec.cr index e300b42..ff8889d 100644 --- a/spec/interpreter/callstack_spec.cr +++ b/spec/interpreter/callstack_spec.cr @@ -81,8 +81,7 @@ describe "Interpreter - Callstack" do it "removes the last entry from the callstack" do itr = Interpreter.new add_breakpoint(itr, "breakpoint") do - expected_stack_size = __args[0].as(TInteger).value - itr.callstack.size.should eq(expected_stack_size) + itr.callstack.size.should eq(__args[0]) end parse_and_interpret! %q( @@ -100,8 +99,7 @@ describe "Interpreter - Callstack" do it "removes the last entry from the callstack" do itr = Interpreter.new add_breakpoint(itr, "breakpoint") do - expected_stack_size = __args[0].as(TInteger).value - itr.callstack.size.should eq(expected_stack_size) + itr.callstack.size.should eq(__args[0]) end parse_and_interpret! %q( @@ -121,8 +119,7 @@ describe "Interpreter - Callstack" do it "removes the `block` from the callstack" do itr = Interpreter.new add_breakpoint(itr, "breakpoint") do - expected_stack_size = __args[0].as(TInteger).value - itr.callstack.size.should eq(expected_stack_size) + itr.callstack.size.should eq(__args[0]) end parse_and_interpret! %q( @@ -137,8 +134,7 @@ describe "Interpreter - Callstack" do it "removes the `next` from the callstack" do itr = Interpreter.new add_breakpoint(itr, "breakpoint") do - expected_stack_size = __args[0].as(TInteger).value - itr.callstack.size.should eq(expected_stack_size) + itr.callstack.size.should eq(__args[0]) end parse_and_interpret! %q( diff --git a/spec/interpreter/closure_scope_spec.cr b/spec/interpreter/closure_scope_spec.cr index fbd3c9d..04baca1 100644 --- a/spec/interpreter/closure_scope_spec.cr +++ b/spec/interpreter/closure_scope_spec.cr @@ -9,7 +9,7 @@ describe "Interpreter - ClosureScope" do scope["b"] end - scope["b"]?.should be(nil) + scope["b"]?.should eq(nil) end describe "#[]" do @@ -97,7 +97,7 @@ describe "Interpreter - ClosureScope" do scope = ClosureScope.new(Scope.new) scope["a"] = TNil.new scope["Thing"] = TType.new("Thing") - scope["x"] = TInteger.new(100_i64) + scope["x"] = 100_i64 scope.values.size.should eq(3) diff --git a/spec/interpreter/invocation_spec.cr b/spec/interpreter/invocation_spec.cr index 6ba594f..a4dcab8 100644 --- a/spec/interpreter/invocation_spec.cr +++ b/spec/interpreter/invocation_spec.cr @@ -82,13 +82,13 @@ TYPE_DEFS = %q( ) -private def it_invokes(prelude, call, expected) +private def it_invokes(prelude, call, expected, file=__FILE__, line=__LINE__, end_line=__END_LINE__) itr = parse_and_interpret(prelude) # Running the prelude will leave the last definition on the stack. For # clarity in the tests, the stack is cleared of any existing values before # making any assertions. itr.stack.clear - it_interprets(call, [expected] of Myst::Value, itr) + it_interprets(call, [expected] of MTValue, itr, file: file, line: line, end_line: end_line) end describe "Interpreter - Invocation" do diff --git a/spec/interpreter/nodes/call_spec.cr b/spec/interpreter/nodes/call_spec.cr index 38f80e0..cfe1597 100644 --- a/spec/interpreter/nodes/call_spec.cr +++ b/spec/interpreter/nodes/call_spec.cr @@ -95,7 +95,7 @@ describe "Interpreter - Call" do # Operators can also be defined statically to do some type algebra. it_interprets %Q( deftype Foo - defstatic #{op}(other : Foo) + defstatic #{op}(other) :called_op_on_type end end diff --git a/spec/interpreter/nodes/interpolation_spec.cr b/spec/interpreter/nodes/interpolation_spec.cr index 7fed289..1c715c1 100644 --- a/spec/interpreter/nodes/interpolation_spec.cr +++ b/spec/interpreter/nodes/interpolation_spec.cr @@ -17,11 +17,11 @@ describe "Interpreter - Interpolation" do it_interprets %q(<[1]>), [TList.new([val(1)])] it_interprets %q(<[1, 2, 3]>), [TList.new([val(1), val(2), val(3)])] it_interprets %q(<[nil, :hi]>), [TList.new([val(nil), val(:hi)])] - it_interprets %q(<[[1, 2], [3, 4]]>), [TList.new([TList.new([val(1), val(2)]), TList.new([val(3), val(4)])] of Myst::Value)] + it_interprets %q(<[[1, 2], [3, 4]]>), [TList.new([TList.new([val(1), val(2)]), TList.new([val(3), val(4)])] of MTValue)] it_interprets %q(<{a: 1 }>), [TMap.new({ val(:a) => val(1) })] it_interprets %q(<{<1>: "int", : :nil }>), [TMap.new({ val(1) => val("int"), val(nil) => val(:nil) })] - it_interprets %q(<{<{a: 1}>: {b: 2}}>), [TMap.new({ TMap.new({ val(:a) => val(1) }) => TMap.new({ val(:b) => val(2) }) } of Myst::Value => Myst::Value)] + it_interprets %q(<{<{a: 1}>: {b: 2}}>), [TMap.new({ TMap.new({ val(:a) => val(1) }) => TMap.new({ val(:b) => val(2) }) } of MTValue => MTValue)] it_interprets %q(<(true && true)>), [val(true)] it_interprets %q(<(false && true)>), [val(false)] diff --git a/spec/interpreter/nodes/literals_spec.cr b/spec/interpreter/nodes/literals_spec.cr index af7e31f..a7986e6 100644 --- a/spec/interpreter/nodes/literals_spec.cr +++ b/spec/interpreter/nodes/literals_spec.cr @@ -17,9 +17,9 @@ describe "Interpreter - Literals" do it_interprets %q([1]), [TList.new([val(1)])] it_interprets %q([1, 2, 3]), [TList.new([val(1), val(2), val(3)])] it_interprets %q([nil, :hi]), [TList.new([val(nil), val(:hi)])] - it_interprets %q([[1, 2], [3, 4]]), [TList.new([TList.new([val(1), val(2)]), TList.new([val(3), val(4)])] of Myst::Value)] + it_interprets %q([[1, 2], [3, 4]]), [TList.new([TList.new([val(1), val(2)]), TList.new([val(3), val(4)])] of MTValue)] it_interprets %q({a: 1}), [TMap.new({ val(:a) => val(1) })] it_interprets %q({<1>: "int", : :nil}), [TMap.new({ val(1) => val("int"), val(nil) => val(:nil) })] - it_interprets %q({<{a: 1}>: {b: 2}}), [TMap.new({ TMap.new({ val(:a) => val(1) }) => TMap.new({ val(:b) => val(2) }) } of Myst::Value => Myst::Value)] + it_interprets %q({<{a: 1}>: {b: 2}}), [TMap.new({ TMap.new({ val(:a) => val(1) }) => TMap.new({ val(:b) => val(2) }) } of MTValue => MTValue)] end diff --git a/spec/interpreter/nodes/match_assign_spec.cr b/spec/interpreter/nodes/match_assign_spec.cr index 6c178b2..597ee49 100644 --- a/spec/interpreter/nodes/match_assign_spec.cr +++ b/spec/interpreter/nodes/match_assign_spec.cr @@ -3,7 +3,7 @@ require "../../support/nodes.cr" require "../../support/interpret.cr" private def it_matches(match, file=__FILE__, line=__LINE__, end_line=__END_LINE__) - it %Q(matches `#{match}`) do + it %Q(matches `#{match}`), file, line, end_line do itr = Interpreter.new program = parse_program(match) result = itr.run(program) @@ -11,7 +11,7 @@ private def it_matches(match, file=__FILE__, line=__LINE__, end_line=__END_LINE_ end private def it_does_not_match(match, file=__FILE__, line=__LINE__, end_line=__END_LINE__) - it %Q(does not match `#{match}`) do + it %Q(does not match `#{match}`), file, line, end_line do itr = Interpreter.new(errput: IO::Memory.new) program = parse_program(match) @@ -50,7 +50,7 @@ describe "Interpreter - MatchAssign" do distinct_types.each_with_index do |a, i| distinct_types.each_with_index do |b, j| next if i == j - it_does_not_match "#{a} =: #{b}", /match/ + it_does_not_match "#{a} =: #{b}" end end @@ -59,8 +59,8 @@ describe "Interpreter - MatchAssign" do it_interprets %q(1 =: 1.0) it_interprets %q(1.0 =: 1) - it_does_not_match %q(1 =: 1.1), /match/ - it_does_not_match %q(1.1 =: 1), /match/ + it_does_not_match %q(1 =: 1.1) + it_does_not_match %q(1.1 =: 1) # Assignments at any level should either create or re-assign the variable @@ -122,7 +122,7 @@ describe "Interpreter - MatchAssign" do it_does_not_match %q( A = false A =: true - ), /match/ + ) # Both styles of matching with Consts works through interpolation. it_interprets %q( =: "hello") @@ -140,5 +140,5 @@ describe "Interpreter - MatchAssign" do it_does_not_match %q( float_type = 1.5.type =: 1 - ), /match/ + ) end diff --git a/spec/interpreter/scope_spec.cr b/spec/interpreter/scope_spec.cr index 75e0c01..0baf0b4 100644 --- a/spec/interpreter/scope_spec.cr +++ b/spec/interpreter/scope_spec.cr @@ -9,7 +9,7 @@ describe "Interpreter - Scope" do scope["b"] end - scope["b"]?.should be(nil) + scope["b"]?.should eq(nil) end describe "#[]" do @@ -71,7 +71,7 @@ describe "Interpreter - Scope" do scope = Scope.new scope["a"] = TNil.new scope["Thing"] = TType.new("Thing") - scope["x"] = TInteger.new(100_i64) + scope["x"] = 100_i64 scope.values.size.should eq(3) diff --git a/spec/interpreter/value_spec.cr b/spec/interpreter/value_spec.cr index 1f46180..38261b4 100644 --- a/spec/interpreter/value_spec.cr +++ b/spec/interpreter/value_spec.cr @@ -3,38 +3,38 @@ require "../spec_helper.cr" describe "Values" do describe "::from_literal" do it "maps NilLiteral to TNil" do - Myst::Value.from_literal(NilLiteral.new).should be_a(TNil) + Interpreter.__value_from_literal(NilLiteral.new).should be_a(TNil) end - it "maps BooleanLiteral to TBoolean" do - Myst::Value.from_literal(BooleanLiteral.new(false)).should be_a(TBoolean) + it "maps BooleanLiteral to Bool" do + Interpreter.__value_from_literal(BooleanLiteral.new(false)).should be_a(Bool) end - it "maps IntegerLiteral to TInteger" do - Myst::Value.from_literal(IntegerLiteral.new("0")).should be_a(TInteger) + it "maps IntegerLiteral to Int64" do + Interpreter.__value_from_literal(IntegerLiteral.new("0")).should be_a(Int64) end - it "maps FloatLiteral to TFloat" do - Myst::Value.from_literal(FloatLiteral.new("0.0")).should be_a(TFloat) + it "maps FloatLiteral to Float64" do + Interpreter.__value_from_literal(FloatLiteral.new("0.0")).should be_a(Float64) end - it "maps StringLiteral to TString" do - Myst::Value.from_literal(StringLiteral.new("hello")).should be_a(TString) + it "maps StringLiteral to String" do + Interpreter.__value_from_literal(StringLiteral.new("hello")).should be_a(String) end it "maps SymbolLiteral to TSymbol" do - Myst::Value.from_literal(SymbolLiteral.new("hi")).should be_a(TSymbol) + Interpreter.__value_from_literal(SymbolLiteral.new("hi")).should be_a(TSymbol) end # Container values like List and Map require some effort from the # interpreter to be generated. As such, Value::from_literal cannot generate # them automatically from a node. it "does not map ListLiterals" do - expect_raises(Exception) { Myst::Value.from_literal(ListLiteral.new).should be_a(TList) } + expect_raises(Exception) { Interpreter.__value_from_literal(ListLiteral.new).should be_a(TList) } end it "does not map MapLiterals" do - expect_raises(Exception) { Myst::Value.from_literal(MapLiteral.new).should be_a(TMap) } + expect_raises(Exception) { Interpreter.__value_from_literal(MapLiteral.new).should be_a(TMap) } end end @@ -61,151 +61,6 @@ describe "Values" do end end - describe "TBoolean" do - it "always equates FALSE and FALSE" do - TBoolean.new(false).should eq(TBoolean.new(false)) - end - - it "always equates TRUE and TRUE" do - TBoolean.new(true).should eq(TBoolean.new(true)) - end - - it "does not equate FALSE and TRUE" do - TBoolean.new(false).should_not eq(TBoolean.new(true)) - end - - it "always hashes FALSE to the same value" do - TBoolean.new(false).hash.should eq(TBoolean.new(false).hash) - end - - it "always hashes TRUE to the same value" do - TBoolean.new(true).hash.should eq(TBoolean.new(true).hash) - end - - it "has a string representation of TRUE as `true`" do - TBoolean.new(true).to_s.should eq("true") - end - - it "has a string representation of FALSE as `false`" do - TBoolean.new(false).to_s.should eq("false") - end - - it "is not truthy when FALSE" do - TBoolean.new(false).truthy?.should eq(false) - end - - it "is truthy when TRUE" do - TBoolean.new(true).truthy?.should eq(true) - end - end - - describe "TInteger" do - it "can contain any 64-bit integer value" do - TInteger.new( 9_223_372_036_854_775_807) - TInteger.new(-9_223_372_036_854_775_807) - end - - it "holds that an integer is equal to itself" do - TInteger.new(100_i64).should eq(TInteger.new(100_i64)) - end - - it "does not hold that two unique integers are equal" do - TInteger.new(100_i64).should_not eq(TInteger.new(101_i64)) - end - - it "always hashes equal integers to the same value" do - TInteger.new(100_i64).hash.should eq(TInteger.new(100_i64).hash) - end - - it "always hashes unique integers to different values" do - TInteger.new(100_i64).hash.should_not eq(TInteger.new(101_i64).hash) - end - - it "can represent its value as a String" do - TInteger.new( 100_i64).to_s.should eq("100") - TInteger.new(-100_i64).to_s.should eq("-100") - end - - it "is always truthy" do - TInteger.new(0_i64).truthy?.should eq(true) - TInteger.new(-100_i64).truthy?.should eq(true) - TInteger.new(1000_i64).truthy?.should eq(true) - end - end - - describe "TFloat" do - it "can contain any 64-bit float value" do - TFloat.new( 1.7976931348623157e+308) - TFloat.new(-1.7976931348623157e+308) - end - - it "holds that an integer is equal to itself" do - TFloat.new(100.0_f64).should eq(TFloat.new(100.0_f64)) - end - - it "does not hold that two unique integers are equal" do - TFloat.new(100.0_f64).should_not eq(TFloat.new(101_f64)) - end - - it "always hashes equal integers to the same value" do - TFloat.new(100.0_f64).hash.should eq(TFloat.new(100.0_f64).hash) - end - - it "always hashes unique integers to different values" do - TFloat.new(100.0_f64).hash.should_not eq(TFloat.new(101_f64).hash) - end - - it "can represent its value as a String" do - TFloat.new( 100.0_f64).to_s.should eq("100.0") - TFloat.new(-100.0_f64).to_s.should eq("-100.0") - end - - it "is always truthy" do - TFloat.new(0.0_f64).truthy?.should eq(true) - TFloat.new(-100.0_f64).truthy?.should eq(true) - TFloat.new(1000.0_f64).truthy?.should eq(true) - end - end - - describe "TString" do - it "can contain strings of arbitrary length" do - TString.new("hello"*1000) - end - - it "can contain escape sequences" do - TString.new("\n") - end - - it "holds that a string is equal to itself" do - TString.new("hi").should eq(TString.new("hi")) - end - - it "always hashes a string to the same value" do - TString.new("hi").hash.should eq(TString.new("hi").hash) - end - - it "always hashes unique strings to different values" do - TString.new("hi").hash.should_not eq(TString.new("hello")) - end - - it "uses its value as its string representation" do - TString.new("hello").to_s.should eq("hello") - end - - it "interprets escape sequences in its string representation" do - TString.new("\nhi\n").to_s.should eq(" -hi -") - end - - it "is always truthy" do - TString.new("").truthy?.should eq(true) - TString.new("\n").truthy?.should eq(true) - TString.new("\0").truthy?.should eq(true) - TString.new("hello world").truthy?.should eq(true) - end - end - describe "TSymbol" do it "can be created from any string value" do TSymbol.new("hello"*1000) @@ -239,30 +94,30 @@ hi end it "can be created with initial elements" do - TList.new([TNil.new, TNil.new] of Myst::Value) + TList.new([TNil.new, TNil.new] of MTValue) end it "can contain any mixture of Values" do - TList.new([TInteger.new(1_i64), TBoolean.new(false), TString.new("hello")]) + TList.new([1_i64, false, "hello"] of MTValue) end it "can contain other lists within itself" do - TList.new([TList.new, TList.new] of Myst::Value) + TList.new([TList.new, TList.new] of MTValue) end it "can dynamically adjust its size" do list = TList.new - list.elements << TInteger.new(0_i64) - list.elements << TString.new("hello") + list.elements << 0_i64 + list.elements << "hello" list.elements.size.should eq(2) end it "is always truthy" do - TList.new.truthy?.should eq(true) - TList.new([TNil.new] of Myst::Value).truthy?.should eq(true) - TList.new([TBoolean.new(false)] of Myst::Value).truthy?.should eq(true) - TList.new([TInteger.new(1_i64), TNil.new]).truthy?.should eq(true) + TList.new.truthy?.should eq(true) + TList.new([TNil.new] of MTValue).truthy?.should eq(true) + TList.new([false] of MTValue).truthy?.should eq(true) + TList.new([1_i64, TNil.new] of MTValue).truthy?.should eq(true) end end @@ -273,29 +128,29 @@ hi end it "can be created with initial elements" do - TMap.new({ TNil.new => TNil.new } of Myst::Value => Myst::Value) + TMap.new({ TNil.new => TNil.new } of MTValue => MTValue) end it "can contain any mixture of Values" do - TMap.new({ TInteger.new(1_i64) => TBoolean.new(false), TString.new("hello") => TSymbol.new("hi")}) + TMap.new({ 1_i64 => false, "hello" => "hi" } of MTValue => MTValue) end it "can contain other maps within itself" do - TMap.new({ TMap.new => TMap.new } of Myst::Value => Myst::Value) + TMap.new({ TMap.new => TMap.new } of MTValue => MTValue) end it "can dynamically adjust its size" do list = TMap.new - list.entries[TBoolean.new(false)] = TInteger.new(0_i64) - list.entries[TBoolean.new(true)] = TString.new("hello") + list.entries[false] = 0_i64 + list.entries[true] = "hello" list.entries.size.should eq(2) end it "is always truthy" do TMap.new.truthy?.should eq(true) - TMap.new({ TNil.new => TNil.new } of Myst::Value => Myst::Value).truthy?.should eq(true) - TMap.new({ TSymbol.new("") => TInteger.new(1_i64) } of Myst::Value => Myst::Value).truthy?.should eq(true) + TMap.new({ TNil.new => TNil.new } of MTValue => MTValue).truthy?.should eq(true) + TMap.new({ TSymbol.new("") => 1_i64 } of MTValue => MTValue).truthy?.should eq(true) end end end diff --git a/spec/support/breakpoints.cr b/spec/support/breakpoints.cr index b62325f..5bce0a3 100644 --- a/spec/support/breakpoints.cr +++ b/spec/support/breakpoints.cr @@ -19,7 +19,7 @@ require "../spec_helper.cr" # # itr = Interpreter.new # add_breakpoint(itr, "breakpoint") do |this, args, block| -# args[0].should be_a(TInteger) +# args[0].should be_a(Int64) # end # # parse_and_interpret %q( @@ -28,12 +28,12 @@ require "../spec_helper.cr" # end # ), interpreter: itr macro add_breakpoint(itr, name) - %handler = ->(this : Myst::Value, __args : Array(Myst::Value), block : TFunctor?) do + %handler = ->(this : MTValue, __args : Array(MTValue), block : TFunctor?) do %result = begin {{ yield }} end - %result.is_a?(Myst::Value) ? %result : TNil.new.as(Myst::Value) + %result.is_a?(MTValue) ? %result : TNil.new.as(MTValue) end {{itr}}.kernel.scope[{{name}}] = TFunctor.new({{name}}, [%handler] of Callable) diff --git a/spec/support/interpret.cr b/spec/support/interpret.cr index 6dc0855..e5caf4b 100644 --- a/spec/support/interpret.cr +++ b/spec/support/interpret.cr @@ -1,7 +1,7 @@ require "../spec_helper.cr" require "./nodes.cr" -def it_interprets(node : String, expected_stack : Array(Myst::Value), itr=Interpreter.new, file=__FILE__, line=__LINE__, end_line=__END_LINE__) +def it_interprets(node : String, expected_stack : Array(MTValue), itr=Interpreter.new, file=__FILE__, line=__LINE__, end_line=__END_LINE__) it %Q(interprets #{node}), file, line, end_line do program = parse_program(node) itr.run(program) @@ -31,11 +31,11 @@ def it_interprets(node : String, file=__FILE__, line=__LINE__, end_line=__END_LI end def it_interprets(node : String, file=__FILE__, line=__LINE__, end_line=__END_LINE__) - it_interprets(node, [] of Myst::Value, Interpreter.new, file, line, end_line) + it_interprets(node, [] of MTValue, Interpreter.new, file, line, end_line) end -def it_interprets_with_assignments(node : String, assignments : Hash(String, Myst::Value), itr=Interpreter.new, file=__FILE__, line=__LINE__, end_line=__END_LINE__) +def it_interprets_with_assignments(node : String, assignments : Hash(String, MTValue), itr=Interpreter.new, file=__FILE__, line=__LINE__, end_line=__END_LINE__) it %Q(interprets #{node}), file, line, end_line do program = parse_program(node) itr.run(program) @@ -99,21 +99,21 @@ end # val(node) # -# Run `Value.from_literal` on the given node and return the result. If `node` +# Run `__value_from_literal` on the given node and return the result. If `node` # is not already a Node, it will be run through `l` first. def val(node : Node) - Myst::Value.from_literal(node).as(Myst::Value) + Interpreter.__value_from_literal(node).as(MTValue) end def val(node : Array(T)) forall T - TList.new(node.map{ |n| val(n) }).as(Myst::Value) + TList.new(node.map{ |n| val(n) }).as(MTValue) end def val(node : Hash(K, V)) forall K, V node.reduce(TMap.new) do |map, (k, v)| map.entries[val(k)] = val(v) map - end.as(Myst::Value) + end.as(MTValue) end def val(node); val(l(node)); end diff --git a/src/myst/interpreter.cr b/src/myst/interpreter.cr index 17a2e9a..b396667 100644 --- a/src/myst/interpreter.cr +++ b/src/myst/interpreter.cr @@ -4,8 +4,8 @@ require "./interpreter/native_lib" module Myst class Interpreter - property stack : Array(Value) - property self_stack : Array(Value) + property stack : Array(MTValue) + property self_stack : Array(MTValue) property scope_stack : Array(Scope) property callstack : Callstack property kernel : TModule @@ -22,11 +22,11 @@ module Myst 2 => errput }) - @stack = [] of Value + @stack = [] of MTValue @scope_stack = [] of Scope @callstack = Callstack.new @kernel = create_kernel - @self_stack = [@kernel] of Value + @self_stack = [@kernel] of MTValue @warnings = 0 end @@ -85,7 +85,7 @@ module Myst self_stack.last end - def push_self(new_self : Value) + def push_self(new_self : MTValue) self_stack.push(new_self) end @@ -119,8 +119,8 @@ module Myst def put_error(error : RuntimeError) value_to_s = __scopeof(error.value)["to_s"].as(TFunctor) - result = Invocation.new(self, value_to_s, error.value, [] of Value, nil).invoke - errput.puts("Uncaught Exception: " + result.as(TString).value) + result = Invocation.new(self, value_to_s, error.value, [] of MTValue, nil).invoke + errput.puts("Uncaught Exception: " + result.as(String)) errput.puts(error.trace) end diff --git a/src/myst/interpreter/closure_scope.cr b/src/myst/interpreter/closure_scope.cr index acc162f..c3f525b 100644 --- a/src/myst/interpreter/closure_scope.cr +++ b/src/myst/interpreter/closure_scope.cr @@ -9,7 +9,7 @@ module Myst property closed_scope : Scope def initialize(@closed_scope : Scope, @parent : Scope? = nil) - @values = {} of String => Value + @values = {} of String => MTValue end def []?(key : String) @@ -20,7 +20,7 @@ module Myst end end - def []=(key : String, value : Value) + def []=(key : String, value : MTValue) if closed_scope.has_key?(key) closed_scope.assign(key, value) else @@ -29,10 +29,10 @@ module Myst end def has_key?(key : String) - !!@values[key]? || closed_scope.has_key?(key) + @values.has_key?(key) || closed_scope.has_key?(key) end - def assign(key : String, value : Value) + def assign(key : String, value : MTValue) if closed_scope.has_key?(key) closed_scope.assign(key, value) else diff --git a/src/myst/interpreter/exceptions.cr b/src/myst/interpreter/exceptions.cr index b7b7760..d5c930b 100644 --- a/src/myst/interpreter/exceptions.cr +++ b/src/myst/interpreter/exceptions.cr @@ -14,10 +14,10 @@ module Myst # The containing error type for any error raised within the language. class RuntimeError < Exception - property value : Value + property value : MTValue property trace : Callstack - def initialize(@value : Value, @trace : Callstack) + def initialize(@value : MTValue, @trace : Callstack) end end @@ -25,8 +25,8 @@ module Myst # to better show the intent of the raised errors, and ensure consistency # between them. class MatchError < RuntimeError - def initialize(@trace : Callstack, message : String = "match failure") - @value = TString.new(message) + def initialize(@trace : Callstack, @message : String = "match failure") + @value = message end end end diff --git a/src/myst/interpreter/invocation.cr b/src/myst/interpreter/invocation.cr index adefe05..8f006ab 100644 --- a/src/myst/interpreter/invocation.cr +++ b/src/myst/interpreter/invocation.cr @@ -11,14 +11,14 @@ module Myst struct Invocation property itr : Interpreter property func : TFunctor - property! receiver : Value? - property args : Array(Value) + property! receiver : MTValue? + property args : Array(MTValue) property! block : TFunctor? @selfstack_size_at_entry : Int32 = -1 @scopestack_size_at_entry : Int32 = -1 @callstack_size_at_entry : Int32 = -1 - def initialize(@itr : Interpreter, @func : TFunctor, @receiver : Value?, @args : Array(Value), @block : TFunctor?) + def initialize(@itr : Interpreter, @func : TFunctor, @receiver : MTValue?, @args : Array(MTValue), @block : TFunctor?) end def invoke @@ -37,12 +37,16 @@ module Myst res = do_call(clause, @receiver, @args, @block) end @itr.pop_scope_override - break res if res + break res unless res.nil? end @itr.pop_callstack(to_size: @callstack_size_at_entry) - result || @itr.__raise_runtime_error("No clause matches with given arguments: #{@args.inspect}") + if result.nil? + @itr.__raise_runtime_error("No clause matches with given arguments: #{@args.inspect}") + else + result + end rescue ex : BreakException if ex.caught? return @itr.stack.pop @@ -113,7 +117,7 @@ module Myst return @itr.stack.pop end - private def do_call(func : TNativeDef, receiver : Value, args : Array(Value), block : TFunctor?) + private def do_call(func : TNativeDef, receiver : MTValue, args : Array(MTValue), block : TFunctor?) func.call(receiver, args, block) end diff --git a/src/myst/interpreter/matcher.cr b/src/myst/interpreter/matcher.cr index 110b53c..4dcdf83 100644 --- a/src/myst/interpreter/matcher.cr +++ b/src/myst/interpreter/matcher.cr @@ -2,7 +2,7 @@ require "./exceptions.cr" module Myst class Interpreter - def match(pattern : Node, value : Value) + def match(pattern : Node, value : MTValue) case pattern when ListLiteral match_list(pattern, value) @@ -31,7 +31,7 @@ module Myst # For simplicity and efficiency, the equality of values according to a # match operation is determined by the native equality of the values, not # by any override of `==`. - private def match_value(pattern : Node, right : Value) + private def match_value(pattern : Node, right : MTValue) visit(pattern) left = stack.pop success = @@ -51,7 +51,7 @@ module Myst success || __raise_runtime_error(MatchError.new(callstack)) end - private def match_list(pattern : ListLiteral, value : Value) + private def match_list(pattern : ListLiteral, value : MTValue) __raise_runtime_error(MatchError.new(callstack)) unless value.is_a?(TList) left, splat, right = chunk_list_pattern(pattern) @@ -68,7 +68,7 @@ module Myst end end - private def match_map(pattern : MapLiteral, value : Value) + private def match_map(pattern : MapLiteral, value : MTValue) __raise_runtime_error(MatchError.new(callstack)) unless value.is_a?(TMap) pattern.entries.each do |entry| diff --git a/src/myst/interpreter/native_lib.cr b/src/myst/interpreter/native_lib.cr index 06506a6..3259820 100644 --- a/src/myst/interpreter/native_lib.cr +++ b/src/myst/interpreter/native_lib.cr @@ -6,19 +6,19 @@ module Myst # function. The function can be either a native function or a source-level # function. The results do not affect the stack, the result of calling the # function will be returned directly. - def call_func(itr, func : TFunctor, args : Array(Value), receiver : Value?=nil) + def call_func(itr, func : TFunctor, args : Array(MTValue), receiver : MTValue?=nil) Invocation.new(itr, func, receiver, args, nil).invoke end # Same as `call_func`, but the function to call is given as a name to # look up on the given receiver. - def call_func_by_name(itr, receiver : Value, name : String, args : Array(Value)) + def call_func_by_name(itr, receiver : MTValue, name : String, args : Array(MTValue)) func = itr.__scopeof(receiver)[name].as(TFunctor) Invocation.new(itr, func, receiver, args, nil).invoke end # Instantiate a given type and invoke its initializer - def instantiate(itr, type : TType, params : Array(Value)) : TInstance + def instantiate(itr, type : TType, params : Array(MTValue)) : TInstance instance = TInstance.new(type) if (initializer = instance.scope["initialize"]?) && initializer.is_a?(TFunctor) @@ -29,7 +29,7 @@ module Myst end macro method(name, this_type, *params, &block) - def {{name.id}}(this : Value, __args : Array(Value), block : TFunctor?) : Value + def {{name.id}}(this : MTValue, __args : Array(MTValue), block : TFunctor?) : MTValue this = this.as({{this_type}}) {% for type, index in params %} @@ -40,19 +40,19 @@ module Myst {{block.body}} end - %result.as(Value) + %result.as(MTValue) end end macro def_method(type, name, impl_name) {{type}}.scope["{{name.id}}"] = TFunctor.new("{{name.id}}", [ - ->{{impl_name.id}}(Value, Array(Value), TFunctor?).as(Callable) + ->{{impl_name.id}}(MTValue, Array(MTValue), TFunctor?).as(Callable) ] of Callable) end macro def_instance_method(type, name, impl_name) {{type}}.instance_scope["{{name.id}}"] = TFunctor.new("{{name.id}}", [ - ->{{impl_name.id}}(Value, Array(Value), TFunctor?).as(Callable) + ->{{impl_name.id}}(MTValue, Array(MTValue), TFunctor?).as(Callable) ] of Callable) end end diff --git a/src/myst/interpreter/native_lib/boolean.cr b/src/myst/interpreter/native_lib/boolean.cr index ce3eefb..281fec5 100644 --- a/src/myst/interpreter/native_lib/boolean.cr +++ b/src/myst/interpreter/native_lib/boolean.cr @@ -1,25 +1,15 @@ module Myst class Interpreter - NativeLib.method :bool_to_s, TBoolean do - TString.new(this.value ? "true" : "false") + NativeLib.method :bool_to_s, Bool do + this.to_s end - NativeLib.method :bool_eq, TBoolean, other : Value do - case other - when TBoolean - TBoolean.new(this.value == other.value) - else - TBoolean.new(false) - end + NativeLib.method :bool_eq, Bool, other : MTValue do + this == other end - NativeLib.method :bool_not_eq, TBoolean, other : Value do - case other - when TBoolean - TBoolean.new(this.value != other.value) - else - TBoolean.new(true) - end + NativeLib.method :bool_not_eq, Bool, other : MTValue do + this != other end def init_boolean(kernel : TModule) diff --git a/src/myst/interpreter/native_lib/file.cr b/src/myst/interpreter/native_lib/file.cr index 3999eca..f8eafe9 100644 --- a/src/myst/interpreter/native_lib/file.cr +++ b/src/myst/interpreter/native_lib/file.cr @@ -1,26 +1,26 @@ module Myst class Interpreter - NativeLib.method :file_init, TInstance, name : TString, mode : TString do - file = File.open(name.value, mode.value) + NativeLib.method :file_init, TInstance, name : String, mode : String do + file = File.open(name, mode) @fd_pool[file.fd] = file - this.ivars["@fd"] = TInteger.new(file.fd.to_i64) + this.ivars["@fd"] = file.fd.to_i64 this.ivars["@mode"] = mode this end NativeLib.method :file_close, TInstance do - fd = this.ivars["@fd"].as(TInteger) - file = @fd_pool[fd.value] + fd = this.ivars["@fd"].as(Int64) + file = @fd_pool[fd] file.close - @fd_pool.delete(fd.value) + @fd_pool.delete(fd) TNil.new end NativeLib.method :file_size, TInstance do - fd = this.ivars["@fd"].as(TInteger) - file = @fd_pool[fd.value].as(File) - TInteger.new(file.size.to_i64) + fd = this.ivars["@fd"].as(Int64) + file = @fd_pool[fd].as(File) + file.size.to_i64 end diff --git a/src/myst/interpreter/native_lib/float.cr b/src/myst/interpreter/native_lib/float.cr index 52455ce..8bc3c12 100644 --- a/src/myst/interpreter/native_lib/float.cr +++ b/src/myst/interpreter/native_lib/float.cr @@ -1,109 +1,99 @@ module Myst class Interpreter - NativeLib.method :float_add, TFloat, other : Value do + NativeLib.method :float_add, Float64, other : MTValue do case other - when TInteger, TFloat - TFloat.new(this.value + other.value) + when Int64, Float64 + this + other else __raise_runtime_error("invalid argument for Float#+: #{__typeof(other).name}") end end - NativeLib.method :float_subtract, TFloat, other : Value do + NativeLib.method :float_subtract, Float64, other : MTValue do case other - when TInteger, TFloat - TFloat.new(this.value - other.value) + when Int64, Float64 + this - other else __raise_runtime_error("invalid argument for Float#-: #{__typeof(other).name}") end end - NativeLib.method :float_multiply, TFloat, other : Value do + NativeLib.method :float_multiply, Float64, other : MTValue do case other - when TInteger, TFloat - TFloat.new(this.value * other.value) + when Int64, Float64 + this * other else __raise_runtime_error("invalid argument for Float#*: #{__typeof(other).name}") end end - NativeLib.method :float_divide, TFloat, other : Value do + NativeLib.method :float_divide, Float64, other : MTValue do case other - when TInteger, TFloat - __raise_runtime_error("Division by zero") if other.value == 0 - TFloat.new(this.value / other.value) + when Int64, Float64 + __raise_runtime_error("Division by zero") if other == 0 + this / other else __raise_runtime_error("invalid argument for Float#/: #{__typeof(other).name}") end end - NativeLib.method :float_modulo, TFloat, other : Value do + NativeLib.method :float_modulo, Float64, other : MTValue do case other - when TInteger, TFloat - __raise_runtime_error("Division by zero") if other.value == 0 - TFloat.new(this.value % other.value) + when Int64, Float64 + __raise_runtime_error("Division by zero") if other == 0 + this % other else __raise_runtime_error("invalid argument for Float#%: #{__typeof(other).name}") end end - NativeLib.method :float_to_s, TFloat do - TString.new(this.value.to_s) + NativeLib.method :float_to_s, Float64 do + this.to_s end - NativeLib.method :float_eq, TFloat, other : Value do - case other - when TFloat, TInteger - TBoolean.new(this.value == other.value) - else - TBoolean.new(false) - end + NativeLib.method :float_eq, Float64, other : MTValue do + this == other end - NativeLib.method :float_not_eq, TFloat, other : Value do - case other - when TFloat, TInteger - TBoolean.new(this.value != other.value) - else - TBoolean.new(true) - end + NativeLib.method :float_not_eq, Float64, other : MTValue do + this != other end - NativeLib.method :float_negate, TFloat do - TFloat.new(-this.value) + NativeLib.method :float_negate, Float64 do + -this end - NativeLib.method :float_lt, TFloat, other : Value do + NativeLib.method :float_lt, Float64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value < other.value) + when Int64, Float64 + this < other else __raise_runtime_error("invalid argument for Float#<: #{__typeof(other).name}") end end - NativeLib.method :float_lte, TFloat, other : Value do + NativeLib.method :float_lte, Float64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value <= other.value) + when Int64, Float64 + this <= other else __raise_runtime_error("invalid argument for Float#<=: #{__typeof(other).name}") end end - NativeLib.method :float_gt, TFloat, other : Value do + NativeLib.method :float_gt, Float64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value > other.value) + when Int64, Float64 + this > other else __raise_runtime_error("invalid argument for Float#>: #{__typeof(other).name}") end end - NativeLib.method :float_gte, TFloat, other : Value do + NativeLib.method :float_gte, Float64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value >= other.value) + when Int64, Float64 + this >= other else __raise_runtime_error("invalid argument for Float#>=: #{__typeof(other).name}") end diff --git a/src/myst/interpreter/native_lib/integer.cr b/src/myst/interpreter/native_lib/integer.cr index d29cec4..9a31d43 100644 --- a/src/myst/interpreter/native_lib/integer.cr +++ b/src/myst/interpreter/native_lib/integer.cr @@ -1,121 +1,102 @@ module Myst class Interpreter - NativeLib.method :int_add, TInteger, other : Value do + NativeLib.method :int_add, Int64, other : MTValue do case other - when TInteger - TInteger.new(this.value + other.value) - when TFloat - TFloat.new(this.value + other.value) + when Int64, Float64 + this + other else __raise_runtime_error("invalid argument for Integer#+: #{__typeof(other).name}") end end - NativeLib.method :int_subtract, TInteger, other : Value do + NativeLib.method :int_subtract, Int64, other : MTValue do case other - when TInteger - TInteger.new(this.value - other.value) - when TFloat - TFloat.new(this.value - other.value) + when Int64, Float64 + this - other else __raise_runtime_error("invalid argument for Integer#-: #{__typeof(other).name}") end end - NativeLib.method :int_multiply, TInteger, other : Value do + NativeLib.method :int_multiply, Int64, other : MTValue do case other - when TInteger - TInteger.new(this.value * other.value) - when TFloat - TFloat.new(this.value * other.value) + when Int64, Float64 + this * other else __raise_runtime_error("invalid argument for Integer#*: #{__typeof(other).name}") end end - NativeLib.method :int_divide, TInteger, other : Value do + NativeLib.method :int_divide, Int64, other : MTValue do case other - when TInteger - __raise_runtime_error("Division by zero") if other.value == 0 - TInteger.new(this.value / other.value) - when TFloat - __raise_runtime_error("Division by zero") if other.value == 0 - TFloat.new(this.value / other.value) + when Int64, Float64 + __raise_runtime_error("Division by zero") if other == 0 + this / other else __raise_runtime_error("invalid argument for Integer#/: #{__typeof(other).name}") end end - NativeLib.method :int_modulo, TInteger, other : Value do + NativeLib.method :int_modulo, Int64, other : MTValue do case other - when TInteger - __raise_runtime_error("Division by zero") if other.value == 0 - TInteger.new(this.value % other.value) - when TFloat - __raise_runtime_error("Division by zero") if other.value == 0 - TFloat.new(this.value.to_f % other.value) + when Int64 + __raise_runtime_error("Division by zero") if other == 0 + this % other + when Float64 + __raise_runtime_error("Division by zero") if other == 0 + this.to_f % other else __raise_runtime_error("invalid argument for Integer#%: #{__typeof(other).name}") end end - NativeLib.method :int_to_s, TInteger do - TString.new(this.value.to_s) + NativeLib.method :int_to_s, Int64 do + this.to_s end - NativeLib.method :int_eq, TInteger, other : Value do - case other - when TInteger, TFloat - TBoolean.new(this.value == other.value) - else - TBoolean.new(false) - end + NativeLib.method :int_eq, Int64, other : MTValue do + this == other end - NativeLib.method :int_not_eq, TInteger, other : Value do - case other - when TInteger, TFloat - TBoolean.new(this.value != other.value) - else - TBoolean.new(true) - end + NativeLib.method :int_not_eq, Int64, other : MTValue do + this != other end - NativeLib.method :int_negate, TInteger do - TInteger.new(-this.value) + NativeLib.method :int_negate, Int64 do + -this end - NativeLib.method :int_lt, TInteger, other : Value do + NativeLib.method :int_lt, Int64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value < other.value) + when Int64, Float64 + this < other else __raise_runtime_error("invalid argument for Integer#<: #{__typeof(other).name}") end end - NativeLib.method :int_lte, TInteger, other : Value do + NativeLib.method :int_lte, Int64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value <= other.value) + when Int64, Float64 + this <= other else __raise_runtime_error("invalid argument for Integer#<=: #{__typeof(other).name}") end end - NativeLib.method :int_gt, TInteger, other : Value do + NativeLib.method :int_gt, Int64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value > other.value) + when Int64, Float64 + this > other else __raise_runtime_error("invalid argument for Integer#>: #{__typeof(other).name}") end end - NativeLib.method :int_gte, TInteger, other : Value do + NativeLib.method :int_gte, Int64, other : MTValue do case other - when TInteger, TFloat - TBoolean.new(this.value >= other.value) + when Int64, Float64 + this >= other else __raise_runtime_error("invalid argument for Integer#>=: #{__typeof(other).name}") end diff --git a/src/myst/interpreter/native_lib/io.cr b/src/myst/interpreter/native_lib/io.cr index ad13901..7467dfa 100644 --- a/src/myst/interpreter/native_lib/io.cr +++ b/src/myst/interpreter/native_lib/io.cr @@ -1,16 +1,16 @@ module Myst class Interpreter - NativeLib.method :io_read, Value, size : TInteger do + NativeLib.method :io_read, MTValue, size : Int64 do __raise_runtime_error("`IO#read` must be implemented by inheriting types.") end - NativeLib.method :io_write, Value, content : Value do + NativeLib.method :io_write, MTValue, content : MTValue do __raise_runtime_error("`IO#write` must be implemented by inheriting types.") end private def make_io_fd(type : TType, id : Int) fd = TInstance.new(type) - fd.ivars["fd"] = TInteger.new(id.to_i64) + fd.ivars["fd"] = id.to_i64 fd end diff --git a/src/myst/interpreter/native_lib/io/file_descriptor.cr b/src/myst/interpreter/native_lib/io/file_descriptor.cr index a503491..add5659 100644 --- a/src/myst/interpreter/native_lib/io/file_descriptor.cr +++ b/src/myst/interpreter/native_lib/io/file_descriptor.cr @@ -1,26 +1,26 @@ module Myst class Interpreter - NativeLib.method :io_fd_init, TInstance, fd : TInteger do - id = fd.value.to_i32 + NativeLib.method :io_fd_init, TInstance, fd : Int64 do + id = fd.to_i32 @fd_pool[id] ||= IO::FileDescriptor.new(id) - this.ivars["fd"] = fd + this.ivars["fd"] = fd.to_i64 end - NativeLib.method :io_fd_read, TInstance, size : TInteger do - fd_id = this.ivars["fd"].as(TInteger).value.to_i32 + NativeLib.method :io_fd_read, TInstance, size : Int64 do + fd_id = this.ivars["fd"].as(Int64).to_i32 fd = @fd_pool[fd_id] - slice = Slice(UInt8).new(size.value) + slice = Slice(UInt8).new(size) fd.read(slice) - TString.new(String.new(slice)) + String.new(slice) end - NativeLib.method :io_fd_write, TInstance, content : TString do - fd_id = this.ivars["fd"].as(TInteger).value.to_i32 + NativeLib.method :io_fd_write, TInstance, content : String do + fd_id = this.ivars["fd"].as(Int64).to_i32 fd = @fd_pool[fd_id] - fd.write(content.value.to_slice) + fd.write(content.to_slice) TNil.new end diff --git a/src/myst/interpreter/native_lib/list.cr b/src/myst/interpreter/native_lib/list.cr index beca4f7..dca0643 100644 --- a/src/myst/interpreter/native_lib/list.cr +++ b/src/myst/interpreter/native_lib/list.cr @@ -11,52 +11,52 @@ module Myst end NativeLib.method :list_size, TList do - TInteger.new(this.elements.size.to_i64) + this.elements.size.to_i64 end NativeLib.method :list_splat, TList do this end - NativeLib.method :list_eq, TList, other : Value do - return TBoolean.new(false) unless other.is_a?(TList) - return TBoolean.new(true) if this == other - return TBoolean.new(false) if this.elements.size != other.elements.size + NativeLib.method :list_eq, TList, other : MTValue do + return false unless other.is_a?(TList) + return true if this == other + return false if this.elements.size != other.elements.size this.elements.zip(other.elements).each do |a, b| - return TBoolean.new(false) unless NativeLib.call_func_by_name(self, a, "==", [b]).truthy? + return false unless NativeLib.call_func_by_name(self, a, "==", [b]).truthy? end - TBoolean.new(true) + true end - NativeLib.method :list_not_eq, TList, other : Value do - return TBoolean.new(true) unless other.is_a?(TList) - return TBoolean.new(false) if this == other - return TBoolean.new(true) if this.elements.size != other.elements.size + NativeLib.method :list_not_eq, TList, other : MTValue do + return true unless other.is_a?(TList) + return false if this == other + return true if this.elements.size != other.elements.size this.elements.zip(other.elements).each do |a, b| - return TBoolean.new(true) if NativeLib.call_func_by_name(self, a, "==", [b]).truthy? + return true if NativeLib.call_func_by_name(self, a, "==", [b]).truthy? end - TBoolean.new(false) + false end NativeLib.method :list_add, TList, other : TList do TList.new(this.elements + other.elements) end - NativeLib.method :list_access, TList, index : TInteger do - if element = this.elements[index.value]? + NativeLib.method :list_access, TList, index : Int64 do + if element = this.elements[index]? element else TNil.new end end - NativeLib.method :list_access_assign, TList, index : TInteger, value : Value do - this.ensure_capacity(index.value + 1) - this.elements[index.value] = value + NativeLib.method :list_access_assign, TList, index : Int64, value : MTValue do + this.ensure_capacity(index + 1) + this.elements[index] = value end NativeLib.method :list_minus, TList, other : TList do @@ -64,23 +64,23 @@ module Myst end NativeLib.method :list_proper_subset, TList, other : TList do - return TBoolean.new(false) unless other.is_a?(TList) - return TBoolean.new(false) if this == other + return false unless other.is_a?(TList) + return false if this == other if (this.elements - other.elements).empty? - TBoolean.new(true) + true else - TBoolean.new(false) + false end end NativeLib.method :list_subset, TList, other : TList do - return TBoolean.new(false) unless other.is_a?(TList) + return false unless other.is_a?(TList) if (this.elements - other.elements).empty? - TBoolean.new(true) + true else - TBoolean.new(false) + false end end diff --git a/src/myst/interpreter/native_lib/map.cr b/src/myst/interpreter/native_lib/map.cr index db794a6..be66d10 100644 --- a/src/myst/interpreter/native_lib/map.cr +++ b/src/myst/interpreter/native_lib/map.cr @@ -11,73 +11,73 @@ module Myst end NativeLib.method :map_size, TMap do - TInteger.new(this.entries.size.to_i64) + this.entries.size.to_i64 end NativeLib.method :map_add, TMap, other : TMap do TMap.new(this.entries.merge(other.entries)) end - NativeLib.method :map_eq, TMap, other : Value do - return TBoolean.new(false) unless other.is_a?(TMap) - return TBoolean.new(true) if this == other - return TBoolean.new(false) if this.entries.size != other.entries.size + NativeLib.method :map_eq, TMap, other : MTValue do + return false unless other.is_a?(TMap) + return true if this == other + return false if this.entries.size != other.entries.size # At this point, `this` and `other` must have the same number of keys, # meaning that if `other` contains all of the keys that `this` does, it # also cannot contain any extra keys, so it's only necessary to iterate # one of the two maps' keys. this.entries.keys.zip(other.entries.keys).each do |a_key, b_key| - return TBoolean.new(false) unless NativeLib.call_func_by_name(self, a_key, "==", [b_key]).truthy? - return TBoolean.new(false) unless NativeLib.call_func_by_name(self, this.entries[a_key], "==", [other.entries[b_key]]).truthy? + return false unless NativeLib.call_func_by_name(self, a_key, "==", [b_key]).truthy? + return false unless NativeLib.call_func_by_name(self, this.entries[a_key], "==", [other.entries[b_key]]).truthy? end - TBoolean.new(true) + true end - NativeLib.method :map_not_eq, TMap, other : Value do - return TBoolean.new(true) unless other.is_a?(TMap) - return TBoolean.new(false) if this == other - return TBoolean.new(true) if this.entries.size != other.entries.size + NativeLib.method :map_not_eq, TMap, other : MTValue do + return true unless other.is_a?(TMap) + return false if this == other + return true if this.entries.size != other.entries.size # At this point, `this` and `other` must have the same number of keys, # meaning that if `other` contains all of the keys that `this` does, it # also cannot contain any extra keys, so it's only necessary to iterate # one of the two maps' keys. this.entries.keys.zip(other.entries.keys).each do |a_key, b_key| - return TBoolean.new(true) if NativeLib.call_func_by_name(self, a_key, "==", [b_key]).truthy? - return TBoolean.new(true) if NativeLib.call_func_by_name(self, this.entries[a_key], "==", [other.entries[b_key]]).truthy? + return true if NativeLib.call_func_by_name(self, a_key, "==", [b_key]).truthy? + return true if NativeLib.call_func_by_name(self, this.entries[a_key], "==", [other.entries[b_key]]).truthy? end - TBoolean.new(true) + true end - NativeLib.method :map_access, TMap, index : Value do + NativeLib.method :map_access, TMap, index : MTValue do this.entries[index]? || TNil.new end - NativeLib.method :map_access_assign, TMap, index : Value, value : Value do + NativeLib.method :map_access_assign, TMap, index : MTValue, value : MTValue do this.entries[index] = value end NativeLib.method :map_proper_subset, TMap, other : TMap do - return TBoolean.new(false) unless other.is_a?(TMap) - return TBoolean.new(false) if this.entries.keys == other.entries.keys + return false unless other.is_a?(TMap) + return false if this.entries.keys == other.entries.keys if (this.entries.keys - other.entries.keys).empty? - TBoolean.new(true) + true else - TBoolean.new(false) + false end end NativeLib.method :map_subset, TMap, other : TMap do - return TBoolean.new(false) unless other.is_a?(TMap) + return false unless other.is_a?(TMap) if (this.entries.keys - other.entries.keys).empty? - TBoolean.new(true) + true else - TBoolean.new(false) + false end end diff --git a/src/myst/interpreter/native_lib/nil.cr b/src/myst/interpreter/native_lib/nil.cr index bdf3bdf..350c854 100644 --- a/src/myst/interpreter/native_lib/nil.cr +++ b/src/myst/interpreter/native_lib/nil.cr @@ -1,24 +1,24 @@ module Myst class Interpreter NativeLib.method :nil_to_s, TNil do - TString.new("") + "" end - NativeLib.method :nil_eq, TNil, other : Value do + NativeLib.method :nil_eq, TNil, other : MTValue do case other when TNil - TBoolean.new(true) + true else - TBoolean.new(false) + false end end - NativeLib.method :nil_not_eq, TNil, other : Value do + NativeLib.method :nil_not_eq, TNil, other : MTValue do case other when TNil - TBoolean.new(false) + false else - TBoolean.new(true) + true end end diff --git a/src/myst/interpreter/native_lib/random.cr b/src/myst/interpreter/native_lib/random.cr index 699f528..ac9148d 100644 --- a/src/myst/interpreter/native_lib/random.cr +++ b/src/myst/interpreter/native_lib/random.cr @@ -1,19 +1,19 @@ module Myst - class Interpreter - NativeLib.method :random_rand, TModule, max : Value? = nil do - if max.is_a? TInteger - TInteger.new(rand(max.value)) - elsif max.is_a? TFloat - TFloat.new(rand(max.value)) + class Interpreter + NativeLib.method :random_rand, TModule, max : MTValue? = nil do + if max.is_a? Int64 + rand(max) + elsif max.is_a? Float64 + rand(max) else - TFloat.new(rand()) + rand() end end def init_random(kernel : TModule) random_module = TModule.new("Random", kernel.scope) - NativeLib.def_method(random_module, :rand, :random_rand) + NativeLib.def_method(random_module, :rand, :random_rand) random_module end diff --git a/src/myst/interpreter/native_lib/string.cr b/src/myst/interpreter/native_lib/string.cr index 58f314d..7611658 100644 --- a/src/myst/interpreter/native_lib/string.cr +++ b/src/myst/interpreter/native_lib/string.cr @@ -1,119 +1,111 @@ module Myst class Interpreter - NativeLib.method :string_add, TString, other : Value do + NativeLib.method :string_add, String, other : MTValue do case other - when TString - TString.new(this.value + other.value) + when String + this + other else __raise_runtime_error("invalid argument for String#+: #{__typeof(other).name}") end end - NativeLib.method :string_multiply, TString, other : Value do + NativeLib.method :string_multiply, String, other : MTValue do case other - when TInteger + when Int64 # String multiplication repeats `this` `arg` times. - TString.new(this.value * other.value) + this * other else __raise_runtime_error("invalid argument for String#*: #{__typeof(other).name}") end end - NativeLib.method :string_to_s, TString do - this.as(TString) + NativeLib.method :string_to_s, String do + this end - NativeLib.method :string_eq, TString, other : Value do - case other - when TString - TBoolean.new(this.value == other.value) - else - TBoolean.new(false) - end + NativeLib.method :string_eq, String, other : MTValue do + this == other end - NativeLib.method :string_not_eq, TString, other : Value do - case other - when TString - TBoolean.new(this.value != other.value) - else - TBoolean.new(true) - end + NativeLib.method :string_not_eq, String, other : MTValue do + this != other end - NativeLib.method :string_split, TString do + NativeLib.method :string_split, String do delimiter = - case delim_arg = __args[0]? + case delim = __args[0]? when nil " " - when TString - delim_arg.value + when String + delim + else + __raise_runtime_error("Delimiter for String#split must be a String value (got #{__typeof(delim).name}") end - TList.new(this.value.split(delimiter).map{ |s| TString.new(s).as(Value) }) + TList.new(this.split(delimiter).map(&.as(MTValue))) end - NativeLib.method :string_size, TString do - TInteger.new(this.value.size.to_i64) + NativeLib.method :string_size, String do + this.size.to_i64 end - NativeLib.method :string_chars, TString do - TList.new(this.value.chars.map { |c| TString.new(c.to_s).as Value }) + NativeLib.method :string_chars, String do + TList.new(this.chars.map { |c| c.to_s.as(MTValue) }) end - NativeLib.method :string_downcase, TString do - TString.new(this.value.downcase) + NativeLib.method :string_downcase, String do + this.downcase end - NativeLib.method :string_upcase, TString do - TString.new(this.value.upcase) + NativeLib.method :string_upcase, String do + this.upcase end - NativeLib.method :string_chomp, TString, other : TString? do - other && return TString.new(this.value.chomp(other.value)) - TString.new(this.value.chomp) + NativeLib.method :string_chomp, String, other : String? do + other && return this.chomp(other) + this.chomp end - NativeLib.method :string_strip, TString do - TString.new(this.value.strip) + NativeLib.method :string_strip, String do + this.strip end - NativeLib.method :string_rstrip, TString do - TString.new(this.value.rstrip) + NativeLib.method :string_rstrip, String do + this.rstrip end - NativeLib.method :string_lstrip, TString do - TString.new(this.value.lstrip) + NativeLib.method :string_lstrip, String do + this.lstrip end - NativeLib.method :string_includes?, TString, other : TString do - TBoolean.new(this.value.includes?(other.value)) + NativeLib.method :string_includes?, String, other : String do + this.includes?(other) end - NativeLib.method :string_at, TString, index : TInteger, length : TInteger? do - idx = index.value + NativeLib.method :string_at, String, index : Int64, length : Int64? do + idx = index result = case length - when TInteger + when Int64 # Explicitly check that `String#[start, count]` will not fail. - if idx < this.value.size && length.value >= 0 - TString.new(this.value[idx, length.value]) + if idx < this.size && length >= 0 + this[idx, length] else - TString.new("") + "" end else - # Use nil-checking to assert that `index.value` is valid. - if char = this.value[index.value]? - TString.new(char.to_s) + # Use nil-checking to assert that `index` is valid. + if char = this[index]? + char.to_s end end result || TNil.new end - NativeLib.method :string_reverse, TString do - TString.new(this.value.reverse) + NativeLib.method :string_reverse, String do + this.reverse end def init_string(kernel : TModule) diff --git a/src/myst/interpreter/native_lib/symbol.cr b/src/myst/interpreter/native_lib/symbol.cr index aa9c07e..e011bda 100644 --- a/src/myst/interpreter/native_lib/symbol.cr +++ b/src/myst/interpreter/native_lib/symbol.cr @@ -1,24 +1,24 @@ module Myst class Interpreter NativeLib.method :symbol_to_s, TSymbol do - TString.new(this.name) + this.name end - NativeLib.method :symbol_eq, TSymbol, other : Value do + NativeLib.method :symbol_eq, TSymbol, other : MTValue do case other when TSymbol - TBoolean.new(this.value == other.value) + this == other else - TBoolean.new(false) + false end end - NativeLib.method :symbol_not_eq, TSymbol, other : Value do + NativeLib.method :symbol_not_eq, TSymbol, other : MTValue do case other when TSymbol - TBoolean.new(this.value != other.value) + this != other else - TBoolean.new(true) + true end end diff --git a/src/myst/interpreter/native_lib/time.cr b/src/myst/interpreter/native_lib/time.cr index c2663c1..425ed9a 100644 --- a/src/myst/interpreter/native_lib/time.cr +++ b/src/myst/interpreter/native_lib/time.cr @@ -1,24 +1,24 @@ module Myst class Interpreter - NativeLib.method :static_time_now, Value do + NativeLib.method :static_time_now, MTValue do seconds, nanoseconds = Crystal::System::Time.compute_utc_seconds_and_nanoseconds offset = Crystal::System::Time.compute_utc_offset(seconds) - + instance = NativeLib.instantiate(self, this.as(TType), [ - TInteger.new(seconds + offset), - TInteger.new(nanoseconds.to_i64) - ] of Value) + seconds + offset, + nanoseconds.to_i64 + ] of MTValue) instance end - NativeLib.method :time_to_s, TInstance, format : TString? do + NativeLib.method :time_to_s, TInstance, format : String? do crystal_time = to_crystal_time(this) if format - TString.new(crystal_time.to_s(format.value)) + crystal_time.to_s(format) else - TString.new(crystal_time.to_s) + crystal_time.to_s end end @@ -34,8 +34,8 @@ module Myst private def to_crystal_time(myst_time : TInstance) Time.new( - seconds: myst_time.ivars["@seconds"].as(TInteger).value, - nanoseconds: myst_time.ivars["@nanoseconds"].as(TInteger).value.to_i32, + seconds: myst_time.ivars["@seconds"].as(Int64), + nanoseconds: myst_time.ivars["@nanoseconds"].as(Int64).to_i32, kind: Time::Kind::Unspecified ) end diff --git a/src/myst/interpreter/native_lib/top_level.cr b/src/myst/interpreter/native_lib/top_level.cr index 2f6188e..a690563 100644 --- a/src/myst/interpreter/native_lib/top_level.cr +++ b/src/myst/interpreter/native_lib/top_level.cr @@ -1,20 +1,14 @@ module Myst class Interpreter - NativeLib.method :mt_exit, Value, status : TInteger? do - real_status = - if status.is_a?(TInteger) - status.value - else - 0 - end + NativeLib.method :mt_exit, MTValue, status : Int64? do + real_status = status.is_a?(Int64) ? status : 0 exit(real_status.to_i32) end - NativeLib.method :mt_sleep, Value, time : TInteger | TFloat? = nil do - - if time.is_a? TInteger || time.is_a? TFloat - sleep(time.value) + NativeLib.method :mt_sleep, MTValue, time : Int64 | Float64? = nil do + if time.is_a?(Int64) || time.is_a?(Float64) + sleep(time) else sleep end diff --git a/src/myst/interpreter/nodes/call.cr b/src/myst/interpreter/nodes/call.cr index 300f1a4..db9f6d3 100644 --- a/src/myst/interpreter/nodes/call.cr +++ b/src/myst/interpreter/nodes/call.cr @@ -3,8 +3,13 @@ module Myst def visit(node : Call) receiver, func = lookup_call(node) - if func + case func + when TFunctor visit_call(node, receiver, func) + when MTValue + # If `func` is _not_ a functor, it must just be a value from a variable + # that didn't get parsed as a Var/Const/etc, so it can + stack.push(func) else if (name = node.name).is_a?(String) __raise_not_found(name, receiver) @@ -16,7 +21,7 @@ module Myst end - private def lookup_call(node : Call) : Tuple(Value, Value?) + private def lookup_call(node : Call) : Tuple(MTValue, MTValue?) # If the Call has a receiver, lookup the Call on that receiver, otherwise # search the current scope. receiver, check_current = @@ -49,7 +54,7 @@ module Myst private def visit_call(node, receiver, func : TFunctor) - args = [] of Value + args = [] of MTValue node.args.each do |elem| elem.accept(self) @@ -77,9 +82,5 @@ module Myst pop_callstack(to_size: original_callstack_size) stack.push(result) end - - private def visit_call(_node, _receiver, value : Value) - stack.push(value) - end end end diff --git a/src/myst/interpreter/nodes/def.cr b/src/myst/interpreter/nodes/def.cr index f2e9fba..06bce39 100644 --- a/src/myst/interpreter/nodes/def.cr +++ b/src/myst/interpreter/nodes/def.cr @@ -7,7 +7,7 @@ module Myst type.scope when {TType, false} type.instance_scope - when {Value, true} + when {MTValue, true} # Any other kind of value is not allowed to define static methods. __raise_runtime_error("Cannot define static method on #{__typeof(current_self).name}") else diff --git a/src/myst/interpreter/nodes/exception_handler.cr b/src/myst/interpreter/nodes/exception_handler.cr index 525bdf7..d5553e3 100644 --- a/src/myst/interpreter/nodes/exception_handler.cr +++ b/src/myst/interpreter/nodes/exception_handler.cr @@ -48,7 +48,7 @@ module Myst end end - private def rescue_matches?(param : Param, arg : Value) + private def rescue_matches?(param : Param, arg : MTValue) self.match(param.pattern, arg) if param.pattern? self.match(Var.new(param.name), arg) if param.name? self.match(param.restriction, arg) if param.restriction? diff --git a/src/myst/interpreter/nodes/literals.cr b/src/myst/interpreter/nodes/literals.cr index 1640e5e..44ec202 100644 --- a/src/myst/interpreter/nodes/literals.cr +++ b/src/myst/interpreter/nodes/literals.cr @@ -1,7 +1,7 @@ module Myst class Interpreter def visit(node : ListLiteral) - elements = [] of Value + elements = [] of MTValue node.elements.each do |elem| elem.accept(self) @@ -22,7 +22,7 @@ module Myst end def visit(node : MapLiteral) - entries = node.entries.reduce(Hash(Value, Value).new) do |map, entry| + entries = node.entries.reduce(Hash(MTValue, MTValue).new) do |map, entry| entry.key.accept(self) key = stack.pop entry.value.accept(self) @@ -38,7 +38,7 @@ module Myst strs = node.components.map do |piece| case piece when StringLiteral - Value.from_literal(piece).as(TString) + Interpreter.__value_from_literal(piece) else visit(piece) expr_result = stack.pop @@ -47,18 +47,18 @@ module Myst self, value_to_s, expr_result, - [] of Value, + [] of MTValue, nil - ).invoke.as(TString) + ).invoke.as(String) end end - full_str = strs.map(&.value).join - stack.push(TString.new(full_str)) + full_str = strs.join + stack.push(full_str) end def visit(node : Literal) - stack.push(Value.from_literal(node)) + stack.push(Interpreter.__value_from_literal(node)) end end end diff --git a/src/myst/interpreter/nodes/magic_const.cr b/src/myst/interpreter/nodes/magic_const.cr index e95eeeb..6ea8cf2 100644 --- a/src/myst/interpreter/nodes/magic_const.cr +++ b/src/myst/interpreter/nodes/magic_const.cr @@ -3,11 +3,11 @@ module Myst def visit(node : MagicConst) case node.type when :"__FILE__" - stack.push(TString.new(node.file)) + stack.push(node.file) when :"__LINE__" - stack.push(TInteger.new(node.line.to_i64)) + stack.push(node.line.to_i64) when :"__DIR__" - stack.push(TString.new(node.dir)) + stack.push(node.dir) end end end diff --git a/src/myst/interpreter/nodes/references.cr b/src/myst/interpreter/nodes/references.cr index 9741b35..1736658 100644 --- a/src/myst/interpreter/nodes/references.cr +++ b/src/myst/interpreter/nodes/references.cr @@ -12,7 +12,7 @@ module Myst # reference to it will initialize it to `nil`. Because of that, a # reference to an instance variable will never fail to lookup (even when # spelled incorrectly). - unless ivar = current_self.ivars[node.name]? + if (ivar = current_self.ivars[node.name]?).nil? ivar = current_self.ivars.assign(node.name, TNil.new) end @@ -20,7 +20,14 @@ module Myst end def visit(node : Const) - if value = (current_scope[node.name]? || __typeof(current_self).scope[node.name]? || recursive_lookup(current_self, node.name)) + value = current_scope[node.name]? + if value.nil? + value = __typeof(current_self).scope[node.name]? + end + if value.nil? + value = recursive_lookup(current_self, node.name) + end + if !value.nil? stack.push(value) else __raise_not_found(node.name, current_self) diff --git a/src/myst/interpreter/nodes/require.cr b/src/myst/interpreter/nodes/require.cr index febcee8..f706a40 100644 --- a/src/myst/interpreter/nodes/require.cr +++ b/src/myst/interpreter/nodes/require.cr @@ -11,11 +11,11 @@ module Myst # The path for a require must be a String, otherwise, the require cannot # be successful. - unless path.is_a?(TString) + unless path.is_a?(String) __raise_runtime_error("Path for `require` must be a String. Got #{path}") end - path_str = path.value + path_str = path # The working directory for the require is always the directory of the @@ -30,7 +30,7 @@ module Myst full_path = resolve_path(path_str, working_dir) # If the file has already been loaded, return false. if @loaded_files[full_path]? - stack.push(TBoolean.new(false)) + stack.push(false) return end @@ -43,7 +43,7 @@ module Myst # the stack. Instead, the return value of a `require` should be either # `true` or `false`, so it must be replaced. @stack.pop - @stack.push(TBoolean.new(true)) + @stack.push(true) end diff --git a/src/myst/interpreter/nodes/unary_ops.cr b/src/myst/interpreter/nodes/unary_ops.cr index e1711a2..2f3215e 100644 --- a/src/myst/interpreter/nodes/unary_ops.cr +++ b/src/myst/interpreter/nodes/unary_ops.cr @@ -4,7 +4,7 @@ module Myst visit(node.value) value = stack.pop() negate = self.__scopeof(value)["negate"].as(TFunctor) - result = Invocation.new(self, negate, value, [] of Value, nil).invoke + result = Invocation.new(self, negate, value, [] of MTValue, nil).invoke stack.push(result) end @@ -15,9 +15,9 @@ module Myst result = if not_method = self.__scopeof(value)["!"]? not_method = not_method.as(TFunctor) - Invocation.new(self, not_method, value, [] of Value , nil).invoke + Invocation.new(self, not_method, value, [] of MTValue , nil).invoke else - TBoolean.new(!value.truthy?) + !value.truthy? end stack.push(result) @@ -29,7 +29,7 @@ module Myst if splat_method = recursive_lookup(value, "*").as?(TFunctor) splat_method = splat_method.as(TFunctor) - result = Invocation.new(self, splat_method, value, [] of Value, nil).invoke + result = Invocation.new(self, splat_method, value, [] of MTValue, nil).invoke else __raise_not_found("* (splat)", value) end diff --git a/src/myst/interpreter/scope.cr b/src/myst/interpreter/scope.cr index 4d393f9..dd82392 100644 --- a/src/myst/interpreter/scope.cr +++ b/src/myst/interpreter/scope.cr @@ -3,29 +3,37 @@ require "./value.cr" module Myst class Scope property parent : Scope? - property values : Hash(String, Value) + property values : Hash(String, MTValue) def initialize(@parent : Scope? = nil) - @values = {} of String => Value + @values = {} of String => MTValue end # The shorthand access notations (`[]?`, `[]`, `[]=`) will all fall back to # the parent scope if the value does not exist in this scope. # # The longhand `has_key?` and `assign` only operate on this scope. - def []?(key : String) : Value? - @values[key]? || @parent.try(&.[key]?) + def []?(key : String) : MTValue? + found = @values[key]? + if found.nil? + found = @parent.try(&.[key]?) + end + found end # A non-nilable variant of `[]?`. While this method may raise an exception, # it is not considered a "public" exception (it is not meant to be # reachable by userland code). Any instance where the exception propogates # outside of the interpreter should be considered a bug. - def [](key : String) : Value - self[key]? || raise IndexError.new("Interpeter Bug: Unmanaged, failed attempt to access `#{key}` from scope: #{self.inspect}") + def [](key : String) : MTValue + found = self[key]? + if found.nil? + raise IndexError.new("Interpeter Bug: Unmanaged, failed attempt to access `#{key}` from scope: #{self.inspect}") + end + found end - def []=(key : String, value : Value) : Value + def []=(key : String, value : MTValue) : MTValue scope = self while scope if scope.has_key?(key) @@ -40,10 +48,10 @@ module Myst def has_key?(key : String) - !!@values[key]? + @values.has_key?(key) end - def assign(key : String, value : Value) + def assign(key : String, value : MTValue) @values[key] = value end diff --git a/src/myst/interpreter/util.cr b/src/myst/interpreter/util.cr index b4aa95e..de3bb8f 100644 --- a/src/myst/interpreter/util.cr +++ b/src/myst/interpreter/util.cr @@ -1,16 +1,35 @@ module Myst class Interpreter + def self.__value_from_literal(literal : Node) + case literal + when IntegerLiteral + literal.value.to_i64 + when FloatLiteral + literal.value.to_f64 + when StringLiteral + literal.value + when SymbolLiteral + TSymbol.new(literal.value) + when BooleanLiteral + literal.value + when NilLiteral + TNil.new + else + raise "Interpreter Bug: Attempting to create an MTValue from a #{literal.class}, which is not a valid Literal type." + end + end + # Resolve the TType object representing the type of `value`. For primitive # types, these are _always_ looked up in the Kernel. For Instances, the # type is looked up from the type reference on the instance itself. For # Types and Modules, the value itself is returned. - def __typeof(value : Value) + def __typeof(value : MTValue) case value when ContainerType value when TInstance value.type - when Value + when MTValue @kernel.scope[value.type_name].as(TType) else __raise_runtime_error("Can't resolve type of #{value}") @@ -20,7 +39,7 @@ module Myst # Resolve the Scope for `value`. For primitives, this returns the instance # scope of the Type for that value. For Instances, Types, and Modules, this # just returns `.scope` for that value. - def __scopeof(value : Value, prefer_instance_scope = false) : Scope + def __scopeof(value : MTValue, prefer_instance_scope = false) : Scope case value when TInstance value.scope @@ -37,9 +56,9 @@ module Myst # raise an appropriate error if the given value is a primitive. # If `operation` is given, it will be used as the error message. macro __disallow_primitives(value, operation=nil) - if {{value}}.is_a?(TInteger) || {{value}}.is_a?(TFloat) || - {{value}}.is_a?(TNil) || {{value}}.is_a?(TBoolean) || - {{value}}.is_a?(TString) + if {{value}}.is_a?(Int64) || {{value}}.is_a?(Float64) || + {{value}}.is_a?(TNil) || {{value}}.is_a?(Bool) || + {{value}}.is_a?(String) __raise_runtime_error({{operation || "Operation disallowed on primitive types"}}) end end @@ -49,7 +68,7 @@ module Myst # ancestors. If the value is not found, a `No variable or method` # RuntimeError will be raised. def lookup(node) - if value = current_scope[node.name]? + unless (value = current_scope[node.name]?).nil? value else __raise_not_found(node.name, current_self) @@ -62,19 +81,24 @@ module Myst # # The method will return `nil` if no matching entry is found. def recursive_lookup(receiver, name, check_current = true) - func = current_scope[name] if check_current && current_scope.has_key?(name) - func ||= __scopeof(receiver)[name]? - case receiver - when TType - func ||= receiver.extended_ancestors.each do |anc| - if found = __scopeof(anc)[name]? - break found + func = current_scope[name] if check_current && current_scope.has_key?(name) + if func.nil? + func = __scopeof(receiver)[name]? + end + + if func.nil? + case receiver + when TType + func ||= receiver.extended_ancestors.each do |anc| + unless (found = __scopeof(anc)[name]?).nil? + break found + end end - end - else - func ||= __typeof(receiver).ancestors.each do |anc| - if found = __scopeof(anc, prefer_instance_scope: true)[name]? - break found + else + func ||= __typeof(receiver).ancestors.each do |anc| + unless (found = __scopeof(anc, prefer_instance_scope: true)[name]?).nil? + break found + end end end end @@ -83,12 +107,12 @@ module Myst end - def __raise_not_found(name, value : Value?) + def __raise_not_found(name, value : MTValue?) type_name = __typeof(value).name error_message = "No variable or method `#{name}` for #{type_name}" if value_to_s = __scopeof(value)["to_s"]? - value_str = NativeLib.call_func_by_name(self, value, "to_s", [] of Value).as(TString).value + value_str = NativeLib.call_func_by_name(self, value, "to_s", [] of MTValue) error_message = "No variable or method `#{name}` for #{value_str}:#{type_name}" end @@ -102,10 +126,10 @@ module Myst # Multiple overloads of this function are provided for simplicity at the # call site. def __raise_runtime_error(message : String) - raise RuntimeError.new(TString.new(message), callstack) + raise RuntimeError.new(message, callstack) end - def __raise_runtime_error(value : Value) + def __raise_runtime_error(value : MTValue) raise RuntimeError.new(value, callstack) end diff --git a/src/myst/interpreter/value.cr b/src/myst/interpreter/value.cr index 821bf8f..593842a 100644 --- a/src/myst/interpreter/value.cr +++ b/src/myst/interpreter/value.cr @@ -1,31 +1,43 @@ module Myst - abstract class Value - def Value.from_literal(literal : Node) - case literal - when IntegerLiteral - TInteger.new(literal.value.to_i64) - when FloatLiteral - TFloat.new(literal.value.to_f64) - when StringLiteral - TString.new(literal.value) - when SymbolLiteral - TSymbol.new(literal.value) - when BooleanLiteral - TBoolean.new(literal.value) - when NilLiteral - TNil.new - else - raise "Interpreter Bug: Attempting to create a Value from a #{literal.class}, which is not a valid Literal type." - end - end + alias MTValue = Int64 | Float64 | Bool | String | MutableValue + + # Define a few methods on the primitive types to allow the interpreter to + # treat them like any other value. + struct ::Int64 + def type_name; "Integer"; end + def truthy?; true; end + + def ivars; raise "Primitive values cannot have instance variables"; end + end + + struct ::Float64 + def type_name; "Float"; end + def truthy?; true; end + + def ivars; raise "Primitive values cannot have instance variables"; end + end + + struct ::Bool + def type_name; "Boolean"; end + def truthy?; self; end + + def ivars; raise "Primitive values cannot have instance variables"; end + end + + class ::String + def type_name; "String"; end + def truthy?; true; end + + def ivars; raise "Primitive values cannot have instance variables"; end + end + abstract class MutableValue # Instance variables are properties tied to the instance of an object. # For consistency between native (Integer, String, etc.) and language- # level types (IO, File, etc.), all values have an `ivars` property. property ivars : Scope = Scope.new - def truthy? true end @@ -35,7 +47,7 @@ module Myst end end - abstract class ContainerType < Value + abstract class ContainerType < MutableValue property name : String = "" # Ancestors are the modules that have been included inside of a Type. For # example, if a module includes Enumerable, then the ancestors for that @@ -82,11 +94,11 @@ module Myst # TODO: revist this when base object for TType is in place # Currently this prevents to_s from being overriden on Types @scope["to_s"] = TFunctor.new("to_s", [ - ->ttype_to_s(Value, Array(Value), TFunctor?)] of Callable) + ->ttype_to_s(MTValue, Array(MTValue), TFunctor?)] of Callable) end def ttype_to_s(_a, _b, _c) - TString.new(@name).as(Value) + @name.as(MTValue) end def type_name @@ -128,7 +140,7 @@ module Myst def_equals_and_hash name, scope, instance_scope end - class TInstance < Value + class TInstance < MutableValue property type : TType property scope : Scope @@ -147,21 +159,7 @@ module Myst def_equals_and_hash type, scope end - - # Primitives are immutable objects - abstract class TPrimitive(T) < Value - property value : T - - def initialize(@value : T); end - - def to_s - value.to_s - end - - def_equals_and_hash value - end - - class TNil < Value + class TNil < MutableValue # All instances of Nil in a program refer to the same object. NIL_OBJECT = TNil.allocate @@ -184,59 +182,10 @@ module Myst def_equals_and_hash end - class TBoolean < TPrimitive(Bool) - def to_s - @value ? "true" : "false" - end - - def truthy? - @value - end - - def type_name - "Boolean" - end - end - - class TInteger < TPrimitive(Int64) - def ==(other : TFloat) - self.value == other.value - end - - def type_name - "Integer" - end - end - - class TFloat < TPrimitive(Float64) - def ==(other : TInteger) - self.value == other.value - end - - def type_name - "Float" - end - end - - class TString < TPrimitive(String) - def type_name - "String" - end - end - - class TSymbol < TPrimitive(UInt64) + class TSymbol < MutableValue SYMBOLS = {} of String => TSymbol @@next_id = 0_u64 - property name : String - - def initialize(@value : UInt64, @name : String) - end - - def type_name - "Symbol" - end - def self.new(name) # TODO: Revert to the following once Crystal 0.24.0 is released. This # currently causes a bug (see crystal-lang/crystal#4600). @@ -252,13 +201,30 @@ module Myst SYMBOLS[name] = instance end end + + + property value : UInt64 + property name : String + + def initialize(@value : UInt64, @name : String) + end + + def to_s + value.to_s + end + + def type_name + "Symbol" + end + + def_equals_and_hash value end - class TList < Value - property elements : Array(Value) + class TList < MutableValue + property elements : Array(MTValue) - def initialize(@elements=[] of Value) + def initialize(@elements=[] of MTValue) end def ensure_capacity(size : Int) @@ -273,10 +239,10 @@ module Myst def_equals_and_hash elements end - class TMap < Value - property entries : Hash(Value, Value) + class TMap < MutableValue + property entries : Hash(MTValue, MTValue) - def initialize(@entries={} of Value => Value) + def initialize(@entries={} of MTValue => MTValue) end def type_name @@ -287,7 +253,7 @@ module Myst end - class TFunctorDef < Value + class TFunctorDef < MutableValue property definition : Def delegate params, block_param, block_param?, body, splat_index?, splat_index, to: definition @@ -298,20 +264,20 @@ module Myst def_equals_and_hash definition end - alias TNativeDef = Value, Array(Value), TFunctor? -> Value + alias TNativeDef = MTValue, Array(MTValue), TFunctor? -> MTValue alias Callable = TFunctorDef | TNativeDef # A Functor is a container for multiple functor definitions, which can either # be language-level or native. - class TFunctor < Value + class TFunctor < MutableValue property name : String property clauses : Array(Callable) property lexical_scope : Scope property? closure : Bool - property! closed_self : Value? + property! closed_self : MTValue? - def initialize(@name : String, @clauses=[] of Callable, @lexical_scope : Scope=Scope.new, @closure : Bool=false, @closed_self : Value?=nil) + def initialize(@name : String, @clauses=[] of Callable, @lexical_scope : Scope=Scope.new, @closure : Bool=false, @closed_self : MTValue?=nil) end def add_clause(definition : Callable)