2023-10-19 00:06:52 +00:00
|
|
|
import { parse } from './parser';
|
2023-08-06 06:38:52 +00:00
|
|
|
|
|
|
|
describe("Parsing Notcl", () => {
|
|
|
|
describe("Commands", () => {
|
2023-08-06 23:52:00 +00:00
|
|
|
it("can parse an empty script", () =>
|
2023-08-06 06:38:52 +00:00
|
|
|
expect(parse("")).toEqual([true, []]));
|
2023-08-06 23:52:00 +00:00
|
|
|
|
|
|
|
it("can parse a multi-word command", () =>
|
|
|
|
expect(parse("a b c")).toEqual([
|
|
|
|
true,
|
2023-10-19 00:06:52 +00:00
|
|
|
[[{ bare: "a" }, { bare: "b" }, { bare: "c" }]],
|
2023-08-06 23:52:00 +00:00
|
|
|
]));
|
|
|
|
|
|
|
|
it("accepts newlines as command separators", () =>
|
|
|
|
expect(parse("a\nb")).toEqual([
|
|
|
|
true,
|
2023-10-19 00:06:52 +00:00
|
|
|
[[{ bare: "a" }], [{ bare: "b" }]],
|
2023-08-06 23:52:00 +00:00
|
|
|
]));
|
|
|
|
it("does not split commands on folded newlines", () =>
|
|
|
|
expect(
|
|
|
|
parse(String.raw`a\
|
|
|
|
b`)
|
2023-10-19 00:06:52 +00:00
|
|
|
).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]]));
|
2023-08-07 00:18:38 +00:00
|
|
|
it("does split words on folded newlines", () =>
|
|
|
|
expect(
|
|
|
|
parse(String.raw`a\
|
|
|
|
b`)
|
2023-10-19 00:06:52 +00:00
|
|
|
).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]]));
|
2023-08-07 00:18:38 +00:00
|
|
|
it("does split commands on newlines with escaped backslashes", () =>
|
2023-08-06 23:52:00 +00:00
|
|
|
expect(
|
|
|
|
parse(String.raw`a\\
|
|
|
|
b`)
|
2023-10-19 00:06:52 +00:00
|
|
|
).toEqual([true, [[{ text: "a\\" }], [{ bare: "b" }]]]));
|
2023-08-06 23:52:00 +00:00
|
|
|
it("does not split commands on folded newlines with escaped backslashes", () =>
|
|
|
|
expect(
|
|
|
|
parse(String.raw`a\\\
|
|
|
|
b`)
|
2023-10-19 00:06:52 +00:00
|
|
|
).toEqual([true, [[{ text: "a\\" }, { bare: "b" }]]]));
|
2023-08-06 23:52:00 +00:00
|
|
|
|
|
|
|
it("accepts semicolons as command separators", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse("a;b")).toEqual([true, [[{ bare: "a" }], [{ bare: "b" }]]]));
|
2023-08-06 23:52:00 +00:00
|
|
|
|
|
|
|
it("tolerates, and ignores, empty commands", () =>
|
|
|
|
expect(parse("a;;b\n\nc")).toEqual([
|
|
|
|
true,
|
2023-10-19 00:06:52 +00:00
|
|
|
[[{ bare: "a" }], [{ bare: "b" }], [{ bare: "c" }]],
|
2023-08-06 23:52:00 +00:00
|
|
|
]));
|
|
|
|
|
|
|
|
test.each([[" a"], ["a "], ["a ;"], ["; a"]])(
|
|
|
|
"tolerates whitespace before and after commands {%s}",
|
2023-10-19 00:06:52 +00:00
|
|
|
(text) => expect(parse(text)).toEqual([true, [[{ bare: "a" }]]])
|
2023-08-06 23:52:00 +00:00
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("Comments", () => {
|
|
|
|
it("ignores comments", () => expect(parse("#comment")).toEqual([true, []]));
|
|
|
|
it("does not treat # in argument position as a comment", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse("a #1")).toEqual([true, [[{ bare: "a" }, { bare: "#1" }]]]));
|
2023-08-06 23:52:00 +00:00
|
|
|
it("can have commands before a comment", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse("a ;#comment")).toEqual([true, [[{ bare: "a" }]]]));
|
2023-08-06 23:52:00 +00:00
|
|
|
|
|
|
|
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`)
|
2023-10-19 00:06:52 +00:00
|
|
|
).toEqual([true, [[{ bare: "b" }]]]));
|
2023-08-06 23:52:00 +00:00
|
|
|
it("continues the comment through a folded newline with escaped backslashes", () =>
|
|
|
|
expect(
|
|
|
|
parse(String.raw`#a\\\
|
|
|
|
b`)
|
|
|
|
).toEqual([true, []]));
|
2023-08-06 06:38:52 +00:00
|
|
|
});
|
2023-08-06 23:52:00 +00:00
|
|
|
|
2023-09-01 02:58:44 +00:00
|
|
|
describe("interpolated words", () => {
|
2023-08-25 15:13:00 +00:00
|
|
|
it("can parse a simple word", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse("a")).toEqual([true, [[{ bare: "a" }]]]));
|
2023-08-25 15:13:00 +00:00
|
|
|
it("can parse a word with non-special punctuation", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse("-switch")).toEqual([true, [[{ bare: "-switch" }]]]));
|
2023-08-11 01:51:23 +00:00
|
|
|
|
2023-08-23 05:09:56 +00:00
|
|
|
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' }]]]));
|
|
|
|
|
2023-08-25 20:28:48 +00:00
|
|
|
it("must close quoted words", () =>
|
|
|
|
expect(parse('"a b')).toMatchObject([false, {}]));
|
|
|
|
|
2023-08-23 05:09:56 +00:00
|
|
|
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", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse('a"')).toEqual([true, [[{ bare: 'a"' }]]]));
|
2023-08-23 05:09:56 +00:00
|
|
|
it("treats a non-leading brace as a plain character", () =>
|
2023-10-19 00:06:52 +00:00
|
|
|
expect(parse("a{")).toEqual([true, [[{ bare: "a{" }]]]));
|
2023-08-23 05:09:56 +00:00
|
|
|
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: "{" }]]]));
|
2023-08-25 15:13:00 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
describe("backslash interpolation", () => {});
|
2023-08-23 05:09:56 +00:00
|
|
|
|
2023-08-25 15:13:00 +00:00
|
|
|
describe("variable interpolation", () => {
|
2023-08-23 05:09:56 +00:00
|
|
|
// variables- bare, brace
|
|
|
|
});
|
2023-08-11 01:51:23 +00:00
|
|
|
|
2023-08-25 23:10:45 +00:00
|
|
|
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,
|
2023-10-19 00:06:52 +00:00
|
|
|
[[{ pieces: [{ script: [[{ bare: "a" }]] }] }]],
|
2023-08-25 23:10:45 +00:00
|
|
|
]));
|
|
|
|
it("can parse multi-word command interpolations", () =>
|
|
|
|
expect(parse("[a b c]")).toEqual([
|
|
|
|
true,
|
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pieces: [
|
|
|
|
{
|
2023-10-19 00:06:52 +00:00
|
|
|
script: [[{ bare: "a" }, { bare: "b" }, { bare: "c" }]],
|
2023-08-25 23:10:45 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
|
|
|
]));
|
|
|
|
it("can parse multi-command command interpolations", () =>
|
|
|
|
expect(parse("[a; b c]")).toEqual([
|
|
|
|
true,
|
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pieces: [
|
|
|
|
{
|
2023-10-19 00:06:52 +00:00
|
|
|
script: [[{ bare: "a" }], [{ bare: "b" }, { bare: "c" }]],
|
2023-08-25 23:10:45 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
|
|
|
]));
|
|
|
|
it("can parse nested command interpolations", () =>
|
|
|
|
expect(parse("[[a]]")).toEqual([
|
|
|
|
true,
|
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pieces: [
|
|
|
|
{
|
2023-10-19 00:06:52 +00:00
|
|
|
script: [[{ pieces: [{ script: [[{ bare: "a" }]] }] }]],
|
2023-08-25 23:10:45 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
|
|
|
]));
|
|
|
|
|
|
|
|
it("can parse pre-word command interpolations", () =>
|
|
|
|
expect(parse("[a]b")).toEqual([
|
|
|
|
true,
|
2023-09-01 02:58:44 +00:00
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
2023-10-19 00:06:52 +00:00
|
|
|
pieces: [{ script: [[{ bare: "a" }]] }, { bare: "b" }],
|
2023-09-01 02:58:44 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
2023-08-25 23:10:45 +00:00
|
|
|
]));
|
|
|
|
it("can parse mid-word command interpolations", () =>
|
|
|
|
expect(parse("a[b]c")).toEqual([
|
|
|
|
true,
|
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pieces: [
|
2023-10-19 00:06:52 +00:00
|
|
|
{ bare: "a" },
|
|
|
|
{ script: [[{ bare: "b" }]] },
|
|
|
|
{ bare: "c" },
|
2023-08-25 23:10:45 +00:00
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
|
|
|
]));
|
|
|
|
it("can parse end-word command interpolations", () =>
|
|
|
|
expect(parse("a[b]")).toEqual([
|
|
|
|
true,
|
2023-09-01 02:58:44 +00:00
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
2023-10-19 00:06:52 +00:00
|
|
|
pieces: [{ bare: "a" }, { script: [[{ bare: "b" }]] }],
|
2023-09-01 02:58:44 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
2023-08-25 23:10:45 +00:00
|
|
|
]));
|
|
|
|
it("can parse multiple command interpolations in a word", () =>
|
|
|
|
expect(parse("[a][b]")).toEqual([
|
|
|
|
true,
|
|
|
|
[
|
|
|
|
[
|
|
|
|
{
|
|
|
|
pieces: [
|
2023-10-19 00:06:52 +00:00
|
|
|
{ script: [[{ bare: "a" }]] },
|
|
|
|
{ script: [[{ bare: "b" }]] },
|
2023-08-25 23:10:45 +00:00
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
],
|
|
|
|
]));
|
|
|
|
|
|
|
|
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, {}])
|
|
|
|
);
|
|
|
|
});
|
2023-08-25 15:13:00 +00:00
|
|
|
|
2023-08-11 01:51:23 +00:00
|
|
|
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" }]]]));
|
|
|
|
|
2023-08-25 20:28:48 +00:00
|
|
|
it("must be closed", () =>
|
|
|
|
expect(parse("{a b")).toMatchObject([false, {}]));
|
|
|
|
|
2023-08-22 03:57:27 +00:00
|
|
|
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" }]]]));
|
2023-08-11 01:51:23 +00:00
|
|
|
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: "\\\\ " }]]]));
|
2023-08-25 20:28:48 +00:00
|
|
|
|
|
|
|
test.each(['{"}"', '"{"}'])(
|
|
|
|
"does not permit overlapping braces and quotes {%s}",
|
|
|
|
(text) => expect(parse(text)).toMatchObject([false, {}])
|
|
|
|
);
|
2023-08-11 01:51:23 +00:00
|
|
|
});
|
2023-08-06 23:52:00 +00:00
|
|
|
|
2023-08-07 03:10:12 +00:00
|
|
|
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();
|
|
|
|
});
|
|
|
|
});
|
2023-08-06 06:38:52 +00:00
|
|
|
});
|