255 lines
8.4 KiB
Nim
255 lines
8.4 KiB
Nim
# Copyright 2022 Mattia Giambirtone & All Contributors
|
|
#
|
|
# 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 of the peon bytecode serializer
|
|
import std/strformat
|
|
import std/strutils
|
|
import std/times
|
|
|
|
|
|
import config
|
|
import errors
|
|
import multibyte
|
|
import frontend/parsing/ast
|
|
import frontend/compiler/targets/bytecode/opcodes
|
|
|
|
|
|
export ast
|
|
|
|
type
|
|
Serializer* = ref object
|
|
file: string
|
|
filename: string
|
|
chunk: Chunk
|
|
Serialized* = ref object
|
|
## Wrapper returned by
|
|
## the Serializer.read*
|
|
## procedures to store
|
|
## metadata
|
|
version*: tuple[major, minor, patch: int]
|
|
branch*: string
|
|
commit*: string
|
|
compileDate*: int
|
|
chunk*: Chunk
|
|
SerializationError* = ref object of PeonException
|
|
|
|
|
|
proc `$`*(self: Serialized): string =
|
|
result = &"Serialized(version={self.version.major}.{self.version.minor}.{self.version.patch}, branch={self.branch}), commitHash={self.commit}, date={self.compileDate}, chunk={self.chunk[]}"
|
|
|
|
|
|
proc error(self: Serializer, message: string) =
|
|
## Raises a formatted SerializationError exception
|
|
raise SerializationError(msg: message, file: self.filename)
|
|
|
|
|
|
proc newSerializer*(self: Serializer = nil): Serializer =
|
|
new(result)
|
|
if self != nil:
|
|
result = self
|
|
result.file = ""
|
|
result.filename = ""
|
|
result.chunk = nil
|
|
|
|
|
|
proc writeHeaders(self: Serializer, stream: var seq[byte]) =
|
|
## Writes the Peon bytecode headers in-place into the
|
|
## given byte sequence
|
|
stream.extend(PeonBytecodeMarker.toBytes())
|
|
stream.add(byte(PEON_VERSION.major))
|
|
stream.add(byte(PEON_VERSION.minor))
|
|
stream.add(byte(PEON_VERSION.patch))
|
|
stream.add(byte(len(PEON_BRANCH)))
|
|
stream.extend(PEON_BRANCH.toBytes())
|
|
stream.extend(PEON_COMMIT_HASH.toBytes())
|
|
stream.extend(getTime().toUnixFloat().int().toBytes())
|
|
|
|
|
|
proc writeLineData(self: Serializer, stream: var seq[byte]) =
|
|
## Writes line information for debugging
|
|
## bytecode instructions to the given byte
|
|
## sequence
|
|
stream.extend(len(self.chunk.lines).toQuad())
|
|
for b in self.chunk.lines:
|
|
stream.extend(b.toTriple())
|
|
|
|
|
|
proc writeFunctions(self: Serializer, stream: var seq[byte]) =
|
|
## Writes debug info about functions to the
|
|
## given byte sequence
|
|
stream.extend(len(self.chunk.functions).toQuad())
|
|
stream.extend(self.chunk.functions)
|
|
|
|
|
|
proc writeConstants(self: Serializer, stream: var seq[byte]) =
|
|
## Writes the constants table in-place into the
|
|
## byte sequence
|
|
stream.extend(self.chunk.consts.len().toQuad())
|
|
stream.extend(self.chunk.consts)
|
|
|
|
|
|
proc writeModules(self: Serializer, stream: var seq[byte]) =
|
|
## Writes module information to the given stream
|
|
stream.extend(self.chunk.modules.len().toQuad())
|
|
stream.extend(self.chunk.modules)
|
|
|
|
|
|
proc writeCode(self: Serializer, stream: var seq[byte]) =
|
|
## Writes the bytecode from the given chunk to the
|
|
## given source stream
|
|
stream.extend(self.chunk.code.len.toTriple())
|
|
stream.extend(self.chunk.code)
|
|
|
|
|
|
proc readHeaders(self: Serializer, stream: seq[byte], serialized: Serialized): int =
|
|
## Reads the bytecode headers from a given sequence
|
|
## of bytes
|
|
var stream = stream
|
|
if stream[0..<len(PeonBytecodeMarker)] != PeonBytecodeMarker.toBytes():
|
|
self.error("malformed bytecode marker")
|
|
result += len(PeonBytecodeMarker)
|
|
stream = stream[len(PeonBytecodeMarker)..^1]
|
|
serialized.version = (major: int(stream[0]), minor: int(stream[1]), patch: int(stream[2]))
|
|
stream = stream[3..^1]
|
|
result += 3
|
|
let branchLength = stream[0]
|
|
stream = stream[1..^1]
|
|
result += 1
|
|
serialized.branch = stream[0..<branchLength].fromBytes()
|
|
stream = stream[branchLength..^1]
|
|
result += int(branchLength)
|
|
serialized.commit = stream[0..<40].fromBytes().toLowerAscii()
|
|
stream = stream[40..^1]
|
|
result += 40
|
|
serialized.compileDate = int(fromLong([stream[0], stream[1], stream[2],
|
|
stream[3], stream[4], stream[5], stream[6], stream[7]]))
|
|
stream = stream[8..^1]
|
|
result += 8
|
|
|
|
|
|
proc readLineData(self: Serializer, stream: seq[byte]): int =
|
|
## Reads line information from a stream
|
|
## of bytes
|
|
let size = [stream[0], stream[1], stream[2], stream[3]].fromQuad()
|
|
result += 4
|
|
var stream = stream[4..^1]
|
|
for i in countup(0, int(size) - 1):
|
|
self.chunk.lines.add(int([stream[0], stream[1], stream[2]].fromTriple()))
|
|
result += 3
|
|
stream = stream[3..^1]
|
|
doAssert len(self.chunk.lines) == int(size)
|
|
|
|
|
|
proc readFunctions(self: Serializer, stream: seq[byte]): int =
|
|
## Reads the function segment from a stream
|
|
## of bytes
|
|
let size = [stream[0], stream[1], stream[2], stream[3]].fromQuad()
|
|
result += 4
|
|
var stream = stream[4..^1]
|
|
for i in countup(0, int(size) - 1):
|
|
self.chunk.functions.add(stream[i])
|
|
inc(result)
|
|
doAssert len(self.chunk.functions) == int(size)
|
|
|
|
|
|
proc readConstants(self: Serializer, stream: seq[byte]): int =
|
|
## Reads the constant table from the given
|
|
## byte sequence
|
|
let size = [stream[0], stream[1], stream[2], stream[3]].fromQuad()
|
|
result += 4
|
|
var stream = stream[4..^1]
|
|
for i in countup(0, int(size) - 1):
|
|
self.chunk.consts.add(stream[i])
|
|
inc(result)
|
|
doAssert len(self.chunk.consts) == int(size)
|
|
|
|
|
|
proc readModules(self: Serializer, stream: seq[byte]): int =
|
|
## Reads module information
|
|
let size = [stream[0], stream[1], stream[2], stream[3]].fromQuad()
|
|
result += 4
|
|
var stream = stream[4..^1]
|
|
for i in countup(0, int(size) - 1):
|
|
self.chunk.modules.add(stream[i])
|
|
inc(result)
|
|
doAssert len(self.chunk.modules) == int(size)
|
|
|
|
|
|
proc readCode(self: Serializer, stream: seq[byte]): int =
|
|
## Reads the bytecode from a given byte sequence
|
|
let size = [stream[0], stream[1], stream[2]].fromTriple()
|
|
var stream = stream[3..^1]
|
|
for i in countup(0, int(size) - 1):
|
|
self.chunk.code.add(stream[i])
|
|
doAssert len(self.chunk.code) == int(size)
|
|
return int(size)
|
|
|
|
|
|
proc dumpBytes*(self: Serializer, chunk: Chunk, filename: string): seq[byte] =
|
|
## Dumps the given chunk to a sequence of bytes and returns it.
|
|
## The filename argument is for error reporting only, use dumpFile
|
|
## to dump bytecode to a file
|
|
self.filename = filename
|
|
self.chunk = chunk
|
|
self.writeHeaders(result)
|
|
self.writeLineData(result)
|
|
self.writeFunctions(result)
|
|
self.writeConstants(result)
|
|
self.writeModules(result)
|
|
self.writeCode(result)
|
|
|
|
|
|
proc dumpFile*(self: Serializer, chunk: Chunk, filename, dest: string) =
|
|
## Dumps the result of dumpBytes to a file at dest
|
|
var fp = open(dest, fmWrite)
|
|
defer: fp.close()
|
|
let data = self.dumpBytes(chunk, filename)
|
|
discard fp.writeBytes(data, 0, len(data))
|
|
|
|
|
|
proc loadBytes*(self: Serializer, stream: seq[byte]): Serialized =
|
|
## Loads the result from dumpBytes to a Serializer object
|
|
## for use in the VM or for inspection
|
|
discard self.newSerializer()
|
|
new(result)
|
|
result.chunk = newChunk()
|
|
self.chunk = result.chunk
|
|
var stream = stream
|
|
try:
|
|
stream = stream[self.readHeaders(stream, result)..^1]
|
|
stream = stream[self.readLineData(stream)..^1]
|
|
stream = stream[self.readFunctions(stream)..^1]
|
|
stream = stream[self.readConstants(stream)..^1]
|
|
stream = stream[self.readModules(stream)..^1]
|
|
stream = stream[self.readCode(stream)..^1]
|
|
except IndexDefect:
|
|
self.error("truncated bytecode stream")
|
|
except AssertionDefect:
|
|
self.error(&"corrupted bytecode stream: {getCurrentExceptionMsg()}")
|
|
|
|
|
|
proc loadFile*(self: Serializer, src: string): Serialized =
|
|
## Loads a bytecode file
|
|
var fp = open(src, fmRead)
|
|
defer: fp.close()
|
|
let size = fp.getFileSize()
|
|
var pos = 0'i64
|
|
var data: seq[byte] = newSeqOfCap[byte](size)
|
|
for _ in 0..<size:
|
|
data.add(0)
|
|
while pos < size:
|
|
discard fp.readBytes(data, pos, size)
|
|
pos = fp.getFilePos()
|
|
return self.loadBytes(data)
|