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()
|
||||
when DEBUG_TRACE_COMPILER:
|
||||
echo "==== COMPILER debugger ends ===="
|
||||
echo "\n==== COMPILER debugger ends ===="
|
||||
echo ""
|
||||
|
||||
if not self.parser.hadError:
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) =
|
||||
|
|
60
src/vm.nim
60
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"
|
||||
|
|
|
@ -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
|
||||
|
||||
import ..meta/chunk
|
||||
import ..meta/valueobject
|
||||
import ..types/objecttype
|
||||
import ..meta/opcode
|
||||
import ..types/jobject
|
||||
import ..util/debug
|
Loading…
Reference in New Issue