From 09e543d78676d37cc1244a97d39a7761521e0136 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Mon, 26 Oct 2020 22:55:20 +0100 Subject: [PATCH] Fixed typos, minor improvements, added isCallable for objects --- src/compiler.nim | 2 +- src/config.nim | 9 ++- src/memory.nim | 5 +- src/types/jobject.nim | 16 +++-- src/util/debug.nim | 2 +- src/vm.nim | 60 +++++++++-------- tests/japl/all.jpl | 139 +++++++++++++++++++++++++++++++++++++++ tests/{ => nim}/test.nim | 5 +- 8 files changed, 197 insertions(+), 41 deletions(-) create mode 100644 tests/japl/all.jpl rename tests/{ => nim}/test.nim (56%) diff --git a/src/compiler.nim b/src/compiler.nim index 8249723..68bef13 100644 --- a/src/compiler.nim +++ b/src/compiler.nim @@ -1094,7 +1094,7 @@ proc compile*(self: ref Compiler, source: string): ptr Function = var function = self.endCompiler() when DEBUG_TRACE_COMPILER: - echo "==== COMPILER debugger ends ====" + echo "\n==== COMPILER debugger ends ====" echo "" if not self.parser.hadError: diff --git a/src/config.nim b/src/config.nim index fc156ff..2260678 100644 --- a/src/config.nim +++ b/src/config.nim @@ -15,15 +15,18 @@ import strformat +const MAP_LOAD_FACTOR = 0.75 # Load factor for builtin hashmaps (TODO) +const ARRAY_GROW_FACTOR = 2 # How much extra memory to allocate for dynamic arrays (TODO) const FRAMES_MAX* = 400 # TODO: Inspect why the VM crashes if this exceeds 400 const JAPL_VERSION* = "0.2.0" const JAPL_RELEASE* = "alpha" const DEBUG_TRACE_VM* = false # Traces VM execution -const DEBUG_TRACE_GC* = true # Traces the garbage collector (TODO) -const DEBUG_TRACE_ALLOCATION* = true # Traces memory allocation/deallocation (WIP) -const DEBUG_TRACE_COMPILER* = false # Traces the compiler (WIP) +const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO) +const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation (WIP) +const DEBUG_TRACE_COMPILER* = false # Traces the compiler const JAPL_VERSION_STRING* = &"JAPL {JAPL_VERSION} ({JAPL_RELEASE}, {CompileDate} {CompileTime})" const HELP_MESSAGE* = """The JAPL runtime interface, Copyright (C) 2020 Mattia Giambirtone + This program is free software, see the license distributed with this program or check http://www.apache.org/licenses/LICENSE-2.0 for more info. diff --git a/src/memory.nim b/src/memory.nim index d793de5..e0240c9 100644 --- a/src/memory.nim +++ b/src/memory.nim @@ -23,6 +23,9 @@ import segfaults +import config +# when DEBUG_TRACE_ALLOCATION: + # import util/debug # TODO: Recursive dependency proc reallocate*(pointr: pointer, oldSize: int, newSize: int): pointer = @@ -60,7 +63,7 @@ template growCapacity*(capacity: int): untyped = if capacity < 8: 8 else: - capacity * 2 + capacity * ARRAY_GROW_FACTOR template allocate*(castTo: untyped, sizeTo: untyped, count: int): untyped = diff --git a/src/types/jobject.nim b/src/types/jobject.nim index c49fa98..7b9e53c 100644 --- a/src/types/jobject.nim +++ b/src/types/jobject.nim @@ -12,8 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -## Base structure for objects in JAPL, all -## types inherit from this simple structure +## JAPL' type system import ../memory import strformat @@ -30,7 +29,7 @@ type ## Lines maps bytecode instructions to line numbers (1 to 1 correspondence) consts*: seq[ptr Obj] code*: seq[uint8] - lines*: seq[int] + lines*: seq[int] # TODO: Run-length encoding ObjectType* {.pure.} = enum ## All the possible object types String, Exception, Function, @@ -96,12 +95,19 @@ proc newChunk*(): Chunk = result = Chunk(consts: @[], code: @[], lines: @[]) +## Utilities that bridge nim and JAPL types + + proc objType*(obj: ptr Obj): ObjectType = ## Returns the type of the object - return obj.kind + result = obj.kind -## Utilities that bridge nim and JAPL types +proc isCallable*(obj: ptr Obj): bool = + ## Returns true if the given object + ## is callable, false otherwise + result = obj.kind in {ObjectType.Function, ObjectType.Class} + proc isNil*(obj: ptr Obj): bool = ## Returns true if the given obj diff --git a/src/util/debug.nim b/src/util/debug.nim index dedceef..94fd7b6 100644 --- a/src/util/debug.nim +++ b/src/util/debug.nim @@ -75,7 +75,7 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int = result = jumpInstruction($opcode, chunk, offset) else: echo &"Unknown opcode {opcode} at index {offset}" - result = offset + 1 + result = offset + 1 proc disassembleChunk*(chunk: Chunk, name: string) = diff --git a/src/vm.nim b/src/vm.nim index d20fbf8..a4aefe1 100644 --- a/src/vm.nim +++ b/src/vm.nim @@ -37,12 +37,15 @@ proc `**`(a, b: float): float = pow(a, b) type KeyboardInterrupt* = object of CatchableError - - InterpretResult {.pure.} = enum + ## Custom exception to handle Ctrl+C + InterpretResult = enum + ## All possible interpretation results Ok, CompileError, RuntimeError - VM* = ref object # The VM object + VM* = ref object + ## A wrapper around the virtual machine + ## functionality lastPop*: ptr Obj frameCount*: int source*: string @@ -69,9 +72,9 @@ proc resetStack*(self: var VM) = self.stackTop = 0 - proc error*(self: var VM, error: ptr JAPLException) = ## Reports runtime errors with a nice traceback + # TODO: Exceptions var previous = "" # All this stuff seems overkill, but it makes the traceback look nicer var repCount = 0 # and if we are here we are far beyond a point where performance matters var mainReached = false @@ -101,7 +104,7 @@ proc error*(self: var VM, error: ptr JAPLException) = proc pop*(self: var VM): ptr Obj = - ## Pops a value off the stack + ## Pops an object off the stack result = self.stack.pop() self.stackTop -= 1 @@ -195,6 +198,7 @@ proc sliceRange(self: var VM): bool = self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'")) return false + proc call(self: var VM, function: ptr Function, argCount: uint8): bool = ## Sets up the call frame and performs error checking ## when calling callables @@ -212,15 +216,17 @@ proc call(self: var VM, function: ptr Function, argCount: uint8): bool = return true -proc callValue(self: var VM, callee: ptr Obj, argCount: uint8): bool = +proc callObject(self: var VM, callee: ptr Obj, argCount: uint8): bool = ## Wrapper around call() to do type checking - case callee.kind: - of ObjectType.Function: - return self.call(cast[ptr Function](callee), argCount) - else: - discard # Not callable - self.error(newTypeError(&"object of type '{callee.typeName}' is not callable")) - return false + if callee.isCallable(): + case callee.kind: + of ObjectType.Function: + return self.call(cast[ptr Function](callee), argCount) + else: # TODO: Classes + discard # Unreachable + else: + self.error(newTypeError(&"object of type '{callee.typeName}' is not callable")) + return false proc run(self: var VM, repl: bool): InterpretResult = @@ -341,10 +347,10 @@ proc run(self: var VM, repl: bool): InterpretResult = discard of OpCode.Slice: if not self.slice(): - return RUNTIME_ERROR + return RuntimeError of OpCode.SliceRange: if not self.sliceRange(): - return RUNTIME_ERROR + return RuntimeError of OpCode.DefineGlobal: if frame.function.chunk.consts.len > 255: var constant = readLongConstant().toStr() @@ -358,14 +364,14 @@ proc run(self: var VM, repl: bool): InterpretResult = var constant = readLongConstant().toStr() if constant notin self.globals: self.error(newReferenceError(&"undefined name '{constant}'")) - return RUNTIME_ERROR + return RuntimeError else: self.push(self.globals[constant]) else: var constant = readConstant().toStr() if constant notin self.globals: self.error(newReferenceError(&"undefined name '{constant}'")) - return RUNTIME_ERROR + return RuntimeError else: self.push(self.globals[constant]) of OpCode.SetGlobal: @@ -373,14 +379,14 @@ proc run(self: var VM, repl: bool): InterpretResult = var constant = readLongConstant().toStr() if constant notin self.globals: self.error(newReferenceError(&"assignment to undeclared name '{constant}'")) - return RUNTIME_ERROR + return RuntimeError else: self.globals[constant] = self.peek(0) else: var constant = readConstant().toStr() if constant notin self.globals: self.error(newReferenceError(&"assignment to undeclared name '{constant}'")) - return RUNTIME_ERROR + return RuntimeError else: self.globals[constant] = self.peek(0) of OpCode.DeleteGlobal: @@ -389,14 +395,14 @@ proc run(self: var VM, repl: bool): InterpretResult = var constant = readLongConstant().toStr() if constant notin self.globals: self.error(newReferenceError(&"undefined name '{constant}'")) - return RUNTIME_ERROR + return RuntimeError else: self.globals.del(constant) else: var constant = readConstant().toStr() if constant notin self.globals: self.error(newReferenceError(&"undefined name '{constant}'")) - return RUNTIME_ERROR + return RuntimeError else: self.globals.del(constant) of OpCode.GetLocal: @@ -435,8 +441,8 @@ proc run(self: var VM, repl: bool): InterpretResult = frame.ip -= int offset of OpCode.Call: var argCount = readByte() - if not self.callValue(self.peek(int argCount), argCount): - return RUNTIME_ERROR + if not self.callObject(self.peek(int argCount), argCount): + return RuntimeError frame = self.frames[self.frameCount - 1] of OpCode.Break: discard @@ -478,7 +484,7 @@ proc freeObject(obj: ptr Obj) = proc freeObjects(self: var VM) = - ## Fress all the allocated objects + ## Frees all the allocated objects ## from the VM var objCount = len(self.objects) for obj in reversed(self.objects): @@ -518,15 +524,15 @@ proc interpret*(self: var VM, source: string, repl: bool = false, file: string): # revisit the best way to transfer marked objects from the compiler # to the vm if compiled == nil: - return COMPILE_ERROR + return CompileError self.push(compiled) - discard self.callValue(compiled, 0) + discard self.callObject(compiled, 0) when DEBUG_TRACE_VM: echo "==== VM debugger starts ====\n" try: result = self.run(repl) except KeyboardInterrupt: self.error(newInterruptedError("")) - return RUNTIME_ERROR + return RuntimeError when DEBUG_TRACE_VM: echo "==== VM debugger ends ====\n" diff --git a/tests/japl/all.jpl b/tests/japl/all.jpl new file mode 100644 index 0000000..5527309 --- /dev/null +++ b/tests/japl/all.jpl @@ -0,0 +1,139 @@ +// Example file to test JAPL's syntax + +// Mathematical expressions + +2 + 2; +-1 * 6; +3 * (9 / 2); // Parentheses for grouping +8 % 2; // Modulo division +6 ** 9; // Exponentiation +~5; // Binary NOT +2 ^ 5; // XOR +3 & 9; // AND +9 | 3; // OR + + +// Variable definition and assignment + +var name = "bob"; // Dynamically typed +name = "joe"; // Can only be assigned if it's defined +del name; // Delete a variable +var foo; // Unitialized variables are equal to nil + +// Scoping + +var a = "global"; +var b = "global1"; +{ // open a new scope + var b = "local"; // Shadow the global variable + print(a); // This falls back to the global scope + print(b); +} +print(a); +print(b); // The outer scope isn't affected + +/* +A multiline comment +yay! +*/ + +// Control flow statements + +var n = 0; +while (n <= 10) { // While loops + if (n <= 5) { // If statements + print(n); + } + n = n + 1; +} + +for (var i = 0; i < 10; i = i + 1) { // For loops + print(i); +} + + +// Functions + +print(clock()); // Function calls + +fun count(n) { // Function definitions + if (n > 1) count(n - 1); // Recursion works + print(n); +} + +count(3); + +// Closures work too! + +var a = "global"; +{ + fun showA() { + print(a); + } + + showA(); + var a = "block"; + showA(); +} + +// Nested functions + +fun makeCounter() { + var i = 0; + fun count() { + i = i + 1; + print(i); + } + + return count; +} + +var counter = makeCounter(); +counter(); // "1". +counter(); // "2". + + +// Classes + +class Person { + + init(name) { // Class initializer + + this.name = name; + } + + greet() { // Methods don't use the 'fun' keyword! + print("Hello, " + this.name); // this refers to the current instance + } +} + +var bob = Person("Bob"); // Object creation +bob.greet(); // Prints Hello, Bob +var greetbob = bob.greet; // Functions and methods are first-class objects! (classes are too) +greetbob(); + + +class Male < Person { // Male inherits from person + + init(name) { + super.init(name); // Inherits constructor behavior + this.sex = "male"; + + } + + greet() { + super.greet(); // Inherits behavior from superclass + } +} + +var mark = Male("Mark"); +mark.greet(); + +// Strings + +"string slicing!"[0]; // 0-indexed +"ranges work too"[0:5]; +"implicit end"[3:]; // Ends at the end of the string +"implicit start"[:5]; // From 0 to 5 +"hello" + " world"; // Strings are immutable! +"hello" * 3; //hellohellohello diff --git a/tests/test.nim b/tests/nim/test.nim similarity index 56% rename from tests/test.nim rename to tests/nim/test.nim index 71855a5..35c3086 100644 --- a/tests/test.nim +++ b/tests/nim/test.nim @@ -1,6 +1,5 @@ # temporary nim file so I can see if the stuff I've touched compiles :'D -import ..meta/chunk -import ..meta/valueobject -import ..types/objecttype +import ..meta/opcode +import ..types/jobject import ..util/debug