From 2c55e38822fcd2fc1a2e9cacaa85b6243de315b9 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 19:16:56 -0400 Subject: [PATCH 01/14] WIP replacement Notcl parser --- src/parser2.ts | 155 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/parser2.ts diff --git a/src/parser2.ts b/src/parser2.ts new file mode 100644 index 0000000..75f9842 --- /dev/null +++ b/src/parser2.ts @@ -0,0 +1,155 @@ +import { + Command, + ErrorResult, + InterpolatedPiece, + Script, + SimplifyWord, + Word, +} from "./words"; + +/** + * Parse out a Notcl script into an easier-to-interpret representation. + * No script is actually executed yet. + * + * @param code code to parse + * @param offset source position of code, if embedded in a larger source document + * @returns parsed list of commands, or error message on failure + */ +export function parse( + code: string, + offset = 0 +): [true, Script] | [false, string] { + try { + const parser = new Parser(code); + const script = parser.parseScript(); + + // TODO: report error with error position + + if (parser.lastIndex != code.length) { + return [false, "Couldn't parse full script"]; + } + + return [true, script]; + } catch (ex) { + return [false, String(ex)]; + } +} + +// --------------------------- + +// Parser for evaluating Notcl scripts + +type TokenType = + | "newline" + | "whitespace" + | "semicolon" + | "{" + | "}" + | "[" + | "]" + | "quote" + | "backslash" + | "comment" + | "text" + | "EOF" + | "ERROR"; + +type Token = [TokenType, string, number]; + +const Tokens: [TokenType, RegExp][] = [ + ["newline", /(\n)/y], + ["whitespace", /([^\S\n]+)/y], + ["text", /([^\s\\;\[\]]+)/y], +]; + +class WipScript { + script: Command[] = []; + wipCommand: Word[] = []; + wipWord: InterpolatedPiece[] = []; + // TODO: thing to fail {}a & ""a + + addWordPiece(piece: InterpolatedPiece) { + this.wipWord.push(piece); + } + finishWord() { + if (this.wipWord.length > 0) { + this.wipCommand.push(SimplifyWord(this.wipWord)); + this.wipWord = []; + } + } + finishCommand() { + this.finishWord(); + if (this.wipCommand.length > 0) { + this.script.push(this.wipCommand); + this.wipCommand = []; + } + } + finishScript(): Script { + this.finishCommand(); + return this.script; + } +} + +class Parser { + lastIndex: number = 0; + next: Token; + + constructor(public text: string) { + this.next = this.advance(); + } + + advance(): Token { + const startPos = this.lastIndex; + if (startPos == this.text.length) { + return (this.next = ["EOF", "<EOF>", startPos]); + } + + for (const [type, regex] of Tokens) { + regex.lastIndex = startPos; + const matches = regex.exec(this.text); + if (matches) { + this.lastIndex = regex.lastIndex; + return (this.next = [type, matches[1], startPos]); + } + } + + return (this.next = ["ERROR", "Token not matched", startPos]); + } + + parseScript(): Script { + const wip = new WipScript(); + + while (true) { + const [type, chars, pos] = this.next; + switch (type) { + case "text": + wip.addWordPiece({ bare: chars, pos }); + break; + + case "whitespace": + wip.finishWord(); + break; + + case "newline": + case "semicolon": + wip.finishCommand(); + break; + + case "EOF": + case "]": + return wip.finishScript(); + + case "{": + case "}": + case "[": + case "quote": + case "backslash": + case "comment": + case "ERROR": + throw new Error(`Unhandled case: ${type} (${chars})`); + } + + this.advance(); + } + } +} From 618de2ac9957c6c12b7df271566e702db2a101fa Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 19:32:58 -0400 Subject: [PATCH 02/14] Add source position to words in new parser --- src/parser2.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 75f9842..163ed25 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -66,15 +66,20 @@ class WipScript { script: Command[] = []; wipCommand: Word[] = []; wipWord: InterpolatedPiece[] = []; + wordPos: number | undefined = undefined; // TODO: thing to fail {}a & ""a - addWordPiece(piece: InterpolatedPiece) { + addWordPiece(piece: InterpolatedPiece, pos?: number) { + if (this.wipWord.length == 0) { + this.wordPos = pos; + } this.wipWord.push(piece); } finishWord() { if (this.wipWord.length > 0) { - this.wipCommand.push(SimplifyWord(this.wipWord)); + this.wipCommand.push(SimplifyWord(this.wipWord, this.wordPos)); this.wipWord = []; + this.wordPos = undefined; } } finishCommand() { @@ -123,7 +128,7 @@ class Parser { const [type, chars, pos] = this.next; switch (type) { case "text": - wip.addWordPiece({ bare: chars, pos }); + wip.addWordPiece({ bare: chars }, pos); break; case "whitespace": @@ -147,6 +152,8 @@ class Parser { case "comment": case "ERROR": throw new Error(`Unhandled case: ${type} (${chars})`); + default: + throw new Error(`Unhandled case: ${type satisfies never} (${chars})`); } this.advance(); From 9b81056d1d003be5c292e45faa7c432fc559bcee Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 19:43:19 -0400 Subject: [PATCH 03/14] Add more recognized tokens --- src/parser2.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/parser2.ts b/src/parser2.ts index 163ed25..83ca5fe 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -59,6 +59,14 @@ type Token = [TokenType, string, number]; const Tokens: [TokenType, RegExp][] = [ ["newline", /(\n)/y], ["whitespace", /([^\S\n]+)/y], + ["semicolon", /(;)/y], + ["{", /(\{)/y], + ["}", /(\})/y], + ["[", /(\[)/y], + ["]", /(\])/y], + ["quote", /(")/y], + ["backslash", /(\\)/y], + ["comment", /(\#)/y], ["text", /([^\s\\;\[\]]+)/y], ]; From 63d6fa836a7b9af60f6f4d60c30cc1896caa9f7f Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 20:02:20 -0400 Subject: [PATCH 04/14] Add expect() & startOfWord() helpers to new parser --- src/parser2.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 83ca5fe..513f563 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -22,15 +22,11 @@ export function parse( try { const parser = new Parser(code); const script = parser.parseScript(); - - // TODO: report error with error position - - if (parser.lastIndex != code.length) { - return [false, "Couldn't parse full script"]; - } + parser.expect("EOF"); return [true, script]; } catch (ex) { + // TODO: report error with error position return [false, String(ex)]; } } @@ -77,8 +73,12 @@ class WipScript { wordPos: number | undefined = undefined; // TODO: thing to fail {}a & ""a + startOfWord(): boolean { + return this.wipWord.length == 0; + } + addWordPiece(piece: InterpolatedPiece, pos?: number) { - if (this.wipWord.length == 0) { + if (this.startOfWord()) { this.wordPos = pos; } this.wipWord.push(piece); @@ -129,6 +129,14 @@ class Parser { return (this.next = ["ERROR", "Token not matched", startPos]); } + expect(type: TokenType) { + if (this.next[0] != type) { + throw new Error( + `Expected ${type}, found ${this.next[0]} (${this.next[1]})` + ); + } + } + parseScript(): Script { const wip = new WipScript(); From ab91d71170b29f363899a058ed16d96717a01c85 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 20:02:47 -0400 Subject: [PATCH 05/14] Support [] interpolation in new parser --- src/parser2.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/parser2.ts b/src/parser2.ts index 513f563..92c7233 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -147,6 +147,14 @@ class Parser { wip.addWordPiece({ bare: chars }, pos); break; + case "[": { + this.advance(); + const script = this.parseScript(); + wip.addWordPiece({ script }); + this.expect("]"); + break; + } + case "whitespace": wip.finishWord(); break; @@ -162,7 +170,6 @@ class Parser { case "{": case "}": - case "[": case "quote": case "backslash": case "comment": From c1ce90fd63637326b2bba3ab5a28fa7fdd98fa16 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 20:26:09 -0400 Subject: [PATCH 06/14] WIP brace parsing for WIP parser --- src/parser2.ts | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 92c7233..9481d80 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -63,7 +63,7 @@ const Tokens: [TokenType, RegExp][] = [ ["quote", /(")/y], ["backslash", /(\\)/y], ["comment", /(\#)/y], - ["text", /([^\s\\;\[\]]+)/y], + ["text", /([^\s;\{\}\[\]"\\\#]+)/y], ]; class WipScript { @@ -144,9 +144,18 @@ class Parser { const [type, chars, pos] = this.next; switch (type) { case "text": + case "}": wip.addWordPiece({ bare: chars }, pos); break; + case "{": { + this.advance(); + const text = this.parseBrace(); + wip.addWordPiece({ text }, pos); + this.expect("}"); + break; + } + case "[": { this.advance(); const script = this.parseScript(); @@ -168,8 +177,6 @@ class Parser { case "]": return wip.finishScript(); - case "{": - case "}": case "quote": case "backslash": case "comment": @@ -182,4 +189,32 @@ class Parser { this.advance(); } } + + parseBrace(): string { + let wip = ""; + + while (true) { + const [type, chars, pos] = this.next; + switch (type) { + case "{": { + wip += "{"; + this.advance(); + wip += this.parseBrace(); + this.expect("}"); + wip += "}"; + break; + } + case "}": + return wip; + case "EOF": + throw new Error("Reached end of input while parsing a brace word"); + case "ERROR": + throw new Error(chars); + default: + wip += chars; + } + + this.advance(); + } + } } From c61496fcc3ea203ea5d8de00df46befe6c147cb5 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Wed, 5 Jun 2024 20:44:13 -0400 Subject: [PATCH 07/14] Be strict about trailing characters after braces. Don't give mid-word braces magic meaning. --- src/parser2.ts | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 9481d80..be21529 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -71,24 +71,31 @@ class WipScript { wipCommand: Word[] = []; wipWord: InterpolatedPiece[] = []; wordPos: number | undefined = undefined; - // TODO: thing to fail {}a & ""a + endOfWordError: string | undefined = undefined; startOfWord(): boolean { return this.wipWord.length == 0; } addWordPiece(piece: InterpolatedPiece, pos?: number) { + if (this.endOfWordError) { + throw new Error(this.endOfWordError); + } if (this.startOfWord()) { this.wordPos = pos; } this.wipWord.push(piece); } + freezeWord(error: string) { + this.endOfWordError = error; + } finishWord() { if (this.wipWord.length > 0) { this.wipCommand.push(SimplifyWord(this.wipWord, this.wordPos)); - this.wipWord = []; - this.wordPos = undefined; } + this.wipWord = []; + this.wordPos = undefined; + this.endOfWordError = undefined; } finishCommand() { this.finishWord(); @@ -149,10 +156,15 @@ class Parser { break; case "{": { - this.advance(); - const text = this.parseBrace(); - wip.addWordPiece({ text }, pos); - this.expect("}"); + if (wip.startOfWord()) { + this.advance(); + const text = this.parseBrace(); + wip.addWordPiece({ text }, pos); + this.expect("}"); + wip.freezeWord("Extra characters after closing brace"); + } else { + wip.addWordPiece({ bare: chars }, pos); + } break; } From b2c5f7ea904e37e301d6be5d37eb644cc8d38f76 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Fri, 7 Jun 2024 22:21:04 -0400 Subject: [PATCH 08/14] Handle backslashes in brace words correctly --- src/parser2.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/parser2.ts b/src/parser2.ts index be21529..93829cd 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -208,6 +208,11 @@ class Parser { while (true) { const [type, chars, pos] = this.next; switch (type) { + case "backslash": + wip += "\\"; + this.advance(); + wip += this.next[1]; + break; case "{": { wip += "{"; this.advance(); From b536d304201afea5d77068ea19ca7d2fe22231bd Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Sat, 8 Jun 2024 00:03:17 -0400 Subject: [PATCH 09/14] sketch out backslash escape handling --- src/parser2.ts | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 93829cd..44796cc 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -189,11 +189,53 @@ class Parser { case "]": return wip.finishScript(); + case "backslash": { + this.advance(); + const [type, chars, pos] = this.next; + switch (type) { + case "newline": + wip.finishWord(); + break; + + case "whitespace": + case "semicolon": + case "{": + case "}": + case "[": + case "]": + case "quote": + case "backslash": + case "comment": + wip.addWordPiece({ text: chars }); + break; + case "text": + switch (chars) { + case "n": + wip.addWordPiece({ text: "\n" }); + break; + default: + throw new Error(`Unknown backslash escape: ${chars}`); + } + break; + case "EOF": + throw new Error( + "Reached end of input while parsing a backslash escape" + ); + case "ERROR": + throw new Error(chars); + default: + throw new Error( + `Unhandled case: ${type satisfies never} (${chars})` + ); + } + break; + } + case "quote": - case "backslash": case "comment": - case "ERROR": throw new Error(`Unhandled case: ${type} (${chars})`); + case "ERROR": + throw new Error(chars); default: throw new Error(`Unhandled case: ${type satisfies never} (${chars})`); } From fa3be1e003aa7fbd3318518d071fd5e5a299f6b9 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Sat, 8 Jun 2024 12:32:58 -0400 Subject: [PATCH 10/14] Support comments in new parser --- src/parser2.ts | 31 ++++++++++++++++++++++++++++--- src/words.ts | 8 ++++++-- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 44796cc..fb7131d 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -77,6 +77,10 @@ class WipScript { return this.wipWord.length == 0; } + startOfCommand(): boolean { + return this.wipWord.length == 0 && this.wipCommand.length == 0; + } + addWordPiece(piece: InterpolatedPiece, pos?: number) { if (this.endOfWordError) { throw new Error(this.endOfWordError); @@ -185,6 +189,28 @@ class Parser { wip.finishCommand(); break; + case "comment": + if (wip.startOfCommand()) { + skipComment: while (this.advance()) { + const [type, chars, pos] = this.next; + switch (type) { + case "newline": + case "EOF": + break skipComment; + case "backslash": + this.advance(); + continue; + case "ERROR": + throw new Error(chars); + default: + continue; + } + } + } else { + wip.addWordPiece({ bare: chars }, pos); + } + break; + case "EOF": case "]": return wip.finishScript(); @@ -206,12 +232,12 @@ class Parser { case "quote": case "backslash": case "comment": - wip.addWordPiece({ text: chars }); + wip.addWordPiece({ text: chars }, pos); break; case "text": switch (chars) { case "n": - wip.addWordPiece({ text: "\n" }); + wip.addWordPiece({ text: "\n" }, pos); break; default: throw new Error(`Unknown backslash escape: ${chars}`); @@ -232,7 +258,6 @@ class Parser { } case "quote": - case "comment": throw new Error(`Unhandled case: ${type} (${chars})`); case "ERROR": throw new Error(chars); diff --git a/src/words.ts b/src/words.ts index 5c8f5b7..7d70c6e 100644 --- a/src/words.ts +++ b/src/words.ts @@ -1,4 +1,4 @@ -import { escapeHtml } from './helpers'; +import { escapeHtml } from "./helpers"; export type SourcePos = number; @@ -117,7 +117,11 @@ export function SimplifyWord( if (consolidated.length == 0) { return { text: "", pos: sourcePosition }; } else if (consolidated.length == 1 && IsTextPiece(consolidated[0])) { - return { ...consolidated[0], pos: sourcePosition }; + if (pieces.every((piece) => "bare" in piece)) { + return { bare: AsText(consolidated[0]), pos: sourcePosition }; + } else { + return { ...consolidated[0], pos: sourcePosition }; + } } else { return { pieces: consolidated }; } From a2c8eb66b991c68aa41b031c554c9857c41ace17 Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Sat, 8 Jun 2024 13:11:46 -0400 Subject: [PATCH 11/14] Factor out backslash parse helper --- src/parser2.ts | 80 +++++++++++++++++++++++++++----------------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index fb7131d..bb0a2ca 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -217,43 +217,7 @@ class Parser { case "backslash": { this.advance(); - const [type, chars, pos] = this.next; - switch (type) { - case "newline": - wip.finishWord(); - break; - - case "whitespace": - case "semicolon": - case "{": - case "}": - case "[": - case "]": - case "quote": - case "backslash": - case "comment": - wip.addWordPiece({ text: chars }, pos); - break; - case "text": - switch (chars) { - case "n": - wip.addWordPiece({ text: "\n" }, pos); - break; - default: - throw new Error(`Unknown backslash escape: ${chars}`); - } - break; - case "EOF": - throw new Error( - "Reached end of input while parsing a backslash escape" - ); - case "ERROR": - throw new Error(chars); - default: - throw new Error( - `Unhandled case: ${type satisfies never} (${chars})` - ); - } + this.parseBackslashEscape(wip, "bare"); break; } @@ -269,6 +233,48 @@ class Parser { } } + parseBackslashEscape(wip: WipScript, wordType: "bare" | "quote") { + const [type, chars, pos] = this.next; + switch (type) { + case "newline": + if (wordType == "bare") { + wip.finishWord(); + } else { + // ignore newline + } + break; + + case "whitespace": + case "semicolon": + case "{": + case "}": + case "[": + case "]": + case "quote": + case "backslash": + case "comment": + wip.addWordPiece({ text: chars }, pos); + break; + case "text": + switch (chars) { + case "n": + wip.addWordPiece({ text: "\n" }, pos); + break; + default: + throw new Error(`Unknown backslash escape: ${chars}`); + } + break; + case "EOF": + throw new Error( + "Reached end of input while parsing a backslash escape" + ); + case "ERROR": + throw new Error(chars); + default: + throw new Error(`Unhandled case: ${type satisfies never} (${chars})`); + } + } + parseBrace(): string { let wip = ""; From ac1a38e75fc40fa99143777097b4dedceca906cc Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Sat, 8 Jun 2024 13:28:21 -0400 Subject: [PATCH 12/14] Tidy up error reporting --- src/parser2.ts | 52 +++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index bb0a2ca..c9a65a8 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -35,6 +35,12 @@ export function parse( // Parser for evaluating Notcl scripts +export class ParseError extends Error { + constructor(message: string, public pos: number) { + super(message); + } +} + type TokenType = | "newline" | "whitespace" @@ -47,8 +53,7 @@ type TokenType = | "backslash" | "comment" | "text" - | "EOF" - | "ERROR"; + | "EOF"; type Token = [TokenType, string, number]; @@ -81,9 +86,9 @@ class WipScript { return this.wipWord.length == 0 && this.wipCommand.length == 0; } - addWordPiece(piece: InterpolatedPiece, pos?: number) { + addWordPiece(piece: InterpolatedPiece, pos: number) { if (this.endOfWordError) { - throw new Error(this.endOfWordError); + throw new ParseError(this.endOfWordError, pos); } if (this.startOfWord()) { this.wordPos = pos; @@ -137,13 +142,14 @@ class Parser { } } - return (this.next = ["ERROR", "Token not matched", startPos]); + throw new ParseError("Token not matched", startPos); } expect(type: TokenType) { if (this.next[0] != type) { - throw new Error( - `Expected ${type}, found ${this.next[0]} (${this.next[1]})` + throw new ParseError( + `Expected ${type}, found ${this.next[0]} (${this.next[1]})`, + this.next[2] ); } } @@ -175,7 +181,7 @@ class Parser { case "[": { this.advance(); const script = this.parseScript(); - wip.addWordPiece({ script }); + wip.addWordPiece({ script }, pos); this.expect("]"); break; } @@ -200,8 +206,6 @@ class Parser { case "backslash": this.advance(); continue; - case "ERROR": - throw new Error(chars); default: continue; } @@ -223,10 +227,11 @@ class Parser { case "quote": throw new Error(`Unhandled case: ${type} (${chars})`); - case "ERROR": - throw new Error(chars); default: - throw new Error(`Unhandled case: ${type satisfies never} (${chars})`); + throw new ParseError( + `Unhandled case: ${type satisfies never} (${chars})`, + pos + ); } this.advance(); @@ -261,17 +266,19 @@ class Parser { wip.addWordPiece({ text: "\n" }, pos); break; default: - throw new Error(`Unknown backslash escape: ${chars}`); + throw new ParseError(`Unknown backslash escape: ${chars}`, pos); } break; case "EOF": - throw new Error( - "Reached end of input while parsing a backslash escape" + throw new ParseError( + "Reached end of input while parsing a backslash escape", + pos ); - case "ERROR": - throw new Error(chars); default: - throw new Error(`Unhandled case: ${type satisfies never} (${chars})`); + throw new ParseError( + `Unhandled case: ${type satisfies never} (${chars})`, + pos + ); } } @@ -297,9 +304,10 @@ class Parser { case "}": return wip; case "EOF": - throw new Error("Reached end of input while parsing a brace word"); - case "ERROR": - throw new Error(chars); + throw new ParseError( + "Reached end of input while parsing a brace word", + pos + ); default: wip += chars; } From 6b1c2c48ef13f3130e8fff9e6810e3b74b7e1b3d Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Sat, 8 Jun 2024 14:00:45 -0400 Subject: [PATCH 13/14] Support quoted words --- src/parser2.ts | 62 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/parser2.ts b/src/parser2.ts index c9a65a8..3fc959c 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -178,6 +178,19 @@ class Parser { break; } + case "quote": { + if (wip.startOfWord()) { + wip.addWordPiece({ text: "" }, pos); + this.advance(); + this.parseQuoteWord(wip); + this.expect("quote"); + wip.freezeWord("Extra characters after quoted word"); + } else { + wip.addWordPiece({ bare: chars }, pos); + } + break; + } + case "[": { this.advance(); const script = this.parseScript(); @@ -225,8 +238,55 @@ class Parser { break; } + default: + throw new ParseError( + `Unhandled case: ${type satisfies never} (${chars})`, + pos + ); + } + + this.advance(); + } + } + + parseQuoteWord(wip: WipScript) { + while (true) { + const [type, chars, pos] = this.next; + switch (type) { + case "text": + case "{": + case "}": + case "]": + case "whitespace": + case "newline": + case "semicolon": + case "comment": + wip.addWordPiece({ text: chars }, pos); + break; + + case "[": { + this.advance(); + const script = this.parseScript(); + wip.addWordPiece({ script }, pos); + this.expect("]"); + break; + } + + case "EOF": + throw new ParseError( + "Reached end of input while parsing a quoted word", + pos + ); + + case "backslash": { + this.advance(); + this.parseBackslashEscape(wip, "quote"); + break; + } + case "quote": - throw new Error(`Unhandled case: ${type} (${chars})`); + return; + default: throw new ParseError( `Unhandled case: ${type satisfies never} (${chars})`, From d22099323675f3a3dff8e3e4c8ac8dcf55861f6a Mon Sep 17 00:00:00 2001 From: Tangent Wantwight <tangent128@gmail.com> Date: Sat, 8 Jun 2024 14:03:31 -0400 Subject: [PATCH 14/14] All original parser tests pass --- src/parser2.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/parser2.ts b/src/parser2.ts index 3fc959c..47f9e9f 100644 --- a/src/parser2.ts +++ b/src/parser2.ts @@ -234,7 +234,7 @@ class Parser { case "backslash": { this.advance(); - this.parseBackslashEscape(wip, "bare"); + this.parseBackslashEscape(wip, pos, "bare"); break; } @@ -280,7 +280,7 @@ class Parser { case "backslash": { this.advance(); - this.parseBackslashEscape(wip, "quote"); + this.parseBackslashEscape(wip, pos, "quote"); break; } @@ -298,8 +298,12 @@ class Parser { } } - parseBackslashEscape(wip: WipScript, wordType: "bare" | "quote") { - const [type, chars, pos] = this.next; + parseBackslashEscape( + wip: WipScript, + pos: number, + wordType: "bare" | "quote" + ) { + const [type, chars] = this.next; switch (type) { case "newline": if (wordType == "bare") {