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() 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:

View File

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

View File

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

View File

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

View File

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

View File

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

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