init commit

sdl
Bronwen 2021-05-02 17:42:04 -04:00
commit 222ae17ccb
12 changed files with 567 additions and 0 deletions

1
.gitignore vendored 100644
View File

@ -0,0 +1 @@
node_modules

50
Instance.js 100644
View File

@ -0,0 +1,50 @@
import Serializable from './Serializable.js';
import minify from './minify.js';
export default class Instance extends Serializable {
module = null;
links = {}
system = null;
// reconstruct context when we need it...
get context() {
const ctx = {};
for(const name in this.links) {
ctx[name] = this.links[name];
}
// ctx.Instance = Instance;
ctx.create = this.system.newInstance.bind(this.system);
console.log('context reconstructed', ctx);
return ctx;
};
constructor(module, location, parameters, system) {
super();
this.module = module;
this.location = location;
this.system = system;
for(const name of this.module.links.optional.arrays) this.links[name] = [];
}
invokeInternal(name, ...args) {
console.trace();
const content = this.module.functions[name];
evalInContext(content, this.context);
}
}
function evalInContext(js, context) {
//# Return the results of the in-line anonymous function we .call with the passed context
const that = this;
return function() {
const preminJs = `
${Object.entries(context).map(([k, v]) => `
const ${k} = this.${k};
`).join('\n')}
${js}`;
const newJs = minify(preminJs);
console.log(`${'='.repeat(80)}\n${newJs}\n${'='.repeat(80)}`)
return eval(newJs);
}.call(context);
}

22
Module.js 100644
View File

@ -0,0 +1,22 @@
export default class Module {
links = {
required: {
single: [],
arrays: []
},
optional: {
single: [],
arrays: []
}
};
globals = [];
functions = [];
identifiers = {};
name = {
space: '',
last: '',
full: ''
}
}

180
Serializable.js 100644
View File

@ -0,0 +1,180 @@
// import { Ubjson } from '@shelacek/ubjson';
import { existsSync, readFileSync, writeFileSync } from 'fs';
export default class Serializable {
// things that need to be stored only in cold
// storage are keyed with a special prefix
static CLASS_REFERENCE = '$$CLASS_NAME';
// things that need to be stored only at runtime
// are keyed with symbols to not interfere with
// user code.
static PERSIST_LOCATION = Symbol('PERSIST_LOCATION');
start() {}
toUbj() {
return Ubjson.encode(this.toSerializableObject());
}
static fromUbj(buffer) {
return this.fromSerializableObject(Ubjson.decode(buffer));
}
toJson() {
return JSON.stringify(this.toSerializableObject(), null, 2);
}
static fromJson(str) {
return this.fromSerializableObject(JSON.parse(str));
}
toSerializableObject() {
const transformValue = (val) => {
if(Array.isArray(val)) {
return transformArray(val);
} else if (val === null || val === undefined) {
return val;
} else if(typeof val === 'object') {
return transformObject(val);
} else {
return val;
}
}
const transformObject = (obj) => {
const clone = {};
for(const prop of Object.keys(obj)) {
if(prop.startsWith('_')) continue;
clone[prop] = transformValue(obj[prop]);
}
if(obj instanceof Serializable) {
clone[Serializable.CLASS_REFERENCE] = obj.constructor.name;
}
return clone;
}
const transformArray = (arr) => {
const clone = [];
for(const item of arr) {
clone.push(transformValue(item));
}
return clone;
}
return transformObject(this);
}
static fromSerializableObject(obj) {
if(obj[Serializable.CLASS_REFERENCE] !== this.name) return null;
const transformValue = (val) => {
if(Array.isArray(val)) {
return transformArray(val);
} else if(val === null || val === undefined) {
return val;
} else if(typeof val === 'object') {
if(Serializable.CLASS_REFERENCE in val) {
const classes = this.serializationDependencies();
const matchingClasses = classes.filter(classObject => classObject.name === val[Serializable.CLASS_REFERENCE]);
if(matchingClasses.length === 1) {
return matchingClasses[0].fromSerializableObject(val);
} else {
return transformObject(val);
}
}
return transformObject(val);
} else {
return val;
}
}
const transformObject = (obj) => {
const clone = {};
for(const prop of Object.keys(obj)) {
if(prop.startsWith('_')) continue;
clone[prop] = transformValue(obj[prop]);
}
return clone;
}
const transformArray = (arr) => {
const clone = [];
for(const item of arr) {
clone.push(transformValue(item));
}
return clone;
}
const clone = transformObject(obj);
if(Serializable.CLASS_REFERENCE in obj)
clone.__proto__ = this.prototype;
clone.restore();
return clone;
}
serialize({
encoding = 'json'
} = {}) {
switch(encoding) {
case 'json': return this.toJson();
case 'ubjson':
case 'ubj': return this.toUbj();
default: {
throw new TypeError('Unknown encoding: ' + encoding);
}
}
}
static deserialize(obj, {
encoding = 'json'
} = {}) {
switch(encoding) {
case 'json': return this.fromJson(obj);
case 'ubjson':
case 'ubj': return this.fromUbj(obj);
default: {
throw new TypeError('Unknown encoding: ' + encoding);
}
}
}
async restore() {}
static createFromDisk(filename, ...args) {
if(existsSync(filename)) {
const instance = this.deserialize(readFileSync(createFilepath(filename)));
instance[Serializable.PERSIST_LOCATION] = createFilepath(filename);
instance.restore();
return instance;
} else {
const instance = new this(...args);
instance[Serializable.PERSIST_LOCATION] = createFilepath(filename);
instance.updateDisk();
return instance;
}
}
updateDisk(filepath) {
// if it hasnt yet been written to disk...
// this can happen if the contrustor
// was called outside of createFromDisk
if(filepath) {
this[Serializable.PERSIST_LOCATION] = createFilepath(filepath);
}
const data = this.serialize();
writeFileSync(this[Serializable.PERSIST_LOCATION], data);
}
}
function createFilepath(path) {
return `data/${path}`;
}

