Add source-position information to bare words

This commit is contained in:
Tangent Wantwight 2023-11-18 18:37:58 -05:00
parent 6ca93d6615
commit d57f409a13
4 changed files with 113 additions and 34 deletions

View file

@ -7,6 +7,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "h1",
"pos": 5,
},
{
"text": "Hello, World!",
@ -15,6 +16,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "para",
"pos": 28,
},
{
"pieces": [
@ -23,12 +25,15 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "2",
"pos": 34,
},
{
"bare": "+",
"pos": 36,
},
{
"bare": "2",
"pos": 38,
},
],
],
@ -39,6 +44,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "block",
"pos": 45,
},
{
"text": "
@ -49,9 +55,11 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "block",
"pos": 213,
},
{
"bare": "-red",
"pos": 219,
},
{
"text": "Beware!",
@ -60,6 +68,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "para",
"pos": 238,
},
{
"text": "All text should be quoted, it's clearer that way. & blockquotes already should contain paragraphs. (maybe normalize nested paragraphs)",
@ -68,6 +77,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "block",
"pos": 384,
},
{
"text": "
@ -92,6 +102,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
[
{
"bare": "para",
"pos": 651,
},
{
"text": "

View file

@ -8,56 +8,97 @@ describe("Parsing Notcl", () => {
it("can parse a multi-word command", () =>
expect(parse("a b c")).toEqual([
true,
[[{ bare: "a" }, { bare: "b" }, { bare: "c" }]],
[
[
{ bare: "a", pos: 0 },
{ bare: "b", pos: 2 },
{ bare: "c", pos: 5 },
],
],
]));
it("accepts newlines as command separators", () =>
expect(parse("a\nb")).toEqual([
true,
[[{ bare: "a" }], [{ bare: "b" }]],
[[{ bare: "a", pos: 0 }], [{ bare: "b", pos: 2 }]],
]));
it("does not split commands on folded newlines", () =>
expect(
parse(String.raw`a\
b`)
).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]]));
).toEqual([
true,
[
[
{ bare: "a", pos: 0 },
{ bare: "b", pos: 2 },
],
],
]));
it("does split words on folded newlines", () =>
expect(
parse(String.raw`a\
b`)
).toEqual([true, [[{ bare: "a" }, { bare: "b" }]]]));
).toEqual([
true,
[
[
{ bare: "a", pos: 0 },
{ bare: "b", pos: 2 },
],
],
]));
it("does split commands on newlines with escaped backslashes", () =>
expect(
parse(String.raw`a\\
b`)
).toEqual([true, [[{ text: "a\\" }], [{ bare: "b" }]]]));
b`)
).toEqual([true, [[{ text: "a\\" }], [{ bare: "b", pos: 4 }]]]));
it("does not split commands on folded newlines with escaped backslashes", () =>
expect(
parse(String.raw`a\\\
b`)
).toEqual([true, [[{ text: "a\\" }, { bare: "b" }]]]));
).toEqual([true, [[{ text: "a\\" }, { bare: "b", pos: 4 }]]]));
it("accepts semicolons as command separators", () =>
expect(parse("a;b")).toEqual([true, [[{ bare: "a" }], [{ bare: "b" }]]]));
expect(parse("a;b")).toEqual([
true,
[[{ bare: "a", pos: 0 }], [{ bare: "b", pos: 2 }]],
]));
it("tolerates, and ignores, empty commands", () =>
expect(parse("a;;b\n\nc")).toEqual([
true,
[[{ bare: "a" }], [{ bare: "b" }], [{ bare: "c" }]],
[
[{ bare: "a", pos: 0 }],
[{ bare: "b", pos: 3 }],
[{ bare: "c", pos: 6 }],
],
]));
test.each([[" a"], ["a "], ["a ;"], ["; a"]])(
"tolerates whitespace before and after commands {%s}",
(text) => expect(parse(text)).toEqual([true, [[{ bare: "a" }]]])
test.each([
[" a", 1],
["a ", 0],
["a ;", 0],
["; a", 2],
])("tolerates whitespace before and after commands {%s}", (text, pos) =>
expect(parse(text)).toEqual([true, [[{ bare: "a", pos }]]])
);
});
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" }]]]));
expect(parse("a #1")).toEqual([
true,
[
[
{ bare: "a", pos: 0 },
{ bare: "#1", pos: 2 },
],
],
]));
it("can have commands before a comment", () =>
expect(parse("a ;#comment")).toEqual([true, [[{ bare: "a" }]]]));
expect(parse("a ;#comment")).toEqual([true, [[{ bare: "a", pos: 0 }]]]));
it("ignores the whole line after a comment", () =>
expect(parse("# comment ; not a command")).toEqual([true, []]));
@ -70,8 +111,8 @@ b`)
it("does not continue the comment through a newline with escaped backslashes", () =>
expect(
parse(String.raw`#a\\
b`)
).toEqual([true, [[{ bare: "b" }]]]));
b`)
).toEqual([true, [[{ bare: "b", pos: 5 }]]]));
it("continues the comment through a folded newline with escaped backslashes", () =>
expect(
parse(String.raw`#a\\\
@ -81,9 +122,12 @@ b`)
describe("interpolated words", () => {
it("can parse a simple word", () =>
expect(parse("a")).toEqual([true, [[{ bare: "a" }]]]));
expect(parse("a")).toEqual([true, [[{ bare: "a", pos: 0 }]]]));
it("can parse a word with non-special punctuation", () =>
expect(parse("-switch")).toEqual([true, [[{ bare: "-switch" }]]]));
expect(parse("-switch")).toEqual([
true,
[[{ bare: "-switch", pos: 0 }]],
]));
it("accepts empty quotes", () =>
expect(parse('""')).toEqual([true, [[{ text: "" }]]]));
@ -104,9 +148,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, [[{ bare: 'a"' }]]]));
expect(parse('a"')).toEqual([true, [[{ bare: 'a"', pos: 0 }]]]));
it("treats a non-leading brace as a plain character", () =>
expect(parse("a{")).toEqual([true, [[{ bare: "a{" }]]]));
expect(parse("a{")).toEqual([true, [[{ bare: "a{", pos: 0 }]]]));
it("treats an escaped quote as a plain character", () =>
expect(parse('\\"')).toEqual([true, [[{ text: '"' }]]]));
it("treats an escaped brace as a plain character", () =>
@ -127,7 +171,7 @@ b`)
it("can parse one-word command interpolations", () =>
expect(parse("[a]")).toEqual([
true,
[[{ pieces: [{ script: [[{ bare: "a" }]] }] }]],
[[{ pieces: [{ script: [[{ bare: "a", pos: 1 }]] }] }]],
]));
it("can parse multi-word command interpolations", () =>
expect(parse("[a b c]")).toEqual([
@ -137,7 +181,13 @@ b`)
{
pieces: [
{
script: [[{ bare: "a" }, { bare: "b" }, { bare: "c" }]],
script: [
[
{ bare: "a", pos: 1 },
{ bare: "b", pos: 3 },
{ bare: "c", pos: 5 },
],
],
},
],
},
@ -152,7 +202,13 @@ b`)
{
pieces: [
{
script: [[{ bare: "a" }], [{ bare: "b" }, { bare: "c" }]],
script: [
[{ bare: "a", pos: 1 }],
[
{ bare: "b", pos: 4 },
{ bare: "c", pos: 6 },
],
],
},
],
},
@ -167,7 +223,9 @@ b`)
{
pieces: [
{
script: [[{ pieces: [{ script: [[{ bare: "a" }]] }] }]],
script: [
[{ pieces: [{ script: [[{ bare: "a", pos: 2 }]] }] }],
],
},
],
},
@ -181,7 +239,10 @@ b`)
[
[
{
pieces: [{ script: [[{ bare: "a" }]] }, { bare: "b" }],
pieces: [
{ script: [[{ bare: "a", pos: 1 }]] },
{ bare: "b", pos: 3 },
],
},
],
],
@ -193,9 +254,9 @@ b`)
[
{
pieces: [
{ bare: "a" },
{ script: [[{ bare: "b" }]] },
{ bare: "c" },
{ bare: "a", pos: 0 },
{ script: [[{ bare: "b", pos: 2 }]] },
{ bare: "c", pos: 4 },
],
},
],
@ -207,7 +268,10 @@ b`)
[
[
{
pieces: [{ bare: "a" }, { script: [[{ bare: "b" }]] }],
pieces: [
{ bare: "a", pos: 0 },
{ script: [[{ bare: "b", pos: 2 }]] },
],
},
],
],
@ -219,8 +283,8 @@ b`)
[
{
pieces: [
{ script: [[{ bare: "a" }]] },
{ script: [[{ bare: "b" }]] },
{ script: [[{ bare: "a", pos: 1 }]] },
{ script: [[{ bare: "b", pos: 4 }]] },
],
},
],

View file

@ -36,7 +36,7 @@ function bareWordTmpl(charRegex: RegExp) {
Bracket,
Regex(charRegex)
.expects("CHAR")
.map(([text]) => ({ bare: text }))
.map(([text], index) => ({ bare: text, pos: index }))
)
)
).map(([, pieces]) => SimplifyWord(pieces));

View file

@ -13,10 +13,14 @@ export class Pattern<out T> {
*
* @param map - Mapping function
*/
public map<U>(map: (value: T) => U): Pattern<U> {
public map<U>(map: (value: T, index: number) => U): Pattern<U> {
return new Pattern((source, index) => {
const [value, furthest, expected] = this.match(source, index);
return [value ? [map(value[0]), value[1]] : null, furthest, expected];
return [
value ? [map(value[0], index), value[1]] : null,
furthest,
expected,
];
}, this.expectLabel);
}