diff --git a/packages/core/bin/syndicatec.js b/packages/core/bin/syndicatec.js index c713452..afd1cb2 100755 --- a/packages/core/bin/syndicatec.js +++ b/packages/core/bin/syndicatec.js @@ -1,4 +1,79 @@ #!/usr/bin/env node -import { main } from '../lib/compiler/main.js'; -main(process.argv); +const yargs = require('yargs/yargs'); +const { hideBin } = require('yargs/helpers'); +const argv = + yargs(hideBin(process.argv)) + .completion() + .command('$0 [input]', 'Compile a single file', (yargs) => { + yargs + .positional('input', { + type: 'string', + description: 'Input filename', + }) + .option('output', { + alias: 'o', + type: 'string', + description: 'Output filename (stdout if omitted)', + default: null, + }) + .option('map', { + type: 'boolean', + description: 'Generate source maps', + default: true, + }) + .option('map-extension', { + type: 'string', + description: 'Extension (e.g. ".map") to add to source map files; if omitted, source maps are generated inline', + default: null, + }) + .option('runtime', { + type: 'string', + description: 'Path to require or import to get the Syndicate runtime', + default: '@syndicate/core', + }) + .option('module', { + type: 'string', + description: 'es6 | require | global', + }) + }) + .argv; + +const fs = require('fs'); +const { compile } = require('../dist/syndicate.js').Compiler; + +// console.log(argv); + +const inputFilename = 'input' in argv ? argv.input : '/dev/stdin'; +const source = fs.readFileSync(inputFilename, 'utf-8'); + +const { text, map } = compile({ + source, + name: inputFilename, + runtime: argv.runtime, + module: argv.module, +}); +map.sourcesContent = [source]; + +function mapDataURL() { + const mapData = Buffer.from(JSON.stringify(map)).toString('base64') + return `data:application/json;base64,${mapData}`; +} + +if (argv.output !== null) { + if (!argv.map) { + fs.writeFileSync(argv.output, text); + } else if (argv.mapExtension) { + const mapFilename = argv.output + argv.mapExtension; + fs.writeFileSync(argv.output, text + `\n//# sourceMappingURL=${mapFilename}`); + fs.writeFileSync(mapFilename, JSON.stringify(map)); + } else { + fs.writeFileSync(argv.output, text + `\n//# sourceMappingURL=${mapDataURL()}`); + } +} else { + if (!argv.map) { + console.log(text); + } else { + console.log(text + `\n//# sourceMappingURL=${mapDataURL()}`); + } +} diff --git a/packages/core/package.json b/packages/core/package.json index 38c566b..ac09c6b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,8 @@ "types": "lib/index.d.ts", "author": "Tony Garnock-Jones ", "dependencies": { - "preserves": "0.4.0" + "preserves": "0.4.0", + "yargs": "^16.2.0" }, "bin": { "syndicatec": "./bin/syndicatec.js" diff --git a/packages/core/src/compiler/main.ts b/packages/core/src/compiler/codegen.ts similarity index 81% rename from packages/core/src/compiler/main.ts rename to packages/core/src/compiler/codegen.ts index 94c9ecd..4586453 100644 --- a/packages/core/src/compiler/main.ts +++ b/packages/core/src/compiler/codegen.ts @@ -1,4 +1,3 @@ -import * as fs from 'fs'; import * as S from '../syntax/index.js'; import { Substitution } from '../syntax/index.js'; import * as G from './grammar.js'; @@ -13,17 +12,41 @@ export function stripShebang(items: S.Items): S.Items { return items; } -export function main(argv: string[]) { - let [ inputFilename ] = argv.slice(2); - inputFilename = inputFilename ?? '/dev/stdin'; - const source = fs.readFileSync(inputFilename, 'utf-8'); +export interface CompileOptions { + source: string, + name?: string, + runtime?: string, + module?: 'es6' | 'require' | 'global', + global?: string, +} + +export interface CompilerOutput { + text: string, + map: S.SourceMap, +} + +export function compile(options: CompileOptions): CompilerOutput { + const inputFilename = options.name ?? '/dev/stdin'; + const source = options.source; + const moduleType = options.module ?? 'es6'; const scanner = new S.StringScanner(S.startPos(inputFilename), source); const reader = new S.LaxReader(scanner); let tree = stripShebang(reader.readToEnd()); let macro = new S.Templates(); - tree = macro.template()`import * as __SYNDICATE__ from '@syndicate/core';\n${tree}`; + const runtime = options.runtime ?? '@syndicate/core'; + switch (moduleType) { + case 'es6': + tree = macro.template()`import * as __SYNDICATE__ from ${JSON.stringify(runtime)};\n${tree}`; + break; + case 'require': + tree = macro.template()`const __SYNDICATE__ = require(${JSON.stringify(runtime)});\n${tree}`; + break; + case 'global': + tree = macro.template()`const __SYNDICATE__ = ${runtime};\n${tree}`; + break; + } let passNumber = 0; let expansionNeeded = true; @@ -167,19 +190,29 @@ export function main(argv: string[]) { s => macro.template()`addChildFacet(function (thisFacet) {${s.body}});`); expand( G.bootStatement, - s => macro.template()`export function ${BootProc}(thisFacet) {${s}}`); + s => { + switch (moduleType) { + case 'es6': + return macro.template()`export function ${BootProc}(thisFacet) {${s}}`; + case 'global': + return macro.template()`module.exports.${BootProc} = function (thisFacet) {${s}};`; + case 'require': + return macro.template()`function ${BootProc}(thisFacet) {${s}}`; + } + }); expandFacetAction( G.stopStatement, s => macro.template()`_stop(function (thisFacet) {${s.body}});`); } // console.log(`\n\n\n======================================== FINAL OUTPUT\n`); - console.log(S.itemText(tree)); + // console.log(S.itemText(tree)); const cw = new S.CodeWriter(inputFilename); cw.emit(tree); - fs.writeFileSync('/tmp/adhoc.syndicate', cw.text); - const mm = cw.map; - mm.sourcesContent = [source]; - fs.writeFileSync('/tmp/adhoc.syndicate.map', JSON.stringify(mm)); + + return { + text: cw.text, + map: cw.map, + }; } diff --git a/packages/core/src/compiler/index.ts b/packages/core/src/compiler/index.ts index 05f3c9e..d9c7045 100644 --- a/packages/core/src/compiler/index.ts +++ b/packages/core/src/compiler/index.ts @@ -1 +1,4 @@ export * as Grammar from './grammar.js'; +export * as Internals from './internals.js'; +export * as Codegen from './codegen.js'; +export { compile, CompileOptions } from './codegen.js';