Comparing version 0.2.0 to 0.2.1
{ | ||
"name": "walt-link", | ||
"version": "0.2.0", | ||
"version": "0.2.1", | ||
"description": "Links .walt WebAssembly programs", | ||
@@ -18,2 +18,5 @@ "main": "src/index.js", | ||
"license": "MIT", | ||
"dependencies": { | ||
"invariant": "^2.2.4" | ||
}, | ||
"devDependencies": { | ||
@@ -24,7 +27,4 @@ "ava": "^0.25.0", | ||
"prettier": "^1.12.0", | ||
"walt-compiler": "^0.5.0" | ||
"walt-buildtools": "^0.0.2" | ||
}, | ||
"peerDependencies": { | ||
"walt-compiler": "^0.5.0" | ||
}, | ||
"prettier": { | ||
@@ -46,6 +46,3 @@ "trailingComma": "es5" | ||
"powerAssert": false | ||
}, | ||
"dependencies": { | ||
"invariant": "^2.2.4" | ||
} | ||
} |
const test = require("ava"); | ||
const { link, buildTree, assemble, mergeStatics, parseImports } = require(".."); | ||
const link = require(".."); | ||
const path = require("path"); | ||
const fs = require("fs"); | ||
const { stringDecoder } = require("walt-compiler"); | ||
const compiler = require("walt-compiler"); | ||
const decodeText = (view, ptr) => { | ||
const decoder = stringDecoder(view, ptr); | ||
const decoder = compiler.stringDecoder(view, ptr); | ||
let iterator = decoder.next(); | ||
@@ -19,5 +19,28 @@ let text = ""; | ||
const resolve = (file, parent) => { | ||
if (parent != null) { | ||
return path.resolve( | ||
path.dirname(parent), | ||
file.slice(-5) === ".walt" ? file : file + ".walt" | ||
); | ||
} | ||
return path.resolve(__dirname, file); | ||
}; | ||
const getFileContents = (file, parent, mode) => { | ||
if (parent != null) { | ||
return fs.readFileSync(resolve(file, parent), mode); | ||
} | ||
return fs.readFileSync(resolve(file, null), mode); | ||
}; | ||
test("returns (src: string) => (importsObj) => Promise<Wasm>", async t => { | ||
const memory = new WebAssembly.Memory({ initial: 1 }); | ||
const factory = link(path.resolve(__dirname, "./index.walt")); | ||
const factory = link("./index.walt", null, { | ||
...compiler, | ||
getFileContents, | ||
resolve, | ||
}); | ||
t.is(typeof factory === "function", true, "linker returns a factory"); | ||
@@ -24,0 +47,0 @@ const wasm = await factory({ |
224
src/index.js
@@ -11,181 +11,8 @@ /** | ||
const compiler = require("walt-compiler"); | ||
const buildTools = require("walt-buildtools"); | ||
const fs = require("fs"); | ||
const path = require("path"); | ||
const fs = require("fs"); | ||
const waltCompiler = require("walt-compiler"); | ||
const { inferImportTypes } = require("./patches"); | ||
const invariant = require("invariant"); | ||
function mergeStatics(tree = {}) { | ||
let statics = {}; | ||
Object.values(tree.modules).forEach(mod => { | ||
const localStatics = mod.ast.meta.AST_METADATA.statics; | ||
Object.assign(statics, localStatics); | ||
}); | ||
return statics; | ||
} | ||
// Parse imports out of an ast | ||
function parseImports(ast, compiler) { | ||
const imports = {}; | ||
compiler.walkNode({ | ||
Import(node, _) { | ||
// Import nodes consist of field and a string literal node | ||
const [fields, module] = node.params; | ||
if (imports[module.value] == null) { | ||
imports[module.value] = []; | ||
} | ||
compiler.walkNode({ | ||
Pair(pair, __) { | ||
// Import pairs consist of identifier and type | ||
const [identifier] = pair.params; | ||
imports[module.value] = Array.from( | ||
new Set(imports[module.value].concat(identifier.value)) | ||
); | ||
}, | ||
})(fields); | ||
}, | ||
})(ast); | ||
return imports; | ||
} | ||
// Build a dependency tree of ASTs given a root module | ||
function buildTree(index, compiler) { | ||
const modules = {}; | ||
const dependency = (module, resolve) => { | ||
const filepath = resolve(module); | ||
if (modules[filepath] != null) { | ||
return modules[filepath]; | ||
} | ||
const src = fs.readFileSync(filepath, "utf8"); | ||
const basic = compiler.parser(src); | ||
const nestedImports = parseImports(basic, compiler); | ||
const deps = {}; | ||
Object.keys(nestedImports).forEach(mod => { | ||
if (mod.indexOf(".") === 0) { | ||
const dep = dependency(mod, file => | ||
path.resolve( | ||
path.dirname(filepath), | ||
file.slice(-5) === ".walt" ? file : file + ".walt" | ||
) | ||
); | ||
deps[mod] = dep; | ||
} | ||
}); | ||
const patched = inferImportTypes(basic, deps, compiler); | ||
const ast = compiler.semantics(patched); | ||
compiler.validate(ast, { | ||
lines: src.split("\n"), | ||
filename: module.split("/").pop(), | ||
}); | ||
const result = { | ||
ast, | ||
deps, | ||
filepath, | ||
}; | ||
modules[filepath] = result; | ||
return result; | ||
}; | ||
const root = dependency(index, file => file); | ||
return { | ||
root, | ||
modules, | ||
}; | ||
} | ||
// Assemble all AST into opcodes/instructions | ||
function assemble(tree, options, compiler) { | ||
return Object.entries(tree.modules).reduce((opcodes, [filepath, mod]) => { | ||
// If the child does not define any static data then we should not attempt to | ||
// generate any. Even if there are GLOBAL data sections. | ||
let statics = mod.ast.meta.AST_METADATA.statics; | ||
if (Object.keys(statics).length > 0) { | ||
// Use global statics object | ||
statics = options.linker.statics; | ||
} | ||
const instructions = compiler.generator( | ||
mod.ast, | ||
Object.assign({}, options, { linker: { statics } }) | ||
); | ||
return Object.assign({}, opcodes, { | ||
[filepath]: instructions, | ||
}); | ||
}, {}); | ||
} | ||
function compile(filepath, compiler) { | ||
const filename = filepath.split("/").pop(); | ||
const options = { | ||
version: 0x1, | ||
filename, | ||
filepath, | ||
}; | ||
const tree = buildTree(filepath, compiler); | ||
const statics = mergeStatics(tree); | ||
const opcodes = assemble( | ||
tree, | ||
Object.assign({}, options, { linker: { statics } }), | ||
compiler | ||
); | ||
return Object.assign(tree, { | ||
statics, | ||
opcodes, | ||
options, | ||
}); | ||
} | ||
// Build the final binary Module set | ||
function build(importsObj, tree, compiler) { | ||
const modules = {}; | ||
const instantiate = filepath => { | ||
if (modules[filepath] != null) { | ||
return modules[filepath]; | ||
} | ||
const mod = tree.modules[filepath]; | ||
return Promise.all( | ||
Object.entries(mod.deps).map(([key, dep]) => { | ||
return instantiate(dep.filepath).then(result => [key, result]); | ||
}) | ||
) | ||
.then(importsMap => { | ||
const imports = importsMap.reduce((acc, [key, module]) => { | ||
acc[key] = module.instance.exports; | ||
return acc; | ||
}, {}); | ||
return WebAssembly.instantiate( | ||
compiler.emitter(tree.opcodes[filepath], tree.options).buffer(), | ||
Object.assign({}, imports, importsObj) | ||
); | ||
}) | ||
.catch(e => { | ||
// TODO: do some logging here | ||
throw e; | ||
}); | ||
}; | ||
modules[tree.root.filepath] = instantiate(tree.root.filepath); | ||
return modules[tree.root.filepath]; | ||
} | ||
/** | ||
@@ -196,3 +23,3 @@ * Link Walt programs together. | ||
* @param {Object} options Reserved | ||
* @param {Object} compiler Custom compiler. Is the node module by default. | ||
* @param {Object} api Custom compiler. Is the node module by default. | ||
* | ||
@@ -207,11 +34,29 @@ * This is here to allow for the compiler to test itself | ||
*/ | ||
function link( | ||
filepath, | ||
options = { logger: console }, | ||
compiler = waltCompiler | ||
) { | ||
const tree = compile(filepath, compiler); | ||
const resolve = (dirname = __dirname) => (file, parent) => { | ||
const root = parent ? path.dirname(parent) : dirname; | ||
return path.resolve(root, file.slice(-5) === ".walt" ? file : file + ".walt"); | ||
}; | ||
const getFileContents = resolver => (file, parent, mode) => { | ||
return fs.readFileSync(resolver(file, parent), mode); | ||
}; | ||
function link(file, options = { logger: console }, api = compiler) { | ||
invariant(api, "An API needs to be provided to the linker."); | ||
if (api.resolve == null) { | ||
api = Object.assign( | ||
{ | ||
resolve: resolve(path.dirname(file)), | ||
getFileContents: getFileContents(resolve(path.dirname(file))), | ||
}, | ||
api | ||
); | ||
} | ||
const tree = buildTools.compile(file, api); | ||
function walt(importsObj = {}) { | ||
return build(importsObj, tree, compiler); | ||
return buildTools.build(importsObj, tree, api); | ||
} | ||
@@ -224,9 +69,2 @@ | ||
module.exports = { | ||
link, | ||
parseImports, | ||
compile, | ||
buildTree, | ||
mergeStatics, | ||
assemble, | ||
}; | ||
module.exports = link; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1
698353
10
142
2