Restructure types; Add a use-case specific options parser; Allow (re)creation of specific parser fixtures

This commit is contained in:
dcodeIO
2018-07-03 03:06:01 +02:00
parent 82da2d1f6d
commit 5ca5df3dc7
29 changed files with 486 additions and 279 deletions

View File

@ -11,34 +11,30 @@
* @module cli/asc
*/
// Use "." instead of "/" as cwd in browsers
if (process.browser) process.cwd = function() { return "."; };
const fs = require("fs");
const path = require("path");
const utf8 = require("@protobufjs/utf8");
const colors = require("./util/colors");
const colorsUtil = require("./util/colors");
const optionsUtil = require("./util/options");
const EOL = process.platform === "win32" ? "\r\n" : "\n";
// Use distribution files if present, otherwise run the sources directly
var assemblyscript, isDev;
var assemblyscript, isDev = false;
(() => {
try {
assemblyscript = require("../dist/assemblyscript.js");
isDev = false;
} catch (e) {
try {
require("ts-node").register({
project: path.join(__dirname, "..", "src", "tsconfig.json"),
files: [ // see: https://github.com/TypeStrong/ts-node/issues/620
path.join(__dirname, "..", "std", "portable.d.ts"),
path.join(__dirname, "..", "src", "glue", "binaryen.d.ts")
]
});
require("ts-node").register({ project: path.join(__dirname, "..", "src", "tsconfig.json") });
require("../src/glue/js");
assemblyscript = require("../src");
isDev = true;
} catch (e) {
// last resort: same directory CommonJS
assemblyscript = eval("require('./assemblyscript')");
isDev = false;
}
}
})();
@ -70,7 +66,7 @@ exports.defaultShrinkLevel = 1;
/** Bundled library files. */
exports.libraryFiles = exports.isBundle ? BUNDLE_LIBRARY : (() => { // set up if not a bundle
const libDir = path.join(__dirname, "..", "std", "assembly");
const libFiles = require("glob").sync("**/*.ts", { cwd: libDir });
const libFiles = require("glob").sync("**/!(*.d).ts", { cwd: libDir });
const bundled = {};
libFiles.forEach(file => bundled[file.replace(/\.ts$/, "")] = fs.readFileSync(path.join(libDir, file), "utf8" ));
return bundled;
@ -80,8 +76,8 @@ exports.libraryFiles = exports.isBundle ? BUNDLE_LIBRARY : (() => { // set up if
exports.definitionFiles = exports.isBundle ? BUNDLE_DEFINITIONS : (() => { // set up if not a bundle
const stdDir = path.join(__dirname, "..", "std");
return {
"assembly": fs.readFileSync(path.join(stdDir, "assembly.d.ts"), "utf8"),
"portable": fs.readFileSync(path.join(stdDir, "portable.d.ts"), "utf8")
"assembly": fs.readFileSync(path.join(stdDir, "assembly", "index.d.ts"), "utf8"),
"portable": fs.readFileSync(path.join(stdDir, "portable", "index.d.ts"), "utf8")
};
})();
@ -94,12 +90,16 @@ exports.compileString = (sources, options) => {
binary: null,
text: null
});
exports.main([
var argv = [
"--binaryFile", "binary",
"--textFile", "text",
...Object.keys(options || {}).map(arg => `--${arg}=${options[arg]}`),
...Object.keys(sources),
], {
];
Object.keys(options || {}).forEach(key => {
var val = options[key];
if (Array.isArray(val)) val.forEach(val => argv.push("--" + key, String(val)));
else argv.push("--" + key, String(val));
});
exports.main(argv.concat(Object.keys(sources)), {
stdout: output.stdout,
stderr: output.stderr,
readFile: name => sources.hasOwnProperty(name) ? sources[name] : null,
@ -129,22 +129,34 @@ exports.main = function main(argv, options, callback) {
if (!stdout) throw Error("'options.stdout' must be specified");
if (!stderr) throw Error("'options.stderr' must be specified");
const args = parseArguments(argv);
const indent = 24;
const opts = optionsUtil.parse(argv, exports.options);
const args = opts.options;
argv = opts.arguments;
if (args.noColors) {
colors.stdout.supported =
colors.stderr.supported = false;
colorsUtil.stdout.supported =
colorsUtil.stderr.supported = false;
} else {
colors.stdout = colors.from(stdout);
colors.stderr = colors.from(stderr);
colorsUtil.stdout = colorsUtil.from(stdout);
colorsUtil.stderr = colorsUtil.from(stderr);
}
// Check for unknown arguments
if (opts.unknown.length) {
opts.unknown.forEach(arg => {
stderr.write(colorsUtil.stderr.yellow("WARN: ") + "Unknown option '" + arg + "'" + EOL);
});
}
// Check for trailing arguments
if (opts.trailing.length) {
stderr.write(colorsUtil.stderr.yellow("WARN: ") + "Unsupported trailing arguments: " + opts.trailing.join(" ") + EOL);
}
// Use default callback if none is provided
if (!callback) callback = function defaultCallback(err) {
var code = 0;
if (err) {
stderr.write(colors.stderr.red("ERROR: ") + err.stack.replace(/^ERROR: /i, "") + EOL);
stderr.write(colorsUtil.stderr.red("ERROR: ") + err.stack.replace(/^ERROR: /i, "") + EOL);
code = 1;
}
return code;
@ -156,43 +168,22 @@ exports.main = function main(argv, options, callback) {
return callback(null);
}
// Print the help message if requested or no source files are provided
if (args.help || args._.length < 1) {
const opts = [];
Object.keys(exports.options).forEach(name => {
var option = exports.options[name];
var text = " ";
text += "--" + name;
if (option.aliases && option.aliases[0].length === 1) {
text += ", -" + option.aliases[0];
}
while (text.length < indent) {
text += " ";
}
if (Array.isArray(option.description)) {
opts.push(text + option.description[0] + option.description.slice(1).map(line => {
for (let i = 0; i < indent; ++i) {
line = " " + line;
}
return EOL + line;
}).join(""));
} else {
opts.push(text + option.description);
}
});
if (args.help || !argv.length) {
var out = args.help ? stdout : stderr;
var color = args.help ? colors.stdout : colors.stderr;
var color = args.help ? colorsUtil.stdout : colorsUtil.stderr;
out.write([
color.white("Syntax"),
color.white("SYNTAX"),
" " + color.cyan("asc") + " [entryFile ...] [options]",
"",
color.white("Examples"),
color.white("EXAMPLES"),
" " + color.cyan("asc") + " hello.ts",
" " + color.cyan("asc") + " hello.ts -b hello.wasm -t hello.wat",
" " + color.cyan("asc") + " hello1.ts hello2.ts -b -O > hello.wasm",
"",
color.white("Options"),
].concat(opts).join(EOL) + EOL);
color.white("OPTIONS"),
].concat(
optionsUtil.help(exports.options, 24, EOL)
).join(EOL) + EOL);
return callback(null);
}
@ -209,7 +200,6 @@ exports.main = function main(argv, options, callback) {
// Set up transforms
const transforms = [];
if (args.transform) {
if (typeof args.transform === "string") args.transform = args.transform.split(",");
args.transform.forEach(transform =>
transforms.push(
require(
@ -246,8 +236,9 @@ exports.main = function main(argv, options, callback) {
}
const customLibDirs = [];
if (args.lib) {
if (typeof args.lib === "string") args.lib = args.lib.split(",");
Array.prototype.push.apply(customLibDirs, args.lib.map(lib => lib.trim()));
let lib = args.lib;
if (typeof lib === "string") lib = lib.split(",");
Array.prototype.push.apply(customLibDirs, lib.map(lib => lib.trim()));
for (let i = 0, k = customLibDirs.length; i < k; ++i) { // custom
let libDir = customLibDirs[i];
let libFiles;
@ -275,10 +266,10 @@ exports.main = function main(argv, options, callback) {
}
// Include entry files
for (let i = 0, k = args._.length; i < k; ++i) {
const filename = args._[i];
for (let i = 0, k = argv.length; i < k; ++i) {
const filename = argv[i];
let sourcePath = filename.replace(/\\/g, "/").replace(/(\.ts|\/)$/, "");
let sourcePath = String(filename).replace(/\\/g, "/").replace(/(\.ts|\/)$/, "");
// Try entryPath.ts, then entryPath/index.ts
let sourceText = readFile(path.join(baseDir, sourcePath) + ".ts");
@ -385,51 +376,28 @@ exports.main = function main(argv, options, callback) {
const program = assemblyscript.finishParsing(parser);
// Set up optimization levels
var optimizeLevel = -1;
var optimizeLevel = 0;
var shrinkLevel = 0;
var debugInfo = !args.noDebug;
if (args.optimize !== false) {
if (typeof args.optimize === "number") {
optimizeLevel = args.optimize;
} else if (args["0"]) {
optimizeLevel = 0;
} else if (args["1"]) {
optimizeLevel = 1;
} else if (args["2"]) {
optimizeLevel = 2;
} else if (args["3"]) {
optimizeLevel = 3;
} else if (args.optimize === true) {
optimizeLevel = exports.defaultOptimizeLevel;
shrinkLevel = exports.defaultShrinkLevel;
} else
optimizeLevel = 0;
}
if (args["s"]) {
shrinkLevel = 1;
} else if (args["z"]) {
shrinkLevel = 2;
if (args.optimize) {
optimizeLevel = exports.defaultOptimizeLevel;
shrinkLevel = exports.defaultShrinkLevel;
}
if (typeof args.optimizeLevel === "number") {
optimizeLevel = args.optimizeLevel;
}
if (typeof args.shrinkLevel === "number") {
shrinkLevel = args.shrinkLevel;
} else if (args.shrinkLevel === "s") {
shrinkLevel = 1;
} else if (args.shrinkLevel === "z") {
shrinkLevel = 2;
}
optimizeLevel = Math.max(optimizeLevel, 0);
shrinkLevel = Math.max(shrinkLevel, 0);
optimizeLevel = Math.min(Math.max(optimizeLevel, 0), 3);
shrinkLevel = Math.min(Math.max(shrinkLevel, 0), 2);
// Begin compilation
const compilerOptions = assemblyscript.createOptions();
assemblyscript.setTarget(compilerOptions, 0);
assemblyscript.setNoTreeShaking(compilerOptions, !!args.noTreeShaking);
assemblyscript.setNoAssert(compilerOptions, !!args.noAssert);
assemblyscript.setImportMemory(compilerOptions, !!args.importMemory);
assemblyscript.setImportTable(compilerOptions, !!args.importTable);
assemblyscript.setNoTreeShaking(compilerOptions, args.noTreeShaking);
assemblyscript.setNoAssert(compilerOptions, args.noAssert);
assemblyscript.setImportMemory(compilerOptions, args.importMemory);
assemblyscript.setImportTable(compilerOptions, args.importTable);
assemblyscript.setMemoryBase(compilerOptions, args.memoryBase >>> 0);
assemblyscript.setSourceMap(compilerOptions, args.sourceMap != null);
assemblyscript.setOptimizeLevelHints(compilerOptions, optimizeLevel, shrinkLevel);
@ -515,7 +483,7 @@ exports.main = function main(argv, options, callback) {
module.setOptimizeLevel(optimizeLevel);
module.setShrinkLevel(shrinkLevel);
module.setDebugInfo(debugInfo);
module.setDebugInfo(!args.noDebug);
var runPasses = [];
if (args.runPasses) {
@ -741,7 +709,7 @@ exports.main = function main(argv, options, callback) {
var files;
try {
stats.readTime += measure(() => {
files = require("glob").sync("*.ts", { cwd: dirname });
files = require("glob").sync("!(*.d).ts", { cwd: dirname });
});
return files;
} catch (e) {
@ -764,25 +732,23 @@ exports.main = function main(argv, options, callback) {
}
}
/** Parses the specified command line arguments. */
function parseArguments(argv) {
const opts = {};
Object.keys(exports.options).forEach(key => {
const opt = exports.options[key];
if (opt.aliases) {
(opts.alias || (opts.alias = {}))[key] = opt.aliases;
}
if (opt.default !== undefined) {
(opts.default || (opts.default = {}))[key] = opt.default;
}
if (opt.type === "string") {
(opts.string || (opts.string = [])).push(key);
} else if (opt.type === "boolean") {
(opts.boolean || (opts.boolean = [])).push(key);
}
});
return require("minimist")(argv, opts);
}
var argumentSubstitutions = {
"-O" : [ "--optimize" ],
"-Os" : [ "--optimize", "--shrinkLevel", "1" ],
"-Oz" : [ "--optimize", "--shrinkLevel", "2" ],
"-O0" : [ "--optimizeLevel", "0", "--shrinkLevel", "0" ],
"-O0s": [ "--optimizeLevel", "0", "--shrinkLevel", "1" ],
"-O0z": [ "--optimizeLevel", "0", "--shrinkLevel", "2" ],
"-O1" : [ "--optimizeLevel", "1", "--shrinkLevel", "0" ],
"-O1s": [ "--optimizeLevel", "1", "--shrinkLevel", "1" ],
"-O1z": [ "--optimizeLevel", "1", "--shrinkLevel", "2" ],
"-O2" : [ "--optimizeLevel", "2", "--shrinkLevel", "0" ],
"-O2s": [ "--optimizeLevel", "2", "--shrinkLevel", "1" ],
"-O2z": [ "--optimizeLevel", "2", "--shrinkLevel", "2" ],
"-O3" : [ "--optimizeLevel", "3", "--shrinkLevel", "0" ],
"-O3s": [ "--optimizeLevel", "3", "--shrinkLevel", "1" ],
"-O3z": [ "--optimizeLevel", "3", "--shrinkLevel", "2" ],
};
/** Checks diagnostics emitted so far for errors. */
function checkDiagnostics(emitter, stderr) {

View File

@ -1,17 +1,17 @@
{
"version": {
"description": "Prints just the compiler's version and exits.",
"type": "boolean",
"aliases": [ "v" ]
"type": "b",
"alias": "v"
},
"help": {
"description": "Prints this message and exits.",
"type": "boolean",
"aliases": [ "h" ]
"type": "b",
"alias": "h"
},
"optimize": {
"description": [
"Optimizes the module. Also accepts the optimize level:",
"Optimizes the module. Also has the usual shorthands:",
"",
" -O Uses defaults. Equivalent to -O2s",
" -O0 Equivalent to --optimizeLevel 0",
@ -22,109 +22,119 @@
" -O3s Equivalent to -O3 with --shrinkLevel 1 etc.",
""
],
"type": "boolean",
"aliases": [ "O" ]
"type": "b",
"alias": "O"
},
"optimizeLevel": {
"description": "How much to focus on optimizing code. [0-3]",
"type": "number"
"type": "i"
},
"shrinkLevel": {
"description": "How much to focus on shrinking code size. [0-2, s=1, z=2]",
"type": "number"
"type": "i"
},
"validate": {
"description": "Validates the module using Binaryen. Exits if invalid.",
"type": "boolean",
"aliases": [ "c", "check" ]
"type": "b",
"alias": "c",
"default": false
},
"baseDir": {
"description": "Specifies the base directory of input and output files.",
"type": "string"
"type": "s",
"default": "."
},
"outFile": {
"description": "Specifies the output file. File extension indicates format.",
"type": "string",
"aliases": [ "o" ]
"type": "s",
"alias": "o"
},
"binaryFile": {
"description": "Specifies the binary output file (.wasm).",
"type": "string",
"aliases": [ "b" ]
"type": "s",
"alias": "b"
},
"textFile": {
"description": "Specifies the text output file (.wat).",
"type": "string",
"aliases": [ "t" ]
"type": "s",
"alias": "t"
},
"asmjsFile": {
"description": "Specifies the asm.js output file (.js).",
"type": "string",
"aliases": [ "a" ]
"type": "s",
"alias": "a"
},
"idlFile": {
"description": "Specifies the WebIDL output file (.webidl).",
"type": "string",
"aliases": [ "i" ]
"type": "s",
"alias": "i"
},
"tsdFile": {
"description": "Specifies the TypeScript definition output file (.d.ts).",
"type": "string",
"aliases": [ "d", "dtsFile" ]
"type": "s",
"alias": "d"
},
"sourceMap": {
"description": [
"Enables source map generation. Optionally takes the URL",
"used to reference the source map from the binary file."
],
"type": "string"
"type": "s"
},
"noTreeShaking": {
"description": "Disables compiler-level tree-shaking, compiling everything.",
"type": "boolean"
"type": "b",
"default": false
},
"noDebug": {
"description": "Disables maintaining of debug information in binaries.",
"type": "boolean"
"type": "b",
"default": false
},
"noAssert": {
"description": "Replaces assertions with just their value without trapping.",
"type": "boolean"
"type": "b",
"default": false
},
"noEmit": {
"description": "Performs compilation as usual but does not emit code.",
"type": "boolean"
"type": "b",
"default": false
},
"importMemory": {
"description": "Imports the memory instance provided by the embedder.",
"type": "boolean"
"type": "b",
"default": false
},
"memoryBase": {
"description": "Sets the start offset of compiler-generated static memory.",
"type": "number"
"type": "i",
"default": 0
},
"importTable": {
"description": "Imports the function table instance provided by the embedder.",
"type": "boolean"
"type": "b",
"default": false
},
"noLib": {
"description": "Does not include the shipped standard library.",
"type": "boolean"
"type": "b",
"default": false
},
"lib": {
"description": [
"Adds one or multiple paths to custom library components and",
"uses exports of all top-level files at this path as globals."
],
"type": "string"
"type": "s"
},
"use": {
"description": [
"Aliases a global object under another name, e.g., to switch",
"the default 'Math' implementation used: --use Math=JSMath"
],
"type": "string",
"aliases": [ "u" ]
"type": "s",
"alias": "u"
},
"trapMode": {
"description": [
@ -135,7 +145,7 @@
" js Replace trapping operations with JS semantics.",
""
],
"type": "string",
"type": "s",
"default": "allow"
},
"runPasses": {
@ -143,7 +153,7 @@
"Specifies additional Binaryen passes to run after other",
"optimizations, if any. See: Binaryen/src/passes/pass.cpp"
],
"type": "string"
"type": "s"
},
"enable": {
"description": [
@ -153,19 +163,34 @@
" mutable-global Enables mutable global imports and exports",
""
],
"type": "string",
"aliases": [ "feature" ]
"type": "s"
},
"transform": {
"description": "Specifies the path to a custom transform to 'require'.",
"type": "string"
"type": "S"
},
"measure": {
"description": "Prints measuring information on I/O and compile times.",
"type": "boolean"
"type": "b",
"default": false
},
"noColors": {
"description": "Disables terminal colors.",
"type": "boolean"
}
"type": "b",
"default": false
},
"-Os": { "value": { "optimize": true, "shrinkLevel": 1 } },
"-Oz": { "value": { "optimize": true, "shrinkLevel": 2 } },
"-O0": { "value": { "optimizeLevel": 0, "shrinkLevel": 0 } },
"-O1": { "value": { "optimizeLevel": 1, "shrinkLevel": 0 } },
"-O2": { "value": { "optimizeLevel": 2, "shrinkLevel": 0 } },
"-O3": { "value": { "optimizeLevel": 3, "shrinkLevel": 0 } },
"-O0s": { "value": { "optimizeLevel": 0, "shrinkLevel": 1 } },
"-O1s": { "value": { "optimizeLevel": 1, "shrinkLevel": 1 } },
"-O2s": { "value": { "optimizeLevel": 2, "shrinkLevel": 1 } },
"-O3s": { "value": { "optimizeLevel": 3, "shrinkLevel": 1 } },
"-O0z": { "value": { "optimizeLevel": 0, "shrinkLevel": 2 } },
"-O1z": { "value": { "optimizeLevel": 1, "shrinkLevel": 2 } },
"-O2z": { "value": { "optimizeLevel": 2, "shrinkLevel": 2 } },
"-O3z": { "value": { "optimizeLevel": 3, "shrinkLevel": 2 } }
}

View File

@ -1,5 +1,5 @@
var proc = typeof process !== "undefined" && process || {};
var isCI = proc.env && "CI" in proc.env;
var isCI = proc.env && "CI" in proc.env; // doesn't work when bundled because 'process' is a mock
function from(stream, base) {
var colors = base || {};

41
cli/util/options.d.ts vendored Normal file
View File

@ -0,0 +1,41 @@
/** Configuration object. */
interface Config {
[key: string]: {
/** Textual description. */
description?: string | string[],
/** Data type. One of (b)oolean [default], (i)nteger, (f)loat or (s)tring. Uppercase means multiple values. */
type?: "b" | "i" | "f" | "s", "I", "F", "S",
/** Substituted options, if any. */
value?: { [key: string]: number | string },
/** Short alias, if any. */
alias?: string
};
}
/** Parsing result. */
interface Result {
/** Parsed options. */
options: { [key: string]: number | string },
/** Unknown options. */
unknown: string[],
/** Normal arguments. */
arguments: string[],
/** Trailing arguments. */
trailing: string[]
}
/** Parses the specified command line arguments according to the given configuration. */
export function parse(argv: string[], config: Config): Result;
/** Help formatting options. */
interface HelpOptions {
/** Leading indent. Defaults to 2. */
indent?: number,
/** Table padding. Defaults to 24. */
padding?: number,
/** End of line character. Defaults to "\n". */
eol?: string
}
/** Generates the help text for the specified configuration. */
export function help(config: Config, options?: HelpOptions): string;

105
cli/util/options.js Normal file
View File

@ -0,0 +1,105 @@
// type | meaning
// -----|---------------
// b | boolean
// i | integer
// f | float
// s | string
// I | integer array
// F | float array
// S | string array
/** Parses the specified command line arguments according to the given configuration. */
function parse(argv, config) {
var options = {};
var unknown = [];
var arguments = [];
var trailing = [];
// make an alias map and initialize defaults
var aliases = {};
Object.keys(config).forEach(key => {
var option = config[key];
if (option.alias != null) {
if (typeof option.alias === "string") aliases[option.alias] = key;
else if (Array.isArray(option.alias)) option.alias.forEach(alias => aliases[alias] = key);
}
if (option.default != null) options[key] = option.default;
});
// iterate over argv
for (var i = 0, k = (argv = argv.slice()).length; i < k; ++i) {
let arg = argv[i];
if (arg == "--") { ++i; break; }
let match = /^(?:(\-\w)|(\-\-\w{2,})(?:=(.*))?)$/.exec(arg), option, key;
if (match) {
if (config[arg]) option = config[key = arg]; // exact
else if (match[1] != null) option = config[key = aliases[match[1].substring(1)]]; // alias
else if (match[2] != null) {
option = config[key = match[2].substring(2)]; // full
if (option && match[3] != null) argv[i--] = match[3];
}
} else {
if (arg.charCodeAt(0) == 45) option = config[key = arg]; // exact
else { arguments.push(arg); continue; } // argument
}
if (option) {
if (option.type == null || option.type === "b") options[key] = true; // flag
else {
if (i + 1 < argv.length && argv[i + 1].charCodeAt(0) != 45) { // present
switch (option.type) {
case "i": options[key] = parseInt(argv[++i], 10); break;
case "I": options[key] = (options[key] || []).concat(parseInt(argv[++i], 10)); break;
case "f": options[key] = parseFloat(argv[++i]); break;
case "F": options[key] = (options[key] || []).concat(parseFloat(argv[++i])); break;
case "s": options[key] = String(argv[++i]); break;
case "S": options[key] = (options[key] || []).concat(argv[++i].split(",")); break;
default: unknown.push(arg); --i;
}
} else { // omitted
switch (option.type) {
case "i":
case "f": options[key] = option.default || 0; break;
case "s": options[key] = option.default || ""; break;
case "I":
case "F":
case "S": options[key] = options.default || []; break;
default: unknown.push(arg);
}
}
}
if (option.value) Object.keys(option.value).forEach(k => options[k] = option.value[k]);
} else unknown.push(arg);
}
while (i < k) trailing.push(argv[i++]); // trailing
return { options, unknown, arguments, trailing };
}
exports.parse = parse;
/** Generates the help text for the specified configuration. */
function help(config, options) {
if (!options) options = {};
var indent = options.indent || 2;
var padding = options.padding || 24;
var eol = options.eol || "\n";
var sb = [];
Object.keys(config).forEach(key => {
var option = config[key];
if (option.description == null) return;
var text = "";
while (text.length < indent) text += " ";
text += "--" + key;
if (option.alias) text += ", -" + option.alias;
while (text.length < padding) text += " ";
if (Array.isArray(option.description)) {
sb.push(text + option.description[0] + option.description.slice(1).map(line => {
for (let i = 0; i < padding; ++i) line = " " + line;
return eol + line;
}).join(""));
} else sb.push(text + option.description);
});
return sb.join(eol);
}
exports.help = help;