@phuetz/code-buddy 0.1.0 → 0.1.1

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.
Files changed (206) hide show
  1. package/.codebuddy/skills/bundled/brave-search/SKILL.md +490 -0
  2. package/.codebuddy/skills/bundled/exa-search/SKILL.md +1122 -0
  3. package/.codebuddy/skills/bundled/perplexity/SKILL.md +748 -0
  4. package/.codebuddy/skills/bundled/playwright/SKILL.md +520 -0
  5. package/.codebuddy/skills/bundled/puppeteer/SKILL.md +708 -0
  6. package/.codebuddy/skills/bundled/web-fetch/SKILL.md +1003 -0
  7. package/README.md +56 -0
  8. package/dist/agent/agent-state.d.ts +3 -3
  9. package/dist/agent/agent-state.js +6 -6
  10. package/dist/agent/agent-state.js.map +1 -1
  11. package/dist/agent/base-agent.d.ts +4 -4
  12. package/dist/agent/base-agent.js +22 -9
  13. package/dist/agent/base-agent.js.map +1 -1
  14. package/dist/agent/cache-trace.d.ts +56 -0
  15. package/dist/agent/cache-trace.js +98 -0
  16. package/dist/agent/cache-trace.js.map +1 -0
  17. package/dist/agent/codebuddy-agent.js +3 -2
  18. package/dist/agent/codebuddy-agent.js.map +1 -1
  19. package/dist/agent/execution/agent-executor.d.ts +4 -4
  20. package/dist/agent/execution/agent-executor.js +41 -7
  21. package/dist/agent/execution/agent-executor.js.map +1 -1
  22. package/dist/agent/facades/agent-context-facade.js +1 -3
  23. package/dist/agent/facades/agent-context-facade.js.map +1 -1
  24. package/dist/agent/facades/message-history-manager.js +14 -12
  25. package/dist/agent/facades/message-history-manager.js.map +1 -1
  26. package/dist/agent/facades/session-facade.d.ts +3 -3
  27. package/dist/agent/facades/session-facade.js +6 -6
  28. package/dist/agent/facades/session-facade.js.map +1 -1
  29. package/dist/agent/history-repair.d.ts +37 -0
  30. package/dist/agent/history-repair.js +124 -0
  31. package/dist/agent/history-repair.js.map +1 -0
  32. package/dist/agent/specialized/archive-agent.d.ts +3 -0
  33. package/dist/agent/specialized/archive-agent.js +71 -31
  34. package/dist/agent/specialized/archive-agent.js.map +1 -1
  35. package/dist/agent/specialized/security-review/agent.js +19 -8
  36. package/dist/agent/specialized/security-review/agent.js.map +1 -1
  37. package/dist/agent/tool-executor.js +5 -0
  38. package/dist/agent/tool-executor.js.map +1 -1
  39. package/dist/agent/turn-diff-tracker.d.ts +79 -0
  40. package/dist/agent/turn-diff-tracker.js +195 -0
  41. package/dist/agent/turn-diff-tracker.js.map +1 -0
  42. package/dist/checkpoints/checkpoint-versioning.js +78 -20
  43. package/dist/checkpoints/checkpoint-versioning.js.map +1 -1
  44. package/dist/cli/config-loader.js +2 -4
  45. package/dist/cli/config-loader.js.map +1 -1
  46. package/dist/commands/handlers/fcs-handlers.js +1 -1
  47. package/dist/commands/handlers/fcs-handlers.js.map +1 -1
  48. package/dist/commands/handlers/memory-handlers.js +2 -1
  49. package/dist/commands/handlers/memory-handlers.js.map +1 -1
  50. package/dist/commands/handlers/worktree-handlers.js +11 -0
  51. package/dist/commands/handlers/worktree-handlers.js.map +1 -1
  52. package/dist/commands/mcp.d.ts +1 -0
  53. package/dist/commands/mcp.js +66 -7
  54. package/dist/commands/mcp.js.map +1 -1
  55. package/dist/commands/pipeline.js +25 -13
  56. package/dist/commands/pipeline.js.map +1 -1
  57. package/dist/config/model-tools.d.ts +41 -0
  58. package/dist/config/model-tools.js +194 -0
  59. package/dist/config/model-tools.js.map +1 -0
  60. package/dist/context/context-manager-v2.d.ts +2 -1
  61. package/dist/context/context-manager-v2.js +34 -5
  62. package/dist/context/context-manager-v2.js.map +1 -1
  63. package/dist/daemon/daemon-manager.js +23 -19
  64. package/dist/daemon/daemon-manager.js.map +1 -1
  65. package/dist/database/database-manager.d.ts +4 -0
  66. package/dist/database/database-manager.js +16 -7
  67. package/dist/database/database-manager.js.map +1 -1
  68. package/dist/desktop-automation/nutjs-provider.js +89 -0
  69. package/dist/desktop-automation/nutjs-provider.js.map +1 -1
  70. package/dist/fcs/builtins.d.ts +2 -6
  71. package/dist/fcs/builtins.js +2 -568
  72. package/dist/fcs/builtins.js.map +1 -1
  73. package/dist/fcs/codebuddy-bindings.d.ts +3 -43
  74. package/dist/fcs/codebuddy-bindings.js +2 -606
  75. package/dist/fcs/codebuddy-bindings.js.map +1 -1
  76. package/dist/fcs/index.d.ts +2 -27
  77. package/dist/fcs/index.js +2 -53
  78. package/dist/fcs/index.js.map +1 -1
  79. package/dist/fcs/lexer.d.ts +2 -37
  80. package/dist/fcs/lexer.js +2 -459
  81. package/dist/fcs/lexer.js.map +1 -1
  82. package/dist/fcs/parser.d.ts +2 -68
  83. package/dist/fcs/parser.js +2 -893
  84. package/dist/fcs/parser.js.map +1 -1
  85. package/dist/fcs/runtime.d.ts +2 -59
  86. package/dist/fcs/runtime.js +2 -623
  87. package/dist/fcs/runtime.js.map +1 -1
  88. package/dist/fcs/script-registry.d.ts +3 -69
  89. package/dist/fcs/script-registry.js +2 -219
  90. package/dist/fcs/script-registry.js.map +1 -1
  91. package/dist/fcs/sync-bindings.d.ts +3 -101
  92. package/dist/fcs/sync-bindings.js +2 -410
  93. package/dist/fcs/sync-bindings.js.map +1 -1
  94. package/dist/fcs/types.d.ts +2 -285
  95. package/dist/fcs/types.js +2 -103
  96. package/dist/fcs/types.js.map +1 -1
  97. package/dist/hooks/use-input-handler.d.ts +1 -1
  98. package/dist/index.js +5 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/input/voice-control.js +11 -5
  101. package/dist/input/voice-control.js.map +1 -1
  102. package/dist/integrations/json-rpc/server.js +5 -5
  103. package/dist/integrations/json-rpc/server.js.map +1 -1
  104. package/dist/integrations/mcp/mcp-server.js +1 -1
  105. package/dist/integrations/mcp/mcp-server.js.map +1 -1
  106. package/dist/mcp/client.js +2 -1
  107. package/dist/mcp/client.js.map +1 -1
  108. package/dist/mcp/config.js +89 -5
  109. package/dist/mcp/config.js.map +1 -1
  110. package/dist/mcp/mcp-client.js +65 -14
  111. package/dist/mcp/mcp-client.js.map +1 -1
  112. package/dist/mcp/transports.d.ts +0 -1
  113. package/dist/mcp/transports.js +1 -5
  114. package/dist/mcp/transports.js.map +1 -1
  115. package/dist/mcp/types.d.ts +2 -0
  116. package/dist/persistence/session-lock.d.ts +42 -0
  117. package/dist/persistence/session-lock.js +165 -0
  118. package/dist/persistence/session-lock.js.map +1 -0
  119. package/dist/persistence/session-store.d.ts +18 -3
  120. package/dist/persistence/session-store.js +90 -21
  121. package/dist/persistence/session-store.js.map +1 -1
  122. package/dist/plugins/isolated-plugin-runner.d.ts +6 -0
  123. package/dist/plugins/isolated-plugin-runner.js +19 -1
  124. package/dist/plugins/isolated-plugin-runner.js.map +1 -1
  125. package/dist/providers/local-llm-provider.js +21 -4
  126. package/dist/providers/local-llm-provider.js.map +1 -1
  127. package/dist/sandbox/docker-sandbox.js +7 -4
  128. package/dist/sandbox/docker-sandbox.js.map +1 -1
  129. package/dist/scripting/builtins.d.ts +8 -3
  130. package/dist/scripting/builtins.js +506 -355
  131. package/dist/scripting/builtins.js.map +1 -1
  132. package/dist/scripting/codebuddy-bindings.d.ts +47 -0
  133. package/dist/scripting/codebuddy-bindings.js +487 -0
  134. package/dist/scripting/codebuddy-bindings.js.map +1 -0
  135. package/dist/scripting/index.d.ts +33 -30
  136. package/dist/scripting/index.js +41 -36
  137. package/dist/scripting/index.js.map +1 -1
  138. package/dist/scripting/lexer.d.ts +31 -13
  139. package/dist/scripting/lexer.js +379 -292
  140. package/dist/scripting/lexer.js.map +1 -1
  141. package/dist/scripting/parser.d.ts +63 -44
  142. package/dist/scripting/parser.js +700 -473
  143. package/dist/scripting/parser.js.map +1 -1
  144. package/dist/scripting/runtime.d.ts +55 -24
  145. package/dist/scripting/runtime.js +600 -288
  146. package/dist/scripting/runtime.js.map +1 -1
  147. package/dist/scripting/script-registry.d.ts +54 -0
  148. package/dist/scripting/script-registry.js +202 -0
  149. package/dist/scripting/script-registry.js.map +1 -0
  150. package/dist/scripting/sync-bindings.d.ts +105 -0
  151. package/dist/scripting/sync-bindings.js +353 -0
  152. package/dist/scripting/sync-bindings.js.map +1 -0
  153. package/dist/scripting/types.d.ts +297 -199
  154. package/dist/scripting/types.js +86 -60
  155. package/dist/scripting/types.js.map +1 -1
  156. package/dist/search/usearch-index.js +42 -7
  157. package/dist/search/usearch-index.js.map +1 -1
  158. package/dist/security/bash-parser.d.ts +51 -0
  159. package/dist/security/bash-parser.js +327 -0
  160. package/dist/security/bash-parser.js.map +1 -0
  161. package/dist/security/skill-scanner.d.ts +36 -0
  162. package/dist/security/skill-scanner.js +149 -0
  163. package/dist/security/skill-scanner.js.map +1 -0
  164. package/dist/security/trust-folders.d.ts +1 -0
  165. package/dist/security/trust-folders.js +19 -1
  166. package/dist/security/trust-folders.js.map +1 -1
  167. package/dist/server/websocket/handler.js +15 -5
  168. package/dist/server/websocket/handler.js.map +1 -1
  169. package/dist/skills/eligibility.js +26 -4
  170. package/dist/skills/eligibility.js.map +1 -1
  171. package/dist/tasks/background-tasks.js +5 -1
  172. package/dist/tasks/background-tasks.js.map +1 -1
  173. package/dist/tools/apply-patch.d.ts +55 -0
  174. package/dist/tools/apply-patch.js +273 -0
  175. package/dist/tools/apply-patch.js.map +1 -0
  176. package/dist/tools/registry/bash-tools.js +6 -3
  177. package/dist/tools/registry/bash-tools.js.map +1 -1
  178. package/dist/tools/registry/misc-tools.js +1 -2
  179. package/dist/tools/registry/misc-tools.js.map +1 -1
  180. package/dist/tools/registry/search-tools.js +1 -1
  181. package/dist/tools/registry/search-tools.js.map +1 -1
  182. package/dist/tools/registry/text-editor-tools.js +1 -1
  183. package/dist/tools/registry/text-editor-tools.js.map +1 -1
  184. package/dist/tools/registry/todo-tools.js +37 -5
  185. package/dist/tools/registry/todo-tools.js.map +1 -1
  186. package/dist/tools/registry/tool-registry.js +5 -4
  187. package/dist/tools/registry/tool-registry.js.map +1 -1
  188. package/dist/tools/registry/web-tools.d.ts +1 -1
  189. package/dist/tools/registry/web-tools.js +28 -8
  190. package/dist/tools/registry/web-tools.js.map +1 -1
  191. package/dist/tools/text-editor.d.ts +1 -1
  192. package/dist/tools/text-editor.js +23 -5
  193. package/dist/tools/text-editor.js.map +1 -1
  194. package/dist/tools/web-search.d.ts +52 -37
  195. package/dist/tools/web-search.js +368 -163
  196. package/dist/tools/web-search.js.map +1 -1
  197. package/dist/ui/components/ChatInterface.d.ts +1 -1
  198. package/dist/utils/head-tail-truncation.d.ts +34 -0
  199. package/dist/utils/head-tail-truncation.js +98 -0
  200. package/dist/utils/head-tail-truncation.js.map +1 -0
  201. package/dist/utils/sanitize.d.ts +5 -0
  202. package/dist/utils/sanitize.js +19 -0
  203. package/dist/utils/sanitize.js.map +1 -1
  204. package/dist/utils/settings-manager.js +4 -4
  205. package/dist/utils/settings-manager.js.map +1 -1
  206. package/package.json +3 -1
