Fixed typos, minor improvements, added isCallable for objects

This commit is contained in:
nocturn9x 2020-10-26 22:55:20 +01:00
parent e87096ce71
commit 09e543d786
8 changed files with 197 additions and 41 deletions

View File

@ -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:

View File

@ -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.

View File

@ -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 =

View File

@ -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

View File

@ -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) =

View File

@ -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"

139
tests/japl/all.jpl Normal file
View File

@ -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

View File

@ -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