@phuetz/code-buddy 0.1.0 → 0.1.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.
- package/.codebuddy/skills/bundled/brave-search/SKILL.md +490 -0
- package/.codebuddy/skills/bundled/exa-search/SKILL.md +1122 -0
- package/.codebuddy/skills/bundled/perplexity/SKILL.md +748 -0
- package/.codebuddy/skills/bundled/playwright/SKILL.md +520 -0
- package/.codebuddy/skills/bundled/puppeteer/SKILL.md +708 -0
- package/.codebuddy/skills/bundled/web-fetch/SKILL.md +1003 -0
- package/README.md +56 -0
- package/dist/agent/agent-state.d.ts +3 -3
- package/dist/agent/agent-state.js +6 -6
- package/dist/agent/agent-state.js.map +1 -1
- package/dist/agent/base-agent.d.ts +4 -4
- package/dist/agent/base-agent.js +22 -9
- package/dist/agent/base-agent.js.map +1 -1
- package/dist/agent/cache-trace.d.ts +56 -0
- package/dist/agent/cache-trace.js +98 -0
- package/dist/agent/cache-trace.js.map +1 -0
- package/dist/agent/codebuddy-agent.js +4 -2
- package/dist/agent/codebuddy-agent.js.map +1 -1
- package/dist/agent/execution/agent-executor.d.ts +4 -4
- package/dist/agent/execution/agent-executor.js +46 -14
- package/dist/agent/execution/agent-executor.js.map +1 -1
- package/dist/agent/facades/agent-context-facade.js +1 -3
- package/dist/agent/facades/agent-context-facade.js.map +1 -1
- package/dist/agent/facades/message-history-manager.js +14 -12
- package/dist/agent/facades/message-history-manager.js.map +1 -1
- package/dist/agent/facades/session-facade.d.ts +3 -3
- package/dist/agent/facades/session-facade.js +6 -6
- package/dist/agent/facades/session-facade.js.map +1 -1
- package/dist/agent/history-repair.d.ts +37 -0
- package/dist/agent/history-repair.js +124 -0
- package/dist/agent/history-repair.js.map +1 -0
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +3 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/isolation/agent-workspace.d.ts +1 -0
- package/dist/agent/isolation/agent-workspace.js +10 -0
- package/dist/agent/isolation/agent-workspace.js.map +1 -1
- package/dist/agent/specialized/archive-agent.d.ts +3 -0
- package/dist/agent/specialized/archive-agent.js +71 -31
- package/dist/agent/specialized/archive-agent.js.map +1 -1
- package/dist/agent/specialized/index.d.ts +9 -8
- package/dist/agent/specialized/index.js +16 -8
- package/dist/agent/specialized/index.js.map +1 -1
- package/dist/agent/specialized/security-review/agent.js +19 -8
- package/dist/agent/specialized/security-review/agent.js.map +1 -1
- package/dist/agent/tool-executor.js +5 -0
- package/dist/agent/tool-executor.js.map +1 -1
- package/dist/agent/turn-diff-tracker.d.ts +79 -0
- package/dist/agent/turn-diff-tracker.js +195 -0
- package/dist/agent/turn-diff-tracker.js.map +1 -0
- package/dist/browser/controller.js +8 -4
- package/dist/browser/controller.js.map +1 -1
- package/dist/browser-automation/browser-manager.js +8 -1
- package/dist/browser-automation/browser-manager.js.map +1 -1
- package/dist/checkpoints/checkpoint-versioning.js +78 -20
- package/dist/checkpoints/checkpoint-versioning.js.map +1 -1
- package/dist/cli/config-loader.js +2 -4
- package/dist/cli/config-loader.js.map +1 -1
- package/dist/codebuddy/client.js +70 -11
- package/dist/codebuddy/client.js.map +1 -1
- package/dist/codebuddy/tools.d.ts +1 -7
- package/dist/codebuddy/tools.js +2 -30
- package/dist/codebuddy/tools.js.map +1 -1
- package/dist/commands/cli/daemon-commands.d.ts +14 -0
- package/dist/commands/cli/daemon-commands.js +166 -0
- package/dist/commands/cli/daemon-commands.js.map +1 -0
- package/dist/commands/cli/speak-command.d.ts +10 -0
- package/dist/commands/cli/speak-command.js +97 -0
- package/dist/commands/cli/speak-command.js.map +1 -0
- package/dist/commands/cli/utility-commands.d.ts +10 -0
- package/dist/commands/cli/utility-commands.js +88 -0
- package/dist/commands/cli/utility-commands.js.map +1 -0
- package/dist/commands/handlers/fcs-handlers.js +1 -1
- package/dist/commands/handlers/fcs-handlers.js.map +1 -1
- package/dist/commands/handlers/memory-handlers.js +2 -1
- package/dist/commands/handlers/memory-handlers.js.map +1 -1
- package/dist/commands/handlers/vibe-handlers.js +0 -1
- package/dist/commands/handlers/vibe-handlers.js.map +1 -1
- package/dist/commands/handlers/worktree-handlers.js +11 -0
- package/dist/commands/handlers/worktree-handlers.js.map +1 -1
- package/dist/commands/index.d.ts +8 -7
- package/dist/commands/index.js +10 -8
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/mcp.d.ts +1 -0
- package/dist/commands/mcp.js +66 -7
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/pipeline.js +25 -13
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/config/hot-reload/watcher.js +4 -4
- package/dist/config/hot-reload/watcher.js.map +1 -1
- package/dist/config/model-tools.d.ts +41 -0
- package/dist/config/model-tools.js +194 -0
- package/dist/config/model-tools.js.map +1 -0
- package/dist/context/context-manager-v2.d.ts +2 -1
- package/dist/context/context-manager-v2.js +34 -5
- package/dist/context/context-manager-v2.js.map +1 -1
- package/dist/context/index.d.ts +12 -12
- package/dist/context/index.js +25 -12
- package/dist/context/index.js.map +1 -1
- package/dist/daemon/daemon-manager.js +23 -19
- package/dist/daemon/daemon-manager.js.map +1 -1
- package/dist/database/database-manager.d.ts +4 -0
- package/dist/database/database-manager.js +16 -7
- package/dist/database/database-manager.js.map +1 -1
- package/dist/desktop-automation/nutjs-provider.js +89 -0
- package/dist/desktop-automation/nutjs-provider.js.map +1 -1
- package/dist/errors/index.d.ts +4 -4
- package/dist/errors/index.js +8 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/fcs/builtins.d.ts +2 -6
- package/dist/fcs/builtins.js +2 -568
- package/dist/fcs/builtins.js.map +1 -1
- package/dist/fcs/codebuddy-bindings.d.ts +3 -43
- package/dist/fcs/codebuddy-bindings.js +2 -606
- package/dist/fcs/codebuddy-bindings.js.map +1 -1
- package/dist/fcs/index.d.ts +2 -27
- package/dist/fcs/index.js +2 -53
- package/dist/fcs/index.js.map +1 -1
- package/dist/fcs/lexer.d.ts +2 -37
- package/dist/fcs/lexer.js +2 -459
- package/dist/fcs/lexer.js.map +1 -1
- package/dist/fcs/parser.d.ts +2 -68
- package/dist/fcs/parser.js +2 -893
- package/dist/fcs/parser.js.map +1 -1
- package/dist/fcs/runtime.d.ts +2 -59
- package/dist/fcs/runtime.js +2 -623
- package/dist/fcs/runtime.js.map +1 -1
- package/dist/fcs/script-registry.d.ts +3 -69
- package/dist/fcs/script-registry.js +2 -219
- package/dist/fcs/script-registry.js.map +1 -1
- package/dist/fcs/sync-bindings.d.ts +3 -101
- package/dist/fcs/sync-bindings.js +2 -410
- package/dist/fcs/sync-bindings.js.map +1 -1
- package/dist/fcs/types.d.ts +2 -285
- package/dist/fcs/types.js +2 -103
- package/dist/fcs/types.js.map +1 -1
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.js +4 -4
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/use-input-handler.d.ts +1 -1
- package/dist/index.js +20 -330
- package/dist/index.js.map +1 -1
- package/dist/input/voice-control.js +11 -5
- package/dist/input/voice-control.js.map +1 -1
- package/dist/integrations/json-rpc/server.d.ts +9 -0
- package/dist/integrations/json-rpc/server.js +43 -13
- package/dist/integrations/json-rpc/server.js.map +1 -1
- package/dist/integrations/mcp/mcp-server.js +1 -1
- package/dist/integrations/mcp/mcp-server.js.map +1 -1
- package/dist/integrations/notification-integrations.d.ts +1 -0
- package/dist/integrations/notification-integrations.js +6 -1
- package/dist/integrations/notification-integrations.js.map +1 -1
- package/dist/mcp/client.js +2 -1
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/config.js +89 -5
- package/dist/mcp/config.js.map +1 -1
- package/dist/mcp/mcp-client.js +65 -14
- package/dist/mcp/mcp-client.js.map +1 -1
- package/dist/mcp/transports.d.ts +0 -1
- package/dist/mcp/transports.js +1 -5
- package/dist/mcp/transports.js.map +1 -1
- package/dist/mcp/types.d.ts +2 -0
- package/dist/memory/index.d.ts +2 -2
- package/dist/memory/index.js +2 -2
- package/dist/memory/index.js.map +1 -1
- package/dist/persistence/session-lock.d.ts +42 -0
- package/dist/persistence/session-lock.js +165 -0
- package/dist/persistence/session-lock.js.map +1 -0
- package/dist/persistence/session-store.d.ts +18 -3
- package/dist/persistence/session-store.js +90 -21
- package/dist/persistence/session-store.js.map +1 -1
- package/dist/plugins/conflict-detection.js +2 -1
- package/dist/plugins/conflict-detection.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -3
- package/dist/plugins/index.js +3 -3
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/isolated-plugin-runner.d.ts +6 -0
- package/dist/plugins/isolated-plugin-runner.js +19 -1
- package/dist/plugins/isolated-plugin-runner.js.map +1 -1
- package/dist/providers/local-llm-provider.js +28 -8
- package/dist/providers/local-llm-provider.js.map +1 -1
- package/dist/sandbox/docker-sandbox.js +7 -4
- package/dist/sandbox/docker-sandbox.js.map +1 -1
- package/dist/scripting/builtins.d.ts +8 -3
- package/dist/scripting/builtins.js +506 -355
- package/dist/scripting/builtins.js.map +1 -1
- package/dist/scripting/codebuddy-bindings.d.ts +47 -0
- package/dist/scripting/codebuddy-bindings.js +488 -0
- package/dist/scripting/codebuddy-bindings.js.map +1 -0
- package/dist/scripting/index.d.ts +33 -30
- package/dist/scripting/index.js +41 -36
- package/dist/scripting/index.js.map +1 -1
- package/dist/scripting/lexer.d.ts +31 -13
- package/dist/scripting/lexer.js +379 -292
- package/dist/scripting/lexer.js.map +1 -1
- package/dist/scripting/parser.d.ts +63 -44
- package/dist/scripting/parser.js +700 -473
- package/dist/scripting/parser.js.map +1 -1
- package/dist/scripting/runtime.d.ts +55 -24
- package/dist/scripting/runtime.js +600 -288
- package/dist/scripting/runtime.js.map +1 -1
- package/dist/scripting/script-registry.d.ts +54 -0
- package/dist/scripting/script-registry.js +202 -0
- package/dist/scripting/script-registry.js.map +1 -0
- package/dist/scripting/sync-bindings.d.ts +105 -0
- package/dist/scripting/sync-bindings.js +353 -0
- package/dist/scripting/sync-bindings.js.map +1 -0
- package/dist/scripting/types.d.ts +297 -199
- package/dist/scripting/types.js +86 -60
- package/dist/scripting/types.js.map +1 -1
- package/dist/search/usearch-index.js +42 -7
- package/dist/search/usearch-index.js.map +1 -1
- package/dist/security/bash-parser.d.ts +51 -0
- package/dist/security/bash-parser.js +327 -0
- package/dist/security/bash-parser.js.map +1 -0
- package/dist/security/index.d.ts +7 -5
- package/dist/security/index.js +8 -7
- package/dist/security/index.js.map +1 -1
- package/dist/security/skill-scanner.d.ts +36 -0
- package/dist/security/skill-scanner.js +149 -0
- package/dist/security/skill-scanner.js.map +1 -0
- package/dist/security/trust-folders.d.ts +1 -0
- package/dist/security/trust-folders.js +19 -1
- package/dist/security/trust-folders.js.map +1 -1
- package/dist/server/auth/index.d.ts +2 -2
- package/dist/server/auth/index.js +2 -2
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/middleware/index.d.ts +5 -5
- package/dist/server/middleware/index.js +5 -5
- package/dist/server/middleware/index.js.map +1 -1
- package/dist/server/middleware/rate-limit.js +15 -3
- package/dist/server/middleware/rate-limit.js.map +1 -1
- package/dist/server/websocket/handler.js +54 -6
- package/dist/server/websocket/handler.js.map +1 -1
- package/dist/skills/eligibility.js +26 -4
- package/dist/skills/eligibility.js.map +1 -1
- package/dist/tasks/background-tasks.js +5 -1
- package/dist/tasks/background-tasks.js.map +1 -1
- package/dist/tools/apply-patch.d.ts +55 -0
- package/dist/tools/apply-patch.js +273 -0
- package/dist/tools/apply-patch.js.map +1 -0
- package/dist/tools/hooks/default-hooks.d.ts +1 -1
- package/dist/tools/hooks/default-hooks.js +2 -1
- package/dist/tools/hooks/default-hooks.js.map +1 -1
- package/dist/tools/index.d.ts +10 -10
- package/dist/tools/index.js +11 -11
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/registry/bash-tools.js +6 -3
- package/dist/tools/registry/bash-tools.js.map +1 -1
- package/dist/tools/registry/misc-tools.js +1 -2
- package/dist/tools/registry/misc-tools.js.map +1 -1
- package/dist/tools/registry/search-tools.js +1 -1
- package/dist/tools/registry/search-tools.js.map +1 -1
- package/dist/tools/registry/text-editor-tools.js +1 -1
- package/dist/tools/registry/text-editor-tools.js.map +1 -1
- package/dist/tools/registry/todo-tools.js +37 -5
- package/dist/tools/registry/todo-tools.js.map +1 -1
- package/dist/tools/registry/tool-registry.js +5 -4
- package/dist/tools/registry/tool-registry.js.map +1 -1
- package/dist/tools/registry/web-tools.d.ts +1 -1
- package/dist/tools/registry/web-tools.js +28 -8
- package/dist/tools/registry/web-tools.js.map +1 -1
- package/dist/tools/text-editor.d.ts +1 -1
- package/dist/tools/text-editor.js +23 -5
- package/dist/tools/text-editor.js.map +1 -1
- package/dist/tools/web-search.d.ts +52 -37
- package/dist/tools/web-search.js +368 -163
- package/dist/tools/web-search.js.map +1 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/errors.js +2 -8
- package/dist/types/errors.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.js +1 -2
- package/dist/types/index.js.map +1 -1
- package/dist/ui/components/ChatInterface.d.ts +1 -1
- package/dist/ui/index.d.ts +17 -21
- package/dist/ui/index.js +25 -22
- package/dist/ui/index.js.map +1 -1
- package/dist/utils/config-validation/schema.d.ts +15 -15
- package/dist/utils/head-tail-truncation.d.ts +34 -0
- package/dist/utils/head-tail-truncation.js +98 -0
- package/dist/utils/head-tail-truncation.js.map +1 -0
- package/dist/utils/logger.js +3 -9
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/sanitize.d.ts +5 -0
- package/dist/utils/sanitize.js +19 -0
- package/dist/utils/sanitize.js.map +1 -1
- package/dist/utils/settings-manager.js +4 -4
- package/dist/utils/settings-manager.js.map +1 -1
- package/dist/workflows/index.d.ts +4 -279
- package/dist/workflows/index.js +8 -822
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/state-manager.d.ts +77 -0
- package/dist/workflows/state-manager.js +198 -0
- package/dist/workflows/state-manager.js.map +1 -0
- package/dist/workflows/step-manager.d.ts +39 -0
- package/dist/workflows/step-manager.js +196 -0
- package/dist/workflows/step-manager.js.map +1 -0
- package/dist/workflows/types.d.ts +87 -0
- package/dist/workflows/types.js +5 -0
- package/dist/workflows/types.js.map +1 -0
- package/dist/workflows/workflow-engine.d.ts +34 -0
- package/dist/workflows/workflow-engine.js +354 -0
- package/dist/workflows/workflow-engine.js.map +1 -0
- package/package.json +5 -1
package/dist/fcs/parser.js
CHANGED
|
@@ -1,896 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
-
|
|
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
|