diff --git a/TODO b/TODO index bc5cd19..f641526 100644 --- a/TODO +++ b/TODO @@ -17,8 +17,6 @@ - rename peg.ts -- rename enchanted words -> bare words - - impl backslash escapes - eval words - error handling diff --git a/src/__snapshots__/parser.test.ts.snap b/src/__snapshots__/parser.test.ts.snap index 9e8fbdd..173dd18 100644 --- a/src/__snapshots__/parser.test.ts.snap +++ b/src/__snapshots__/parser.test.ts.snap @@ -6,7 +6,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` [ [ { - "enchanted": "h1", + "bare": "h1", }, { "text": "Hello, World!", @@ -14,7 +14,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` ], [ { - "enchanted": "para", + "bare": "para", }, { "pieces": [ @@ -22,13 +22,13 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` "script": [ [ { - "enchanted": "2", + "bare": "2", }, { - "enchanted": "+", + "bare": "+", }, { - "enchanted": "2", + "bare": "2", }, ], ], @@ -38,7 +38,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` ], [ { - "enchanted": "block", + "bare": "block", }, { "text": " @@ -48,10 +48,10 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` ], [ { - "enchanted": "block", + "bare": "block", }, { - "enchanted": "-red", + "bare": "-red", }, { "text": "Beware!", @@ -59,7 +59,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` ], [ { - "enchanted": "para", + "bare": "para", }, { "text": "All text should be quoted, it's clearer that way. & blockquotes already should contain paragraphs. (maybe normalize nested paragraphs)", @@ -67,7 +67,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` ], [ { - "enchanted": "block", + "bare": "block", }, { "text": " @@ -91,7 +91,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = ` ], [ { - "enchanted": "para", + "bare": "para", }, { "text": " diff --git a/src/parser.test.ts b/src/parser.test.ts index 24e56e2..41fc865 100644 --- a/src/parser.test.ts +++ b/src/parser.test.ts @@ -1,4 +1,4 @@ -import { parse } from "./parser"; +import { parse } from './parser'; describe("Parsing Notcl", () => { describe("Commands", () => { @@ -8,62 +8,56 @@ describe("Parsing Notcl", () => { it("can parse a multi-word command", () => expect(parse("a b c")).toEqual([ true, - [[{ enchanted: "a" }, { enchanted: "b" }, { enchanted: "c" }]], + [[{ bare: "a" }, { bare: "b" }, { bare: "c" }]], ])); it("accepts newlines as command separators", () => expect(parse("a\nb")).toEqual([ true, - [[{ enchanted: "a" }], [{ enchanted: "b" }]], + [[{ bare: "a" }], [{ bare: "b" }]], ])); it("does not split commands on folded newlines", () => expect( parse(String.raw`a\ b`) - ).toEqual([true, [[{ enchanted: "a" }, { enchanted: "b" }]]])); + ).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]])); it("does split words on folded newlines", () => expect( parse(String.raw`a\ b`) - ).toEqual([true, [[{ enchanted: "a" }, { enchanted: "b" }]]])); + ).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]])); it("does split commands on newlines with escaped backslashes", () => expect( parse(String.raw`a\\ b`) - ).toEqual([true, [[{ text: "a\\" }], [{ enchanted: "b" }]]])); + ).toEqual([true, [[{ text: "a\\" }], [{ bare: "b" }]]])); it("does not split commands on folded newlines with escaped backslashes", () => expect( parse(String.raw`a\\\ b`) - ).toEqual([true, [[{ text: "a\\" }, { enchanted: "b" }]]])); + ).toEqual([true, [[{ text: "a\\" }, { bare: "b" }]]])); it("accepts semicolons as command separators", () => - expect(parse("a;b")).toEqual([ - true, - [[{ enchanted: "a" }], [{ enchanted: "b" }]], - ])); + expect(parse("a;b")).toEqual([true, [[{ bare: "a" }], [{ bare: "b" }]]])); it("tolerates, and ignores, empty commands", () => expect(parse("a;;b\n\nc")).toEqual([ true, - [[{ enchanted: "a" }], [{ enchanted: "b" }], [{ enchanted: "c" }]], + [[{ bare: "a" }], [{ bare: "b" }], [{ bare: "c" }]], ])); test.each([[" a"], ["a "], ["a ;"], ["; a"]])( "tolerates whitespace before and after commands {%s}", - (text) => expect(parse(text)).toEqual([true, [[{ enchanted: "a" }]]]) + (text) => expect(parse(text)).toEqual([true, [[{ bare: "a" }]]]) ); }); describe("Comments", () => { it("ignores comments", () => expect(parse("#comment")).toEqual([true, []])); it("does not treat # in argument position as a comment", () => - expect(parse("a #1")).toEqual([ - true, - [[{ enchanted: "a" }, { enchanted: "#1" }]], - ])); + expect(parse("a #1")).toEqual([true, [[{ bare: "a" }, { bare: "#1" }]]])); it("can have commands before a comment", () => - expect(parse("a ;#comment")).toEqual([true, [[{ enchanted: "a" }]]])); + expect(parse("a ;#comment")).toEqual([true, [[{ bare: "a" }]]])); it("ignores the whole line after a comment", () => expect(parse("# comment ; not a command")).toEqual([true, []])); @@ -77,7 +71,7 @@ b`) expect( parse(String.raw`#a\\ b`) - ).toEqual([true, [[{ enchanted: "b" }]]])); + ).toEqual([true, [[{ bare: "b" }]]])); it("continues the comment through a folded newline with escaped backslashes", () => expect( parse(String.raw`#a\\\ @@ -87,9 +81,9 @@ b`) describe("interpolated words", () => { it("can parse a simple word", () => - expect(parse("a")).toEqual([true, [[{ enchanted: "a" }]]])); + expect(parse("a")).toEqual([true, [[{ bare: "a" }]]])); it("can parse a word with non-special punctuation", () => - expect(parse("-switch")).toEqual([true, [[{ enchanted: "-switch" }]]])); + expect(parse("-switch")).toEqual([true, [[{ bare: "-switch" }]]])); it("accepts empty quotes", () => expect(parse('""')).toEqual([true, [[{ text: "" }]]])); @@ -110,9 +104,9 @@ b`) expect(parse("a\\ b")).toEqual([true, [[{ text: "a b" }]]])); it("treats a non-leading quote as a plain character", () => - expect(parse('a"')).toEqual([true, [[{ enchanted: 'a"' }]]])); + expect(parse('a"')).toEqual([true, [[{ bare: 'a"' }]]])); it("treats a non-leading brace as a plain character", () => - expect(parse("a{")).toEqual([true, [[{ enchanted: "a{" }]]])); + expect(parse("a{")).toEqual([true, [[{ bare: "a{" }]]])); it("treats an escaped quote as a plain character", () => expect(parse('\\"')).toEqual([true, [[{ text: '"' }]]])); it("treats an escaped brace as a plain character", () => @@ -133,7 +127,7 @@ b`) it("can parse one-word command interpolations", () => expect(parse("[a]")).toEqual([ true, - [[{ pieces: [{ script: [[{ enchanted: "a" }]] }] }]], + [[{ pieces: [{ script: [[{ bare: "a" }]] }] }]], ])); it("can parse multi-word command interpolations", () => expect(parse("[a b c]")).toEqual([ @@ -143,13 +137,7 @@ b`) { pieces: [ { - script: [ - [ - { enchanted: "a" }, - { enchanted: "b" }, - { enchanted: "c" }, - ], - ], + script: [[{ bare: "a" }, { bare: "b" }, { bare: "c" }]], }, ], }, @@ -164,10 +152,7 @@ b`) { pieces: [ { - script: [ - [{ enchanted: "a" }], - [{ enchanted: "b" }, { enchanted: "c" }], - ], + script: [[{ bare: "a" }], [{ bare: "b" }, { bare: "c" }]], }, ], }, @@ -182,7 +167,7 @@ b`) { pieces: [ { - script: [[{ pieces: [{ script: [[{ enchanted: "a" }]] }] }]], + script: [[{ pieces: [{ script: [[{ bare: "a" }]] }] }]], }, ], }, @@ -196,7 +181,7 @@ b`) [ [ { - pieces: [{ script: [[{ enchanted: "a" }]] }, { enchanted: "b" }], + pieces: [{ script: [[{ bare: "a" }]] }, { bare: "b" }], }, ], ], @@ -208,9 +193,9 @@ b`) [ { pieces: [ - { enchanted: "a" }, - { script: [[{ enchanted: "b" }]] }, - { enchanted: "c" }, + { bare: "a" }, + { script: [[{ bare: "b" }]] }, + { bare: "c" }, ], }, ], @@ -222,7 +207,7 @@ b`) [ [ { - pieces: [{ enchanted: "a" }, { script: [[{ enchanted: "b" }]] }], + pieces: [{ bare: "a" }, { script: [[{ bare: "b" }]] }], }, ], ], @@ -234,8 +219,8 @@ b`) [ { pieces: [ - { script: [[{ enchanted: "a" }]] }, - { script: [[{ enchanted: "b" }]] }, + { script: [[{ bare: "a" }]] }, + { script: [[{ bare: "b" }]] }, ], }, ], diff --git a/src/parser.ts b/src/parser.ts index 354bef5..67c1631 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -36,7 +36,7 @@ function bareWordTmpl(charRegex: RegExp) { Bracket, Regex(charRegex) .expects("CHAR") - .map(([text]) => ({ enchanted: text })) + .map(([text]) => ({ bare: text })) ) ) ).map(([, pieces]) => SimplifyWord(pieces)); diff --git a/src/vm.ts b/src/vm.ts index fb8fa7d..9294a35 100644 --- a/src/vm.ts +++ b/src/vm.ts @@ -1,6 +1,5 @@ import { - AsHtml, AsText, Concat, EnchantedWord, HtmlWord, InterpolatedPiece, Script, TextPiece, TextWord, - Word + AsHtml, AsText, BareWord, Concat, HtmlWord, InterpolatedPiece, Script, TextPiece, TextWord, Word } from './words'; /** @@ -29,8 +28,8 @@ export type Vm = { function evaluateWord( state: Vm, word: Word | InterpolatedPiece -): TextWord | EnchantedWord | HtmlWord { - if ("enchanted" in word || "text" in word || "html" in word) { +): TextWord | BareWord | HtmlWord { + if ("bare" in word || "text" in word || "html" in word) { return word; } else if ("variable" in word) { return { text: "" }; diff --git a/src/words.test.ts b/src/words.test.ts index f55bdb8..13488ff 100644 --- a/src/words.test.ts +++ b/src/words.test.ts @@ -1,41 +1,37 @@ -import { AsHtml, AsText, Concat } from "./words"; +import { AsHtml, AsText, Concat } from './words'; describe("Text Words", () => { test.each([ - [{ enchanted: "apple" }, "apple"], - [{ enchanted: "" }, ""], + [{ bare: "apple" }, "apple"], + [{ bare: "" }, ""], [{ text: "banana" }, "banana"], [{ text: "kiwi" }, "kiwi"], [{ html: "cherry" }, "cherry"], ])("AsText(%s)", (word, expected) => expect(AsText(word)).toEqual(expected)); test.each([ - [{ enchanted: "apple" }, "apple"], - [{ enchanted: "" }, "<pie>"], + [{ bare: "apple" }, "apple"], + [{ bare: "" }, "<pie>"], [{ text: "banana" }, "banana"], [{ text: "kiwi" }, "<b>kiwi"], [{ html: "cherry" }, "cherry"], ])("AsHtml(%s)", (word, expected) => expect(AsHtml(word)).toEqual(expected)); test.each([ - [null, { enchanted: "1" }, { text: "1" }], - [null, { enchanted: ">1" }, { text: ">1" }], + [null, { bare: "1" }, { text: "1" }], + [null, { bare: ">1" }, { text: ">1" }], [null, { text: "2" }, { text: "2" }], [null, { text: "3" }, { text: "3" }], [null, { html: "2" }, { html: "2" }], [null, { html: "3" }, { html: "3" }], - [{ enchanted: "&pple" }, { enchanted: "1" }, { text: "&pple1" }], - [{ enchanted: "&pple" }, { enchanted: ">1" }, { text: "&pple>1" }], - [{ enchanted: "&pple" }, { text: "2" }, { text: "&pple2" }], - [{ enchanted: "&pple" }, { text: "3" }, { text: "&pple3" }], - [{ enchanted: "&pple" }, { html: "2" }, { html: "&pple2" }], - [ - { enchanted: "&pple" }, - { html: "3" }, - { html: "&pple3" }, - ], - [{ text: "anana" }, { enchanted: "1" }, { text: "anana1" }], - [{ text: "anana" }, { enchanted: ">1" }, { text: "anana>1" }], + [{ bare: "&pple" }, { bare: "1" }, { text: "&pple1" }], + [{ bare: "&pple" }, { bare: ">1" }, { text: "&pple>1" }], + [{ bare: "&pple" }, { text: "2" }, { text: "&pple2" }], + [{ bare: "&pple" }, { text: "3" }, { text: "&pple3" }], + [{ bare: "&pple" }, { html: "2" }, { html: "&pple2" }], + [{ bare: "&pple" }, { html: "3" }, { html: "&pple3" }], + [{ text: "anana" }, { bare: "1" }, { text: "anana1" }], + [{ text: "anana" }, { bare: ">1" }, { text: "anana>1" }], [{ text: "anana" }, { text: "2" }, { text: "anana2" }], [{ text: "anana" }, { text: "3" }, { text: "anana3" }], [{ text: "anana" }, { html: "2" }, { html: "<b>anana2" }], @@ -44,8 +40,8 @@ describe("Text Words", () => { { html: "3" }, { html: "<b>anana3" }, ], - [{ html: "" }, { enchanted: "1" }, { html: "1" }], - [{ html: "" }, { enchanted: ">1" }, { html: ">1" }], + [{ html: "" }, { bare: "1" }, { html: "1" }], + [{ html: "" }, { bare: ">1" }, { html: ">1" }], [{ html: "" }, { text: "2" }, { html: "2" }], [ { html: "" }, diff --git a/src/words.ts b/src/words.ts index 01fe836..d4503d0 100644 --- a/src/words.ts +++ b/src/words.ts @@ -1,8 +1,8 @@ -import { escapeHtml } from "./helpers"; +import { escapeHtml } from './helpers'; /** - * A word whose value is text with provenance- this literal value appeared in the source - * code, and was not the result of any backslash, variable, or command substitutions. + * A word whose value is text with provenance- this literal value appeared in the source code, + * and was neither quoted nor the result of any backslash, variable, or command substitutions. * * This provides a level of intentionality that commands can use to distinguish switches: * @@ -13,8 +13,8 @@ import { escapeHtml } from "./helpers"; * puts $var text ;# The value of $var is part of the message to print, even if the value happens to be "-stderr" * ``` */ -export type EnchantedWord = { - enchanted: string; +export type BareWord = { + bare: string; }; /** @@ -32,7 +32,7 @@ export type HtmlWord = { html: string; }; -export type TextPiece = EnchantedWord | TextWord | HtmlWord; +export type TextPiece = BareWord | TextWord | HtmlWord; export type VariablePiece = { variable: string }; export type ScriptPiece = { script: Script }; export type InterpolatedPiece = TextPiece | VariablePiece | ScriptPiece; @@ -46,8 +46,8 @@ export type InterpolatedWord = { }; export function AsText(word: TextPiece): string { - if ("enchanted" in word) { - return word.enchanted; + if ("bare" in word) { + return word.bare; } else if ("text" in word) { return word.text; } else if ("html" in word) { @@ -57,8 +57,8 @@ export function AsText(word: TextPiece): string { } } export function AsHtml(word: TextPiece): string { - if ("enchanted" in word) { - return escapeHtml(word.enchanted); + if ("bare" in word) { + return escapeHtml(word.bare); } else if ("text" in word) { return escapeHtml(word.text); } else if ("html" in word) { @@ -71,7 +71,7 @@ export function AsHtml(word: TextPiece): string { // safely concatenate text pieces, converting as needed export function Concat(left: TextPiece | null, right: TextPiece) { if (left === null) { - return "enchanted" in right ? { text: right.enchanted } : right; + return "bare" in right ? { text: right.bare } : right; } if ("html" in left || "html" in right) { return { html: AsHtml(left) + AsHtml(right) }; @@ -81,14 +81,12 @@ export function Concat(left: TextPiece | null, right: TextPiece) { } function IsTextPiece(piece: InterpolatedPiece | undefined): piece is TextPiece { - return piece - ? "text" in piece || "enchanted" in piece || "html" in piece - : false; + return piece ? "text" in piece || "bare" in piece || "html" in piece : false; } export function SimplifyWord( pieces: InterpolatedPiece[] -): InterpolatedWord | EnchantedWord | TextWord | HtmlWord { +): InterpolatedWord | BareWord | TextWord | HtmlWord { const consolidated: InterpolatedPiece[] = []; for (const piece of pieces) { const top = consolidated[consolidated.length - 1]; @@ -108,6 +106,6 @@ export function SimplifyWord( } } -export type Word = EnchantedWord | TextWord | HtmlWord | InterpolatedWord; +export type Word = BareWord | TextWord | HtmlWord | InterpolatedWord; export type Command = Word[]; export type Script = Command[];