# 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. import ../frontend/meta/errors import ../frontend/meta/bytecode import ../config import multibyte import ../frontend/compiler import strformat import strutils import nimSHA2 import times 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 fileHash*: string peonVer*: tuple[major, minor, patch: int] peonBranch*: string commitHash*: string compileDate*: int chunk*: Chunk proc `$`*(self: Serialized): string = result = &"Serialized(fileHash={self.fileHash}, version={self.peonVer.major}.{self.peonVer.minor}.{self.peonVer.patch}, branch={self.peonBranch}), commitHash={self.commitHash}, date={self.compileDate}, chunk={self.chunk[]}" proc error(self: Serializer, message: string) = ## Raises a formatted SerializationError exception raise newException(SerializationError, &"A fatal error occurred while (de)serializing '{self.filename}' -> {message}") proc newSerializer*(self: Serializer = nil): Serializer = new(result) if self != nil: result = self result.file = "" result.filename = "" result.chunk = nil ## Basic routines and helpers to convert various objects from and to to their byte representation proc toBytes(self: Serializer, s: string): seq[byte] = for c in s: result.add(byte(c)) proc toBytes(self: Serializer, s: int): array[8, uint8] = result = cast[array[8, uint8]](s) proc toBytes(self: Serializer, d: SHA256Digest): seq[byte] = for b in d: result.add(b) proc bytesToString(self: Serializer, input: seq[byte]): string = for b in input: result.add(char(b)) proc extend[T](s: var seq[T], a: openarray[T]) = ## Extends s with the elements of a for e in a: s.add(e) proc writeHeaders(self: Serializer, stream: var seq[byte], file: string) = ## Writes the Peon bytecode headers in-place into a byte stream stream.extend(self.toBytes(BYTECODE_MARKER)) 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(self.toBytes(PEON_BRANCH)) stream.extend(self.toBytes(PEON_COMMIT_HASH)) stream.extend(self.toBytes(getTime().toUnixFloat().int())) stream.extend(self.toBytes(computeSHA256(file))) proc writeLineData(self: Serializer, stream: var seq[byte]) = ## Writes line information for debugging ## bytecode instructions stream.extend(len(self.chunk.lines).toQuad()) for b in self.chunk.lines: stream.extend(b.toTriple()) proc writeConstants(self: Serializer, stream: var seq[byte]) = ## Writes the constants table in-place into the ## given stream stream.extend(self.chunk.consts.len().toQuad()) for constant in self.chunk.consts: stream.add(constant) 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 stream ## of bytes var stream = stream if stream[0..