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