microvium
Version:
A compact, embeddable scripting engine for microcontrollers for executing small scripts written in a subset of JavaScript.
395 lines • 17.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runApp = exports.delay = void 0;
const lib_1 = __importStar(require("../lib"));
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const colors_1 = __importDefault(require("colors"));
const node_style_importer_1 = require("./node-style-importer");
const utils_1 = require("./utils");
const decode_snapshot_1 = require("./decode-snapshot");
const inquirer_1 = __importDefault(require("inquirer"));
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
exports.delay = delay;
async function runApp(args, silent, printHelp) {
if (args.mapFile) {
// I renamed this arg because the naming was confusing and inconsistent to me.
throw new Error("Use --output-disassembly (with no filename argument) instead of --map-file");
}
const opts = {};
let didSomething = false;
let usedVM = false;
// console.dir(args);
if (args.debug) {
// TODO(low): How does node.js decide the debug port?
opts.debugConfiguration = { port: 8080 };
}
if (args.outputIL)
opts.outputIL = true;
const importTable = { ...lib_1.defaultHostEnvironment };
const vm = lib_1.default.create(importTable, opts);
(0, lib_1.addDefaultGlobals)(vm);
const importDependency = (0, node_style_importer_1.nodeStyleImporter)(vm, {
fileSystemAccess: 'unrestricted',
allowNodeCoreModules: true
});
if (args.generateLib) {
await runLibGenerator();
didSomething = true;
}
if (args.generatePort) {
await runPortGenerator();
didSomething = true;
}
if (args.eval) {
vm.evaluateModule({ sourceText: args.eval, importDependency });
didSomething = true;
usedVM = true;
}
if (args.input.length > 0) {
for (const inputFilename of args.input) {
if (!fs.existsSync(inputFilename)) {
throw new utils_1.MicroviumUsageError(`File not found: "${inputFilename}"`);
}
const sourceText = fs.readFileSync(inputFilename, 'utf-8');
(0, utils_1.hardAssert)(typeof sourceText === 'string');
const importDependency = (0, node_style_importer_1.nodeStyleImporter)(vm, {
fileSystemAccess: 'unrestricted',
allowNodeCoreModules: true,
basedir: path.dirname(inputFilename)
});
vm.evaluateModule({ sourceText, debugFilename: inputFilename, importDependency });
didSomething = true;
usedVM = true;
}
}
if (!didSomething) {
printHelp && printHelp();
return;
}
// The default is be make a snapshot if there are input files (if the VM was used)
const makeSnapshot = usedVM && !args.noSnapshot;
if (makeSnapshot) {
let snapshotFilename = args.snapshotFilename;
if (!snapshotFilename) {
if (args.input.length > 0) {
const fn = args.input[0];
if (fn.endsWith('.mvm.js'))
snapshotFilename = fn.slice(0, -7) + '.mvm-bc';
else
snapshotFilename = changeExtension(args.input[0], '.mvm-bc');
}
else {
snapshotFilename = "script.mvm-bc";
}
}
;
const snapshottingOpts = {};
if (args.outputIL) {
snapshottingOpts.outputSnapshotIL = true;
snapshottingOpts.snapshotILFilename = snapshotFilename + '.il';
}
if (args.outputSourceMap) {
snapshottingOpts.generateSourceMap = true;
}
const snapshot = vm.createSnapshot(snapshottingOpts);
fs.writeFileSync(snapshotFilename, snapshot.data);
console.error(`Output generated: ${snapshotFilename}`);
console.error(`${snapshot.data.length} bytes`);
if (args.outputDisassembly) {
fs.writeFileSync(snapshotFilename + '.disassembly', (0, decode_snapshot_1.decodeSnapshot)(snapshot).disassembly);
}
if (args.outputBytes) {
console.log(`{${[...snapshot.data].map(b => `0x${b.toString(16).padStart(2, '0')}`).join(',')}}`);
}
}
else {
if (args.snapshotFilename) {
!silent && console.log(colors_1.default.yellow('Cannot use `--no-snapshot` option with `--snapshot`'));
printHelp && printHelp();
}
if (args.outputDisassembly) {
!silent && console.log(colors_1.default.yellow('Cannot use `--no-snapshot` option with `--output-disassembly`'));
printHelp && printHelp();
}
}
}
exports.runApp = runApp;
async function runLibGenerator() {
try {
console.log("\nThe following will create the Microvium C library files in the local directory.");
const { continue_ } = await inquirer_1.default.prompt([{
type: 'confirm',
name: 'continue_',
message: 'Do you want to continue?'
}]);
if (!continue_) {
console.log('Code generation cancelled\n');
return;
}
console.log('\n Creating files...');
await interactiveCopyFiles([{
source: 'dist-c/microvium.c',
dest: './microvium.c',
description: 'The Microvium engine',
}, {
source: 'dist-c/microvium.h',
dest: './microvium.h',
description: 'Header file to #include',
}, {
source: 'dist-c/microvium_port_example.h',
dest: './microvium_port_example.h',
description: 'Example port file',
}]);
console.log(' Done');
}
catch (e) {
console.error(e.message);
}
}
async function runPortGenerator() {
let portFileContents;
try {
const portFileDestName = './microvium_port.h';
console.log(`\n${colors_1.default.yellow('Any of the following choices can be modified later in')} ${colors_1.default.bold('microvium_port.h')}`);
console.log('Just press enter on any question to accept the default (conservative) choice.');
await (0, exports.delay)(1000);
console.log('');
const setupQuestions = [{
type: 'list',
name: 'pointerSize',
message: 'Architecture `void*` pointer size',
choices: [
'8-bit',
'16-bit',
'32-bit',
'64-bit',
],
default: '32-bit'
}, {
type: 'list',
message: 'What should happen when the VM encounters an error?',
name: 'errorBehavior',
choices: [{
name: 'Endless loop to trigger WDT: `while (1) {}`',
value: 'endless-loop',
short: 'Endless loop'
}, {
name: 'Use C stdlib to exit: `assert(false), exit(1)`',
value: 'assert-and-exit',
short: 'Assert & exit'
}, {
name: "I'll Implement my own `mvmFatalError` function somewhere",
value: 'external-error',
short: 'Call mvmFatalError'
}],
default: 'assert-and-exit'
}, {
type: 'list',
name: 'nativeLongPointer',
message: 'Can the bytecode be addressed by a `const void*` pointer?',
choices: [{
name: 'Yes (more efficient)',
value: true,
short: 'Yes'
}, {
name: 'No (more portable)',
value: false,
short: 'No'
}],
default: (answers) => answers.pointerSize === '32-bit' || answers.pointerSize === '64-bit'
}, {
type: 'list',
name: 'supportFloat',
message: 'Enable 64-bit float support?',
choices: [{
name: 'Yes (recommended)',
value: true,
short: 'Yes'
}, {
name: 'No (smaller ROM footprint)',
value: false,
short: 'No'
}],
default: true
}, {
type: 'list',
name: 'overflowChecks',
message: 'Enable integer overflow checks?',
choices: [{
name: 'Yes (recommended)',
value: true,
short: 'Yes'
}, {
name: 'No (smaller ROM footprint)',
value: false,
short: 'No'
}],
default: true
}, {
type: 'confirm',
name: 'debugAPI',
message: 'Enable debug API',
default: true
}, {
type: 'number',
name: 'stackSize',
message: 'VM Stack size (bytes)',
default: 256
}, {
type: 'number',
name: 'maxHeapSize',
message: 'VM Max Heap Size (bytes)',
default: 1024
},];
const answers = await inquirer_1.default.prompt(setupQuestions);
portFileContents = fs.readFileSync(path.join(__dirname, '../..', 'dist-c/microvium_port_example.h'), 'utf8');
define('MVM_STACK_SIZE', answers.stackSize);
define('MVM_MAX_HEAP_SIZE', answers.maxHeapSize);
define('MVM_NATIVE_POINTER_IS_16_BIT', answers.pointerSize === '16-bit' ? 1 : 0);
define('MVM_SUPPORT_FLOAT', answers.supportFloat ? 1 : 0);
define('MVM_PORT_INT32_OVERFLOW_CHECKS', answers.overflowChecks ? 1 : 0);
define('MVM_SAFE_MODE', 0);
define('MVM_DONT_TRUST_BYTECODE', 0);
if (answers.nativeLongPointer) {
define('MVM_LONG_PTR_TYPE', 'void*');
define('MVM_LONG_PTR_NEW(p)', '((MVM_LONG_PTR_TYPE)p)');
define('MVM_LONG_PTR_TRUNCATE(p)', '((void*)p)');
define('MVM_LONG_PTR_ADD(p, s)', '((void*)((uint8_t*)p + (intptr_t)s))');
define('MVM_LONG_PTR_SUB(p2, p1)', '((int16_t)((uint8_t*)p2 - (uint8_t*)p1))');
define('MVM_READ_LONG_PTR_1(lpSource)', '(*((uint8_t*)lpSource))');
define('MVM_READ_LONG_PTR_2(lpSource)', '(*((uint16_t*)lpSource))');
define('MVM_READ_LONG_PTR_4(lpSource)', '(*((uint32_t*)lpSource))');
define('MVM_LONG_MEM_CMP(p1, p2, size)', 'memcmp(p1, p2, size)');
define('MVM_LONG_MEM_CPY(target, source, size)', 'memcpy(target, source, size)');
}
else {
define('MVM_LONG_PTR_TYPE', 'int32_t');
define('MVM_LONG_PTR_NEW(p)', '((MVM_LONG_PTR_TYPE)p)');
define('MVM_LONG_PTR_TRUNCATE(p)', '((void*)p)');
define('MVM_LONG_PTR_ADD(p, s)', 'p + (int32_t)s');
define('MVM_LONG_PTR_SUB(p2, p1)', '((int16_t)(p2 - p1))');
unableToDefine('MVM_READ_LONG_PTR_1(lpSource)');
unableToDefine('MVM_READ_LONG_PTR_2(lpSource)');
unableToDefine('MVM_READ_LONG_PTR_4(lpSource)');
unableToDefine('MVM_LONG_MEM_CMP(p1, p2, size)');
unableToDefine('MVM_LONG_MEM_CPY(target, source, size)');
}
define('MVM_FATAL_ERROR(vm, e)', answers.errorBehavior === 'endless-loop' ? 'mvm_endlessLoop()' :
answers.errorBehavior === 'assert-and-exit' ? '(assert(false), exit(e))' :
answers.errorBehavior === 'external-error' ? 'vmFatalError(vm, e)' :
(0, utils_1.unexpected)());
if (answers.errorBehavior === 'endless-loop') {
prependCode('static void mvm_endlessLoop() { while (1); }');
}
if (answers.errorBehavior === 'external-error') {
prependCode('// To be implemented in the host\nvoid vmFatalError(mvm_VM* vm, mvm_TeError err);');
}
define('MVM_INCLUDE_SNAPSHOT_CAPABILITY', 0);
define('MVM_INCLUDE_DEBUG_CAPABILITY', answers.debugAPI ? 1 : 0);
console.log('');
if (fs.existsSync(portFileDestName)) {
console.log(colors_1.default.red(`\n${colors_1.default.bold('WARNING')}: This will overwrite the existing file ${portFileDestName}`));
const { overwrite } = await inquirer_1.default.prompt([{
type: 'confirm',
name: 'overwrite',
message: `Continue?`,
default: true
}]);
if (!overwrite) {
console.log('Port file generation cancelled\n');
return;
}
}
console.log(' Creating port file...');
fs.writeFileSync(portFileDestName, portFileContents);
console.log(` ${colors_1.default.green(portFileDestName)}`);
console.log(` ${colors_1.default.bold('Done')}`);
console.log('');
console.log(`See ${colors_1.default.cyan('https://microvium.com/getting-started')} for more information`);
}
catch (e) {
console.error(e.message);
}
function define(name, value) {
const pattern = new RegExp(`^(#define ${escapeRegExp(name)}) (.*)$`, 'gm');
if (!portFileContents.match(pattern)) {
console.error(' ' + colors_1.default.yellow(`WARNING: could not find #define named "${name}" in port file`));
}
portFileContents = portFileContents.replace(pattern, `$1 ${value}`);
}
function unableToDefine(name) {
define(name, '<define me>');
console.error(' ' + colors_1.default.yellow(`WARNING: unable to automatically #define "${name}" in port file. Please provide the implementation manually.`));
}
function prependCode(code) {
const anchor = '#include <stdint.h>';
portFileContents = portFileContents.replace(anchor, anchor + '\n\n' + code);
}
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}
}
// https://stackoverflow.com/a/57371333
function changeExtension(file, ext) {
return path.join(path.dirname(file), path.basename(file, path.extname(file)) + ext);
}
async function interactiveCopyFiles(filesToCopy) {
const filesToOverwrite = filesToCopy.filter(f => fs.existsSync(f.dest));
if (filesToOverwrite.length) {
console.log(colors_1.default.red(`\n${colors_1.default.bold('WARNING')}: This will overwrite the following files:`));
for (const f of filesToOverwrite) {
console.log(` ${f.dest}`);
}
const { overwrite } = await inquirer_1.default.prompt([{
type: 'confirm',
name: 'overwrite',
message: `Continue?`,
default: true
}]);
if (!overwrite) {
console.log('Code generation cancelled\n');
return;
}
}
// Note: the artificial delays make it easier for a user to "follow along"
// with what the generator is doing.
await (0, exports.delay)(500);
const maxDestFilenameLength = Math.max(...filesToCopy.map(f => f.dest.length));
for (const { source, dest, description } of filesToCopy) {
await copyFile(source, dest);
console.log(` ${colors_1.default.green(dest.padEnd(maxDestFilenameLength, ' '))} ${colors_1.default.white(description)}`);
await (0, exports.delay)(200);
}
}
async function copyFile(source, dest) {
const contents = fs.readFileSync(path.join(__dirname, '../..', source), 'utf-8');
fs.writeFileSync(dest, contents);
}
//# sourceMappingURL=run-app.js.map