@@ -1,896 +1,5 @@
1
1
  /**
2
- * FileCommander Script (FCS) Parser
3
- *
4
- * 100% compatible AST parser for FCS files
2
+ * @deprecated Use src/scripting/parser.ts instead. This file re-exports for backward compatibility.
5
3
  */
6
- import { TokenType, } from './types.js';
7
- import { tokenize as lexerTokenize } from './lexer.js';
8
- import { createLoopGuard, LoopTimeoutError } from '../utils/errors.js';
9
- /** Maximum recursion depth for recursive descent parsing */
10
- const MAX_RECURSION_DEPTH = 500;
11
- export class FCSParser {
12
- tokens;
13
- current = 0;
14
- recursionDepth = 0;
15
- constructor(tokens) {
16
- // Filter out comments
17
- this.tokens = tokens.filter(t => t.type !== TokenType.Comment);
18
- }
19
- /**
20
- * Track recursion depth to prevent stack overflow on deeply nested input
21
- */
22
- enterRecursion(context) {
23
- this.recursionDepth++;
24
- if (this.recursionDepth > MAX_RECURSION_DEPTH) {
25
- throw new LoopTimeoutError(`Maximum recursion depth exceeded in ${context}. Input may be too deeply nested or malformed.`, MAX_RECURSION_DEPTH, context);
26
- }
27
- }
28
- exitRecursion() {
29
- this.recursionDepth--;
30
- }
31
- parse() {
32
- const statements = [];
33
- const guard = createLoopGuard({
34
- maxIterations: 100000,
35
- context: 'FCS program parsing',
36
- });
37
- while (!this.isAtEnd()) {
38
- guard();
39
- this.skipNewlines();
40
- if (this.isAtEnd())
41
- break;
42
- const stmt = this.parseDeclaration();
43
- if (stmt) {
44
- statements.push(stmt);
45
- }
46
- this.skipNewlines();
47
- }
48
- return { type: 'Program', statements };
49
- }
50
- // ============================================
51
- // Statement Parsing
52
- // ============================================
53
- parseDeclaration() {
54
- this.enterRecursion('parseDeclaration');
55
- try {
56
- // Check for decorators
57
- const decorators = [];
58
- const decoratorGuard = createLoopGuard({
59
- maxIterations: 1000,
60
- context: 'FCS decorator parsing',
61
- });
62
- while (this.match(TokenType.Decorator)) {
63
- decoratorGuard();
64
- decorators.push(this.previous().value);
65
- }
66
- if (this.matchKeyword('func', 'function') || this.matchKeyword('async')) {
67
- return this.parseFunctionDeclaration();
68
- }
69
- if (this.matchKeyword('class')) {
70
- return this.parseClassDeclaration();
71
- }
72
- if (this.matchKeyword('let', 'const', 'var')) {
73
- return this.parseVarDeclaration();
74
- }
75
- if (this.matchKeyword('import')) {
76
- return this.parseImportStatement();
77
- }
78
- if (this.matchKeyword('test')) {
79
- return this.parseTestDeclaration();
80
- }
81
- return this.parseStatement();
82
- }
83
- catch (error) {
84
- // Re-throw recursion/loop errors without synchronizing
85
- if (error instanceof LoopTimeoutError) {
86
- throw error;
87
- }
88
- this.synchronize();
89
- return null;
90
- }
91
- finally {
92
- this.exitRecursion();
93
- }
94
- }
95
- parseStatement() {
96
- if (this.matchKeyword('if'))
97
- return this.parseIfStatement();
98
- if (this.matchKeyword('while'))
99
- return this.parseWhileStatement();
100
- if (this.matchKeyword('for'))
101
- return this.parseForStatement();
102
- if (this.matchKeyword('return'))
103
- return this.parseReturnStatement();
104
- if (this.matchKeyword('break'))
105
- return this.parseBreakStatement();
106
- if (this.matchKeyword('continue'))
107
- return this.parseContinueStatement();
108
- if (this.matchKeyword('try'))
109
- return this.parseTryStatement();
110
- if (this.matchKeyword('throw'))
111
- return this.parseThrowStatement();
112
- if (this.matchKeyword('assert'))
113
- return this.parseAssertStatement();
114
- if (this.match(TokenType.LeftBrace))
115
- return this.parseBlockStatement();
116
- return this.parseExpressionStatement();
117
- }
118
- parseFunctionDeclaration() {
119
- const isAsync = this.previous().value === 'async';
120
- if (isAsync) {
121
- this.consumeKeyword('func', 'function');
122
- }
123
- const name = this.consume(TokenType.Identifier, "Expected function name").value;
124
- this.consume(TokenType.LeftParen, "Expected '(' after function name");
125
- const parameters = this.parseParameters();
126
- this.consume(TokenType.RightParen, "Expected ')' after parameters");
127
- let returnType;
128
- if (this.match(TokenType.Colon)) {
129
- returnType = this.consume(TokenType.Identifier, "Expected return type").value;
130
- }
131
- this.consume(TokenType.LeftBrace, "Expected '{' before function body");
132
- const body = this.parseBlockStatement();
133
- return {
134
- type: 'FunctionDeclaration',
135
- name,
136
- parameters,
137
- body,
138
- isAsync,
139
- returnType,
140
- };
141
- }
142
- parseParameters() {
143
- const parameters = [];
144
- if (!this.check(TokenType.RightParen)) {
145
- do {
146
- const name = this.consume(TokenType.Identifier, "Expected parameter name").value;
147
- let type;
148
- let defaultValue;
149
- if (this.match(TokenType.Colon)) {
150
- type = this.consume(TokenType.Identifier, "Expected parameter type").value;
151
- }
152
- if (this.match(TokenType.Assign)) {
153
- defaultValue = this.parseExpression();
154
- }
155
- parameters.push({ name, type, defaultValue });
156
- } while (this.match(TokenType.Comma));
157
- }
158
- return parameters;
159
- }
160
- parseVarDeclaration() {
161
- const keyword = this.previous().value;
162
- const isConst = keyword === 'const';
163
- const name = this.consume(TokenType.Identifier, "Expected variable name").value;
164
- let varType;
165
- if (this.match(TokenType.Colon)) {
166
- varType = this.consume(TokenType.Identifier, "Expected type").value;
167
- }
168
- let initializer = null;
169
- if (this.match(TokenType.Assign)) {
170
- initializer = this.parseExpression();
171
- }
172
- else if (isConst) {
173
- throw new Error("Const variable must be initialized");
174
- }
175
- this.consumeStatementEnd();
176
- return {
177
- type: 'VarDeclaration',
178
- name,
179
- initializer,
180
- isConst,
181
- varType,
182
- };
183
- }
184
- parseClassDeclaration() {
185
- const name = this.consume(TokenType.Identifier, "Expected class name").value;
186
- let baseClass;
187
- if (this.match(TokenType.Colon)) {
188
- baseClass = this.consume(TokenType.Identifier, "Expected base class name").value;
189
- }
190
- this.consume(TokenType.LeftBrace, "Expected '{' before class body");
191
- const members = [];
192
- const guard = createLoopGuard({
193
- maxIterations: 10000,
194
- context: 'FCS class body parsing',
195
- });
196
- while (!this.check(TokenType.RightBrace) && !this.isAtEnd()) {
197
- guard();
198
- this.skipNewlines();
199
- if (this.check(TokenType.RightBrace))
200
- break;
201
- if (this.matchKeyword('func', 'function', 'async')) {
202
- members.push(this.parseFunctionDeclaration());
203
- }
204
- else if (this.matchKeyword('let', 'const', 'var')) {
205
- members.push(this.parseVarDeclaration());
206
- }
207
- else {
208
- // Skip unexpected tokens in class body to prevent infinite loop
209
- this.advance();
210
- }
211
- this.skipNewlines();
212
- }
213
- this.consume(TokenType.RightBrace, "Expected '}' after class body");
214
- return {
215
- type: 'ClassDeclaration',
216
- name,
217
- baseClass,
218
- members,
219
- };
220
- }
221
- parseIfStatement() {
222
- // Optional parentheses
223
- const hasParen = this.match(TokenType.LeftParen);
224
- const condition = this.parseExpression();
225
- if (hasParen) {
226
- this.consume(TokenType.RightParen, "Expected ')' after condition");
227
- }
228
- const thenBranch = this.match(TokenType.LeftBrace)
229
- ? this.parseBlockStatement()
230
- : this.parseStatement();
231
- let elseBranch = null;
232
- if (this.matchKeyword('else')) {
233
- if (this.matchKeyword('if')) {
234
- elseBranch = this.parseIfStatement();
235
- }
236
- else {
237
- elseBranch = this.match(TokenType.LeftBrace)
238
- ? this.parseBlockStatement()
239
- : this.parseStatement();
240
- }
241
- }
242
- return {
243
- type: 'If',
244
- condition,
245
- thenBranch,
246
- elseBranch,
247
- };
248
- }
249
- parseWhileStatement() {
250
- const hasParen = this.match(TokenType.LeftParen);
251
- const condition = this.parseExpression();
252
- if (hasParen) {
253
- this.consume(TokenType.RightParen, "Expected ')' after condition");
254
- }
255
- const body = this.match(TokenType.LeftBrace)
256
- ? this.parseBlockStatement()
257
- : this.parseStatement();
258
- return {
259
- type: 'While',
260
- condition,
261
- body,
262
- };
263
- }
264
- parseForStatement() {
265
- const variable = this.consume(TokenType.Identifier, "Expected variable name").value;
266
- this.consumeKeyword('in');
267
- const iterable = this.parseExpression();
268
- const body = this.match(TokenType.LeftBrace)
269
- ? this.parseBlockStatement()
270
- : this.parseStatement();
271
- return {
272
- type: 'For',
273
- variable,
274
- iterable,
275
- body,
276
- };
277
- }
278
- parseBlockStatement() {
279
- const statements = [];
280
- const guard = createLoopGuard({
281
- maxIterations: 100000,
282
- context: 'FCS block statement parsing',
283
- });
284
- while (!this.check(TokenType.RightBrace) && !this.isAtEnd()) {
285
- guard();
286
- this.skipNewlines();
287
- if (this.check(TokenType.RightBrace))
288
- break;
289
- const stmt = this.parseDeclaration();
290
- if (stmt) {
291
- statements.push(stmt);
292
- }
293
- this.skipNewlines();
294
- }
295
- this.consume(TokenType.RightBrace, "Expected '}' after block");
296
- return { type: 'Block', statements };
297
- }
298
- parseExpressionStatement() {
299
- const expression = this.parseExpression();
300
- this.consumeStatementEnd();
301
- return { type: 'ExpressionStmt', expression };
302
- }
303
- parseReturnStatement() {
304
- let value = null;
305
- if (!this.check(TokenType.Newline) && !this.check(TokenType.Semicolon) && !this.check(TokenType.RightBrace)) {
306
- value = this.parseExpression();
307
- }
308
- this.consumeStatementEnd();
309
- return { type: 'Return', value };
310
- }
311
- parseBreakStatement() {
312
- this.consumeStatementEnd();
313
- return { type: 'Break' };
314
- }
315
- parseContinueStatement() {
316
- this.consumeStatementEnd();
317
- return { type: 'Continue' };
318
- }
319
- parseTryStatement() {
320
- this.consume(TokenType.LeftBrace, "Expected '{' after 'try'");
321
- const tryBlock = this.parseBlockStatement();
322
- const catchClauses = [];
323
- const catchGuard = createLoopGuard({
324
- maxIterations: 1000,
325
- context: 'FCS catch clause parsing',
326
- });
327
- while (this.matchKeyword('catch')) {
328
- catchGuard();
329
- let variable;
330
- let type;
331
- if (this.match(TokenType.LeftParen)) {
332
- if (this.match(TokenType.Identifier)) {
333
- variable = this.previous().value;
334
- if (this.match(TokenType.Colon)) {
335
- type = this.consume(TokenType.Identifier, "Expected exception type").value;
336
- }
337
- }
338
- this.consume(TokenType.RightParen, "Expected ')' after catch parameters");
339
- }
340
- this.consume(TokenType.LeftBrace, "Expected '{' after catch");
341
- const body = this.parseBlockStatement();
342
- catchClauses.push({ variable, type, body });
343
- }
344
- let finallyBlock = null;
345
- if (this.matchKeyword('finally')) {
346
- this.consume(TokenType.LeftBrace, "Expected '{' after 'finally'");
347
- finallyBlock = this.parseBlockStatement();
348
- }
349
- return {
350
- type: 'Try',
351
- tryBlock,
352
- catchClauses,
353
- finallyBlock,
354
- };
355
- }
356
- parseThrowStatement() {
357
- const expression = this.parseExpression();
358
- this.consumeStatementEnd();
359
- return { type: 'Throw', expression };
360
- }
361
- parseImportStatement() {
362
- const names = [];
363
- let module = '';
364
- let alias;
365
- if (this.match(TokenType.LeftBrace)) {
366
- // import { name1, name2 } from "module"
367
- do {
368
- names.push(this.consume(TokenType.Identifier, "Expected import name").value);
369
- } while (this.match(TokenType.Comma));
370
- this.consume(TokenType.RightBrace, "Expected '}' after import names");
371
- this.consumeKeyword('from');
372
- module = this.consume(TokenType.String, "Expected module name").value;
373
- }
374
- else if (this.match(TokenType.Multiply)) {
375
- // import * as alias from "module"
376
- this.consumeKeyword('as');
377
- alias = this.consume(TokenType.Identifier, "Expected alias name").value;
378
- this.consumeKeyword('from');
379
- module = this.consume(TokenType.String, "Expected module name").value;
380
- }
381
- else if (this.check(TokenType.String)) {
382
- // import "module"
383
- module = this.consume(TokenType.String, "Expected module name").value;
384
- }
385
- else if (this.check(TokenType.Identifier)) {
386
- // import name OR import name from "module"
387
- const firstIdent = this.consume(TokenType.Identifier, "Expected import name").value;
388
- if (this.checkKeyword('from')) {
389
- names.push(firstIdent);
390
- this.consumeKeyword('from');
391
- module = this.consume(TokenType.String, "Expected module name").value;
392
- }
393
- else {
394
- // Simple import like: import fc
395
- module = firstIdent;
396
- }
397
- }
398
- this.consumeStatementEnd();
399
- return {
400
- type: 'Import',
401
- module,
402
- names,
403
- alias,
404
- };
405
- }
406
- parseTestDeclaration() {
407
- const name = this.consume(TokenType.String, "Expected test name as string").value;
408
- const tags = [];
409
- if (this.match(TokenType.LeftBracket)) {
410
- do {
411
- if (this.match(TokenType.String)) {
412
- tags.push(this.previous().value);
413
- }
414
- } while (this.match(TokenType.Comma));
415
- this.consume(TokenType.RightBracket, "Expected ']' after tags");
416
- }
417
- this.consume(TokenType.LeftBrace, "Expected '{' before test body");
418
- const body = this.parseBlockStatement();
419
- return {
420
- type: 'TestDeclaration',
421
- name,
422
- body,
423
- tags,
424
- };
425
- }
426
- parseAssertStatement() {
427
- const condition = this.parseExpression();
428
- let message;
429
- if (this.match(TokenType.Comma)) {
430
- if (this.match(TokenType.String)) {
431
- message = this.previous().value;
432
- }
433
- }
434
- this.consumeStatementEnd();
435
- return {
436
- type: 'Assert',
437
- condition,
438
- message,
439
- };
440
- }
441
- // ============================================
442
- // Expression Parsing
443
- // ============================================
444
- parseExpression() {
445
- return this.parseAssignment();
446
- }
447
- parseAssignment() {
448
- const expr = this.parseTernary();
449
- if (this.match(TokenType.Assign, TokenType.PlusAssign, TokenType.MinusAssign, TokenType.MultiplyAssign, TokenType.DivideAssign)) {
450
- const operator = this.previous().type;
451
- const value = this.parseAssignment();
452
- return {
453
- type: 'Assignment',
454
- target: expr,
455
- operator,
456
- value,
457
- };
458
- }
459
- return expr;
460
- }
461
- parseTernary() {
462
- let expr = this.parsePipeline();
463
- // Check for ? token (stored as Colon with value '?')
464
- if (this.peek().value === '?') {
465
- this.advance();
466
- const consequent = this.parseExpression();
467
- this.consume(TokenType.Colon, "Expected ':' in ternary expression");
468
- const alternate = this.parseTernary();
469
- return {
470
- type: 'Ternary',
471
- condition: expr,
472
- consequent,
473
- alternate,
474
- };
475
- }
476
- return expr;
477
- }
478
- parsePipeline() {
479
- let expr = this.parseLogicalOr();
480
- const guard = createLoopGuard({
481
- maxIterations: 10000,
482
- context: 'FCS pipeline expression parsing',
483
- });
484
- while (this.match(TokenType.Pipeline)) {
485
- guard();
486
- const right = this.parseLogicalOr();
487
- // Transform pipeline: expr |> func => func(expr)
488
- if (right.type === 'Call') {
489
- const call = right;
490
- call.arguments.unshift(expr);
491
- expr = call;
492
- }
493
- else if (right.type === 'Identifier') {
494
- expr = {
495
- type: 'Call',
496
- callee: right,
497
- arguments: [expr],
498
- };
499
- }
500
- else {
501
- throw new Error("Invalid pipeline target");
502
- }
503
- }
504
- return expr;
505
- }
506
- parseLogicalOr() {
507
- let expr = this.parseLogicalAnd();
508
- const guard = createLoopGuard({
509
- maxIterations: 10000,
510
- context: 'FCS logical OR expression parsing',
511
- });
512
- while (this.match(TokenType.Or)) {
513
- guard();
514
- const operator = this.previous().type;
515
- const right = this.parseLogicalAnd();
516
- expr = { type: 'Binary', left: expr, operator, right };
517
- }
518
- return expr;
519
- }
520
- parseLogicalAnd() {
521
- let expr = this.parseEquality();
522
- const guard = createLoopGuard({
523
- maxIterations: 10000,
524
- context: 'FCS logical AND expression parsing',
525
- });
526
- while (this.match(TokenType.And)) {
527
- guard();
528
- const operator = this.previous().type;
529
- const right = this.parseEquality();
530
- expr = { type: 'Binary', left: expr, operator, right };
531
- }
532
- return expr;
533
- }
534
- parseEquality() {
535
- let expr = this.parseComparison();
536
- const guard = createLoopGuard({
537
- maxIterations: 10000,
538
- context: 'FCS equality expression parsing',
539
- });
540
- while (this.match(TokenType.Equal, TokenType.NotEqual)) {
541
- guard();
542
- const operator = this.previous().type;
543
- const right = this.parseComparison();
544
- expr = { type: 'Binary', left: expr, operator, right };
545
- }
546
- return expr;
547
- }
548
- parseComparison() {
549
- let expr = this.parseAddition();
550
- const guard = createLoopGuard({
551
- maxIterations: 10000,
552
- context: 'FCS comparison expression parsing',
553
- });
554
- while (this.match(TokenType.Less, TokenType.Greater, TokenType.LessEqual, TokenType.GreaterEqual)) {
555
- guard();
556
- const operator = this.previous().type;
557
- const right = this.parseAddition();
558
- expr = { type: 'Binary', left: expr, operator, right };
559
- }
560
- return expr;
561
- }
562
- parseAddition() {
563
- let expr = this.parseMultiplication();
564
- const guard = createLoopGuard({
565
- maxIterations: 10000,
566
- context: 'FCS addition expression parsing',
567
- });
568
- while (this.match(TokenType.Plus, TokenType.Minus)) {
569
- guard();
570
- const operator = this.previous().type;
571
- const right = this.parseMultiplication();
572
- expr = { type: 'Binary', left: expr, operator, right };
573
- }
574
- return expr;
575
- }
576
- parseMultiplication() {
577
- let expr = this.parsePower();
578
- const guard = createLoopGuard({
579
- maxIterations: 10000,
580
- context: 'FCS multiplication expression parsing',
581
- });
582
- while (this.match(TokenType.Multiply, TokenType.Divide, TokenType.Modulo)) {
583
- guard();
584
- const operator = this.previous().type;
585
- const right = this.parsePower();
586
- expr = { type: 'Binary', left: expr, operator, right };
587
- }
588
- return expr;
589
- }
590
- parsePower() {
591
- let expr = this.parseUnary();
592
- if (this.match(TokenType.Power)) {
593
- const right = this.parsePower(); // Right associative
594
- expr = { type: 'Binary', left: expr, operator: TokenType.Power, right };
595
- }
596
- return expr;
597
- }
598
- parseUnary() {
599
- if (this.match(TokenType.Not, TokenType.Minus)) {
600
- const operator = this.previous().type;
601
- const operand = this.parseUnary();
602
- return { type: 'Unary', operator, operand };
603
- }
604
- return this.parsePostfix();
605
- }
606
- parsePostfix() {
607
- let expr = this.parsePrimary();
608
- // Guard against infinite loops in postfix parsing (deeply nested calls/members)
609
- const guard = createLoopGuard({
610
- maxIterations: 10000,
611
- context: 'FCS postfix expression parsing',
612
- });
613
- while (true) {
614
- guard();
615
- if (this.match(TokenType.LeftParen)) {
616
- expr = this.parseCall(expr);
617
- }
618
- else if (this.match(TokenType.LeftBracket)) {
619
- const index = this.parseExpression();
620
- this.consume(TokenType.RightBracket, "Expected ']' after index");
621
- expr = { type: 'Index', object: expr, index };
622
- }
623
- else if (this.match(TokenType.Dot)) {
624
- const member = this.consume(TokenType.Identifier, "Expected member name").value;
625
- expr = { type: 'Member', object: expr, member, computed: false };
626
- }
627
- else {
628
- break;
629
- }
630
- }
631
- return expr;
632
- }
633
- parseCall(callee) {
634
- const args = [];
635
- const namedArgs = {};
636
- if (!this.check(TokenType.RightParen)) {
637
- do {
638
- // Check for named argument: name: value
639
- if (this.check(TokenType.Identifier) && this.checkNext(TokenType.Colon)) {
640
- const name = this.advance().value;
641
- this.advance(); // :
642
- namedArgs[name] = this.parseExpression();
643
- }
644
- else {
645
- args.push(this.parseExpression());
646
- }
647
- } while (this.match(TokenType.Comma));
648
- }
649
- this.consume(TokenType.RightParen, "Expected ')' after arguments");
650
- const result = {
651
- type: 'Call',
652
- callee,
653
- arguments: args,
654
- };
655
- if (Object.keys(namedArgs).length > 0) {
656
- result.namedArgs = namedArgs;
657
- }
658
- return result;
659
- }
660
- parsePrimary() {
661
- if (this.match(TokenType.Boolean)) {
662
- return {
663
- type: 'Literal',
664
- value: this.previous().value === 'true',
665
- tokenType: TokenType.Boolean,
666
- };
667
- }
668
- if (this.match(TokenType.Null)) {
669
- return { type: 'Literal', value: null, tokenType: TokenType.Null };
670
- }
671
- if (this.match(TokenType.Number)) {
672
- const value = this.previous().value;
673
- const num = value.includes('.') ? parseFloat(value) : parseInt(value, 10);
674
- return { type: 'Literal', value: num, tokenType: TokenType.Number };
675
- }
676
- if (this.match(TokenType.String)) {
677
- const value = this.previous().value;
678
- // Check for interpolation
679
- if (value.includes('${')) {
680
- return this.parseStringInterpolation(value);
681
- }
682
- return { type: 'Literal', value, tokenType: TokenType.String };
683
- }
684
- if (this.match(TokenType.Identifier)) {
685
- return { type: 'Identifier', name: this.previous().value };
686
- }
687
- if (this.match(TokenType.LeftParen)) {
688
- // Check for lambda
689
- if (this.check(TokenType.Identifier) || this.check(TokenType.RightParen)) {
690
- const checkpoint = this.current;
691
- const parameters = [];
692
- if (!this.check(TokenType.RightParen)) {
693
- do {
694
- if (this.match(TokenType.Identifier)) {
695
- parameters.push(this.previous().value);
696
- }
697
- else {
698
- this.current = checkpoint;
699
- break;
700
- }
701
- } while (this.match(TokenType.Comma));
702
- }
703
- if (this.current !== checkpoint && this.match(TokenType.RightParen) && this.match(TokenType.Arrow)) {
704
- const body = this.match(TokenType.LeftBrace)
705
- ? this.parseBlockStatement()
706
- : this.parseExpression();
707
- return { type: 'Lambda', parameters, body };
708
- }
709
- this.current = checkpoint;
710
- }
711
- const expr = this.parseExpression();
712
- this.consume(TokenType.RightParen, "Expected ')' after expression");
713
- return expr;
714
- }
715
- if (this.match(TokenType.LeftBracket)) {
716
- return this.parseArrayLiteral();
717
- }
718
- if (this.match(TokenType.LeftBrace)) {
719
- return this.parseDictLiteral();
720
- }
721
- throw new Error(`Unexpected token: ${this.peek().type} (${this.peek().value}) at line ${this.peek().line}`);
722
- }
723
- parseStringInterpolation(value) {
724
- const parts = [];
725
- let current = 0;
726
- const guard = createLoopGuard({
727
- maxIterations: 10000,
728
- context: 'FCS string interpolation parsing',
729
- });
730
- while (current < value.length) {
731
- guard();
732
- const start = value.indexOf('${', current);
733
- if (start === -1) {
734
- if (current < value.length) {
735
- parts.push({
736
- type: 'Literal',
737
- value: value.substring(current),
738
- tokenType: TokenType.String,
739
- });
740
- }
741
- break;
742
- }
743
- if (start > current) {
744
- parts.push({
745
- type: 'Literal',
746
- value: value.substring(current, start),
747
- tokenType: TokenType.String,
748
- });
749
- }
750
- const end = value.indexOf('}', start + 2);
751
- if (end === -1) {
752
- throw new Error("Unterminated string interpolation");
753
- }
754
- const exprCode = value.substring(start + 2, end);
755
- const tokens = lexerTokenize(exprCode);
756
- const parser = new FCSParser(tokens);
757
- parts.push(parser.parseExpression());
758
- current = end + 1;
759
- }
760
- return { type: 'Interpolation', parts };
761
- }
762
- parseArrayLiteral() {
763
- const elements = [];
764
- if (!this.check(TokenType.RightBracket)) {
765
- do {
766
- elements.push(this.parseExpression());
767
- } while (this.match(TokenType.Comma));
768
- }
769
- this.consume(TokenType.RightBracket, "Expected ']' after array elements");
770
- return { type: 'Array', elements };
771
- }
772
- parseDictLiteral() {
773
- const elements = new Map();
774
- if (!this.check(TokenType.RightBrace)) {
775
- do {
776
- let key;
777
- if (this.match(TokenType.String)) {
778
- key = this.previous().value;
779
- }
780
- else if (this.match(TokenType.Identifier)) {
781
- key = this.previous().value;
782
- }
783
- else {
784
- throw new Error("Expected string or identifier for dict key");
785
- }
786
- this.consume(TokenType.Colon, "Expected ':' after dict key");
787
- const value = this.parseExpression();
788
- elements.set(key, value);
789
- } while (this.match(TokenType.Comma));
790
- }
791
- this.consume(TokenType.RightBrace, "Expected '}' after dict elements");
792
- return { type: 'Dict', elements };
793
- }
794
- // ============================================
795
- // Helper Methods
796
- // ============================================
797
- isAtEnd() {
798
- return this.peek().type === TokenType.EOF;
799
- }
800
- peek() {
801
- return this.tokens[Math.min(this.current, this.tokens.length - 1)];
802
- }
803
- previous() {
804
- return this.tokens[this.current - 1];
805
- }
806
- advance() {
807
- if (!this.isAtEnd())
808
- this.current++;
809
- return this.previous();
810
- }
811
- check(type) {
812
- return this.peek().type === type;
813
- }
814
- checkNext(type) {
815
- if (this.current + 1 >= this.tokens.length)
816
- return false;
817
- return this.tokens[this.current + 1].type === type;
818
- }
819
- checkKeyword(...keywords) {
820
- if (this.peek().type !== TokenType.Keyword)
821
- return false;
822
- return keywords.includes(this.peek().value);
823
- }
824
- match(...types) {
825
- for (const type of types) {
826
- if (this.check(type)) {
827
- this.advance();
828
- return true;
829
- }
830
- }
831
- return false;
832
- }
833
- matchKeyword(...keywords) {
834
- if (this.checkKeyword(...keywords)) {
835
- this.advance();
836
- return true;
837
- }
838
- return false;
839
- }
840
- consume(type, message) {
841
- if (this.check(type))
842
- return this.advance();
843
- throw new Error(`${message} at line ${this.peek().line}, got ${this.peek().type}`);
844
- }
845
- consumeKeyword(...keywords) {
846
- if (this.checkKeyword(...keywords)) {
847
- this.advance();
848
- }
849
- else {
850
- throw new Error(`Expected one of [${keywords.join(', ')}] at line ${this.peek().line}`);
851
- }
852
- }
853
- consumeStatementEnd() {
854
- if (this.match(TokenType.Semicolon) || this.match(TokenType.Newline)) {
855
- return;
856
- }
857
- if (this.isAtEnd())
858
- return;
859
- // Allow implicit semicolon before certain tokens
860
- if (this.check(TokenType.RightBrace) || this.checkKeyword('else')) {
861
- return;
862
- }
863
- }
864
- skipNewlines() {
865
- const guard = createLoopGuard({
866
- maxIterations: 100000,
867
- context: 'FCS newline skipping',
868
- });
869
- while (this.match(TokenType.Newline)) {
870
- guard();
871
- // Skip
872
- }
873
- }
874
- synchronize() {
875
- this.advance();
876
- const guard = createLoopGuard({
877
- maxIterations: 100000,
878
- context: 'FCS error synchronization',
879
- });
880
- while (!this.isAtEnd()) {
881
- guard();
882
- if (this.previous().type === TokenType.Semicolon)
883
- return;
884
- if (this.previous().type === TokenType.Newline)
885
- return;
886
- if (this.checkKeyword('if', 'for', 'while', 'let', 'const', 'func', 'function', 'class', 'return')) {
887
- return;
888
- }
889
- this.advance();
890
- }
891
- }
892
- }
893
- export function parse(tokens) {
894
- return new FCSParser(tokens).parse();
895
- }
4
+ export { FCSParser, Parser, parse } from '../scripting/parser.js';
896
5
  //# sourceMappingURL=parser.js.map