35
System.js 100644
View File

@ -0,0 +1,35 @@
import Instance from './Instance.js';
import Serializable from './Serializable.js';
export default class System extends Serializable {
instances = [];
modules = null;
constructor(modules, location = '.running') {
super();
this.modules = modules;
try {
mkdirSync(location);
} catch {}
this.newInstance('world');
}
createInstance(name, args = {}) {
return new Instance(this.modules[name], null, args, this);
}
linkInstance(instance) {
return new Proxy(instance, {
get(target, prop, receiver) {
return target[prop];
}
});
}
newInstance(name, args = {}) {
const instance = this.createInstance(name, args);
const link = this.linkInstance(instance);
instance.invokeInternal('restore');
return link;
}
}

16
grammar.ne 100644
View File

@ -0,0 +1,16 @@
@lexer lexer
PROGRAM -> _ STATEMENT:+ _ {% ([,stuff,]) => { return stuff } %}
STATEMENT -> _ LINK_DECLARATION _ %SEMICOLON:? {% ([,stuff]) => { return stuff } %}
| _ %RESTORE _ JS_BLOCK _ %SEMICOLON:? {% ([,,,block]) => { return { type: 'restore', block: block } } %}
| _ %NAMESPACE __ NAMESPACE _ %SEMICOLON:? {% ([,,,namespace]) => { return { type: 'namespace', namespace: namespace[0] } } %}
LINK_DECLARATION -> _ %LINK __ IDENTIFIER {% ([,,,id]) => {return{ type: 'link', array: false, required: false, name: id }} %}
| _ %LINK _ %ARRAY __ IDENTIFIER {% ([,,,,,id]) => {return{ type: 'link', array: true, required: false, name: id }} %}
| _ %REQUIRED __ %LINK __ IDENTIFIER {% ([,,,,,id]) => {return{ type: 'link', array: false, required: true, name: id }} %}
| _ %REQUIRED __ %LINK _ %ARRAY __ IDENTIFIER {% ([,,,,,,,id]) => {return{ type: 'link', array: true, required: true, name: id }} %}
NAMESPACE -> IDENTIFIER
| IDENTIFIER %DOTOP NAMESPACE {% ([a,,b]) => { return [`${a}.${b}`] } %}
IDENTIFIER -> %IDENTIFIER {% ([id]) => id.value %}
JS_BLOCK -> %JS_BLOCK {% ([block]) => minify(block.value.substring(2, block.value.length - 2)) %}
_ -> null | %SPACE {% () => undefined %}
__ -> %SPACE {% () => undefined %}

12
minify.js 100644
View File

@ -0,0 +1,12 @@
import uglify from 'uglify-js';
export default (code) => {
return uglify.minify(code, {
compress: {
dead_code: true,
global_defs: {
DEBUG: false
}
}
}).code;
}

19
package.json 100644
View File

@ -0,0 +1,19 @@
{
"name": "lang",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"type": "module",
"bin": {
"run": "run.js"
},
"scripts": {
"c": "nearleyc"
},
"dependencies": {
"moo": "^0.5.1",
"nearley": "^2.20.1",
"nedb": "^1.8.0",
"uglify-js": "^3.13.5"
}
}

101
run.js 100755
View File

