shell-dsl 0.0.1 → 0.0.3
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.
- package/README.md +572 -28
- package/dist/cjs/index.cjs +72 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/src/errors.cjs +73 -0
- package/dist/cjs/src/errors.cjs.map +10 -0
- package/dist/cjs/src/fs/index.cjs +37 -0
- package/dist/cjs/src/fs/index.cjs.map +10 -0
- package/dist/cjs/src/fs/memfs-adapter.cjs +221 -0
- package/dist/cjs/src/fs/memfs-adapter.cjs.map +10 -0
- package/dist/cjs/src/index.cjs +80 -0
- package/dist/cjs/src/index.cjs.map +10 -0
- package/dist/cjs/src/interpreter/context.cjs +47 -0
- package/dist/cjs/src/interpreter/context.cjs.map +10 -0
- package/dist/cjs/src/interpreter/index.cjs +39 -0
- package/dist/cjs/src/interpreter/index.cjs.map +10 -0
- package/dist/cjs/src/interpreter/interpreter.cjs +381 -0
- package/dist/cjs/src/interpreter/interpreter.cjs.map +10 -0
- package/dist/cjs/src/io/index.cjs +44 -0
- package/dist/cjs/src/io/index.cjs.map +10 -0
- package/dist/cjs/src/io/stdin.cjs +99 -0
- package/dist/cjs/src/io/stdin.cjs.map +10 -0
- package/dist/cjs/src/io/stdout.cjs +203 -0
- package/dist/cjs/src/io/stdout.cjs.map +10 -0
- package/dist/cjs/src/lexer/index.cjs +40 -0
- package/dist/cjs/src/lexer/index.cjs.map +10 -0
- package/dist/cjs/src/lexer/lexer.cjs +335 -0
- package/dist/cjs/src/lexer/lexer.cjs.map +10 -0
- package/dist/cjs/src/lexer/tokens.cjs +66 -0
- package/dist/cjs/src/lexer/tokens.cjs.map +10 -0
- package/dist/cjs/src/parser/ast.cjs +75 -0
- package/dist/cjs/src/parser/ast.cjs.map +10 -0
- package/dist/cjs/src/parser/index.cjs +49 -0
- package/dist/cjs/src/parser/index.cjs.map +10 -0
- package/dist/cjs/src/parser/parser.cjs +216 -0
- package/dist/cjs/src/parser/parser.cjs.map +10 -0
- package/dist/cjs/src/shell-dsl.cjs +187 -0
- package/dist/cjs/src/shell-dsl.cjs.map +10 -0
- package/dist/cjs/src/shell-promise.cjs +159 -0
- package/dist/cjs/src/shell-promise.cjs.map +10 -0
- package/dist/cjs/src/types.cjs +43 -0
- package/dist/cjs/src/types.cjs.map +10 -0
- package/dist/cjs/src/utils/escape.cjs +53 -0
- package/dist/cjs/src/utils/escape.cjs.map +10 -0
- package/dist/cjs/src/utils/index.cjs +38 -0
- package/dist/cjs/src/utils/index.cjs.map +10 -0
- package/dist/mjs/index.mjs +42 -0
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/mjs/src/errors.mjs +42 -0
- package/dist/mjs/src/errors.mjs.map +10 -0
- package/dist/mjs/src/fs/index.mjs +7 -0
- package/dist/mjs/src/fs/index.mjs.map +10 -0
- package/dist/mjs/src/fs/memfs-adapter.mjs +178 -0
- package/dist/mjs/src/fs/memfs-adapter.mjs.map +10 -0
- package/dist/mjs/src/index.mjs +61 -0
- package/dist/mjs/src/index.mjs.map +10 -0
- package/dist/mjs/src/interpreter/context.mjs +17 -0
- package/dist/mjs/src/interpreter/context.mjs.map +10 -0
- package/dist/mjs/src/interpreter/index.mjs +9 -0
- package/dist/mjs/src/interpreter/index.mjs.map +10 -0
- package/dist/mjs/src/interpreter/interpreter.mjs +351 -0
- package/dist/mjs/src/interpreter/interpreter.mjs.map +10 -0
- package/dist/mjs/src/io/index.mjs +14 -0
- package/dist/mjs/src/io/index.mjs.map +10 -0
- package/dist/mjs/src/io/stdin.mjs +68 -0
- package/dist/mjs/src/io/stdin.mjs.map +10 -0
- package/dist/mjs/src/io/stdout.mjs +172 -0
- package/dist/mjs/src/io/stdout.mjs.map +10 -0
- package/dist/mjs/src/lexer/index.mjs +10 -0
- package/dist/mjs/src/lexer/index.mjs.map +10 -0
- package/dist/mjs/src/lexer/lexer.mjs +305 -0
- package/dist/mjs/src/lexer/lexer.mjs.map +10 -0
- package/dist/mjs/src/lexer/tokens.mjs +36 -0
- package/dist/mjs/src/lexer/tokens.mjs.map +10 -0
- package/dist/mjs/src/parser/ast.mjs +45 -0
- package/dist/mjs/src/parser/ast.mjs.map +10 -0
- package/dist/mjs/src/parser/index.mjs +30 -0
- package/dist/mjs/src/parser/index.mjs.map +10 -0
- package/dist/mjs/src/parser/parser.mjs +189 -0
- package/dist/mjs/src/parser/parser.mjs.map +10 -0
- package/dist/mjs/src/shell-dsl.mjs +157 -0
- package/dist/mjs/src/shell-dsl.mjs.map +10 -0
- package/dist/mjs/src/shell-promise.mjs +129 -0
- package/dist/mjs/src/shell-promise.mjs.map +10 -0
- package/dist/mjs/src/types.mjs +13 -0
- package/dist/mjs/src/types.mjs.map +10 -0
- package/dist/mjs/src/utils/escape.mjs +23 -0
- package/dist/mjs/src/utils/escape.mjs.map +10 -0
- package/dist/mjs/src/utils/index.mjs +8 -0
- package/dist/mjs/src/utils/index.mjs.map +10 -0
- package/dist/types/commands/cat.d.ts +2 -0
- package/dist/types/commands/cp.d.ts +2 -0
- package/dist/types/commands/echo.d.ts +2 -0
- package/dist/types/commands/grep.d.ts +2 -0
- package/dist/types/commands/head.d.ts +2 -0
- package/dist/types/commands/index.d.ts +20 -0
- package/dist/types/commands/ls.d.ts +2 -0
- package/dist/types/commands/mkdir.d.ts +2 -0
- package/dist/types/commands/mv.d.ts +2 -0
- package/dist/types/commands/pwd.d.ts +2 -0
- package/dist/types/commands/rm.d.ts +2 -0
- package/dist/types/commands/sort.d.ts +2 -0
- package/dist/types/commands/tail.d.ts +2 -0
- package/dist/types/commands/tee.d.ts +2 -0
- package/dist/types/commands/test.d.ts +3 -0
- package/dist/types/commands/touch.d.ts +2 -0
- package/dist/types/commands/true-false.d.ts +3 -0
- package/dist/types/commands/uniq.d.ts +2 -0
- package/dist/types/commands/wc.d.ts +2 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/src/errors.d.ts +16 -0
- package/dist/types/src/fs/index.d.ts +1 -0
- package/dist/types/src/fs/memfs-adapter.d.ts +3 -0
- package/dist/types/src/index.d.ts +15 -0
- package/dist/types/src/interpreter/context.d.ts +11 -0
- package/dist/types/src/interpreter/index.d.ts +2 -0
- package/dist/types/src/interpreter/interpreter.d.ts +32 -0
- package/dist/types/src/io/index.d.ts +2 -0
- package/dist/types/src/io/stdin.d.ts +11 -0
- package/dist/types/src/io/stdout.d.ts +40 -0
- package/dist/types/src/lexer/index.d.ts +3 -0
- package/dist/types/src/lexer/lexer.d.ts +24 -0
- package/dist/types/src/lexer/tokens.d.ts +38 -0
- package/dist/types/src/parser/ast.d.ts +64 -0
- package/dist/types/src/parser/index.d.ts +3 -0
- package/dist/types/src/parser/parser.d.ts +23 -0
- package/dist/types/src/shell-dsl.d.ts +32 -0
- package/dist/types/src/shell-promise.d.ts +39 -0
- package/dist/types/src/types.d.ts +76 -0
- package/dist/types/src/utils/escape.d.ts +2 -0
- package/dist/types/src/utils/index.d.ts +1 -0
- package/package.json +46 -6
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
// src/lexer/lexer.ts
|
|
2
|
+
import { LexError } from "../errors.mjs";
|
|
3
|
+
var GLOB_CHARS = new Set(["*", "?", "[", "{", "}"]);
|
|
4
|
+
var WORD_BREAK_CHARS = new Set([
|
|
5
|
+
" ",
|
|
6
|
+
"\t",
|
|
7
|
+
`
|
|
8
|
+
`,
|
|
9
|
+
"\r",
|
|
10
|
+
"|",
|
|
11
|
+
"&",
|
|
12
|
+
";",
|
|
13
|
+
">",
|
|
14
|
+
"<",
|
|
15
|
+
"(",
|
|
16
|
+
")",
|
|
17
|
+
"$",
|
|
18
|
+
"'",
|
|
19
|
+
'"',
|
|
20
|
+
"`"
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
class Lexer {
|
|
24
|
+
source;
|
|
25
|
+
pos = 0;
|
|
26
|
+
line = 1;
|
|
27
|
+
column = 1;
|
|
28
|
+
constructor(source) {
|
|
29
|
+
this.source = source;
|
|
30
|
+
}
|
|
31
|
+
tokenize() {
|
|
32
|
+
const tokens = [];
|
|
33
|
+
while (!this.isAtEnd()) {
|
|
34
|
+
this.skipWhitespace();
|
|
35
|
+
if (this.isAtEnd())
|
|
36
|
+
break;
|
|
37
|
+
const token = this.nextToken();
|
|
38
|
+
if (token) {
|
|
39
|
+
tokens.push(token);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
tokens.push({ type: "eof" });
|
|
43
|
+
return tokens;
|
|
44
|
+
}
|
|
45
|
+
nextToken() {
|
|
46
|
+
const char = this.peek();
|
|
47
|
+
if (char === "#") {
|
|
48
|
+
this.skipComment();
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
if (char === "|") {
|
|
52
|
+
this.advance();
|
|
53
|
+
if (this.peek() === "|") {
|
|
54
|
+
this.advance();
|
|
55
|
+
return { type: "or" };
|
|
56
|
+
}
|
|
57
|
+
return { type: "pipe" };
|
|
58
|
+
}
|
|
59
|
+
if (char === "&") {
|
|
60
|
+
this.advance();
|
|
61
|
+
if (this.peek() === "&") {
|
|
62
|
+
this.advance();
|
|
63
|
+
return { type: "and" };
|
|
64
|
+
}
|
|
65
|
+
if (this.peek() === ">") {
|
|
66
|
+
this.advance();
|
|
67
|
+
if (this.peek() === ">") {
|
|
68
|
+
this.advance();
|
|
69
|
+
return { type: "redirect", mode: "&>>" };
|
|
70
|
+
}
|
|
71
|
+
return { type: "redirect", mode: "&>" };
|
|
72
|
+
}
|
|
73
|
+
return { type: "word", value: "&" };
|
|
74
|
+
}
|
|
75
|
+
if (char === ";") {
|
|
76
|
+
this.advance();
|
|
77
|
+
return { type: "semicolon" };
|
|
78
|
+
}
|
|
79
|
+
if (char === ">") {
|
|
80
|
+
this.advance();
|
|
81
|
+
if (this.peek() === ">") {
|
|
82
|
+
this.advance();
|
|
83
|
+
return { type: "redirect", mode: ">>" };
|
|
84
|
+
}
|
|
85
|
+
return { type: "redirect", mode: ">" };
|
|
86
|
+
}
|
|
87
|
+
if (char === "<") {
|
|
88
|
+
this.advance();
|
|
89
|
+
return { type: "redirect", mode: "<" };
|
|
90
|
+
}
|
|
91
|
+
if (char === "1" || char === "2") {
|
|
92
|
+
const fd = char;
|
|
93
|
+
const nextChar = this.peekAhead(1);
|
|
94
|
+
if (nextChar === ">") {
|
|
95
|
+
this.advance();
|
|
96
|
+
this.advance();
|
|
97
|
+
if (fd === "2") {
|
|
98
|
+
if (this.peek() === "&" && this.peekAhead(1) === "1") {
|
|
99
|
+
this.advance();
|
|
100
|
+
this.advance();
|
|
101
|
+
return { type: "redirect", mode: "2>&1" };
|
|
102
|
+
}
|
|
103
|
+
if (this.peek() === ">") {
|
|
104
|
+
this.advance();
|
|
105
|
+
return { type: "redirect", mode: "2>>" };
|
|
106
|
+
}
|
|
107
|
+
return { type: "redirect", mode: "2>" };
|
|
108
|
+
} else {
|
|
109
|
+
if (this.peek() === "&" && this.peekAhead(1) === "2") {
|
|
110
|
+
this.advance();
|
|
111
|
+
this.advance();
|
|
112
|
+
return { type: "redirect", mode: "1>&2" };
|
|
113
|
+
}
|
|
114
|
+
if (this.peek() === ">") {
|
|
115
|
+
this.advance();
|
|
116
|
+
return { type: "redirect", mode: ">>" };
|
|
117
|
+
}
|
|
118
|
+
return { type: "redirect", mode: ">" };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (char === "$") {
|
|
123
|
+
return this.readVariable();
|
|
124
|
+
}
|
|
125
|
+
if (char === "'") {
|
|
126
|
+
return this.readSingleQuote();
|
|
127
|
+
}
|
|
128
|
+
if (char === '"') {
|
|
129
|
+
return this.readDoubleQuote();
|
|
130
|
+
}
|
|
131
|
+
return this.readWord();
|
|
132
|
+
}
|
|
133
|
+
readVariable() {
|
|
134
|
+
this.advance();
|
|
135
|
+
if (this.peek() === "(") {
|
|
136
|
+
this.advance();
|
|
137
|
+
const command = this.readUntilMatchingParen();
|
|
138
|
+
return { type: "substitution", command };
|
|
139
|
+
}
|
|
140
|
+
if (this.peek() === "{") {
|
|
141
|
+
this.advance();
|
|
142
|
+
let name2 = "";
|
|
143
|
+
while (!this.isAtEnd() && this.peek() !== "}") {
|
|
144
|
+
name2 += this.advance();
|
|
145
|
+
}
|
|
146
|
+
if (this.peek() === "}") {
|
|
147
|
+
this.advance();
|
|
148
|
+
}
|
|
149
|
+
return { type: "variable", name: name2 };
|
|
150
|
+
}
|
|
151
|
+
let name = "";
|
|
152
|
+
while (!this.isAtEnd() && this.isVarChar(this.peek())) {
|
|
153
|
+
name += this.advance();
|
|
154
|
+
}
|
|
155
|
+
if (name === "") {
|
|
156
|
+
return { type: "word", value: "$" };
|
|
157
|
+
}
|
|
158
|
+
return { type: "variable", name };
|
|
159
|
+
}
|
|
160
|
+
readUntilMatchingParen() {
|
|
161
|
+
let depth = 1;
|
|
162
|
+
let result = "";
|
|
163
|
+
while (!this.isAtEnd() && depth > 0) {
|
|
164
|
+
const char = this.peek();
|
|
165
|
+
if (char === "(") {
|
|
166
|
+
depth++;
|
|
167
|
+
} else if (char === ")") {
|
|
168
|
+
depth--;
|
|
169
|
+
if (depth === 0) {
|
|
170
|
+
this.advance();
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
result += this.advance();
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
readSingleQuote() {
|
|
179
|
+
this.advance();
|
|
180
|
+
let value = "";
|
|
181
|
+
while (!this.isAtEnd() && this.peek() !== "'") {
|
|
182
|
+
value += this.advance();
|
|
183
|
+
}
|
|
184
|
+
if (this.peek() === "'") {
|
|
185
|
+
this.advance();
|
|
186
|
+
} else {
|
|
187
|
+
throw new LexError("Unterminated single quote", this.pos, this.line, this.column);
|
|
188
|
+
}
|
|
189
|
+
return { type: "singleQuote", value };
|
|
190
|
+
}
|
|
191
|
+
readDoubleQuote() {
|
|
192
|
+
this.advance();
|
|
193
|
+
const parts = [];
|
|
194
|
+
let currentString = "";
|
|
195
|
+
while (!this.isAtEnd() && this.peek() !== '"') {
|
|
196
|
+
const char = this.peek();
|
|
197
|
+
if (char === "\\") {
|
|
198
|
+
this.advance();
|
|
199
|
+
if (!this.isAtEnd()) {
|
|
200
|
+
const escaped = this.advance();
|
|
201
|
+
if (["$", '"', "\\", "`", `
|
|
202
|
+
`].includes(escaped)) {
|
|
203
|
+
currentString += escaped;
|
|
204
|
+
} else {
|
|
205
|
+
currentString += "\\" + escaped;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else if (char === "$") {
|
|
209
|
+
if (currentString) {
|
|
210
|
+
parts.push(currentString);
|
|
211
|
+
currentString = "";
|
|
212
|
+
}
|
|
213
|
+
parts.push(this.readVariable());
|
|
214
|
+
} else {
|
|
215
|
+
currentString += this.advance();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (currentString) {
|
|
219
|
+
parts.push(currentString);
|
|
220
|
+
}
|
|
221
|
+
if (this.peek() === '"') {
|
|
222
|
+
this.advance();
|
|
223
|
+
} else {
|
|
224
|
+
throw new LexError("Unterminated double quote", this.pos, this.line, this.column);
|
|
225
|
+
}
|
|
226
|
+
return { type: "doubleQuote", parts };
|
|
227
|
+
}
|
|
228
|
+
readWord() {
|
|
229
|
+
let value = "";
|
|
230
|
+
let hasGlobChars = false;
|
|
231
|
+
while (!this.isAtEnd() && !this.isWordBreak(this.peek())) {
|
|
232
|
+
const char = this.peek();
|
|
233
|
+
if (char === "\\") {
|
|
234
|
+
this.advance();
|
|
235
|
+
if (!this.isAtEnd()) {
|
|
236
|
+
value += this.advance();
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
if (GLOB_CHARS.has(char)) {
|
|
240
|
+
hasGlobChars = true;
|
|
241
|
+
}
|
|
242
|
+
value += this.advance();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const assignmentMatch = value.match(/^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$/);
|
|
246
|
+
if (assignmentMatch) {
|
|
247
|
+
return {
|
|
248
|
+
type: "assignment",
|
|
249
|
+
name: assignmentMatch[1],
|
|
250
|
+
value: assignmentMatch[2]
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (hasGlobChars) {
|
|
254
|
+
return { type: "glob", pattern: value };
|
|
255
|
+
}
|
|
256
|
+
return { type: "word", value };
|
|
257
|
+
}
|
|
258
|
+
isWordBreak(char) {
|
|
259
|
+
return WORD_BREAK_CHARS.has(char);
|
|
260
|
+
}
|
|
261
|
+
isVarChar(char) {
|
|
262
|
+
return /[a-zA-Z0-9_]/.test(char);
|
|
263
|
+
}
|
|
264
|
+
skipWhitespace() {
|
|
265
|
+
while (!this.isAtEnd() && /\s/.test(this.peek())) {
|
|
266
|
+
this.advance();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
skipComment() {
|
|
270
|
+
while (!this.isAtEnd() && this.peek() !== `
|
|
271
|
+
`) {
|
|
272
|
+
this.advance();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
peek() {
|
|
276
|
+
return this.source[this.pos] ?? "";
|
|
277
|
+
}
|
|
278
|
+
peekAhead(n) {
|
|
279
|
+
return this.source[this.pos + n] ?? "";
|
|
280
|
+
}
|
|
281
|
+
advance() {
|
|
282
|
+
const char = this.source[this.pos];
|
|
283
|
+
this.pos++;
|
|
284
|
+
if (char === `
|
|
285
|
+
`) {
|
|
286
|
+
this.line++;
|
|
287
|
+
this.column = 1;
|
|
288
|
+
} else {
|
|
289
|
+
this.column++;
|
|
290
|
+
}
|
|
291
|
+
return char;
|
|
292
|
+
}
|
|
293
|
+
isAtEnd() {
|
|
294
|
+
return this.pos >= this.source.length;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function lex(source) {
|
|
298
|
+
return new Lexer(source).tokenize();
|
|
299
|
+
}
|
|
300
|
+
export {
|
|
301
|
+
lex,
|
|
302
|
+
Lexer
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
//# debugId=819722A2CE193AAF64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lexer/lexer.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { LexError } from \"../errors.mjs\";\nimport type { Token, RedirectMode } from \"./tokens.mjs\";\n\nconst GLOB_CHARS = new Set([\"*\", \"?\", \"[\", \"{\", \"}\"]);\nconst WORD_BREAK_CHARS = new Set([\n \" \",\n \"\\t\",\n \"\\n\",\n \"\\r\",\n \"|\",\n \"&\",\n \";\",\n \">\",\n \"<\",\n \"(\",\n \")\",\n \"$\",\n \"'\",\n '\"',\n \"`\",\n]);\n\nexport class Lexer {\n private source: string;\n private pos: number = 0;\n private line: number = 1;\n private column: number = 1;\n\n constructor(source: string) {\n this.source = source;\n }\n\n tokenize(): Token[] {\n const tokens: Token[] = [];\n\n while (!this.isAtEnd()) {\n this.skipWhitespace();\n if (this.isAtEnd()) break;\n\n const token = this.nextToken();\n if (token) {\n tokens.push(token);\n }\n }\n\n tokens.push({ type: \"eof\" });\n return tokens;\n }\n\n private nextToken(): Token | null {\n const char = this.peek();\n\n // Comments\n if (char === \"#\") {\n this.skipComment();\n return null;\n }\n\n // Operators and redirects\n if (char === \"|\") {\n this.advance();\n if (this.peek() === \"|\") {\n this.advance();\n return { type: \"or\" };\n }\n return { type: \"pipe\" };\n }\n\n if (char === \"&\") {\n this.advance();\n if (this.peek() === \"&\") {\n this.advance();\n return { type: \"and\" };\n }\n if (this.peek() === \">\") {\n this.advance();\n if (this.peek() === \">\") {\n this.advance();\n return { type: \"redirect\", mode: \"&>>\" };\n }\n return { type: \"redirect\", mode: \"&>\" };\n }\n // Background execution (&) - treat as word for now\n return { type: \"word\", value: \"&\" };\n }\n\n if (char === \";\") {\n this.advance();\n return { type: \"semicolon\" };\n }\n\n // Redirects\n if (char === \">\") {\n this.advance();\n if (this.peek() === \">\") {\n this.advance();\n return { type: \"redirect\", mode: \">>\" };\n }\n return { type: \"redirect\", mode: \">\" };\n }\n\n if (char === \"<\") {\n this.advance();\n return { type: \"redirect\", mode: \"<\" };\n }\n\n // File descriptor redirects (2>, 2>>, 2>&1, 1>&2)\n if (char === \"1\" || char === \"2\") {\n const fd = char;\n const nextChar = this.peekAhead(1);\n if (nextChar === \">\") {\n this.advance(); // consume fd\n this.advance(); // consume >\n if (fd === \"2\") {\n if (this.peek() === \"&\" && this.peekAhead(1) === \"1\") {\n this.advance(); // consume &\n this.advance(); // consume 1\n return { type: \"redirect\", mode: \"2>&1\" };\n }\n if (this.peek() === \">\") {\n this.advance();\n return { type: \"redirect\", mode: \"2>>\" };\n }\n return { type: \"redirect\", mode: \"2>\" };\n } else {\n // 1>&2\n if (this.peek() === \"&\" && this.peekAhead(1) === \"2\") {\n this.advance(); // consume &\n this.advance(); // consume 2\n return { type: \"redirect\", mode: \"1>&2\" };\n }\n if (this.peek() === \">\") {\n this.advance();\n return { type: \"redirect\", mode: \">>\" };\n }\n return { type: \"redirect\", mode: \">\" };\n }\n }\n }\n\n // Variables and substitutions\n if (char === \"$\") {\n return this.readVariable();\n }\n\n // Single quotes\n if (char === \"'\") {\n return this.readSingleQuote();\n }\n\n // Double quotes\n if (char === '\"') {\n return this.readDoubleQuote();\n }\n\n // Word (including potential globs and assignments)\n return this.readWord();\n }\n\n private readVariable(): Token {\n this.advance(); // consume $\n\n // Command substitution $(...)\n if (this.peek() === \"(\") {\n this.advance(); // consume (\n const command = this.readUntilMatchingParen();\n return { type: \"substitution\", command };\n }\n\n // ${VAR} syntax\n if (this.peek() === \"{\") {\n this.advance(); // consume {\n let name = \"\";\n while (!this.isAtEnd() && this.peek() !== \"}\") {\n name += this.advance();\n }\n if (this.peek() === \"}\") {\n this.advance(); // consume }\n }\n return { type: \"variable\", name };\n }\n\n // $VAR syntax\n let name = \"\";\n while (!this.isAtEnd() && this.isVarChar(this.peek())) {\n name += this.advance();\n }\n\n if (name === \"\") {\n return { type: \"word\", value: \"$\" };\n }\n\n return { type: \"variable\", name };\n }\n\n private readUntilMatchingParen(): string {\n let depth = 1;\n let result = \"\";\n\n while (!this.isAtEnd() && depth > 0) {\n const char = this.peek();\n if (char === \"(\") {\n depth++;\n } else if (char === \")\") {\n depth--;\n if (depth === 0) {\n this.advance(); // consume closing )\n break;\n }\n }\n result += this.advance();\n }\n\n return result;\n }\n\n private readSingleQuote(): Token {\n this.advance(); // consume opening '\n let value = \"\";\n\n while (!this.isAtEnd() && this.peek() !== \"'\") {\n value += this.advance();\n }\n\n if (this.peek() === \"'\") {\n this.advance(); // consume closing '\n } else {\n throw new LexError(\"Unterminated single quote\", this.pos, this.line, this.column);\n }\n\n return { type: \"singleQuote\", value };\n }\n\n private readDoubleQuote(): Token {\n this.advance(); // consume opening \"\n const parts: Array<string | Token> = [];\n let currentString = \"\";\n\n while (!this.isAtEnd() && this.peek() !== '\"') {\n const char = this.peek();\n\n if (char === \"\\\\\") {\n this.advance();\n if (!this.isAtEnd()) {\n const escaped = this.advance();\n // In double quotes, only certain chars are special\n if ([\"$\", '\"', \"\\\\\", \"`\", \"\\n\"].includes(escaped)) {\n currentString += escaped;\n } else {\n currentString += \"\\\\\" + escaped;\n }\n }\n } else if (char === \"$\") {\n if (currentString) {\n parts.push(currentString);\n currentString = \"\";\n }\n parts.push(this.readVariable());\n } else {\n currentString += this.advance();\n }\n }\n\n if (currentString) {\n parts.push(currentString);\n }\n\n if (this.peek() === '\"') {\n this.advance(); // consume closing \"\n } else {\n throw new LexError(\"Unterminated double quote\", this.pos, this.line, this.column);\n }\n\n return { type: \"doubleQuote\", parts };\n }\n\n private readWord(): Token {\n let value = \"\";\n let hasGlobChars = false;\n\n while (!this.isAtEnd() && !this.isWordBreak(this.peek())) {\n const char = this.peek();\n\n if (char === \"\\\\\") {\n this.advance();\n if (!this.isAtEnd()) {\n value += this.advance();\n }\n } else {\n if (GLOB_CHARS.has(char)) {\n hasGlobChars = true;\n }\n value += this.advance();\n }\n }\n\n // Check if this is an assignment (VAR=value)\n const assignmentMatch = value.match(/^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$/);\n if (assignmentMatch) {\n return {\n type: \"assignment\",\n name: assignmentMatch[1]!,\n value: assignmentMatch[2]!,\n };\n }\n\n if (hasGlobChars) {\n return { type: \"glob\", pattern: value };\n }\n\n return { type: \"word\", value };\n }\n\n private isWordBreak(char: string): boolean {\n return WORD_BREAK_CHARS.has(char);\n }\n\n private isVarChar(char: string): boolean {\n return /[a-zA-Z0-9_]/.test(char);\n }\n\n private skipWhitespace(): void {\n while (!this.isAtEnd() && /\\s/.test(this.peek())) {\n this.advance();\n }\n }\n\n private skipComment(): void {\n while (!this.isAtEnd() && this.peek() !== \"\\n\") {\n this.advance();\n }\n }\n\n private peek(): string {\n return this.source[this.pos] ?? \"\";\n }\n\n private peekAhead(n: number): string {\n return this.source[this.pos + n] ?? \"\";\n }\n\n private advance(): string {\n const char = this.source[this.pos]!;\n this.pos++;\n if (char === \"\\n\") {\n this.line++;\n this.column = 1;\n } else {\n this.column++;\n }\n return char;\n }\n\n private isAtEnd(): boolean {\n return this.pos >= this.source.length;\n }\n}\n\nexport function lex(source: string): Token[] {\n return new Lexer(source).tokenize();\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAAA;AAGA,IAAM,aAAa,IAAI,IAAI,CAAC,KAAK,KAAK,KAAK,KAAK,GAAG,CAAC;AACpD,IAAM,mBAAmB,IAAI,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAAA;AAEM,MAAM,MAAM;AAAA,EACT;AAAA,EACA,MAAc;AAAA,EACd,OAAe;AAAA,EACf,SAAiB;AAAA,EAEzB,WAAW,CAAC,QAAgB;AAAA,IAC1B,KAAK,SAAS;AAAA;AAAA,EAGhB,QAAQ,GAAY;AAAA,IAClB,MAAM,SAAkB,CAAC;AAAA,IAEzB,OAAO,CAAC,KAAK,QAAQ,GAAG;AAAA,MACtB,KAAK,eAAe;AAAA,MACpB,IAAI,KAAK,QAAQ;AAAA,QAAG;AAAA,MAEpB,MAAM,QAAQ,KAAK,UAAU;AAAA,MAC7B,IAAI,OAAO;AAAA,QACT,OAAO,KAAK,KAAK;AAAA,MACnB;AAAA,IACF;AAAA,IAEA,OAAO,KAAK,EAAE,MAAM,MAAM,CAAC;AAAA,IAC3B,OAAO;AAAA;AAAA,EAGD,SAAS,GAAiB;AAAA,IAChC,MAAM,OAAO,KAAK,KAAK;AAAA,IAGvB,IAAI,SAAS,KAAK;AAAA,MAChB,KAAK,YAAY;AAAA,MACjB,OAAO;AAAA,IACT;AAAA,IAGA,IAAI,SAAS,KAAK;AAAA,MAChB,KAAK,QAAQ;AAAA,MACb,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,QACvB,KAAK,QAAQ;AAAA,QACb,OAAO,EAAE,MAAM,KAAK;AAAA,MACtB;AAAA,MACA,OAAO,EAAE,MAAM,OAAO;AAAA,IACxB;AAAA,IAEA,IAAI,SAAS,KAAK;AAAA,MAChB,KAAK,QAAQ;AAAA,MACb,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,QACvB,KAAK,QAAQ;AAAA,QACb,OAAO,EAAE,MAAM,MAAM;AAAA,MACvB;AAAA,MACA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,QACvB,KAAK,QAAQ;AAAA,QACb,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,UACvB,KAAK,QAAQ;AAAA,UACb,OAAO,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,QACzC;AAAA,QACA,OAAO,EAAE,MAAM,YAAY,MAAM,KAAK;AAAA,MACxC;AAAA,MAEA,OAAO,EAAE,MAAM,QAAQ,OAAO,IAAI;AAAA,IACpC;AAAA,IAEA,IAAI,SAAS,KAAK;AAAA,MAChB,KAAK,QAAQ;AAAA,MACb,OAAO,EAAE,MAAM,YAAY;AAAA,IAC7B;AAAA,IAGA,IAAI,SAAS,KAAK;AAAA,MAChB,KAAK,QAAQ;AAAA,MACb,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,QACvB,KAAK,QAAQ;AAAA,QACb,OAAO,EAAE,MAAM,YAAY,MAAM,KAAK;AAAA,MACxC;AAAA,MACA,OAAO,EAAE,MAAM,YAAY,MAAM,IAAI;AAAA,IACvC;AAAA,IAEA,IAAI,SAAS,KAAK;AAAA,MAChB,KAAK,QAAQ;AAAA,MACb,OAAO,EAAE,MAAM,YAAY,MAAM,IAAI;AAAA,IACvC;AAAA,IAGA,IAAI,SAAS,OAAO,SAAS,KAAK;AAAA,MAChC,MAAM,KAAK;AAAA,MACX,MAAM,WAAW,KAAK,UAAU,CAAC;AAAA,MACjC,IAAI,aAAa,KAAK;AAAA,QACpB,KAAK,QAAQ;AAAA,QACb,KAAK,QAAQ;AAAA,QACb,IAAI,OAAO,KAAK;AAAA,UACd,IAAI,KAAK,KAAK,MAAM,OAAO,KAAK,UAAU,CAAC,MAAM,KAAK;AAAA,YACpD,KAAK,QAAQ;AAAA,YACb,KAAK,QAAQ;AAAA,YACb,OAAO,EAAE,MAAM,YAAY,MAAM,OAAO;AAAA,UAC1C;AAAA,UACA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,YACvB,KAAK,QAAQ;AAAA,YACb,OAAO,EAAE,MAAM,YAAY,MAAM,MAAM;AAAA,UACzC;AAAA,UACA,OAAO,EAAE,MAAM,YAAY,MAAM,KAAK;AAAA,QACxC,EAAO;AAAA,UAEL,IAAI,KAAK,KAAK,MAAM,OAAO,KAAK,UAAU,CAAC,MAAM,KAAK;AAAA,YACpD,KAAK,QAAQ;AAAA,YACb,KAAK,QAAQ;AAAA,YACb,OAAO,EAAE,MAAM,YAAY,MAAM,OAAO;AAAA,UAC1C;AAAA,UACA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,YACvB,KAAK,QAAQ;AAAA,YACb,OAAO,EAAE,MAAM,YAAY,MAAM,KAAK;AAAA,UACxC;AAAA,UACA,OAAO,EAAE,MAAM,YAAY,MAAM,IAAI;AAAA;AAAA,MAEzC;AAAA,IACF;AAAA,IAGA,IAAI,SAAS,KAAK;AAAA,MAChB,OAAO,KAAK,aAAa;AAAA,IAC3B;AAAA,IAGA,IAAI,SAAS,KAAK;AAAA,MAChB,OAAO,KAAK,gBAAgB;AAAA,IAC9B;AAAA,IAGA,IAAI,SAAS,KAAK;AAAA,MAChB,OAAO,KAAK,gBAAgB;AAAA,IAC9B;AAAA,IAGA,OAAO,KAAK,SAAS;AAAA;AAAA,EAGf,YAAY,GAAU;AAAA,IAC5B,KAAK,QAAQ;AAAA,IAGb,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,MACvB,KAAK,QAAQ;AAAA,MACb,MAAM,UAAU,KAAK,uBAAuB;AAAA,MAC5C,OAAO,EAAE,MAAM,gBAAgB,QAAQ;AAAA,IACzC;AAAA,IAGA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,MACvB,KAAK,QAAQ;AAAA,MACb,IAAI,QAAO;AAAA,MACX,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM,KAAK;AAAA,QAC7C,SAAQ,KAAK,QAAQ;AAAA,MACvB;AAAA,MACA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,QACvB,KAAK,QAAQ;AAAA,MACf;AAAA,MACA,OAAO,EAAE,MAAM,YAAY,YAAK;AAAA,IAClC;AAAA,IAGA,IAAI,OAAO;AAAA,IACX,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,UAAU,KAAK,KAAK,CAAC,GAAG;AAAA,MACrD,QAAQ,KAAK,QAAQ;AAAA,IACvB;AAAA,IAEA,IAAI,SAAS,IAAI;AAAA,MACf,OAAO,EAAE,MAAM,QAAQ,OAAO,IAAI;AAAA,IACpC;AAAA,IAEA,OAAO,EAAE,MAAM,YAAY,KAAK;AAAA;AAAA,EAG1B,sBAAsB,GAAW;AAAA,IACvC,IAAI,QAAQ;AAAA,IACZ,IAAI,SAAS;AAAA,IAEb,OAAO,CAAC,KAAK,QAAQ,KAAK,QAAQ,GAAG;AAAA,MACnC,MAAM,OAAO,KAAK,KAAK;AAAA,MACvB,IAAI,SAAS,KAAK;AAAA,QAChB;AAAA,MACF,EAAO,SAAI,SAAS,KAAK;AAAA,QACvB;AAAA,QACA,IAAI,UAAU,GAAG;AAAA,UACf,KAAK,QAAQ;AAAA,UACb;AAAA,QACF;AAAA,MACF;AAAA,MACA,UAAU,KAAK,QAAQ;AAAA,IACzB;AAAA,IAEA,OAAO;AAAA;AAAA,EAGD,eAAe,GAAU;AAAA,IAC/B,KAAK,QAAQ;AAAA,IACb,IAAI,QAAQ;AAAA,IAEZ,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM,KAAK;AAAA,MAC7C,SAAS,KAAK,QAAQ;AAAA,IACxB;AAAA,IAEA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,MACvB,KAAK,QAAQ;AAAA,IACf,EAAO;AAAA,MACL,MAAM,IAAI,SAAS,6BAA6B,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM;AAAA;AAAA,IAGlF,OAAO,EAAE,MAAM,eAAe,MAAM;AAAA;AAAA,EAG9B,eAAe,GAAU;AAAA,IAC/B,KAAK,QAAQ;AAAA,IACb,MAAM,QAA+B,CAAC;AAAA,IACtC,IAAI,gBAAgB;AAAA,IAEpB,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM,KAAK;AAAA,MAC7C,MAAM,OAAO,KAAK,KAAK;AAAA,MAEvB,IAAI,SAAS,MAAM;AAAA,QACjB,KAAK,QAAQ;AAAA,QACb,IAAI,CAAC,KAAK,QAAQ,GAAG;AAAA,UACnB,MAAM,UAAU,KAAK,QAAQ;AAAA,UAE7B,IAAI,CAAC,KAAK,KAAK,MAAM,KAAK;AAAA,CAAI,EAAE,SAAS,OAAO,GAAG;AAAA,YACjD,iBAAiB;AAAA,UACnB,EAAO;AAAA,YACL,iBAAiB,OAAO;AAAA;AAAA,QAE5B;AAAA,MACF,EAAO,SAAI,SAAS,KAAK;AAAA,QACvB,IAAI,eAAe;AAAA,UACjB,MAAM,KAAK,aAAa;AAAA,UACxB,gBAAgB;AAAA,QAClB;AAAA,QACA,MAAM,KAAK,KAAK,aAAa,CAAC;AAAA,MAChC,EAAO;AAAA,QACL,iBAAiB,KAAK,QAAQ;AAAA;AAAA,IAElC;AAAA,IAEA,IAAI,eAAe;AAAA,MACjB,MAAM,KAAK,aAAa;AAAA,IAC1B;AAAA,IAEA,IAAI,KAAK,KAAK,MAAM,KAAK;AAAA,MACvB,KAAK,QAAQ;AAAA,IACf,EAAO;AAAA,MACL,MAAM,IAAI,SAAS,6BAA6B,KAAK,KAAK,KAAK,MAAM,KAAK,MAAM;AAAA;AAAA,IAGlF,OAAO,EAAE,MAAM,eAAe,MAAM;AAAA;AAAA,EAG9B,QAAQ,GAAU;AAAA,IACxB,IAAI,QAAQ;AAAA,IACZ,IAAI,eAAe;AAAA,IAEnB,OAAO,CAAC,KAAK,QAAQ,KAAK,CAAC,KAAK,YAAY,KAAK,KAAK,CAAC,GAAG;AAAA,MACxD,MAAM,OAAO,KAAK,KAAK;AAAA,MAEvB,IAAI,SAAS,MAAM;AAAA,QACjB,KAAK,QAAQ;AAAA,QACb,IAAI,CAAC,KAAK,QAAQ,GAAG;AAAA,UACnB,SAAS,KAAK,QAAQ;AAAA,QACxB;AAAA,MACF,EAAO;AAAA,QACL,IAAI,WAAW,IAAI,IAAI,GAAG;AAAA,UACxB,eAAe;AAAA,QACjB;AAAA,QACA,SAAS,KAAK,QAAQ;AAAA;AAAA,IAE1B;AAAA,IAGA,MAAM,kBAAkB,MAAM,MAAM,iCAAiC;AAAA,IACrE,IAAI,iBAAiB;AAAA,MACnB,OAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,gBAAgB;AAAA,QACtB,OAAO,gBAAgB;AAAA,MACzB;AAAA,IACF;AAAA,IAEA,IAAI,cAAc;AAAA,MAChB,OAAO,EAAE,MAAM,QAAQ,SAAS,MAAM;AAAA,IACxC;AAAA,IAEA,OAAO,EAAE,MAAM,QAAQ,MAAM;AAAA;AAAA,EAGvB,WAAW,CAAC,MAAuB;AAAA,IACzC,OAAO,iBAAiB,IAAI,IAAI;AAAA;AAAA,EAG1B,SAAS,CAAC,MAAuB;AAAA,IACvC,OAAO,eAAe,KAAK,IAAI;AAAA;AAAA,EAGzB,cAAc,GAAS;AAAA,IAC7B,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,KAAK,KAAK,CAAC,GAAG;AAAA,MAChD,KAAK,QAAQ;AAAA,IACf;AAAA;AAAA,EAGM,WAAW,GAAS;AAAA,IAC1B,OAAO,CAAC,KAAK,QAAQ,KAAK,KAAK,KAAK,MAAM;AAAA,GAAM;AAAA,MAC9C,KAAK,QAAQ;AAAA,IACf;AAAA;AAAA,EAGM,IAAI,GAAW;AAAA,IACrB,OAAO,KAAK,OAAO,KAAK,QAAQ;AAAA;AAAA,EAG1B,SAAS,CAAC,GAAmB;AAAA,IACnC,OAAO,KAAK,OAAO,KAAK,MAAM,MAAM;AAAA;AAAA,EAG9B,OAAO,GAAW;AAAA,IACxB,MAAM,OAAO,KAAK,OAAO,KAAK;AAAA,IAC9B,KAAK;AAAA,IACL,IAAI,SAAS;AAAA,GAAM;AAAA,MACjB,KAAK;AAAA,MACL,KAAK,SAAS;AAAA,IAChB,EAAO;AAAA,MACL,KAAK;AAAA;AAAA,IAEP,OAAO;AAAA;AAAA,EAGD,OAAO,GAAY;AAAA,IACzB,OAAO,KAAK,OAAO,KAAK,OAAO;AAAA;AAEnC;AAEO,SAAS,GAAG,CAAC,QAAyB;AAAA,EAC3C,OAAO,IAAI,MAAM,MAAM,EAAE,SAAS;AAAA;",
|
|
8
|
+
"debugId": "819722A2CE193AAF64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// src/lexer/tokens.ts
|
|
2
|
+
function tokenToString(token) {
|
|
3
|
+
switch (token.type) {
|
|
4
|
+
case "word":
|
|
5
|
+
return token.value;
|
|
6
|
+
case "pipe":
|
|
7
|
+
return "|";
|
|
8
|
+
case "and":
|
|
9
|
+
return "&&";
|
|
10
|
+
case "or":
|
|
11
|
+
return "||";
|
|
12
|
+
case "semicolon":
|
|
13
|
+
return ";";
|
|
14
|
+
case "redirect":
|
|
15
|
+
return token.mode;
|
|
16
|
+
case "variable":
|
|
17
|
+
return `$${token.name}`;
|
|
18
|
+
case "substitution":
|
|
19
|
+
return `$(${token.command})`;
|
|
20
|
+
case "glob":
|
|
21
|
+
return token.pattern;
|
|
22
|
+
case "singleQuote":
|
|
23
|
+
return `'${token.value}'`;
|
|
24
|
+
case "doubleQuote":
|
|
25
|
+
return `"${token.parts.map((p) => typeof p === "string" ? p : tokenToString(p)).join("")}"`;
|
|
26
|
+
case "assignment":
|
|
27
|
+
return `${token.name}=${typeof token.value === "string" ? token.value : token.value.map(tokenToString).join("")}`;
|
|
28
|
+
case "eof":
|
|
29
|
+
return "<EOF>";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
tokenToString
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
//# debugId=D103DC901FB8A54E64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/lexer/tokens.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export type RedirectMode =\n | \">\"\n | \">>\"\n | \"<\"\n | \"2>\"\n | \"2>>\"\n | \"&>\"\n | \"&>>\"\n | \"2>&1\"\n | \"1>&2\";\n\nexport type Token =\n | { type: \"word\"; value: string }\n | { type: \"pipe\" }\n | { type: \"and\" }\n | { type: \"or\" }\n | { type: \"semicolon\" }\n | { type: \"redirect\"; mode: RedirectMode }\n | { type: \"variable\"; name: string }\n | { type: \"substitution\"; command: string }\n | { type: \"glob\"; pattern: string }\n | { type: \"singleQuote\"; value: string }\n | { type: \"doubleQuote\"; parts: Array<string | Token> }\n | { type: \"assignment\"; name: string; value: string | Token[] }\n | { type: \"eof\" };\n\nexport function tokenToString(token: Token): string {\n switch (token.type) {\n case \"word\":\n return token.value;\n case \"pipe\":\n return \"|\";\n case \"and\":\n return \"&&\";\n case \"or\":\n return \"||\";\n case \"semicolon\":\n return \";\";\n case \"redirect\":\n return token.mode;\n case \"variable\":\n return `$${token.name}`;\n case \"substitution\":\n return `$(${token.command})`;\n case \"glob\":\n return token.pattern;\n case \"singleQuote\":\n return `'${token.value}'`;\n case \"doubleQuote\":\n return `\"${token.parts.map((p) => (typeof p === \"string\" ? p : tokenToString(p))).join(\"\")}\"`;\n case \"assignment\":\n return `${token.name}=${typeof token.value === \"string\" ? token.value : token.value.map(tokenToString).join(\"\")}`;\n case \"eof\":\n return \"<EOF>\";\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AA0BO,SAAS,aAAa,CAAC,OAAsB;AAAA,EAClD,QAAQ,MAAM;AAAA,SACP;AAAA,MACH,OAAO,MAAM;AAAA,SACV;AAAA,MACH,OAAO;AAAA,SACJ;AAAA,MACH,OAAO;AAAA,SACJ;AAAA,MACH,OAAO;AAAA,SACJ;AAAA,MACH,OAAO;AAAA,SACJ;AAAA,MACH,OAAO,MAAM;AAAA,SACV;AAAA,MACH,OAAO,IAAI,MAAM;AAAA,SACd;AAAA,MACH,OAAO,KAAK,MAAM;AAAA,SACf;AAAA,MACH,OAAO,MAAM;AAAA,SACV;AAAA,MACH,OAAO,IAAI,MAAM;AAAA,SACd;AAAA,MACH,OAAO,IAAI,MAAM,MAAM,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,cAAc,CAAC,CAAE,EAAE,KAAK,EAAE;AAAA,SACtF;AAAA,MACH,OAAO,GAAG,MAAM,QAAQ,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,MAAM,MAAM,IAAI,aAAa,EAAE,KAAK,EAAE;AAAA,SAC3G;AAAA,MACH,OAAO;AAAA;AAAA;",
|
|
8
|
+
"debugId": "D103DC901FB8A54E64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/parser/ast.ts
|
|
2
|
+
function isCommandNode(node) {
|
|
3
|
+
return node.type === "command";
|
|
4
|
+
}
|
|
5
|
+
function isPipelineNode(node) {
|
|
6
|
+
return node.type === "pipeline";
|
|
7
|
+
}
|
|
8
|
+
function isAndNode(node) {
|
|
9
|
+
return node.type === "and";
|
|
10
|
+
}
|
|
11
|
+
function isOrNode(node) {
|
|
12
|
+
return node.type === "or";
|
|
13
|
+
}
|
|
14
|
+
function isSequenceNode(node) {
|
|
15
|
+
return node.type === "sequence";
|
|
16
|
+
}
|
|
17
|
+
function isLiteralNode(node) {
|
|
18
|
+
return node.type === "literal";
|
|
19
|
+
}
|
|
20
|
+
function isVariableNode(node) {
|
|
21
|
+
return node.type === "variable";
|
|
22
|
+
}
|
|
23
|
+
function isSubstitutionNode(node) {
|
|
24
|
+
return node.type === "substitution";
|
|
25
|
+
}
|
|
26
|
+
function isGlobNode(node) {
|
|
27
|
+
return node.type === "glob";
|
|
28
|
+
}
|
|
29
|
+
function isConcatNode(node) {
|
|
30
|
+
return node.type === "concat";
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
isVariableNode,
|
|
34
|
+
isSubstitutionNode,
|
|
35
|
+
isSequenceNode,
|
|
36
|
+
isPipelineNode,
|
|
37
|
+
isOrNode,
|
|
38
|
+
isLiteralNode,
|
|
39
|
+
isGlobNode,
|
|
40
|
+
isConcatNode,
|
|
41
|
+
isCommandNode,
|
|
42
|
+
isAndNode
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//# debugId=A4A8963D49D6651C64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/parser/ast.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export type RedirectMode =\n | \">\"\n | \">>\"\n | \"<\"\n | \"2>\"\n | \"2>>\"\n | \"&>\"\n | \"&>>\"\n | \"2>&1\"\n | \"1>&2\";\n\nexport interface Redirect {\n mode: RedirectMode;\n target: ASTNode;\n}\n\nexport type ASTNode =\n | CommandNode\n | PipelineNode\n | AndNode\n | OrNode\n | SequenceNode\n | LiteralNode\n | VariableNode\n | SubstitutionNode\n | GlobNode\n | ConcatNode;\n\nexport interface CommandNode {\n type: \"command\";\n name: ASTNode;\n args: ASTNode[];\n redirects: Redirect[];\n assignments: Array<{ name: string; value: ASTNode }>;\n}\n\nexport interface PipelineNode {\n type: \"pipeline\";\n commands: ASTNode[];\n}\n\nexport interface AndNode {\n type: \"and\";\n left: ASTNode;\n right: ASTNode;\n}\n\nexport interface OrNode {\n type: \"or\";\n left: ASTNode;\n right: ASTNode;\n}\n\nexport interface SequenceNode {\n type: \"sequence\";\n commands: ASTNode[];\n}\n\nexport interface LiteralNode {\n type: \"literal\";\n value: string;\n}\n\nexport interface VariableNode {\n type: \"variable\";\n name: string;\n}\n\nexport interface SubstitutionNode {\n type: \"substitution\";\n command: ASTNode;\n}\n\nexport interface GlobNode {\n type: \"glob\";\n pattern: string;\n}\n\nexport interface ConcatNode {\n type: \"concat\";\n parts: ASTNode[];\n}\n\n// Type guards\nexport function isCommandNode(node: ASTNode): node is CommandNode {\n return node.type === \"command\";\n}\n\nexport function isPipelineNode(node: ASTNode): node is PipelineNode {\n return node.type === \"pipeline\";\n}\n\nexport function isAndNode(node: ASTNode): node is AndNode {\n return node.type === \"and\";\n}\n\nexport function isOrNode(node: ASTNode): node is OrNode {\n return node.type === \"or\";\n}\n\nexport function isSequenceNode(node: ASTNode): node is SequenceNode {\n return node.type === \"sequence\";\n}\n\nexport function isLiteralNode(node: ASTNode): node is LiteralNode {\n return node.type === \"literal\";\n}\n\nexport function isVariableNode(node: ASTNode): node is VariableNode {\n return node.type === \"variable\";\n}\n\nexport function isSubstitutionNode(node: ASTNode): node is SubstitutionNode {\n return node.type === \"substitution\";\n}\n\nexport function isGlobNode(node: ASTNode): node is GlobNode {\n return node.type === \"glob\";\n}\n\nexport function isConcatNode(node: ASTNode): node is ConcatNode {\n return node.type === \"concat\";\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAoFO,SAAS,aAAa,CAAC,MAAoC;AAAA,EAChE,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,cAAc,CAAC,MAAqC;AAAA,EAClE,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,SAAS,CAAC,MAAgC;AAAA,EACxD,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,QAAQ,CAAC,MAA+B;AAAA,EACtD,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,cAAc,CAAC,MAAqC;AAAA,EAClE,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,aAAa,CAAC,MAAoC;AAAA,EAChE,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,cAAc,CAAC,MAAqC;AAAA,EAClE,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,kBAAkB,CAAC,MAAyC;AAAA,EAC1E,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,UAAU,CAAC,MAAiC;AAAA,EAC1D,OAAO,KAAK,SAAS;AAAA;AAGhB,SAAS,YAAY,CAAC,MAAmC;AAAA,EAC9D,OAAO,KAAK,SAAS;AAAA;",
|
|
8
|
+
"debugId": "A4A8963D49D6651C64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// src/parser/index.ts
|
|
2
|
+
import { Parser, parse } from "./parser.mjs";
|
|
3
|
+
import {
|
|
4
|
+
isCommandNode,
|
|
5
|
+
isPipelineNode,
|
|
6
|
+
isAndNode,
|
|
7
|
+
isOrNode,
|
|
8
|
+
isSequenceNode,
|
|
9
|
+
isLiteralNode,
|
|
10
|
+
isVariableNode,
|
|
11
|
+
isSubstitutionNode,
|
|
12
|
+
isGlobNode,
|
|
13
|
+
isConcatNode
|
|
14
|
+
} from "./ast.mjs";
|
|
15
|
+
export {
|
|
16
|
+
parse,
|
|
17
|
+
isVariableNode,
|
|
18
|
+
isSubstitutionNode,
|
|
19
|
+
isSequenceNode,
|
|
20
|
+
isPipelineNode,
|
|
21
|
+
isOrNode,
|
|
22
|
+
isLiteralNode,
|
|
23
|
+
isGlobNode,
|
|
24
|
+
isConcatNode,
|
|
25
|
+
isCommandNode,
|
|
26
|
+
isAndNode,
|
|
27
|
+
Parser
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
//# debugId=FE914D4DC4891D7B64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/parser/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"export { Parser, parse } from \"./parser.mjs\";\nexport type {\n ASTNode,\n Redirect,\n RedirectMode,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n} from \"./ast.mjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n} from \"./ast.mjs\";\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAAA;AAgBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;",
|
|
8
|
+
"debugId": "FE914D4DC4891D7B64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|