Removed serialization for singletons as those are encoded as bytecode instructions (whoopsie), and updated the bytecode spec accordingly. Added minor QoL improvements to the parser/compiler REPL

This commit is contained in:
nocturn9x 2021-12-22 17:13:03 +01:00
parent 303a98b9e4
commit e234c8caaf
4 changed files with 13 additions and 57 deletions

View File

@ -30,15 +30,9 @@ __Note__: The conventions about number literals described in the document laying
## Compile-time type specifiers
To distinguish the different kinds of values that JAPL can represent at compile time, type specifiers are prepended to a given series of bytes to tell the deserializer what kind of object that specific sequence should deserialize into. It is important that each compile-time object specifies the size of its value in bytes using a 3-byte (aka 24 bit) integer (referred to as "size specifier" from now on, without quotes), after the type specifier. The following sections about object representation assume the appropriate type and size specifiers have been used and will therefore omit them to avoid repetition. Some types (such as singletons) do not need a size specifier as they're only one byte long: these cases are an exception rather than the rule and are explicitly marked as such in this document.
To distinguish the different kinds of values that JAPL can represent at compile time, type specifiers are prepended to a given series of bytes to tell the deserializer what kind of object that specific sequence should deserialize into. It is important that each compile-time object specifies the size of its value in bytes using a 3-byte (aka 24 bit) integer (referred to as "size specifier" from now on, without quotes), after the type specifier. The following sections about object representation assume the appropriate type and size specifiers have been used and will therefore omit them to avoid repetition. Some types (such as singletons) are encoded with a dedicated bytecode instruction rather than as a constant (booleans, nan and inf are notable examples of this).
Below a list of all type specifiers:
- `0xC` -> true*
- `0xD` -> false*
- `0xF` -> nil*
- `0xA` -> nan*
- `0xB` -> inf*
- `0x0` -> Identifier
- `0x1` -> Number
- `0x2` -> String
@ -52,8 +46,6 @@ Below a list of all type specifiers:
- `0x10` -> Lambda declarations (aka anonymous functions)
__Note__: The types whose name is followed by an asterisk require no size specifier, as they're 1 byte long and adding one would only waste space.
## Object representation
### Numbers

View File

@ -144,16 +144,6 @@ proc writeConstants(self: Serializer, stream: var seq[byte]) =
stream.add(0x0)
stream.extend(len(constant.token.lexeme).toTriple())
stream.add(self.toBytes(constant.token.lexeme))
of trueExpr:
stream.add(0xC)
of falseExpr:
stream.add(0xD)
of nilExpr:
stream.add(0xF)
of nanExpr:
stream.add(0xA)
of infExpr:
stream.add(0xB)
else:
self.error(&"unknown constant kind in chunk table ({constant.kind})")
stream.add(0x59) # End marker
@ -215,27 +205,8 @@ proc readConstants(self: Serializer, stream: seq[byte]): int =
let size = self.bytesToInt([stream[0], stream[1], stream[2]])
stream = stream[3..^1]
discard self.chunk.addConstant(newIdentExpr(Token(lexeme: self.bytesToString(stream[0..<size]))))
stream = stream[size..^1]
inc(count, size + 4)
of 0xC:
discard self.chunk.addConstant(newTrueExpr(nil))
stream = stream[1..^1]
inc(count)
of 0xD:
discard self.chunk.addConstant(newFalseExpr(nil))
stream = stream[1..^1]
inc(count)
of 0xF:
discard self.chunk.addConstant(newNilExpr(nil))
stream = stream[1..^1]
inc(count)
of 0xA:
discard self.chunk.addConstant(newNaNExpr(nil))
stream = stream[1..^1]
inc(count)
of 0xB:
discard self.chunk.addConstant(newInfExpr(nil))
stream = stream[1..^1]
inc(count)
else:
self.error(&"unknown constant kind in chunk table (0x{stream[0].toHex()})")
result = count
@ -300,19 +271,3 @@ proc loadBytes*(self: Serializer, stream: seq[byte]): Serialized =
self.error("truncated bytecode file")
except AssertionDefect:
self.error("corrupted bytecode file")

View File

@ -27,7 +27,7 @@ const SKIP_STDLIB_INIT* = false # Skips stdlib initialization (can be imported m
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler
const JAPL_VERSION_STRING* = &"JAPL {JAPL_VERSION.major}.{JAPL_VERSION.minor}.{JAPL_VERSION.patch} ({JAPL_RELEASE}, {CompileDate}, {CompileTime}) on branch {JAPL_BRANCH} ({JAPL_COMMIT_HASH[0..8]})"
const JAPL_VERSION_STRING* = &"JAPL {JAPL_VERSION.major}.{JAPL_VERSION.minor}.{JAPL_VERSION.patch} {JAPL_RELEASE} ({JAPL_BRANCH}, {CompileDate}, {CompileTime}, {JAPL_COMMIT_HASH[0..8]}) [Nim {NimVersion}] on {hostOS} ({hostCPU})"
const HELP_MESSAGE* = """The JAPL programming language, Copyright (C) 2021 Mattia Giambirtone & All Contributors
This program is free software, see the license distributed with this program or check

View File

@ -11,12 +11,15 @@
# 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.
## Test module to wire up JAPL components
import backend/lexer
import backend/parser
import backend/optimizer
import backend/compiler
import util/debugger
import backend/serializer
import config
import strformat
@ -46,11 +49,17 @@ proc main() =
var compiler = initCompiler()
var serializer = initSerializer()
echo "NimVM REPL\n"
echo JAPL_VERSION_STRING
while true:
try:
stdout.write(">>> ")
source = stdin.readLine()
if source == "//clear" or source == "// clear":
echo "\x1Bc" & JAPL_VERSION_STRING
continue
elif source == "//exit" or source == "// exit":
echo "Goodbye!"
break
except IOError:
echo ""
break