prototype-3x5/src/parser.test.ts

336 lines
10 KiB
TypeScript

import { parse } from './parser';
describe("Parsing Notcl", () => {
describe("Commands", () => {
it("can parse an empty script", () =>
expect(parse("")).toEqual([true, []]));
it("can parse a multi-word command", () =>
expect(parse("a b c")).toEqual([
true,
[[{ bare: "a" }, { bare: "b" }, { bare: "c" }]],
]));
it("accepts newlines as command separators", () =>
expect(parse("a\nb")).toEqual([
true,
[[{ bare: "a" }], [{ bare: "b" }]],
]));
it("does not split commands on folded newlines", () =>
expect(
parse(String.raw`a\
b`)
).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]]));
it("does split words on folded newlines", () =>
expect(
parse(String.raw`a\
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\\" }], [{ bare: "b" }]]]));
it("does not split commands on folded newlines with escaped backslashes", () =>
expect(
parse(String.raw`a\\\
b`)
).toEqual([true, [[{ text: "a\\" }, { bare: "b" }]]]));
it("accepts semicolons as command separators", () =>
expect(parse("a;b")).toEqual([true, [[{ bare: "a" }], [{ bare: "b" }]]]));
it("tolerates, and ignores, empty commands", () =>
expect(parse("a;;b\n\nc")).toEqual([
true,
[[{ 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, [[{ 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, [[{ bare: "a" }, { bare: "#1" }]]]));
it("can have commands before a comment", () =>
expect(parse("a ;#comment")).toEqual([true, [[{ bare: "a" }]]]));
it("ignores the whole line after a comment", () =>
expect(parse("# comment ; not a command")).toEqual([true, []]));
it("continues the comment through a folded newline", () =>
expect(
parse(String.raw`#a\
b`)
).toEqual([true, []]));
it("does not continue the comment through a newline with escaped backslashes", () =>
expect(
parse(String.raw`#a\\
b`)
).toEqual([true, [[{ bare: "b" }]]]));
it("continues the comment through a folded newline with escaped backslashes", () =>
expect(
parse(String.raw`#a\\\
b`)
).toEqual([true, []]));
});
describe("interpolated words", () => {
it("can parse a simple word", () =>
expect(parse("a")).toEqual([true, [[{ bare: "a" }]]]));
it("can parse a word with non-special punctuation", () =>
expect(parse("-switch")).toEqual([true, [[{ bare: "-switch" }]]]));
it("accepts empty quotes", () =>
expect(parse('""')).toEqual([true, [[{ text: "" }]]]));
it("accepts quoted words", () =>
expect(parse('"a"')).toEqual([true, [[{ text: "a" }]]]));
it("accepts quoted words with spaces", () =>
expect(parse('"a b"')).toEqual([true, [[{ text: "a b" }]]]));
it("allows escaped quotes inside a quote", () =>
expect(parse('"a\\"b"')).toEqual([true, [[{ text: 'a"b' }]]]));
it("must close quoted words", () =>
expect(parse('"a b')).toMatchObject([false, {}]));
it("does not allow trailing characters after a closing quote", () =>
expect(parse('""a')).toMatchObject([false, {}]));
it("accepts escaped spaces", () =>
expect(parse("a\\ b")).toEqual([true, [[{ text: "a b" }]]]));
it("treats a non-leading quote as a plain character", () =>
expect(parse('a"')).toEqual([true, [[{ bare: 'a"' }]]]));
it("treats a non-leading brace as a plain character", () =>
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", () =>
expect(parse("\\{")).toEqual([true, [[{ text: "{" }]]]));
it("treats a quoted brace as a plain character", () =>
expect(parse('"{"')).toEqual([true, [[{ text: "{" }]]]));
});
describe("backslash interpolation", () => {});
describe("variable interpolation", () => {
// variables- bare, brace
});
describe("command interpolation", () => {
it("can parse empty command interpolations", () =>
expect(parse("[]")).toEqual([true, [[{ pieces: [{ script: [] }] }]]]));
it("can parse one-word command interpolations", () =>
expect(parse("[a]")).toEqual([
true,
[[{ pieces: [{ script: [[{ bare: "a" }]] }] }]],
]));
it("can parse multi-word command interpolations", () =>
expect(parse("[a b c]")).toEqual([
true,
[
[
{
pieces: [
{
script: [[{ bare: "a" }, { bare: "b" }, { bare: "c" }]],
},
],
},
],
],
]));
it("can parse multi-command command interpolations", () =>
expect(parse("[a; b c]")).toEqual([
true,
[
[
{
pieces: [
{
script: [[{ bare: "a" }], [{ bare: "b" }, { bare: "c" }]],
},
],
},
],
],
]));
it("can parse nested command interpolations", () =>
expect(parse("[[a]]")).toEqual([
true,
[
[
{
pieces: [
{
script: [[{ pieces: [{ script: [[{ bare: "a" }]] }] }]],
},
],
},
],
],
]));
it("can parse pre-word command interpolations", () =>
expect(parse("[a]b")).toEqual([
true,
[
[
{
pieces: [{ script: [[{ bare: "a" }]] }, { bare: "b" }],
},
],
],
]));
it("can parse mid-word command interpolations", () =>
expect(parse("a[b]c")).toEqual([
true,
[
[
{
pieces: [
{ bare: "a" },
{ script: [[{ bare: "b" }]] },
{ bare: "c" },
],
},
],
],
]));
it("can parse end-word command interpolations", () =>
expect(parse("a[b]")).toEqual([
true,
[
[
{
pieces: [{ bare: "a" }, { script: [[{ bare: "b" }]] }],
},
],
],
]));
it("can parse multiple command interpolations in a word", () =>
expect(parse("[a][b]")).toEqual([
true,
[
[
{
pieces: [
{ script: [[{ bare: "a" }]] },
{ script: [[{ bare: "b" }]] },
],
},
],
],
]));
test.each(["[", "a["])(
"does not permit unterminated commands {%s}",
(text) => expect(parse(text)).toMatchObject([false, {}])
);
test.each(['["]"', '"["]', "[{]}", "{[}]"])(
"does not permit overlapping commands and quotes {%s}",
(text) => expect(parse(text)).toMatchObject([false, {}])
);
});
describe("brace words", () => {
it("can parse empty braces", () =>
expect(parse("{}")).toEqual([true, [[{ text: "" }]]]));
it("can parse braces with text", () =>
expect(parse("{a b c}")).toEqual([true, [[{ text: "a b c" }]]]));
it("can parse nested braces", () =>
expect(parse("{{}}")).toEqual([true, [[{ text: "{}" }]]]));
it("can parse nested braces with text", () =>
expect(parse("{a{b}c}")).toEqual([true, [[{ text: "a{b}c" }]]]));
it("must be closed", () =>
expect(parse("{a b")).toMatchObject([false, {}]));
it("does not allow trailing characters after a closing brace", () =>
expect(parse("{}a")).toMatchObject([false, {}]));
it("doesn't count suppressed braces for nesting", () =>
expect(parse(String.raw`{a\{b}`)).toEqual([true, [[{ text: "a\\{b" }]]]));
it("doesn't count suppressed braces for unnesting", () =>
expect(parse(String.raw`{a\}b}`)).toEqual([true, [[{ text: "a\\}b" }]]]));
it("nests braces after suppressed backslashes", () =>
expect(parse(String.raw`{a\\{b}}`)).toEqual([
true,
[[{ text: "a\\\\{b}" }]],
]));
it("permits newlines in braces", () =>
expect(parse("{\n}")).toEqual([true, [[{ text: "\n" }]]]));
it("folds newlines in braces", () =>
expect(
parse(String.raw`{\
}`)
).toEqual([true, [[{ text: " " }]]]));
it("doesn't fold newlines in braces with escaped backslashes", () =>
expect(
parse(String.raw`{\\
}`)
).toEqual([true, [[{ text: "\\\\\n" }]]]));
it("folds newlines in braces with escaped backslashes", () =>
expect(
parse(String.raw`{\\\
}`)
).toEqual([true, [[{ text: "\\\\ " }]]]));
test.each(['{"}"', '"{"}'])(
"does not permit overlapping braces and quotes {%s}",
(text) => expect(parse(text)).toMatchObject([false, {}])
);
});
describe("Misc", () => {
test("Big mess of markup", () => {
expect(
parse(String.raw`
h1 "Hello, World!"
para [2 + 2]
block {
This is a paragraph of text, with one [b bold] word. Yes, this means there has to be some magic in text processing... <b>this</b> won't work.
}
block -red "Beware!"
para "All text should be quoted, it's clearer that way. & blockquotes already should contain paragraphs. (maybe normalize nested paragraphs)"
block {
First block
} {
Second block
Is this markdown-parsed?
[button "No we want to render UI" \\{noop}]
} {
Since we want escapes to work, these blocks [i will] be subject to substitutions.
}
# A comment
para {
line endings escaped\
one slash
not escaped if \\
two slashes
escaped with a slash if \\\
three slashes
not escaped with two slashes if \\\\
four slashes
escaped with two slashes if \\\\\
five slashes
not escaped with three slashes if \\\\\\
six slashes
}
`)
).toMatchSnapshot();
});
});
});