@ -0,0 +1,101 @@
#!/usr/bin/env node
import { resolve, parse } from 'path';
import { readFileSync, readdirSync, readFile, fstat, mkdirSync } from 'fs';
import nearley from 'nearley';
import compile from 'nearley/lib/compile.js';
import generate from 'nearley/lib/generate.js';
import nearleyGrammar from 'nearley/lib/nearley-language-bootstrapped.js';
import moo from 'moo';
const grammarFile = 'grammar.ne';
import Serializable from './Serializable.js';
import Module from './Module.js';
import System from './System.js';
import Instance from './Instance.js';
import minify from './minify.js';
Array.prototype.empty = function empty() {
return this.length === 0;
}
function createParser() {
// Parse the grammar source into an AST
const grammarParser = new nearley.Parser(nearleyGrammar);
grammarParser.feed(readFileSync(grammarFile).toString());
const grammarAst = grammarParser.results[0]; // TODO check for errors
// Compile the AST into a set of rules
const grammarInfoObject = compile(grammarAst, {});
// Generate JavaScript code from the rules
const grammarJs = generate(grammarInfoObject, "grammar");
const lexer = moo.compile({
LINK: 'link',
RESTORE: 'restore',
NAMESPACE: 'namespace',
REQUIRED: 'required',
ARRAY: '[]',
OBJECT: '{}',
DOTOP: '.',
JS_BLOCK: /\[\[[^]*?\n\]\]$/,
IDENTIFIER: /[a-zA-Z][a-zA-Z0-9]*/,
SPACE: {match: /\s+/, lineBreaks: true},
SEMICOLON: ';'
});
// Pretend this is a CommonJS environment to catch exports from the grammar.
const module = { exports: {} };
eval(grammarJs);
const grammar = module.exports;
return new nearley.Parser(nearley.Grammar.fromCompiled(grammar))
}
const systemLocation = resolve(process.argv[2]);
const entry = process.argv[3];
const modules = {};
readdirSync(systemLocation).map(v => resolve(systemLocation, v)).map(parseModule);
const sys = new System(modules);
function parseModule(location) {
const parser = createParser();
const contents = readFileSync(location).toString();
const name = parse(location).name;
parser.feed(contents);
parser.finish();
const module = new Module();
const parsed = parser.results[0];
module.name.last = name;
module.name.full = name;
for(const item of parsed) {
switch(item.type) {
case 'link': {
if(item.name in module.identifiers)
throw new Error('Identifier ' + item.name + ' already declared!');
module.identifiers[item.name] = 'link';
module.links
[item.required ? 'required' : 'optional']
[item.array ? 'arrays' : 'single']
.push(item.name);
break;
}
case 'namespace': {
module.name.space = item.namespace;
module.name.full = module.name.space + '.' + module.name.last;
break;
}
case 'restore': {
if(item.name in module.identifiers)
throw new Error('Identifier ' + item.name + ' already declared!');
module.identifiers['restore'] = 'function';
module.functions['restore'] = item.block;
break;
}
}
}
modules[module.name.full] = module;
}

5
test/forest.v 100644
View File

@ -0,0 +1,5 @@
namespace places;
required link world;
link[] roads;

16
test/world.v 100644
View File

@ -0,0 +1,16 @@
link[] places;
link place;
link character;
restore [[
if(places.empty()) {
for(let i = 0; i < 3; i ++) {
const place = create('places.forest', {
world: this
});
places.push(place);
}
}
console.log(places);
]]

110
yarn.lock 100644
View File

@ -0,0 +1,110 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
async@0.2.10:
version "0.2.10"
resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
integrity sha1-trvgsGdLnXGXCMo43owjfLUmw9E=
binary-search-tree@0.2.5:
version "0.2.5"
resolved "https://registry.yarnpkg.com/binary-search-tree/-/binary-search-tree-0.2.5.tgz#7dbb3b210fdca082450dad2334c304af39bdc784"
integrity sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=
dependencies:
underscore "~1.4.4"
commander@^2.19.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
discontinuous-range@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
immediate@~3.0.5:
version "3.0.6"
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
lie@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
integrity sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=
dependencies:
immediate "~3.0.5"
localforage@^1.3.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1"
integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g==
dependencies:
lie "3.1.1"
minimist@^1.2.5:
version "1.2.5"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
dependencies:
minimist "^1.2.5"
moo@^0.5.0, moo@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
nearley@^2.20.1:
version "2.20.1"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474"
integrity sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==
dependencies:
commander "^2.19.0"
moo "^0.5.0"
railroad-diagrams "^1.0.0"
randexp "0.4.6"
nedb@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/nedb/-/nedb-1.8.0.tgz#0e3502cd82c004d5355a43c9e55577bd7bd91d88"
integrity sha1-DjUCzYLABNU1WkPJ5VV3vXvZHYg=
dependencies:
async "0.2.10"
binary-search-tree "0.2.5"
localforage "^1.3.0"
mkdirp "~0.5.1"
underscore "~1.4.4"
railroad-diagrams@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e"
integrity sha1-635iZ1SN3t+4mcG5Dlc3RVnN234=
randexp@0.4.6:
version "0.4.6"
resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3"
integrity sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==
dependencies:
discontinuous-range "1.0.0"
ret "~0.1.10"
ret@~0.1.10:
version "0.1.15"
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
uglify-js@^3.13.5:
version "3.13.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.5.tgz#5d71d6dbba64cf441f32929b1efce7365bb4f113"
integrity sha512-xtB8yEqIkn7zmOyS2zUNBsYCBRhDkvlNxMMY2smuJ/qA8NCHeQvKCF3i9Z4k8FJH4+PJvZRtMrPynfZ75+CSZw==
underscore@~1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604"
integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ=