tarsec 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,112 +0,0 @@
1
- /** Words: quoting, escapes, and expansions. A word is one or more
2
- * adjacent parts; only the whole word is a lexeme, so parts never eat
3
- * trailing whitespace (`$(pwd) x` must not glue `x` onto the word). */
4
- import { lazy, many, many1, many1WithJoin, map, not, or, seq, } from "../../combinators.js";
5
- import { anyChar, char, compileCharPredicate, label, oneOf, str, takeWhile, takeWhile1, } from "../../parsers.js";
6
- import { trace } from "../../trace.js";
7
- import { bashLexemes, METACHARACTERS, reservedToken } from "./lexemes.js";
8
- import { list0 as list0Import } from "./lists.js";
9
- function literal(text) {
10
- return { tag: "literal", text };
11
- }
12
- /** Merge adjacent literal parts so ASTs read naturally. */
13
- function mergeLiterals(parts) {
14
- const merged = [];
15
- for (const part of parts) {
16
- const previous = merged[merged.length - 1];
17
- if (part.tag === "literal" && (previous === null || previous === void 0 ? void 0 : previous.tag) === "literal") {
18
- merged[merged.length - 1] = literal(previous.text + part.text);
19
- }
20
- else {
21
- merged.push(part);
22
- }
23
- }
24
- return merged;
25
- }
26
- const WORD_STOP = METACHARACTERS + "'\"`$\\";
27
- const isWordStop = compileCharPredicate(WORD_STOP);
28
- const NOT_SINGLE_QUOTE = (code) => code !== 0x27; // '
29
- const plainRun = map(takeWhile1((code) => !isWordStop(code), "word characters"), literal);
30
- // No escapes exist inside single quotes; scanning to the next quote is
31
- // exactly bash's rule.
32
- const singleQuoted = label("a single-quoted string", seq([char("'"), takeWhile(NOT_SINGLE_QUOTE), char("'")], (results) => ({ tag: "singleQuoted", text: results[1] })));
33
- const IDENT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
34
- const SPECIAL_VARIABLES = "?!#@*$-0123456789";
35
- // No lone-`$` fallback: a `$` that starts no supported expansion is a
36
- // parse error, which is what makes `$'...'` and `$"..."` fail loudly.
37
- const variablePart = seq([char("$"), or(oneOf(SPECIAL_VARIABLES), many1WithJoin(oneOf(IDENT_CHARS)))], (results) => ({ tag: "variable", name: results[1] }));
38
- // Balanced braces by recursion instead of a depth counter: a body is a
39
- // sequence of non-brace runs and nested `{...}` groups.
40
- const braceBody = map(many(or(takeWhile1((code) => code !== 0x7b && code !== 0x7d, "brace content"), // { }
41
- seq([char("{"), lazy(() => braceBody), char("}")], (results) => "{" + results[1] + "}"))), (chunks) => chunks.join(""));
42
- const paramExpansion = seq([str("${"), braceBody, char("}")], (results) => ({
43
- tag: "paramExpansion",
44
- expression: results[1],
45
- }));
46
- const parenBodyImpl = map(many(or(takeWhile1((code) => code !== 0x28 && code !== 0x29, "paren content"), // ( )
47
- seq([char("("), lazy(() => parenBodyImpl), char(")")], (results) => "(" + results[1] + ")"))), (chunks) => chunks.join(""));
48
- /** Balanced parens, for `$(( ))` and `(( ))`. Sound because quotes are
49
- * not delimiters in bash arithmetic. Declared as a function so the
50
- * words/commands module cycle is safe under any import order. */
51
- export function parenBody(input) {
52
- return parenBodyImpl(input);
53
- }
54
- const arithmeticExpansion = seq([str("$(("), parenBody, str("))")], (results) => ({
55
- tag: "arithmeticExpansion",
56
- expression: results[1],
57
- }));
58
- // `char(")")`, not `symbol(")")`: word parts must not eat trailing
59
- // whitespace. `list0` is deferred to parse time: see the module-cycle
60
- // note in commands.ts.
61
- const commandSubstitution = seq([str("$("), lazy(() => list0Import), char(")")], (results) => ({
62
- tag: "commandSubstitution",
63
- command: results[1],
64
- }));
65
- const unquotedEscape = seq([char("\\"), anyChar],
66
- // Backslash-newline inside a word disappears (line continuation).
67
- (results) => literal(results[1] === "\n" ? "" : results[1]));
68
- const DQ_ESCAPABLE = '"$`\\';
69
- const doubleQuoteEscape = or(seq([char("\\"), oneOf(DQ_ESCAPABLE)], (results) => literal(results[1])), seq([char("\\"), char("\n")], () => literal("")),
70
- // Backslash before any other character stays literal.
71
- map(char("\\"), () => literal("\\")));
72
- const isDoubleQuoteStop = compileCharPredicate('"$`\\');
73
- const doubleQuoteLiteral = map(takeWhile1((code) => !isDoubleQuoteStop(code), "string characters"), literal);
74
- // No backtick alternative: an unescaped backtick inside double quotes
75
- // fails the parse (backtick substitution is unsupported; use `$()`).
76
- const doubleQuoted = seq([
77
- char('"'),
78
- many(or(arithmeticExpansion, commandSubstitution, paramExpansion, variablePart, doubleQuoteEscape, doubleQuoteLiteral)),
79
- char('"'),
80
- ], (results) => ({
81
- tag: "doubleQuoted",
82
- parts: mergeLiterals(results[1]),
83
- }));
84
- const wordPart = or(singleQuoted, doubleQuoted, arithmeticExpansion, commandSubstitution, paramExpansion, variablePart, unquotedEscape, plainRun);
85
- const wordParserImpl = bashLexemes.lexeme(trace("bash:word", map(many1(wordPart), (parts) => ({
86
- tag: "word",
87
- parts: mergeLiterals(parts),
88
- }))));
89
- /** A word: quoting and expansions handled, trailing whitespace eaten.
90
- * Declared as a function so the words/lists/commands module cycle is
91
- * safe under any import order. */
92
- export function wordParser(input) {
93
- return wordParserImpl(input);
94
- }
95
- /** A word shaped like an assignment (`name=` / `name+=`). At command
96
- * position this is never a plain command word: `name=` was already taken
97
- * by the assignment parser, so what reaches here is unsupported syntax
98
- * (append, arrays) and must fail rather than parse as a command. */
99
- export const assignmentLookalike = seq([many1WithJoin(oneOf(IDENT_CHARS)), or(str("+="), str("="))], (results) => results[0]);
100
- const commandWordImpl = seq([not(reservedToken), not(assignmentLookalike), wordParser], (results) => results[2]);
101
- /** A word at command position: rejects reserved words standing alone,
102
- * which is how body lists stop at `fi`/`done`/`esac`. Quoted or extended
103
- * words (`"fi"`, `fi.txt`) pass, matching bash. */
104
- export function commandWord(input) {
105
- return commandWordImpl(input);
106
- }
107
- const identifierRunImpl = many1WithJoin(oneOf(IDENT_CHARS));
108
- /** A run of identifier characters, shared with the assignment parser.
109
- * Declared as a function for module-cycle safety. */
110
- export function identifierRun(input) {
111
- return identifierRunImpl(input);
112
- }