@powerlang/egg-js
Version:
A Smalltalk VM that runs on top of JavaScript
222 lines (199 loc) • 6.77 kB
JavaScript
"use strict";
import "./st-glue.js";
import EggMetacircularRuntime from "./interpreter/EggMetacircularRuntime.js";
import ImageSegmentReader from "./ImageSegmentReader.js";
import path from "path";
import fs, { readFileSync } from "fs";
import { fileURLToPath } from 'url';
import EggByteObject from "./interpreter/EggByteObject.js";
import EggObject from "./interpreter/EggObject.js";
import EggSmallInteger from "./interpreter/EggSmallInteger.js";
let Egg = class {
constructor() {
this.modules = new Map();
this.runtime = EggMetacircularRuntime.new();
}
/// public API
loadKernelFile(kernelPath = "Kernel.json") {
const module = this.loadModuleFromFile(kernelPath, false);
this.initializeKernel(module);
}
loadModule(name) {
const kernel = this.kernel.exports["Kernel"];
const symbol = this.runtime.addSymbol_(name);
return this.runtime.sendLocal_to_with_("load:", kernel, [symbol]);
}
architectureName() {
return typeof window === "undefined"
? `Node ${process.version}`
: "Browser";
}
millisecondClockValue() {
return Date.now();
}
send(receiver, message, args = []) {
return this.runtime.sendLocal_to_with_(message, receiver, args);
}
/// private methods
initializeKernel(module) {
this.kernel = module;
this.runtime.bootstrapper_(this);
//Object.assign(this.runtime, this.kernel.meta);
Object.assign(this.runtime, this.kernelMeta());
Object.assign(this.runtime, this.kernelObjects());
const symbols = this.kernel.exports["KnownSymbols"]._slots.map(
(symbol) => symbol.asLocalString()._arrow(symbol)
);
this.initializeSymbolTable();
this.runtime.knownSymbols_(new Map(Object.entries(symbols)));
this.runtime.initializeInterpreter();
this.initializeDNUMethod();
}
initializeSymbolTable() {
const symbol = this.kernel.exports["SymbolTable"];
this.runtime.symbolTable_(symbol);
}
initializeDNUMethod() {
const proto = this.kernel.exports["ProtoObject"];
const symbol = this.runtime.symbolFromLocal_(
"_doesNotUnderstand:with:"
);
const behavior = this.runtime.speciesInstanceBehavior_(proto);
const method = this.runtime.doLookup_startingAt_(symbol, behavior);
this.runtime.doesNotUnderstandMethod_(method);
}
// this method is directly called from the autogenerated interpreter
loadModule_(name) {
const reader = this.loadModuleFromFile(name + ".json");
return reader.exports["__module__"];
}
bindModuleImports(reader) {
reader.imports = reader.data.imports.map((descriptor) =>
this.bindModuleImport(descriptor)
);
}
bindModuleImport(descriptor) {
const linker = this.transferImportLiteral(descriptor[0]);
let token = null;
if (descriptor.length == 1) token = this.kernel.exports["nil"];
else if (descriptor.length == 2)
token = this.transferImportLiteral(descriptor[1]);
else token = this.transferImportLiteral(descriptor.slice(1)); // all elems but first (the linker)
const ref = this.runtime.sendLocal_to_with_(
"linker:token:",
this.kernel.exports["SymbolicReference"],
[linker, token]
);
return this.runtime.sendLocal_to_("link", ref);
}
transferImportLiteral(anObject) {
if (Number.isInteger(anObject))
return this.runtime.newInteger_(anObject);
if (typeof anObject === "string")
return this.runtime.addSymbol_(anObject);
if (Array.isArray(anObject)) {
let transferred = anObject.map((o) =>
this.transferImportLiteral(o)
);
return this.runtime.newArray_(transferred);
}
debugger;
}
loadModuleFromFile(filename, sendJustLoaded = true) {
const filepath = this.findInPath(filename);
if (filepath === undefined) {
throw new Error(`Failed to load ${filename}`);
}
const reader = new ImageSegmentReader();
console.log(`Loading module ${filename}...`);
reader.loadFile(filepath);
this.bindModuleImports(reader);
reader.loadObjects();
if (sendJustLoaded) {
const module_ = reader.exports["__module__"];
this.runtime.sendLocal_to_("justLoaded", module_);
}
return reader;
}
findInPath(imageSegmentFile) {
const root = path.dirname(fileURLToPath(import.meta.url));
const dirs = ["../../", "./", "../", root];
const searched = "image-segments/" + imageSegmentFile;
for (const dir of dirs) {
const filePath = path.join(dir, searched);
try {
const stats = fs.statSync(filePath);
if (stats.isFile()) {
return filePath;
}
} catch (err) {
continue;
}
}
}
kernelObjects() {
return {
_falseObj: this.kernel.exports["false"],
_trueObj: this.kernel.exports["true"],
_nilObj: this.kernel.exports["nil"],
_arrayClass: this.kernel.exports["Array"],
_metaclassClass: this.kernel.exports["Metaclass"],
_methodClass: this.kernel.exports["CompiledMethod"],
_smallIntegerClass: this.kernel.exports["SmallInteger"],
_largePositiveIntegerClass:
this.kernel.exports["LargePositiveInteger"],
_largeNegativeIntegerClass:
this.kernel.exports["LargeNegativeInteger"],
_blockClass: this.kernel.exports["CompiledBlock"],
_byteArrayClass: this.kernel.exports["ByteArray"],
_stringClass: this.kernel.exports["String"],
_closureClass: this.kernel.exports["Closure"],
_behaviorClass: this.kernel.exports["Behavior"],
};
}
// TODO: (difficulty easy)
// this method should not exist, because the image segment should be self-descriptive.
// but that needs to be implemented, meanwhile we resort to hardcoded info
//
kernelMeta() {
return {
_wordSize: 8,
_behaviorNextIndex: 3,
_behaviorMethodDictionaryIndex: 2,
_behaviorClassIndex: 1,
_classNameIndex: 6,
_methodFlagsIndex: 1,
_maxSMI: BigInt("4611686018427387903"),
_minSMI: BigInt("-4611686018427387904"),
_speciesInstanceBehaviorIndex: 2,
_speciesFormatIndex: 3,
_compiledCodeExecutableCodeIndex: 2,
_methodTreecodesIndex: 3,
_methodInstSize: 6,
_methodClassBindingIndex: 4,
_speciesSuperclassIndex: 1,
_speciesIvarsIndex: 5,
_dictionaryTableIndex: 2,
_classClassVariablesIndex: 8,
_metaclassInstanceClassIndex: 6,
_classNamespacesIndex: 8,
_classModuleIndex: 9,
_moduleNamespaceIndex: 4,
_closureBlockIndex: 1,
_blockMethodIndex: 3,
_processStackSPIndex: 2,
};
}
readFile_(filename) {
let stats;
try {
stats = fs.statSync(filename);
if (!stats.isFile()) return this.runtime.nil();
} catch (ignored) {}
if (!stats) this.runtime.nil();
const text = readFileSync(filename, "utf-8");
return this.runtime.newString_(text);
}
};
export default Egg;
export { EggByteObject, EggObject, EggSmallInteger };