diff --git a/package.json b/package.json index 94b6e7c..17b6b96 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,11 @@ "bin": { "please": "please" }, + "//": "The ugly `bash -c` contortions in the scripts below are to work around .", "scripts": { - "0": "node ./dist/language/cli/0.js", - "1": "node ./dist/language/cli/1.js", - "2": "node ./dist/language/cli/2.js", + "0": "bash -c 'node ./dist/language/cli/0.js \"$@\"; echo' bash", + "1": "bash -c 'node ./dist/language/cli/1.js \"$@\"; echo' bash", + "2": "bash -c 'node ./dist/language/cli/2.js \"$@\"; echo' bash", "build": "tsc --build tsconfig.app.json --force", "build:tests": "tsc --project tsconfig.app.json --outDir dist-test --declarationDir dist && tsc --build tsconfig.test.json", "clean": "rm -rf dist* *.tsbuildinfo", diff --git a/src/language/cli/output.ts b/src/language/cli/output.ts index 7605d32..8b04f31 100644 --- a/src/language/cli/output.ts +++ b/src/language/cli/output.ts @@ -69,19 +69,5 @@ export const writeOutput = ( throw new Error(outputAsString.value.message) } else { writeStream.write(outputAsString.value) - - // Writing a newline ensures that output is flushed before terminating, - // otherwise nothing may be printed to the console. See: - // - - // - - // - - // - …and many other near-duplicate issues - // - // I've tried other workarounds such as explicitly terminating via - // `process.exit`, passing a callback to `writeStream.write` (ensuring the - // returned `Promise` is not resolved until it is called), and explicitly - // calling `writeStream.end`/`writeStream.uncork` and so far this is the - // only workaround which reliably results in the desired behavior. - writeStream.write('\n') } } diff --git a/src/language/compiling/unparsing.test.ts b/src/language/compiling/unparsing.test.ts index a69ccb2..a3599f1 100644 --- a/src/language/compiling/unparsing.test.ts +++ b/src/language/compiling/unparsing.test.ts @@ -57,55 +57,55 @@ const outputs = ( testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( 'unparsing', [ - [{}, outputs({ inlinePlz: '{}', prettyPlz: '{}', prettyJson: '{}' })], - ['a', outputs({ inlinePlz: 'a', prettyPlz: 'a', prettyJson: '"a"' })], + [{}, outputs({ inlinePlz: '{}', prettyPlz: '{}\n', prettyJson: '{}\n' })], + ['a', outputs({ inlinePlz: 'a', prettyPlz: 'a\n', prettyJson: '"a"\n' })], [ 'Hello, world!', outputs({ inlinePlz: '"Hello, world!"', - prettyPlz: '"Hello, world!"', - prettyJson: '"Hello, world!"', + prettyPlz: '"Hello, world!"\n', + prettyJson: '"Hello, world!"\n', }), ], [ '@test', outputs({ inlinePlz: '"@test"', - prettyPlz: '"@test"', - prettyJson: '"@test"', + prettyPlz: '"@test"\n', + prettyJson: '"@test"\n', }), ], [ { 0: 'a' }, outputs({ inlinePlz: '{ a }', - prettyPlz: '{\n a\n}', - prettyJson: '{\n "0": "a"\n}', + prettyPlz: '{\n a\n}\n', + prettyJson: '{\n "0": "a"\n}\n', }), ], [ { 1: 'a' }, outputs({ inlinePlz: '{ 1: a }', - prettyPlz: '{\n 1: a\n}', - prettyJson: '{\n "1": "a"\n}', + prettyPlz: '{\n 1: a\n}\n', + prettyJson: '{\n "1": "a"\n}\n', }), ], [ { 0: 'a', 1: 'b', 3: 'c', somethingElse: 'd' }, outputs({ inlinePlz: '{ a, b, 3: c, somethingElse: d }', - prettyPlz: '{\n a\n b\n 3: c\n somethingElse: d\n}', + prettyPlz: '{\n a\n b\n 3: c\n somethingElse: d\n}\n', prettyJson: - '{\n "0": "a",\n "1": "b",\n "3": "c",\n "somethingElse": "d"\n}', + '{\n "0": "a",\n "1": "b",\n "3": "c",\n "somethingElse": "d"\n}\n', }), ], [ { a: { b: { c: 'd' } } }, outputs({ inlinePlz: '{ a: { b: { c: d } } }', - prettyPlz: '{\n a: {\n b: {\n c: d\n }\n }\n}', - prettyJson: '{\n "a": {\n "b": {\n "c": "d"\n }\n }\n}', + prettyPlz: '{\n a: {\n b: {\n c: d\n }\n }\n}\n', + prettyJson: '{\n "a": {\n "b": {\n "c": "d"\n }\n }\n}\n', }), ], [ @@ -127,9 +127,10 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( }, outputs({ inlinePlz: '{ identity: a => :a, test: :identity("it works!") }', - prettyPlz: '{\n identity: a => :a\n test: :identity("it works!")\n}', + prettyPlz: + '{\n identity: a => :a\n test: :identity("it works!")\n}\n', prettyJson: - '{\n "identity": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@lookup",\n "1": {\n "0": "a"\n }\n }\n }\n },\n "test": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "0": "identity"\n }\n },\n "argument": "it works!"\n }\n }\n}', + '{\n "identity": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@lookup",\n "1": {\n "0": "a"\n }\n }\n }\n },\n "test": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "0": "identity"\n }\n },\n "argument": "it works!"\n }\n }\n}\n', }), ], [ @@ -148,9 +149,9 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( }, outputs({ inlinePlz: '(a => :a)("it works!")', - prettyPlz: '(a => :a)("it works!")', + prettyPlz: '(a => :a)("it works!")\n', prettyJson: - '{\n "0": "@apply",\n "1": {\n "function": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@lookup",\n "1": {\n "0": "a"\n }\n }\n }\n },\n "argument": "it works!"\n }\n}', + '{\n "0": "@apply",\n "1": {\n "function": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@lookup",\n "1": {\n "0": "a"\n }\n }\n }\n },\n "argument": "it works!"\n }\n}\n', }), ], [ @@ -174,9 +175,9 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( }, outputs({ inlinePlz: '@runtime { context => :context.program.start_time }', - prettyPlz: '@runtime {\n context => :context.program.start_time\n}', + prettyPlz: '@runtime {\n context => :context.program.start_time\n}\n', prettyJson: - '{\n "0": "@runtime",\n "1": {\n "0": {\n "0": "@function",\n "1": {\n "parameter": "context",\n "body": {\n "0": "@index",\n "1": {\n "object": {\n "0": "@lookup",\n "1": {\n "key": "context"\n }\n },\n "query": {\n "0": "program",\n "1": "start_time"\n }\n }\n }\n }\n }\n }\n}', + '{\n "0": "@runtime",\n "1": {\n "0": {\n "0": "@function",\n "1": {\n "parameter": "context",\n "body": {\n "0": "@index",\n "1": {\n "object": {\n "0": "@lookup",\n "1": {\n "key": "context"\n }\n },\n "query": {\n "0": "program",\n "1": "start_time"\n }\n }\n }\n }\n }\n }\n}\n', }), ], [ @@ -198,9 +199,9 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( inlinePlz: '{ a.b: { "c \\"d\\"": { e.f: g } }, test: :"a.b"."c \\"d\\""."e.f" }', prettyPlz: - '{\n a.b: {\n "c \\"d\\"": {\n e.f: g\n }\n }\n test: :"a.b"."c \\"d\\""."e.f"\n}', + '{\n a.b: {\n "c \\"d\\"": {\n e.f: g\n }\n }\n test: :"a.b"."c \\"d\\""."e.f"\n}\n', prettyJson: - '{\n "a.b": {\n "c \\"d\\"": {\n "e.f": "g"\n }\n },\n "test": {\n "0": "@index",\n "1": {\n "object": {\n "0": "@lookup",\n "1": {\n "0": "a.b"\n }\n },\n "query": {\n "0": "c \\"d\\"",\n "1": "e.f"\n }\n }\n }\n}', + '{\n "a.b": {\n "c \\"d\\"": {\n "e.f": "g"\n }\n },\n "test": {\n "0": "@index",\n "1": {\n "object": {\n "0": "@lookup",\n "1": {\n "0": "a.b"\n }\n },\n "query": {\n "0": "c \\"d\\"",\n "1": "e.f"\n }\n }\n }\n}\n', }), ], [ @@ -224,9 +225,9 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( }, outputs({ inlinePlz: '1 + 2', - prettyPlz: '1 + 2', + prettyPlz: '1 + 2\n', prettyJson: - '{\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "2"\n }\n },\n "argument": "1"\n }\n}', + '{\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "2"\n }\n },\n "argument": "1"\n }\n}\n', }), ], [ @@ -347,9 +348,9 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( inlinePlz: '{ five: 5, answer: 1 + 2 - (3 + 4) < :five && :boolean.not(true) }', prettyPlz: - '{\n five: 5\n answer: 1 + 2 - (3 + 4) < :five && :boolean.not(true)\n}', + '{\n five: 5\n answer: 1 + 2 - (3 + 4) < :five && :boolean.not(true)\n}\n', prettyJson: - '{\n "five": "5",\n "answer": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "&&"\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@index",\n "1": {\n "object": {\n "0": "@lookup",\n "1": {\n "key": "boolean"\n }\n },\n "query": {\n "0": "not"\n }\n }\n },\n "argument": "true"\n }\n }\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "<"\n }\n },\n "argument": {\n "0": "@lookup",\n "1": {\n "key": "five"\n }\n }\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "-"\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "4"\n }\n },\n "argument": "3"\n }\n }\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "2"\n }\n },\n "argument": "1"\n }\n }\n }\n }\n }\n }\n }\n }\n}', + '{\n "five": "5",\n "answer": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "&&"\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@index",\n "1": {\n "object": {\n "0": "@lookup",\n "1": {\n "key": "boolean"\n }\n },\n "query": {\n "0": "not"\n }\n }\n },\n "argument": "true"\n }\n }\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "<"\n }\n },\n "argument": {\n "0": "@lookup",\n "1": {\n "key": "five"\n }\n }\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "-"\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "4"\n }\n },\n "argument": "3"\n }\n }\n }\n },\n "argument": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "2"\n }\n },\n "argument": "1"\n }\n }\n }\n }\n }\n }\n }\n }\n}\n', }), ], [ @@ -435,9 +436,9 @@ testCases(unparsers, input => `unparsing \`${JSON.stringify(input)}\``)( }, outputs({ inlinePlz: '((a => :a + 1) >> (a => :a - 1))(1)', - prettyPlz: '((a => :a + 1) >> (a => :a - 1))(1)', + prettyPlz: '((a => :a + 1) >> (a => :a - 1))(1)\n', prettyJson: - '{\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": ">>"\n }\n },\n "argument": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "-"\n }\n },\n "argument": "1"\n }\n },\n "argument": {\n "0": "@lookup",\n "1": {\n "key": "a"\n }\n }\n }\n }\n }\n }\n }\n },\n "argument": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "1"\n }\n },\n "argument": {\n "0": "@lookup",\n "1": {\n "key": "a"\n }\n }\n }\n }\n }\n }\n }\n },\n "argument": "1"\n }\n}', + '{\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": ">>"\n }\n },\n "argument": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "-"\n }\n },\n "argument": "1"\n }\n },\n "argument": {\n "0": "@lookup",\n "1": {\n "key": "a"\n }\n }\n }\n }\n }\n }\n }\n },\n "argument": {\n "0": "@function",\n "1": {\n "parameter": "a",\n "body": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@apply",\n "1": {\n "function": {\n "0": "@lookup",\n "1": {\n "key": "+"\n }\n },\n "argument": "1"\n }\n },\n "argument": {\n "0": "@lookup",\n "1": {\n "key": "a"\n }\n }\n }\n }\n }\n }\n }\n },\n "argument": "1"\n }\n}\n', }), ], ], diff --git a/src/language/unparsing.ts b/src/language/unparsing.ts index 0750bce..729d1ea 100644 --- a/src/language/unparsing.ts +++ b/src/language/unparsing.ts @@ -1,4 +1,5 @@ import type { Either } from '@matt.kantor/either' +import either from '@matt.kantor/either' import type { UnserializableValueError } from './errors.js' import type { Atom, Molecule } from './parsing.js' import type { Notation } from './unparsing/unparsing-utilities.js' @@ -13,6 +14,9 @@ export const unparse = ( value: Atom | Molecule, notation: Notation, ): Either => - typeof value === 'object' - ? notation.molecule(value, notation) - : notation.atom(value) + either.map( + typeof value === 'object' + ? notation.molecule(value, notation) + : notation.atom(value), + output => output.concat(notation.suffix), + ) diff --git a/src/language/unparsing/inline-plz.ts b/src/language/unparsing/inline-plz.ts index 28d7cf3..0f0d4f8 100644 --- a/src/language/unparsing/inline-plz.ts +++ b/src/language/unparsing/inline-plz.ts @@ -39,4 +39,5 @@ const unparseMolecule = moleculeUnparser( export const inlinePlz: Notation = { atom: unparseAtom, molecule: unparseMolecule, + suffix: '', } diff --git a/src/language/unparsing/pretty-json.ts b/src/language/unparsing/pretty-json.ts index 20a16f8..73a57c8 100644 --- a/src/language/unparsing/pretty-json.ts +++ b/src/language/unparsing/pretty-json.ts @@ -52,4 +52,5 @@ const unparseAtomOrMolecule = (value: Atom | Molecule) => export const prettyJson: Notation = { atom: unparseAtom, molecule: unparseMolecule, + suffix: '\n', } diff --git a/src/language/unparsing/pretty-plz.ts b/src/language/unparsing/pretty-plz.ts index 2b15cf2..ce13b9f 100644 --- a/src/language/unparsing/pretty-plz.ts +++ b/src/language/unparsing/pretty-plz.ts @@ -39,4 +39,5 @@ const unparseMolecule = moleculeUnparser( export const prettyPlz: Notation = { atom: unparseAtom, molecule: unparseMolecule, + suffix: '\n', } diff --git a/src/language/unparsing/sugar-free-pretty-plz.ts b/src/language/unparsing/sugar-free-pretty-plz.ts index a6742ab..610efad 100644 --- a/src/language/unparsing/sugar-free-pretty-plz.ts +++ b/src/language/unparsing/sugar-free-pretty-plz.ts @@ -30,4 +30,5 @@ const unparseAtomOrMolecule = (value: Atom | Molecule) => export const sugarFreePrettyPlz: Notation = { atom: unparseAtom, molecule: unparseMolecule, + suffix: '\n', } diff --git a/src/language/unparsing/unparsing-utilities.ts b/src/language/unparsing/unparsing-utilities.ts index d716f8c..7878814 100644 --- a/src/language/unparsing/unparsing-utilities.ts +++ b/src/language/unparsing/unparsing-utilities.ts @@ -9,6 +9,7 @@ export type Notation = { value: Molecule, notation: Notation, ) => Either + readonly suffix: string } export const indent = (spaces: number, textToIndent: string) => {