Rename enchanted words to bare words

This commit is contained in:
Tangent Wantwight 2023-10-18 20:06:52 -04:00
parent 8863290725
commit 981ddd7a6d
7 changed files with 74 additions and 98 deletions

2
TODO
View File

@ -17,8 +17,6 @@
- rename peg.ts
- rename enchanted words -> bare words
- impl backslash escapes
- eval words
- error handling

View File

@ -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": "

View File

@ -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" }]] },
],
},
],

View File

@ -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));

View File

@ -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: "" };

View File

@ -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: "<pie>" }, "<pie>"],
[{ bare: "apple" }, "apple"],
[{ bare: "<pie>" }, "<pie>"],
[{ text: "banana" }, "banana"],
[{ text: "<b>kiwi" }, "<b>kiwi"],
[{ html: "<b>cherry</b>" }, "<b>cherry</b>"],
])("AsText(%s)", (word, expected) => expect(AsText(word)).toEqual(expected));
test.each([
[{ enchanted: "apple" }, "apple"],
[{ enchanted: "<pie>" }, "&lt;pie&gt;"],
[{ bare: "apple" }, "apple"],
[{ bare: "<pie>" }, "&lt;pie&gt;"],
[{ text: "banana" }, "banana"],
[{ text: "<b>kiwi" }, "&lt;b&gt;kiwi"],
[{ html: "<b>cherry</b>" }, "<b>cherry</b>"],
])("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: "<b>3</b>" }, { text: "<b>3</b>" }],
[null, { html: "2" }, { html: "2" }],
[null, { html: "<b>3</b>" }, { html: "<b>3</b>" }],
[{ enchanted: "&pple" }, { enchanted: "1" }, { text: "&pple1" }],
[{ enchanted: "&pple" }, { enchanted: ">1" }, { text: "&pple>1" }],
[{ enchanted: "&pple" }, { text: "2" }, { text: "&pple2" }],
[{ enchanted: "&pple" }, { text: "<b>3</b>" }, { text: "&pple<b>3</b>" }],
[{ enchanted: "&pple" }, { html: "2" }, { html: "&amp;pple2" }],
[
{ enchanted: "&pple" },
{ html: "<b>3</b>" },
{ html: "&amp;pple<b>3</b>" },
],
[{ text: "<b>anana" }, { enchanted: "1" }, { text: "<b>anana1" }],
[{ text: "<b>anana" }, { enchanted: ">1" }, { text: "<b>anana>1" }],
[{ bare: "&pple" }, { bare: "1" }, { text: "&pple1" }],
[{ bare: "&pple" }, { bare: ">1" }, { text: "&pple>1" }],
[{ bare: "&pple" }, { text: "2" }, { text: "&pple2" }],
[{ bare: "&pple" }, { text: "<b>3</b>" }, { text: "&pple<b>3</b>" }],
[{ bare: "&pple" }, { html: "2" }, { html: "&amp;pple2" }],
[{ bare: "&pple" }, { html: "<b>3</b>" }, { html: "&amp;pple<b>3</b>" }],
[{ text: "<b>anana" }, { bare: "1" }, { text: "<b>anana1" }],
[{ text: "<b>anana" }, { bare: ">1" }, { text: "<b>anana>1" }],
[{ text: "<b>anana" }, { text: "2" }, { text: "<b>anana2" }],
[{ text: "<b>anana" }, { text: "<b>3</b>" }, { text: "<b>anana<b>3</b>" }],
[{ text: "<b>anana" }, { html: "2" }, { html: "&lt;b&gt;anana2" }],
@ -44,8 +40,8 @@ describe("Text Words", () => {
{ html: "<b>3</b>" },
{ html: "&lt;b&gt;anana<b>3</b>" },
],
[{ html: "<img />" }, { enchanted: "1" }, { html: "<img />1" }],
[{ html: "<img />" }, { enchanted: ">1" }, { html: "<img />&gt;1" }],
[{ html: "<img />" }, { bare: "1" }, { html: "<img />1" }],
[{ html: "<img />" }, { bare: ">1" }, { html: "<img />&gt;1" }],
[{ html: "<img />" }, { text: "2" }, { html: "<img />2" }],
[
{ html: "<img />" },

View File

@ -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[];