mirror of https://github.com/japl-lang/japl.git
Fixed typos, minor improvements, added isCallable for objects
This commit is contained in:
parent
e87096ce71
commit
09e543d786
|
@ -1094,7 +1094,7 @@ proc compile*(self: ref Compiler, source: string): ptr Function =
|
||||||
|
|
||||||
var function = self.endCompiler()
|
var function = self.endCompiler()
|
||||||
when DEBUG_TRACE_COMPILER:
|
when DEBUG_TRACE_COMPILER:
|
||||||
echo "==== COMPILER debugger ends ===="
|
echo "\n==== COMPILER debugger ends ===="
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
if not self.parser.hadError:
|
if not self.parser.hadError:
|
||||||
|
|
|
@ -15,15 +15,18 @@
|
||||||
import strformat
|
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 FRAMES_MAX* = 400 # TODO: Inspect why the VM crashes if this exceeds 400
|
||||||
const JAPL_VERSION* = "0.2.0"
|
const JAPL_VERSION* = "0.2.0"
|
||||||
const JAPL_RELEASE* = "alpha"
|
const JAPL_RELEASE* = "alpha"
|
||||||
const DEBUG_TRACE_VM* = false # Traces VM execution
|
const DEBUG_TRACE_VM* = false # Traces VM execution
|
||||||
const DEBUG_TRACE_GC* = true # Traces the garbage collector (TODO)
|
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
|
||||||
const DEBUG_TRACE_ALLOCATION* = true # Traces memory allocation/deallocation (WIP)
|
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation (WIP)
|
||||||
const DEBUG_TRACE_COMPILER* = false # Traces the compiler (WIP)
|
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
|
||||||
const JAPL_VERSION_STRING* = &"JAPL {JAPL_VERSION} ({JAPL_RELEASE}, {CompileDate} {CompileTime})"
|
const JAPL_VERSION_STRING* = &"JAPL {JAPL_VERSION} ({JAPL_RELEASE}, {CompileDate} {CompileTime})"
|
||||||
const HELP_MESSAGE* = """The JAPL runtime interface, Copyright (C) 2020 Mattia Giambirtone
|
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
|
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.
|
http://www.apache.org/licenses/LICENSE-2.0 for more info.
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@
|
||||||
|
|
||||||
|
|
||||||
import segfaults
|
import segfaults
|
||||||
|
import config
|
||||||
|
# when DEBUG_TRACE_ALLOCATION:
|
||||||
|
# import util/debug # TODO: Recursive dependency
|
||||||
|
|
||||||
|
|
||||||
proc reallocate*(pointr: pointer, oldSize: int, newSize: int): pointer =
|
proc reallocate*(pointr: pointer, oldSize: int, newSize: int): pointer =
|
||||||
|
@ -60,7 +63,7 @@ template growCapacity*(capacity: int): untyped =
|
||||||
if capacity < 8:
|
if capacity < 8:
|
||||||
8
|
8
|
||||||
else:
|
else:
|
||||||
capacity * 2
|
capacity * ARRAY_GROW_FACTOR
|
||||||
|
|
||||||
|
|
||||||
template allocate*(castTo: untyped, sizeTo: untyped, count: int): untyped =
|
template allocate*(castTo: untyped, sizeTo: untyped, count: int): untyped =
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
## Base structure for objects in JAPL, all
|
## JAPL' type system
|
||||||
## types inherit from this simple structure
|
|
||||||
|
|
||||||
import ../memory
|
import ../memory
|
||||||
import strformat
|
import strformat
|
||||||
|
@ -30,7 +29,7 @@ type
|
||||||
## Lines maps bytecode instructions to line numbers (1 to 1 correspondence)
|
## Lines maps bytecode instructions to line numbers (1 to 1 correspondence)
|
||||||
consts*: seq[ptr Obj]
|
consts*: seq[ptr Obj]
|
||||||
code*: seq[uint8]
|
code*: seq[uint8]
|
||||||
lines*: seq[int]
|
lines*: seq[int] # TODO: Run-length encoding
|
||||||
ObjectType* {.pure.} = enum
|
ObjectType* {.pure.} = enum
|
||||||
## All the possible object types
|
## All the possible object types
|
||||||
String, Exception, Function,
|
String, Exception, Function,
|
||||||
|
@ -96,12 +95,19 @@ proc newChunk*(): Chunk =
|
||||||
result = Chunk(consts: @[], code: @[], lines: @[])
|
result = Chunk(consts: @[], code: @[], lines: @[])
|
||||||
|
|
||||||
|
|
||||||
|
## Utilities that bridge nim and JAPL types
|
||||||
|
|
||||||
|
|
||||||
proc objType*(obj: ptr Obj): ObjectType =
|
proc objType*(obj: ptr Obj): ObjectType =
|
||||||
## Returns the type of the object
|
## 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 =
|
proc isNil*(obj: ptr Obj): bool =
|
||||||
## Returns true if the given obj
|
## Returns true if the given obj
|
||||||
|
|
|
@ -75,7 +75,7 @@ proc disassembleInstruction*(chunk: Chunk, offset: int): int =
|
||||||
result = jumpInstruction($opcode, chunk, offset)
|
result = jumpInstruction($opcode, chunk, offset)
|
||||||
else:
|
else:
|
||||||
echo &"Unknown opcode {opcode} at index {offset}"
|
echo &"Unknown opcode {opcode} at index {offset}"
|
||||||
result = offset + 1
|
result = offset + 1
|
||||||
|
|
||||||
|
|
||||||
proc disassembleChunk*(chunk: Chunk, name: string) =
|
proc disassembleChunk*(chunk: Chunk, name: string) =
|
||||||
|
|
60
src/vm.nim
60
src/vm.nim
|
@ -37,12 +37,15 @@ proc `**`(a, b: float): float = pow(a, b)
|
||||||
|
|
||||||
type
|
type
|
||||||
KeyboardInterrupt* = object of CatchableError
|
KeyboardInterrupt* = object of CatchableError
|
||||||
|
## Custom exception to handle Ctrl+C
|
||||||
InterpretResult {.pure.} = enum
|
InterpretResult = enum
|
||||||
|
## All possible interpretation results
|
||||||
Ok,
|
Ok,
|
||||||
CompileError,
|
CompileError,
|
||||||
RuntimeError
|
RuntimeError
|
||||||
VM* = ref object # The VM object
|
VM* = ref object
|
||||||
|
## A wrapper around the virtual machine
|
||||||
|
## functionality
|
||||||
lastPop*: ptr Obj
|
lastPop*: ptr Obj
|
||||||
frameCount*: int
|
frameCount*: int
|
||||||
source*: string
|
source*: string
|
||||||
|
@ -69,9 +72,9 @@ proc resetStack*(self: var VM) =
|
||||||
self.stackTop = 0
|
self.stackTop = 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc error*(self: var VM, error: ptr JAPLException) =
|
proc error*(self: var VM, error: ptr JAPLException) =
|
||||||
## Reports runtime errors with a nice traceback
|
## Reports runtime errors with a nice traceback
|
||||||
|
# TODO: Exceptions
|
||||||
var previous = "" # All this stuff seems overkill, but it makes the traceback look nicer
|
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 repCount = 0 # and if we are here we are far beyond a point where performance matters
|
||||||
var mainReached = false
|
var mainReached = false
|
||||||
|
@ -101,7 +104,7 @@ proc error*(self: var VM, error: ptr JAPLException) =
|
||||||
|
|
||||||
|
|
||||||
proc pop*(self: var VM): ptr Obj =
|
proc pop*(self: var VM): ptr Obj =
|
||||||
## Pops a value off the stack
|
## Pops an object off the stack
|
||||||
result = self.stack.pop()
|
result = self.stack.pop()
|
||||||
self.stackTop -= 1
|
self.stackTop -= 1
|
||||||
|
|
||||||
|
@ -195,6 +198,7 @@ proc sliceRange(self: var VM): bool =
|
||||||
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'"))
|
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'"))
|
||||||
return false
|
return false
|
||||||
|
|
||||||
|
|
||||||
proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
|
proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
|
||||||
## Sets up the call frame and performs error checking
|
## Sets up the call frame and performs error checking
|
||||||
## when calling callables
|
## when calling callables
|
||||||
|
@ -212,15 +216,17 @@ proc call(self: var VM, function: ptr Function, argCount: uint8): bool =
|
||||||
return true
|
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
|
## Wrapper around call() to do type checking
|
||||||
case callee.kind:
|
if callee.isCallable():
|
||||||
of ObjectType.Function:
|
case callee.kind:
|
||||||
return self.call(cast[ptr Function](callee), argCount)
|
of ObjectType.Function:
|
||||||
else:
|
return self.call(cast[ptr Function](callee), argCount)
|
||||||
discard # Not callable
|
else: # TODO: Classes
|
||||||
self.error(newTypeError(&"object of type '{callee.typeName}' is not callable"))
|
discard # Unreachable
|
||||||
return false
|
else:
|
||||||
|
self.error(newTypeError(&"object of type '{callee.typeName}' is not callable"))
|
||||||
|
return false
|
||||||
|
|
||||||
|
|
||||||
proc run(self: var VM, repl: bool): InterpretResult =
|
proc run(self: var VM, repl: bool): InterpretResult =
|
||||||
|
@ -341,10 +347,10 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
||||||
discard
|
discard
|
||||||
of OpCode.Slice:
|
of OpCode.Slice:
|
||||||
if not self.slice():
|
if not self.slice():
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
of OpCode.SliceRange:
|
of OpCode.SliceRange:
|
||||||
if not self.sliceRange():
|
if not self.sliceRange():
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
of OpCode.DefineGlobal:
|
of OpCode.DefineGlobal:
|
||||||
if frame.function.chunk.consts.len > 255:
|
if frame.function.chunk.consts.len > 255:
|
||||||
var constant = readLongConstant().toStr()
|
var constant = readLongConstant().toStr()
|
||||||
|
@ -358,14 +364,14 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
||||||
var constant = readLongConstant().toStr()
|
var constant = readLongConstant().toStr()
|
||||||
if constant notin self.globals:
|
if constant notin self.globals:
|
||||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
else:
|
else:
|
||||||
self.push(self.globals[constant])
|
self.push(self.globals[constant])
|
||||||
else:
|
else:
|
||||||
var constant = readConstant().toStr()
|
var constant = readConstant().toStr()
|
||||||
if constant notin self.globals:
|
if constant notin self.globals:
|
||||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
else:
|
else:
|
||||||
self.push(self.globals[constant])
|
self.push(self.globals[constant])
|
||||||
of OpCode.SetGlobal:
|
of OpCode.SetGlobal:
|
||||||
|
@ -373,14 +379,14 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
||||||
var constant = readLongConstant().toStr()
|
var constant = readLongConstant().toStr()
|
||||||
if constant notin self.globals:
|
if constant notin self.globals:
|
||||||
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
|
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
else:
|
else:
|
||||||
self.globals[constant] = self.peek(0)
|
self.globals[constant] = self.peek(0)
|
||||||
else:
|
else:
|
||||||
var constant = readConstant().toStr()
|
var constant = readConstant().toStr()
|
||||||
if constant notin self.globals:
|
if constant notin self.globals:
|
||||||
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
|
self.error(newReferenceError(&"assignment to undeclared name '{constant}'"))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
else:
|
else:
|
||||||
self.globals[constant] = self.peek(0)
|
self.globals[constant] = self.peek(0)
|
||||||
of OpCode.DeleteGlobal:
|
of OpCode.DeleteGlobal:
|
||||||
|
@ -389,14 +395,14 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
||||||
var constant = readLongConstant().toStr()
|
var constant = readLongConstant().toStr()
|
||||||
if constant notin self.globals:
|
if constant notin self.globals:
|
||||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
else:
|
else:
|
||||||
self.globals.del(constant)
|
self.globals.del(constant)
|
||||||
else:
|
else:
|
||||||
var constant = readConstant().toStr()
|
var constant = readConstant().toStr()
|
||||||
if constant notin self.globals:
|
if constant notin self.globals:
|
||||||
self.error(newReferenceError(&"undefined name '{constant}'"))
|
self.error(newReferenceError(&"undefined name '{constant}'"))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
else:
|
else:
|
||||||
self.globals.del(constant)
|
self.globals.del(constant)
|
||||||
of OpCode.GetLocal:
|
of OpCode.GetLocal:
|
||||||
|
@ -435,8 +441,8 @@ proc run(self: var VM, repl: bool): InterpretResult =
|
||||||
frame.ip -= int offset
|
frame.ip -= int offset
|
||||||
of OpCode.Call:
|
of OpCode.Call:
|
||||||
var argCount = readByte()
|
var argCount = readByte()
|
||||||
if not self.callValue(self.peek(int argCount), argCount):
|
if not self.callObject(self.peek(int argCount), argCount):
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
frame = self.frames[self.frameCount - 1]
|
frame = self.frames[self.frameCount - 1]
|
||||||
of OpCode.Break:
|
of OpCode.Break:
|
||||||
discard
|
discard
|
||||||
|
@ -478,7 +484,7 @@ proc freeObject(obj: ptr Obj) =
|
||||||
|
|
||||||
|
|
||||||
proc freeObjects(self: var VM) =
|
proc freeObjects(self: var VM) =
|
||||||
## Fress all the allocated objects
|
## Frees all the allocated objects
|
||||||
## from the VM
|
## from the VM
|
||||||
var objCount = len(self.objects)
|
var objCount = len(self.objects)
|
||||||
for obj in reversed(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
|
# revisit the best way to transfer marked objects from the compiler
|
||||||
# to the vm
|
# to the vm
|
||||||
if compiled == nil:
|
if compiled == nil:
|
||||||
return COMPILE_ERROR
|
return CompileError
|
||||||
self.push(compiled)
|
self.push(compiled)
|
||||||
discard self.callValue(compiled, 0)
|
discard self.callObject(compiled, 0)
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo "==== VM debugger starts ====\n"
|
echo "==== VM debugger starts ====\n"
|
||||||
try:
|
try:
|
||||||
result = self.run(repl)
|
result = self.run(repl)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.error(newInterruptedError(""))
|
self.error(newInterruptedError(""))
|
||||||
return RUNTIME_ERROR
|
return RuntimeError
|
||||||
when DEBUG_TRACE_VM:
|
when DEBUG_TRACE_VM:
|
||||||
echo "==== VM debugger ends ====\n"
|
echo "==== VM debugger ends ====\n"
|
||||||
|
|
|
@ -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
|
|
@ -1,6 +1,5 @@
|
||||||
# temporary nim file so I can see if the stuff I've touched compiles :'D
|
# temporary nim file so I can see if the stuff I've touched compiles :'D
|
||||||
|
|
||||||
import ..meta/chunk
|
import ..meta/opcode
|
||||||
import ..meta/valueobject
|
import ..types/jobject
|
||||||
import ..types/objecttype
|
|
||||||
import ..util/debug
|
import ..util/debug
|
Loading…
Reference in New Issue