Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Productive2 2020-10-25 15:10:32 +01:00
commit 26f035a998
11 changed files with 147 additions and 301 deletions

View File

@ -322,7 +322,7 @@ proc strVal(self: ref Compiler, canAssign: bool) =
var str = self.parser.previous().lexeme
var delimiter = &"{str[0]}" # TODO: Add proper escape sequences support
str = str.unescape(delimiter, delimiter)
self.emitConstant(self.markObject(jstring.newString(str)))
self.emitConstant(self.markObject(jobject.newString(str)))
proc bracketAssign(self: ref Compiler, canAssign: bool) =
@ -389,15 +389,13 @@ proc literal(self: ref Compiler, canAssign: bool) =
proc number(self: ref Compiler, canAssign: bool) =
## Parses numerical constants
var value = self.parser.previous().lexeme
var obj: ptr Obj
try:
if "." in value:
obj = parseFloat(value).asFloat()
self.emitConstant(parseFloat(value).asFloat())
else:
obj = parseInt(value).asInt()
except OverflowError:
self.emitConstant(parseInt(value).asInt())
except ValueError:
self.compileError("number literal is too big")
self.emitConstant(obj)
proc grouping(self: ref Compiler, canAssign: bool) =
@ -447,13 +445,13 @@ proc synchronize(self: ref Compiler) =
proc identifierConstant(self: ref Compiler, tok: Token): uint8 =
## Emits instructions for identifiers
return self.makeConstant(self.markObject(jstring.newString(tok.lexeme)))
return self.makeConstant(self.markObject(jobject.newString(tok.lexeme)))
proc identifierLongConstant(self: ref Compiler, tok: Token): array[3, uint8] =
## Same as identifierConstant, but this is used when the constant table is longer
## than 255 elements
return self.makeLongConstant(self.markObject(jstring.newString(tok.lexeme)))
return self.makeLongConstant(self.markObject(jobject.newString(tok.lexeme)))
proc addLocal(self: ref Compiler, name: Token) =
@ -1123,7 +1121,7 @@ proc initCompiler*(context: FunctionType, enclosing: ref Compiler = nil, parser:
inc(result.localCount)
result.function = result.markObject(newFunction())
if context != SCRIPT: # If we're compiling a function, we give it its name
result.function.name = jstring.newString(enclosing.parser.previous().lexeme)
result.function.name = jobject.newString(enclosing.parser.previous().lexeme)
# This way the compiler can be executed on its own
# without the VM

View File

@ -17,7 +17,7 @@
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* = true # Traces VM execution
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* = true # Traces the compiler (TODO)

View File

@ -149,10 +149,10 @@ proc parseNumber(self: var Lexer) =
## Parses numeric literals
while isDigit(self.peek()):
discard self.step()
if self.peek() == '.':
if self.peek() == '.':
discard self.step()
while self.peek().isDigit():
discard self.step()
while self.peek().isDigit():
discard self.step()
self.tokens.add(self.createToken(TokenType.NUMBER))

View File

@ -18,7 +18,7 @@
import strformat
import parseopt
import os
import common
import config
import vm
proc repl() =

View File

@ -1,57 +0,0 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Defines JAPL exceptions
import jstring
import jobject
import strformat
proc stringify*(self: ptr JAPLException): string =
return &"{self.errName.stringify}: {self.message.stringify}"
proc isFalsey*(self: ptr JAPLException): bool =
return false
proc newTypeError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = newString("TypeError")
result.message = newString(message)
proc newIndexError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = newString("IndexError")
result.message = newString(message)
proc newReferenceError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = newString("ReferenceError")
result.message = newString(message)
proc newInterruptedError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = newString("InterruptedError")
result.message = newString(message)
proc newRecursionError*(message: string): ptr JAPLException =
result = allocateObj(JAPLException, ObjectType.Exception)
result.errName = newString("RecursionError")
result.message = newString(message)

View File

@ -1,59 +0,0 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Implementation for function objects in JAPL.
# In JAPL, functions (like any other entity) are First Class Objects.
# Each function owns its chunk object, which makes the implementation
# easier and opens up for interesting features in the future, like
# code objects that can be compiled inside the JAPL runtime, pretty much
# like in Python
import jstring
import jobject
import strformat
import ../meta/opcode
type
FunctionType* = enum
FUNC, SCRIPT
proc newFunction*(name: string = "", chunk: Chunk = newChunk(), arity: int = 0): ptr Function =
result = allocateObj(Function, ObjectType.Function)
if name.len > 1:
result.name = newString(name)
else:
result.name = nil
result.arity = arity
result.chunk = chunk
proc isFalsey*(fn: ptr Function): bool =
return false
proc stringify*(fn: ptr Function): string =
if fn.name != nil:
result = &"<function '{stringify(fn.name)}'>"
else:
result = &"<code object>"
proc valuesEqual*(a, b: ptr Function): bool =
result = a.name.stringify == b.name.stringify
proc typeName*(self: ptr Function): string =
result = "function"

View File

@ -1,22 +0,0 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#TODO: Implement
proc hashFloat(f: float): uint32 =
# TODO: Any improvement?
result = 2166136261u32
result = result xor uint32 f
result *= 16777619

View File

@ -1,16 +0,0 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import jobject

View File

@ -16,6 +16,8 @@
## types inherit from this simple structure
import ../memory
import ../meta/opcode
import strformat
type
@ -63,34 +65,6 @@ type
message*: ptr String
proc `convert`*(a: ptr Obj): ptr Obj =
## Performs conversions from a JAPL
## supertype to a subtype
case a.kind:
of ObjectType.String:
result = cast[ptr String](a)
of ObjectType.Function:
result = cast[ptr Function](a)
of ObjectType.Integer:
result = cast[ptr Integer](a)
of ObjectType.Float:
result = cast[ptr Float](a)
of ObjectType.Bool:
result = cast[ptr Bool](a)
of ObjectType.Nan:
result = cast[ptr NotANumber](a)
of ObjectType.Infinity:
result = cast[ptr Infinity](a)
of ObjectType.BaseObject:
result = cast[ptr Obj](a)
of ObjectType.Class, ObjectType.Module:
discard # TODO: Implement
else:
raise newException(Exception, "Attempted JAPL type conversion with unknown source object")
proc allocateObject*(size: int, kind: ObjectType): ptr Obj =
## Wrapper around reallocate to create a new generic JAPL object
result = cast[ptr Obj](reallocate(nil, 0, size))
@ -108,35 +82,146 @@ proc objType*(obj: ptr Obj): ObjectType =
return obj.kind
# Methods for string objects
proc stringify*(s: ptr String): string =
result = ""
for i in 0..<s.len:
result = result & (&"{s.str[i]}")
proc isFalsey*(s: ptr String): bool =
result = s.len == 0
proc hash*(self: ptr String): uint32 =
result = 2166136261u32
var i = 0
while i < self.len:
result = result xor uint32 self.str[i]
result *= 16777619
i += 1
return result
proc eq*(a: ptr String, b: ptr String): bool =
if a.len != b.len:
return false
elif a.hash != b.hash:
return false
for i in 0..a.len - 1:
if a.str[i] != b.str[i]:
return false
return true
proc newString*(str: string): ptr String =
# TODO -> Unicode
result = allocateObj(String, ObjectType.String)
result.str = allocate(UncheckedArray[char], char, len(str))
for i in 0..len(str) - 1:
result.str[i] = str[i]
result.len = len(str)
result.hashValue = result.hash()
proc typeName*(s: ptr String): string =
return "string"
proc asStr*(s: string): ptr Obj =
## Creates a string object
result = newString(s)
# End of string object methods
# Integer object methods
proc stringify(self: ptr Integer): string =
result = $self.intValue
proc stringify(self: ptr Float): string =
result = $self.floatValue
# End of integer object methods
# Function object methods
type
FunctionType* = enum
FUNC, SCRIPT
proc newFunction*(name: string = "", chunk: Chunk = newChunk(), arity: int = 0): ptr Function =
result = allocateObj(Function, ObjectType.Function)
if name.len > 1:
result.name = newString(name)
else:
result.name = nil
result.arity = arity
result.chunk = chunk
proc isFalsey*(fn: ptr Function): bool =
return false
proc valuesEqual*(a, b: ptr Function): bool =
result = a.name.stringify == b.name.stringify
proc typeName*(self: ptr Function): string =
result = "function"
proc stringify*(fn: ptr Function): string =
if fn.name != nil:
result = "<function " & stringify(fn.name) & ">" # idk why this doesn't work with &"{...}", too tired to investigate
else:
result = "<code object>"
## Generic base methods
proc stringify*(obj: ptr Obj): string =
## Returns a string representation
## of the object
if obj.kind != ObjectType.BaseObject: # NOTE: Consider how to reduce the boilerplate
var newObj = convert obj
result = newObj.stringify()
else:
if obj.kind == ObjectType.String:
result = cast[ptr String](obj).stringify()
elif obj.kind == ObjectType.Function:
result = cast[ptr Function](obj).stringify()
elif obj.kind == ObjectType.Integer:
result = cast[ptr Integer](obj).stringify()
elif obj.kind == ObjectType.Float:
result = cast[ptr Float](obj).stringify()
elif obj.kind == ObjectType.Bool:
result = cast[ptr Bool](obj).stringify()
elif obj.kind == ObjectType.Nan:
result = cast[ptr NotANumber](obj).stringify()
elif obj.kind == ObjectType.Infinity:
result = cast[ptr Infinity](obj).stringify()
elif obj.kind == ObjectType.BaseObject:
result = "<object (built-in type)>"
else:
discard # Unreachable
proc isFalsey*(obj: ptr Obj): bool =
## Returns true if the given
## object is falsey
if obj.kind != ObjectType.BaseObject: # NOTE: Consider how to reduce the boilerplate
var newObj = convert obj
result = newObj.isFalsey()
else:
result = false
result = false
proc typeName*(obj: ptr Obj): string =
## This method should return the
## name of the object type
if obj.kind != ObjectType.BaseObject:
var newObj = convert obj
result = newObj.typeName()
else:
result = "object"
result = "object"
proc bool*(obj: ptr Obj): bool =
@ -145,28 +230,17 @@ proc bool*(obj: ptr Obj): bool =
## or not. Returns true if the
## object is truthy, or false
## if it is falsey
if obj.kind != ObjectType.BaseObject:
var newObj = convert obj
result = newObj.bool()
else:
result = false
result = false
proc eq*(a: ptr Obj, b: ptr Obj): bool =
## Compares two objects for equality
if a.kind != ObjectType.BaseObject:
var newObj = convert(a)
result = newObj.eq(b)
else:
result = a.kind == b.kind
result = a.kind == b.kind
proc hash*(self: ptr Obj): uint32 =
# TODO: Make this actually useful
if self.kind == ObjectType.BaseObject:
result = 2166136261u32
else:
result = convert(self).hash()
result = 2166136261u32
proc add(self, other: ptr Obj): ptr Obj =

View File

@ -1,72 +0,0 @@
# Copyright 2020 Mattia Giambirtone
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# This module implements the interface for strings in JAPL.
# Strings are manually-allocated arrays of characters, and are
# therefore immutable from the user's perspective. They are
# natively ASCII encoded, but soon they will support for unicode.
import jobject
import strformat
import ../memory
proc stringify*(s: ptr String): string =
result = ""
for i in 0..<s.len:
result = result & (&"{s.str[i]}")
proc isFalsey*(s: ptr String): bool =
result = s.len == 0
proc hash*(self: ptr String): uint32 =
result = 2166136261u32
var i = 0
while i < self.len:
result = result xor uint32 self.str[i]
result *= 16777619
i += 1
return result
proc eq*(a: ptr String, b: ptr String): bool =
if a.len != b.len:
return false
elif a.hash != b.hash:
return false
for i in 0..a.len - 1:
if a.str[i] != b.str[i]:
return false
return true
proc newString*(str: string): ptr String =
# TODO -> Unicode
result = allocateObj(String, ObjectType.String)
result.str = allocate(UncheckedArray[char], char, len(str))
for i in 0..len(str) - 1:
result.str[i] = str[i]
result.len = len(str)
result.hashValue = result.hash()
proc typeName*(s: ptr String): string =
return "string"
proc asStr*(s: string): ptr Obj =
## Creates a string object
result = newString(s)

View File

@ -154,7 +154,7 @@ proc slice(self: var VM): bool =
self.error(newIndexError("string index out of bounds"))
return false
else:
self.push(addObject(addr self, jstring.newString(&"{str[index]}")))
self.push(addObject(addr self, jobject.newString(&"{str[index]}")))
return true
else:
self.error(newTypeError(&"unsupported slicing for object of type '{peeked.typeName()}'"))
@ -185,14 +185,14 @@ proc sliceRange(self: var VM): bool =
if startIndex < 0:
sliceStart = (len(str) + sliceEnd.toInt()).asInt()
elif startIndex - 1 > len(str) - 1:
self.push(addObject(addr self, jstring.newString("")))
self.push(addObject(addr self, jobject.newString("")))
return true
if endIndex - 1 > len(str) - 1:
sliceEnd = len(str).asInt()
if startIndex > endIndex:
self.push(addObject(addr self, jstring.newString("")))
self.push(addObject(addr self, jobject.newString("")))
return true
self.push(addObject(addr self, jstring.newString(str[sliceStart.toInt()..<sliceEnd.toInt()])))
self.push(addObject(addr self, jobject.newString(str[sliceStart.toInt()..<sliceEnd.toInt()])))
return true
else:
self.error(newTypeError(&"unsupported slicing for object of type '{popped.typeName()}'"))