@@ -1,2 +0,3 @@ | ||
| import type { ArithmeticExpression } from "./types.ts"; | ||
| import type { ArithmeticCommandExpansion, ArithmeticExpression } from "./types.ts"; | ||
| export declare function drainArithCmdExps(): ArithmeticCommandExpansion[] | null; | ||
| export declare function parseArithmeticExpression(src: string, offset?: number): ArithmeticExpression | null; |
+20
-1
@@ -71,3 +71,10 @@ import { CH_TAB, CH_NL, CH_SPACE, CH_BANG, CH_DOLLAR, CH_PERCENT, CH_AMP, CH_LPAREN, CH_RPAREN, CH_STAR, CH_PLUS, CH_COMMA, CH_DASH, CH_SLASH, CH_0, CH_9, CH_COLON, CH_LT, CH_EQ, CH_GT, CH_QUESTION, CH_A, CH_Z, CH_LBRACKET, CH_RBRACKET, CH_CARET, CH_UNDERSCORE, CH_a, CH_z, CH_LBRACE, CH_PIPE, CH_RBRACE, CH_TILDE, } from "./chars.js"; | ||
| } | ||
| let pendingArithCmdExps = null; | ||
| export function drainArithCmdExps() { | ||
| const out = pendingArithCmdExps; | ||
| pendingArithCmdExps = null; | ||
| return out; | ||
| } | ||
| export function parseArithmeticExpression(src, offset = 0) { | ||
| pendingArithCmdExps = null; | ||
| let pos = 0; | ||
@@ -360,3 +367,3 @@ const len = src.length; | ||
| // $( command substitution ) | ||
| pos++; | ||
| pos++; // skip ( | ||
| let depth = 1; | ||
@@ -371,2 +378,14 @@ while (pos < len && depth > 0) { | ||
| } | ||
| const text = src.slice(start, pos); | ||
| const inner = text.slice(2, -1); // remove "$(" and ")" | ||
| const node = { | ||
| type: "ArithmeticCommandExpansion", | ||
| pos: start + offset, | ||
| end: pos + offset, | ||
| text, | ||
| inner, | ||
| script: undefined, | ||
| }; | ||
| (pendingArithCmdExps ??= []).push(node); | ||
| return node; | ||
| } | ||
@@ -373,0 +392,0 @@ } |
+19
-3
| import { LexContext, Token, Lexer, TokenValue } from "./lexer.js"; | ||
| import { parseArithmeticExpression } from "./arithmetic.js"; | ||
| import { computeWordParts } from "./parts.js"; | ||
| import { parseArithmeticExpression, drainArithCmdExps } from "./arithmetic.js"; | ||
| import { computeWordParts, computeHereDocBodyParts } from "./parts.js"; | ||
| import { WordImpl } from "./word.js"; | ||
| WordImpl._resolve = computeWordParts; | ||
| WordImpl._resolveWord = computeWordParts; | ||
| WordImpl._resolveHeredocBody = computeHereDocBodyParts; | ||
| class ArithmeticCommandImpl { | ||
@@ -20,2 +21,3 @@ type = "ArithmeticCommand"; | ||
| this.#expression = parseArithmeticExpression(this.body, this.pos + 2) ?? undefined; | ||
| resolveDrainedArithCmdExps(); | ||
| } | ||
@@ -59,2 +61,3 @@ return this.#expression; | ||
| offsetArith(expr, this.#initPos); | ||
| resolveDrainedArithCmdExps(); | ||
| this.#initialize = expr ?? undefined; | ||
@@ -77,2 +80,3 @@ } | ||
| offsetArith(expr, this.#testPos); | ||
| resolveDrainedArithCmdExps(); | ||
| this.#test = expr ?? undefined; | ||
@@ -95,2 +99,3 @@ } | ||
| offsetArith(expr, this.#updatePos); | ||
| resolveDrainedArithCmdExps(); | ||
| this.#update = expr ?? undefined; | ||
@@ -148,2 +153,13 @@ } | ||
| } | ||
| function resolveDrainedArithCmdExps() { | ||
| const list = drainArithCmdExps(); | ||
| if (!list) | ||
| return; | ||
| for (const node of list) { | ||
| if (node.inner !== undefined) { | ||
| node.script = parse(node.inner); | ||
| node.inner = undefined; | ||
| } | ||
| } | ||
| } | ||
| // Lookup tables for O(1) token classification (replaces sequential comparisons) | ||
@@ -150,0 +166,0 @@ const listTerminators = new Uint8Array(37); |
+6
-0
@@ -9,1 +9,7 @@ import type { Word, WordPart } from "./types.ts"; | ||
| export declare function computeWordParts(source: string, word: Word): WordPart[] | undefined; | ||
| /** | ||
| * Compute parts for an unquoted heredoc body. | ||
| * Heredoc bodies use different scanning rules than shell words: newlines are | ||
| * literal and single/double quotes have no special meaning. | ||
| */ | ||
| export declare function computeHereDocBodyParts(source: string, word: Word): WordPart[] | undefined; |
+21
-18
@@ -11,25 +11,28 @@ import { Lexer } from "./lexer.js"; | ||
| const lexer = new Lexer(source); | ||
| let parts; | ||
| // Heredoc bodies contain newlines — use dedicated scanning | ||
| if (word.text.includes("\n") && word.pos > 0) { | ||
| parts = lexer.buildHereDocParts(word.pos, word.end); | ||
| } | ||
| else { | ||
| parts = lexer.buildWordParts(word.pos); | ||
| } | ||
| const parts = lexer.buildWordParts(word.pos); | ||
| if (!parts) | ||
| return undefined; | ||
| // Resolve command expansions: parse inner scripts | ||
| for (const exp of lexer.getCollectedExpansions()) { | ||
| resolveExpansion(exp); | ||
| } | ||
| resolveCollected(lexer); | ||
| return parts; | ||
| } | ||
| function resolveExpansion(e) { | ||
| if (e.inner !== undefined && e._part) { | ||
| e._part.script = parse(e.inner); | ||
| e._part.inner = undefined; | ||
| e._part = undefined; | ||
| e.inner = undefined; | ||
| /** | ||
| * Compute parts for an unquoted heredoc body. | ||
| * Heredoc bodies use different scanning rules than shell words: newlines are | ||
| * literal and single/double quotes have no special meaning. | ||
| */ | ||
| export function computeHereDocBodyParts(source, word) { | ||
| const lexer = new Lexer(source); | ||
| const parts = lexer.buildHereDocParts(word.pos, word.end); | ||
| if (!parts) | ||
| return undefined; | ||
| resolveCollected(lexer); | ||
| return parts; | ||
| } | ||
| function resolveCollected(lexer) { | ||
| for (const e of lexer.getCollectedExpansions()) { | ||
| if (e.inner !== undefined) { | ||
| e.script = parse(e.inner); | ||
| e.inner = undefined; | ||
| } | ||
| } | ||
| } |
+2
-0
@@ -309,2 +309,4 @@ export function print(script) { | ||
| } | ||
| case "ArithmeticCommandExpansion": | ||
| return e.text; // preserve original text for roundtrip | ||
| } | ||
@@ -311,0 +313,0 @@ } |
+10
-5
@@ -84,3 +84,3 @@ export interface Word { | ||
| } | ||
| export type ArithmeticExpression = ArithmeticBinary | ArithmeticUnary | ArithmeticTernary | ArithmeticGroup | ArithmeticWord; | ||
| export type ArithmeticExpression = ArithmeticBinary | ArithmeticUnary | ArithmeticTernary | ArithmeticGroup | ArithmeticWord | ArithmeticCommandExpansion; | ||
| export interface ArithmeticBinary { | ||
@@ -122,2 +122,10 @@ type: "ArithmeticBinary"; | ||
| } | ||
| export interface ArithmeticCommandExpansion { | ||
| type: "ArithmeticCommandExpansion"; | ||
| pos: number; | ||
| end: number; | ||
| text: string; | ||
| inner: string | undefined; | ||
| script: Script | undefined; | ||
| } | ||
| export type DoubleQuotedChild = LiteralPart | SimpleExpansionPart | ParameterExpansionPart | CommandExpansionPart | ArithmeticExpansionPart; | ||
@@ -336,5 +344,2 @@ export type WordPart = LiteralPart | SingleQuotedPart | DoubleQuotedPart | AnsiCQuotedPart | LocaleStringPart | SimpleExpansionPart | ParameterExpansionPart | CommandExpansionPart | ArithmeticExpansionPart | ProcessSubstitutionPart | ExtendedGlobPart | BraceExpansionPart; | ||
| } | ||
| export interface DeferredCommandExpansion { | ||
| inner?: string; | ||
| _part?: CommandExpansionPart | ProcessSubstitutionPart; | ||
| } | ||
| export type DeferredCommandExpansion = CommandExpansionPart | ProcessSubstitutionPart | ArithmeticCommandExpansion; |
+4
-4
| import type { Word, WordPart } from "./types.ts"; | ||
| type PartsResolver = (source: string, word: Word) => WordPart[] | undefined; | ||
| export type PartsResolver = (source: string, word: Word) => WordPart[] | undefined; | ||
| export declare class WordImpl implements Word { | ||
| #private; | ||
| static _resolve: PartsResolver; | ||
| static _resolveWord: PartsResolver; | ||
| static _resolveHeredocBody: PartsResolver; | ||
| text: string; | ||
| pos: number; | ||
| end: number; | ||
| constructor(text: string, pos: number, end: number, source?: string); | ||
| constructor(text: string, pos: number, end: number, source?: string, resolver?: PartsResolver); | ||
| get value(): string; | ||
@@ -21,2 +22,1 @@ get parts(): WordPart[] | undefined; | ||
| } | ||
| export {}; |
+6
-3
@@ -8,3 +8,4 @@ function dequoteValue(parts) { | ||
| export class WordImpl { | ||
| static _resolve; | ||
| static _resolveWord; | ||
| static _resolveHeredocBody; | ||
| text; | ||
@@ -14,5 +15,6 @@ pos; | ||
| #source; | ||
| #resolver; | ||
| #parts; | ||
| #value = null; | ||
| constructor(text, pos, end, source) { | ||
| constructor(text, pos, end, source, resolver) { | ||
| this.text = text; | ||
@@ -22,2 +24,3 @@ this.pos = pos; | ||
| this.#source = source ?? ""; | ||
| this.#resolver = resolver ?? WordImpl._resolveWord; | ||
| this.#parts = source !== undefined ? null : undefined; | ||
@@ -56,3 +59,3 @@ } | ||
| if (this.#parts === null) { | ||
| this.#parts = WordImpl._resolve(this.#source, this) ?? undefined; | ||
| this.#parts = this.#resolver(this.#source, this) ?? undefined; | ||
| } | ||
@@ -59,0 +62,0 @@ return this.#parts; |
+1
-1
| { | ||
| "name": "unbash", | ||
| "version": "2.2.0", | ||
| "version": "3.0.0", | ||
| "description": "Fast 0-deps bash parser written in TypeScript", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
+2
-2
@@ -119,3 +119,3 @@ # unbash | ||
| - [unbash-playground.statichost.page][6] | ||
| - [unbash.statichost.page][6] | ||
| - [ast-explorer.dev][7] | ||
@@ -132,3 +132,3 @@ | ||
| [5]: https://github.com/ericcornelissen/bash-parser | ||
| [6]: https://unbash-playground.statichost.page | ||
| [6]: https://unbash.statichost.page | ||
| [7]: https://ast-explorer.dev/#eNoVjDsKwzAQRK8yDK5CyAGS2nVAId02jixZAbFr/Kls393rbh7zeBsrn5xLqpV3jr5X/XVzcYgOKRaD8LoNzffTBqFotgkZf8XtUW14oTdRIHaLq00WYscwpRFtCO8g2psm75n3toPHCdz+Ivg= |
Sorry, the diff of this file is too big to display
176899
1.36%4977
1.18%