Implement "quoted words"
This commit is contained in:
parent
8c3953cdf0
commit
ab4cd761b8
4 changed files with 107 additions and 79 deletions
|
@ -9,10 +9,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
|
|||
"enchanted": "h1",
|
||||
},
|
||||
{
|
||||
"text": ""Hello,",
|
||||
},
|
||||
{
|
||||
"text": "World!"",
|
||||
"text": "Hello, World!",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -47,7 +44,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
|
|||
"enchanted": "-red",
|
||||
},
|
||||
{
|
||||
"text": ""Beware!"",
|
||||
"text": "Beware!",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
@ -55,61 +52,7 @@ exports[`Parsing Notcl Misc Big mess of markup 1`] = `
|
|||
"enchanted": "para",
|
||||
},
|
||||
{
|
||||
"text": ""All",
|
||||
},
|
||||
{
|
||||
"enchanted": "text",
|
||||
},
|
||||
{
|
||||
"enchanted": "should",
|
||||
},
|
||||
{
|
||||
"enchanted": "be",
|
||||
},
|
||||
{
|
||||
"enchanted": "quoted,",
|
||||
},
|
||||
{
|
||||
"enchanted": "it's",
|
||||
},
|
||||
{
|
||||
"enchanted": "clearer",
|
||||
},
|
||||
{
|
||||
"enchanted": "that",
|
||||
},
|
||||
{
|
||||
"enchanted": "way.",
|
||||
},
|
||||
{
|
||||
"enchanted": "&",
|
||||
},
|
||||
{
|
||||
"enchanted": "blockquotes",
|
||||
},
|
||||
{
|
||||
"enchanted": "already",
|
||||
},
|
||||
{
|
||||
"enchanted": "should",
|
||||
},
|
||||
{
|
||||
"enchanted": "contain",
|
||||
},
|
||||
{
|
||||
"enchanted": "paragraphs.",
|
||||
},
|
||||
{
|
||||
"enchanted": "(maybe",
|
||||
},
|
||||
{
|
||||
"enchanted": "normalize",
|
||||
},
|
||||
{
|
||||
"enchanted": "nested",
|
||||
},
|
||||
{
|
||||
"text": "paragraphs)"",
|
||||
"text": "All text should be quoted, it's clearer that way. & blockquotes already should contain paragraphs. (maybe normalize nested paragraphs)",
|
||||
},
|
||||
],
|
||||
[
|
||||
|
|
|
@ -33,12 +33,12 @@ b`)
|
|||
expect(
|
||||
parse(String.raw`a\\
|
||||
b`)
|
||||
).toEqual([true, [[{ text: "a\\\\" }], [{ enchanted: "b" }]]]));
|
||||
).toEqual([true, [[{ text: "a\\" }], [{ enchanted: "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\\" }, { enchanted: "b" }]]]));
|
||||
|
||||
it("accepts semicolons as command separators", () =>
|
||||
expect(parse("a;b")).toEqual([
|
||||
|
@ -100,7 +100,37 @@ b`)
|
|||
// no before folded newline that gives us backslashes
|
||||
});
|
||||
|
||||
describe("interpolated words", () => {});
|
||||
describe("interpolated words", () => {
|
||||
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("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, [[{ text: 'a"' }]]]));
|
||||
it("treats a non-leading brace as a plain character", () =>
|
||||
expect(parse("a{")).toEqual([true, [[{ text: "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: "{" }]]]));
|
||||
|
||||
// Interpolated words
|
||||
// variables- bare, brace
|
||||
// command subst
|
||||
});
|
||||
|
||||
describe("brace words", () => {
|
||||
it("can parse empty braces", () =>
|
||||
|
@ -144,15 +174,6 @@ b`)
|
|||
).toEqual([true, [[{ text: "\\\\ " }]]]));
|
||||
});
|
||||
|
||||
// "Quotes"
|
||||
// no trailing chars
|
||||
|
||||
// Interpolated words
|
||||
// bare
|
||||
// quotes
|
||||
// variables- bare, brace
|
||||
// command subst
|
||||
|
||||
describe("Misc", () => {
|
||||
test("Big mess of markup", () => {
|
||||
expect(
|
||||
|
|
40
src/notcl.ts
40
src/notcl.ts
|
@ -4,6 +4,7 @@ import {
|
|||
Word as WordType,
|
||||
TextWord,
|
||||
EnchantedWord as EnchantedWordType,
|
||||
SimplifyWord,
|
||||
} from "./words";
|
||||
|
||||
export type Command = WordType[];
|
||||
|
@ -19,9 +20,35 @@ const EnchantedWord = Regex(/[^\]\[\}\{$\\";\s]+(?=[\s;]|$)/y)
|
|||
.map(([enchanted]) => ({ enchanted } as EnchantedWordType))
|
||||
.expects("ENCHANTED_WORD");
|
||||
|
||||
const BasicWord = Regex(/(?!\{)[^\s;]+/y)
|
||||
.map(([text]) => ({ text } as TextWord))
|
||||
.expects("BASIC_WORD");
|
||||
const BackslashEscape = Regex(/\\(.)/y)
|
||||
.expects("\\")
|
||||
.map(([, char]) => ({ text: char }));
|
||||
|
||||
const BareWord = Sequence(
|
||||
AtLeast(
|
||||
1,
|
||||
Choose(
|
||||
BackslashEscape,
|
||||
Regex(/[^\s\\;]+/y)
|
||||
.expects("CHAR")
|
||||
.map(([text]) => ({ text }))
|
||||
)
|
||||
)
|
||||
).map(([pieces]) => SimplifyWord(pieces));
|
||||
|
||||
const QuotedWord = Sequence(
|
||||
Regex(/"/y).expects('"'),
|
||||
AtLeast(
|
||||
0,
|
||||
Choose(
|
||||
BackslashEscape,
|
||||
Regex(/[^"\\]+/y)
|
||||
.expects("CHAR")
|
||||
.map(([text]) => ({ text }))
|
||||
)
|
||||
),
|
||||
Regex(/"/y).expects('"')
|
||||
).map(([, pieces]) => SimplifyWord(pieces));
|
||||
|
||||
const Brace: Pattern<string> = Sequence(
|
||||
Regex(/\{/y).expects("{"),
|
||||
|
@ -40,12 +67,13 @@ const Brace: Pattern<string> = Sequence(
|
|||
)
|
||||
),
|
||||
Regex(/\}/y).expects("}")
|
||||
).map(([_left, fragments, _right]) => fragments.join(""));
|
||||
).map(([, fragments]) => fragments.join(""));
|
||||
|
||||
const Word = Choose<WordType>(
|
||||
EnchantedWord,
|
||||
BasicWord,
|
||||
Brace.map((text) => ({ text } as TextWord))
|
||||
Brace.map((text) => ({ text } as TextWord)),
|
||||
QuotedWord,
|
||||
BareWord
|
||||
);
|
||||
|
||||
const CommandTerminator = Regex(/[\n;]/y)
|
||||
|
|
38
src/words.ts
38
src/words.ts
|
@ -29,12 +29,48 @@ export type HtmlWord = {
|
|||
html: string;
|
||||
};
|
||||
|
||||
export type TextPiece = TextWord;
|
||||
export type VariablePiece = { variable: string };
|
||||
export type CommandPiece = { command: unknown };
|
||||
export type InterpolatedPiece = TextPiece | VariablePiece | CommandPiece;
|
||||
|
||||
/**
|
||||
* A word whose value needs to be determined by evaluating some combination of variable and command
|
||||
* substitutions, and concatenating the results with any literal spans.
|
||||
*/
|
||||
export type InterpolatedWord = {
|
||||
pieces: [];
|
||||
pieces: InterpolatedPiece[];
|
||||
};
|
||||
|
||||
function IsTextPiece(piece: InterpolatedPiece | undefined): piece is TextPiece {
|
||||
return piece ? "text" in piece : false;
|
||||
}
|
||||
|
||||
// safely concatenate text pieces, converting as needed
|
||||
export function Concat(left: TextPiece, right: TextPiece) {
|
||||
return { text: left.text + right.text };
|
||||
}
|
||||
|
||||
export function SimplifyWord(
|
||||
pieces: InterpolatedPiece[]
|
||||
): InterpolatedWord | TextWord {
|
||||
const consolidated: InterpolatedPiece[] = [];
|
||||
for (const piece of pieces) {
|
||||
const top = consolidated[consolidated.length - 1];
|
||||
if (IsTextPiece(top) && IsTextPiece(piece)) {
|
||||
consolidated[consolidated.length - 1] = Concat(top, piece);
|
||||
} else {
|
||||
consolidated.push(piece);
|
||||
}
|
||||
}
|
||||
|
||||
if (consolidated.length == 0) {
|
||||
return { text: "" };
|
||||
} else if (consolidated.length == 1 && IsTextPiece(consolidated[0])) {
|
||||
return consolidated[0];
|
||||
} else {
|
||||
return { pieces: consolidated };
|
||||
}
|
||||
}
|
||||
|
||||
export type Word = EnchantedWord | TextWord | HtmlWord | InterpolatedWord;
|
||||
|
|
Loading…
Reference in a new issue