@holoscript/core 1.0.0-alpha.1 → 2.0.0
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/package.json +10 -9
- package/src/HoloScript2DParser.js +227 -0
- package/src/HoloScript2DParser.ts +5 -0
- package/src/HoloScriptCodeParser.js +1102 -0
- package/src/HoloScriptCodeParser.ts +145 -20
- package/src/HoloScriptDebugger.js +458 -0
- package/src/HoloScriptParser.js +338 -0
- package/src/HoloScriptPlusParser.js +371 -0
- package/src/HoloScriptPlusParser.ts +543 -0
- package/src/HoloScriptRuntime.js +1399 -0
- package/src/HoloScriptRuntime.test.js +351 -0
- package/src/HoloScriptRuntime.ts +257 -3
- package/src/HoloScriptTypeChecker.js +356 -0
- package/src/__tests__/GraphicsServices.test.js +357 -0
- package/src/__tests__/GraphicsServices.test.ts +427 -0
- package/src/__tests__/HoloScriptPlusParser.test.js +317 -0
- package/src/__tests__/HoloScriptPlusParser.test.ts +392 -0
- package/src/__tests__/integration.test.js +336 -0
- package/src/__tests__/performance.bench.js +218 -0
- package/src/__tests__/type-checker.test.js +60 -0
- package/src/__tests__/type-checker.test.ts +73 -0
- package/src/index.js +217 -0
- package/src/index.ts +158 -18
- package/src/interop/Interoperability.js +413 -0
- package/src/interop/Interoperability.ts +494 -0
- package/src/logger.js +42 -0
- package/src/parser/EnhancedParser.js +205 -0
- package/src/parser/EnhancedParser.ts +251 -0
- package/src/parser/HoloScriptPlusParser.js +928 -0
- package/src/parser/HoloScriptPlusParser.ts +1089 -0
- package/src/runtime/HoloScriptPlusRuntime.js +674 -0
- package/src/runtime/HoloScriptPlusRuntime.ts +861 -0
- package/src/runtime/PerformanceTelemetry.js +323 -0
- package/src/runtime/PerformanceTelemetry.ts +467 -0
- package/src/runtime/RuntimeOptimization.js +361 -0
- package/src/runtime/RuntimeOptimization.ts +416 -0
- package/src/services/HololandGraphicsPipelineService.js +506 -0
- package/src/services/HololandGraphicsPipelineService.ts +662 -0
- package/src/services/PlatformPerformanceOptimizer.js +356 -0
- package/src/services/PlatformPerformanceOptimizer.ts +503 -0
- package/src/state/ReactiveState.js +427 -0
- package/src/state/ReactiveState.ts +572 -0
- package/src/tools/DeveloperExperience.js +376 -0
- package/src/tools/DeveloperExperience.ts +438 -0
- package/src/traits/AIDriverTrait.js +322 -0
- package/src/traits/AIDriverTrait.test.js +329 -0
- package/src/traits/AIDriverTrait.test.ts +357 -0
- package/src/traits/AIDriverTrait.ts +474 -0
- package/src/traits/LightingTrait.js +313 -0
- package/src/traits/LightingTrait.test.js +410 -0
- package/src/traits/LightingTrait.test.ts +462 -0
- package/src/traits/LightingTrait.ts +505 -0
- package/src/traits/MaterialTrait.js +194 -0
- package/src/traits/MaterialTrait.test.js +286 -0
- package/src/traits/MaterialTrait.test.ts +329 -0
- package/src/traits/MaterialTrait.ts +324 -0
- package/src/traits/RenderingTrait.js +356 -0
- package/src/traits/RenderingTrait.test.js +363 -0
- package/src/traits/RenderingTrait.test.ts +427 -0
- package/src/traits/RenderingTrait.ts +555 -0
- package/src/traits/VRTraitSystem.js +740 -0
- package/src/traits/VRTraitSystem.ts +1040 -0
- package/src/traits/VoiceInputTrait.js +284 -0
- package/src/traits/VoiceInputTrait.test.js +226 -0
- package/src/traits/VoiceInputTrait.test.ts +252 -0
- package/src/traits/VoiceInputTrait.ts +401 -0
- package/src/types/AdvancedTypeSystem.js +226 -0
- package/src/types/AdvancedTypeSystem.ts +494 -0
- package/src/types/HoloScriptPlus.d.ts +853 -0
- package/src/types.js +6 -0
- package/src/types.ts +96 -1
- package/tsconfig.json +1 -1
- package/tsup.config.d.ts +2 -0
- package/tsup.config.js +18 -0
- package/LICENSE +0 -21
- package/dist/chunk-3X2EGU7Z.cjs +0 -52
- package/dist/chunk-3X2EGU7Z.cjs.map +0 -1
- package/dist/chunk-723TPVHD.js +0 -1074
- package/dist/chunk-723TPVHD.js.map +0 -1
- package/dist/chunk-EOKNAVDO.cjs +0 -424
- package/dist/chunk-EOKNAVDO.cjs.map +0 -1
- package/dist/chunk-HQZ3HUMY.js +0 -1087
- package/dist/chunk-HQZ3HUMY.js.map +0 -1
- package/dist/chunk-KWYIVRIH.js +0 -344
- package/dist/chunk-KWYIVRIH.js.map +0 -1
- package/dist/chunk-LKH4ZAN6.js +0 -421
- package/dist/chunk-LKH4ZAN6.js.map +0 -1
- package/dist/chunk-SATNCODL.js +0 -45
- package/dist/chunk-SATNCODL.js.map +0 -1
- package/dist/chunk-VMZN4EVR.cjs +0 -347
- package/dist/chunk-VMZN4EVR.cjs.map +0 -1
- package/dist/chunk-VV3UUUYP.cjs +0 -1089
- package/dist/chunk-VV3UUUYP.cjs.map +0 -1
- package/dist/chunk-XRYTSQHZ.cjs +0 -1076
- package/dist/chunk-XRYTSQHZ.cjs.map +0 -1
- package/dist/debugger.cjs +0 -19
- package/dist/debugger.cjs.map +0 -1
- package/dist/debugger.d.cts +0 -171
- package/dist/debugger.d.ts +0 -171
- package/dist/debugger.js +0 -6
- package/dist/debugger.js.map +0 -1
- package/dist/index.cjs +0 -755
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -169
- package/dist/index.d.ts +0 -169
- package/dist/index.js +0 -699
- package/dist/index.js.map +0 -1
- package/dist/parser.cjs +0 -13
- package/dist/parser.cjs.map +0 -1
- package/dist/parser.d.cts +0 -154
- package/dist/parser.d.ts +0 -154
- package/dist/parser.js +0 -4
- package/dist/parser.js.map +0 -1
- package/dist/runtime.cjs +0 -13
- package/dist/runtime.cjs.map +0 -1
- package/dist/runtime.d.cts +0 -147
- package/dist/runtime.d.ts +0 -147
- package/dist/runtime.js +0 -4
- package/dist/runtime.js.map +0 -1
- package/dist/type-checker.cjs +0 -16
- package/dist/type-checker.cjs.map +0 -1
- package/dist/type-checker.d.cts +0 -105
- package/dist/type-checker.d.ts +0 -105
- package/dist/type-checker.js +0 -3
- package/dist/type-checker.js.map +0 -1
- package/dist/types-WQSk1Qs2.d.cts +0 -238
- package/dist/types-WQSk1Qs2.d.ts +0 -238
|
@@ -67,6 +67,23 @@ export class HoloScriptCodeParser {
|
|
|
67
67
|
private warnings: string[] = [];
|
|
68
68
|
private tokens: Token[] = [];
|
|
69
69
|
private position: number = 0;
|
|
70
|
+
private keywordSet: Set<string>;
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
// Pre-compute keyword set for O(1) lookup instead of O(n) array search
|
|
74
|
+
this.keywordSet = new Set([
|
|
75
|
+
'orb', 'function', 'connect', 'to', 'as', 'gate', 'stream', 'from', 'through', 'return',
|
|
76
|
+
'if', 'else', 'nexus', 'building', 'pillar', 'foundation',
|
|
77
|
+
'for', 'while', 'forEach', 'in', 'of', 'break', 'continue',
|
|
78
|
+
'import', 'export', 'module', 'use',
|
|
79
|
+
'type', 'interface', 'extends', 'implements',
|
|
80
|
+
'async', 'await', 'spawn', 'parallel',
|
|
81
|
+
'class', 'new', 'this', 'super', 'static', 'private', 'public',
|
|
82
|
+
'try', 'catch', 'finally', 'throw',
|
|
83
|
+
'const', 'let', 'var',
|
|
84
|
+
'animate', 'modify', 'pulse', 'move', 'show', 'hide',
|
|
85
|
+
]);
|
|
86
|
+
}
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
89
|
* Parse HoloScript code string into AST
|
|
@@ -132,25 +149,6 @@ export class HoloScriptCodeParser {
|
|
|
132
149
|
let column = 1;
|
|
133
150
|
let i = 0;
|
|
134
151
|
|
|
135
|
-
const keywords = [
|
|
136
|
-
'orb', 'function', 'connect', 'to', 'as', 'gate', 'stream', 'from', 'through', 'return',
|
|
137
|
-
'if', 'else', 'nexus', 'building', 'pillar', 'foundation',
|
|
138
|
-
// Phase 2: Loop constructs
|
|
139
|
-
'for', 'while', 'forEach', 'in', 'of', 'break', 'continue',
|
|
140
|
-
// Phase 2: Module system
|
|
141
|
-
'import', 'export', 'module', 'use',
|
|
142
|
-
// Phase 2: Type system
|
|
143
|
-
'type', 'interface', 'extends', 'implements',
|
|
144
|
-
// Phase 2: Async
|
|
145
|
-
'async', 'await', 'spawn', 'parallel',
|
|
146
|
-
// Phase 2: Object-oriented
|
|
147
|
-
'class', 'new', 'this', 'super', 'static', 'private', 'public',
|
|
148
|
-
// Phase 2: Error handling
|
|
149
|
-
'try', 'catch', 'finally', 'throw',
|
|
150
|
-
// Phase 2: Constants
|
|
151
|
-
'const', 'let', 'var',
|
|
152
|
-
];
|
|
153
|
-
|
|
154
152
|
while (i < code.length) {
|
|
155
153
|
const char = code[i];
|
|
156
154
|
|
|
@@ -231,7 +229,7 @@ export class HoloScriptCodeParser {
|
|
|
231
229
|
column++;
|
|
232
230
|
}
|
|
233
231
|
|
|
234
|
-
const isKeyword =
|
|
232
|
+
const isKeyword = this.keywordSet.has(ident.toLowerCase());
|
|
235
233
|
tokens.push({
|
|
236
234
|
type: isKeyword ? 'keyword' : 'identifier',
|
|
237
235
|
value: ident,
|
|
@@ -341,10 +339,24 @@ export class HoloScriptCodeParser {
|
|
|
341
339
|
case 'export':
|
|
342
340
|
return this.parseExport();
|
|
343
341
|
// Phase 2: Variable declarations
|
|
342
|
+
// UI Extensions
|
|
343
|
+
case 'ui2d':
|
|
344
|
+
case 'card':
|
|
345
|
+
case 'metric':
|
|
346
|
+
case 'button':
|
|
347
|
+
case 'row':
|
|
348
|
+
case 'col':
|
|
349
|
+
case 'text':
|
|
350
|
+
return this.parseUIElement();
|
|
344
351
|
case 'const':
|
|
345
352
|
case 'let':
|
|
346
353
|
case 'var':
|
|
347
354
|
return this.parseVariableDeclaration();
|
|
355
|
+
// DSL-first commands (Phase 54)
|
|
356
|
+
case 'animate':
|
|
357
|
+
return this.parseAnimate();
|
|
358
|
+
case 'modify':
|
|
359
|
+
return this.parseModify();
|
|
348
360
|
default:
|
|
349
361
|
this.advance();
|
|
350
362
|
return null;
|
|
@@ -1055,6 +1067,119 @@ export class HoloScriptCodeParser {
|
|
|
1055
1067
|
return null;
|
|
1056
1068
|
}
|
|
1057
1069
|
|
|
1070
|
+
/**
|
|
1071
|
+
* Parse animate command: animate target property: "..." from: 0 to: 1 duration: 1000
|
|
1072
|
+
*/
|
|
1073
|
+
private parseAnimate(): ASTNode | null {
|
|
1074
|
+
this.expect('keyword', 'animate');
|
|
1075
|
+
const target = this.expectIdentifier();
|
|
1076
|
+
if (!target) return null;
|
|
1077
|
+
|
|
1078
|
+
const properties: Record<string, unknown> = {};
|
|
1079
|
+
|
|
1080
|
+
// Parse inline properties
|
|
1081
|
+
while (this.position < this.tokens.length) {
|
|
1082
|
+
this.skipNewlines();
|
|
1083
|
+
const t = this.currentToken();
|
|
1084
|
+
if (!t || t.type === 'newline' || (t.type === 'keyword' && this.keywordSet.has(t.value.toLowerCase()))) break;
|
|
1085
|
+
|
|
1086
|
+
const prop = this.parseProperty();
|
|
1087
|
+
if (prop) {
|
|
1088
|
+
properties[prop.key] = prop.value;
|
|
1089
|
+
} else {
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
return {
|
|
1095
|
+
type: 'expression-statement',
|
|
1096
|
+
expression: `animate("${target}", ${JSON.stringify(properties)})`,
|
|
1097
|
+
position: { x: 0, y: 0, z: 0 },
|
|
1098
|
+
} as ASTNode;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Parse modify command: modify target { prop: value }
|
|
1103
|
+
*/
|
|
1104
|
+
private parseModify(): ASTNode | null {
|
|
1105
|
+
this.expect('keyword', 'modify');
|
|
1106
|
+
const target = this.expectIdentifier();
|
|
1107
|
+
if (!target) return null;
|
|
1108
|
+
|
|
1109
|
+
const properties: Record<string, unknown> = {};
|
|
1110
|
+
|
|
1111
|
+
if (this.check('punctuation', '{')) {
|
|
1112
|
+
this.advance();
|
|
1113
|
+
while (!this.check('punctuation', '}') && this.position < this.tokens.length) {
|
|
1114
|
+
this.skipNewlines();
|
|
1115
|
+
if (this.check('punctuation', '}')) break;
|
|
1116
|
+
|
|
1117
|
+
const prop = this.parseProperty();
|
|
1118
|
+
if (prop) {
|
|
1119
|
+
properties[prop.key] = prop.value;
|
|
1120
|
+
}
|
|
1121
|
+
this.skipNewlines();
|
|
1122
|
+
}
|
|
1123
|
+
this.expect('punctuation', '}');
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
return {
|
|
1127
|
+
type: 'expression-statement',
|
|
1128
|
+
expression: `modify("${target}", ${JSON.stringify(properties)})`,
|
|
1129
|
+
position: { x: 0, y: 0, z: 0 },
|
|
1130
|
+
} as ASTNode;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
/**
|
|
1134
|
+
* Parse UI Element: ui2d dashboard#id { ... }
|
|
1135
|
+
*/
|
|
1136
|
+
private parseUIElement(): ASTNode | null {
|
|
1137
|
+
const typeToken = this.currentToken();
|
|
1138
|
+
if (!typeToken) return null;
|
|
1139
|
+
|
|
1140
|
+
const elementType = typeToken.value;
|
|
1141
|
+
this.advance();
|
|
1142
|
+
|
|
1143
|
+
let elementId = `${elementType}_${Date.now()}`;
|
|
1144
|
+
|
|
1145
|
+
// Check for ID syntax
|
|
1146
|
+
if (this.currentToken()?.type === 'punctuation' && this.currentToken()?.value === '#') {
|
|
1147
|
+
this.advance();
|
|
1148
|
+
const idToken = this.currentToken();
|
|
1149
|
+
if (idToken) {
|
|
1150
|
+
elementId = idToken.value;
|
|
1151
|
+
this.advance();
|
|
1152
|
+
}
|
|
1153
|
+
} else if (this.currentToken()?.type === 'identifier' && this.currentToken()?.value.startsWith('#')) {
|
|
1154
|
+
elementId = this.currentToken()?.value.slice(1) || elementId;
|
|
1155
|
+
this.advance();
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const properties: Record<string, any> = {};
|
|
1159
|
+
|
|
1160
|
+
if (this.check('punctuation', '{')) {
|
|
1161
|
+
this.advance();
|
|
1162
|
+
while (!this.check('punctuation', '}') && this.position < this.tokens.length) {
|
|
1163
|
+
this.skipNewlines();
|
|
1164
|
+
if (this.check('punctuation', '}')) break;
|
|
1165
|
+
|
|
1166
|
+
const prop = this.parseProperty();
|
|
1167
|
+
if (prop) {
|
|
1168
|
+
properties[prop.key] = prop.value;
|
|
1169
|
+
}
|
|
1170
|
+
this.skipNewlines();
|
|
1171
|
+
}
|
|
1172
|
+
this.expect('punctuation', '}');
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return {
|
|
1176
|
+
type: 'ui2d',
|
|
1177
|
+
name: elementType,
|
|
1178
|
+
properties: { id: elementId, ...properties },
|
|
1179
|
+
position: { x: 0, y: 0, z: 0 }
|
|
1180
|
+
} as ASTNode;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1058
1183
|
private skipNewlines(): void {
|
|
1059
1184
|
while (this.currentToken()?.type === 'newline') {
|
|
1060
1185
|
this.advance();
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HoloScript Debugger
|
|
3
|
+
*
|
|
4
|
+
* Step-through debugging with breakpoints, call stack inspection,
|
|
5
|
+
* and variable watch capabilities.
|
|
6
|
+
*/
|
|
7
|
+
import { HoloScriptRuntime } from './HoloScriptRuntime';
|
|
8
|
+
import { HoloScriptCodeParser } from './HoloScriptCodeParser';
|
|
9
|
+
/**
|
|
10
|
+
* HoloScript Debugger with breakpoints and step-through execution
|
|
11
|
+
*/
|
|
12
|
+
export class HoloScriptDebugger {
|
|
13
|
+
constructor(runtime) {
|
|
14
|
+
this.breakpoints = new Map();
|
|
15
|
+
this.callStack = [];
|
|
16
|
+
this.currentAST = [];
|
|
17
|
+
this.currentNodeIndex = 0;
|
|
18
|
+
this.frameIdCounter = 0;
|
|
19
|
+
this.breakpointIdCounter = 0;
|
|
20
|
+
this.eventHandlers = new Map();
|
|
21
|
+
this.state = {
|
|
22
|
+
status: 'stopped',
|
|
23
|
+
currentLine: 0,
|
|
24
|
+
currentColumn: 0,
|
|
25
|
+
currentNode: null,
|
|
26
|
+
callStack: [],
|
|
27
|
+
breakpoints: [],
|
|
28
|
+
};
|
|
29
|
+
this.runtime = runtime || new HoloScriptRuntime();
|
|
30
|
+
this.parser = new HoloScriptCodeParser();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Load source code for debugging
|
|
34
|
+
*/
|
|
35
|
+
loadSource(code, file) {
|
|
36
|
+
const parseResult = this.parser.parse(code);
|
|
37
|
+
if (!parseResult.success) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
errors: parseResult.errors.map(e => `Line ${e.line}: ${e.message}`),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
this.currentAST = parseResult.ast;
|
|
44
|
+
this.currentNodeIndex = 0;
|
|
45
|
+
this.state.status = 'stopped';
|
|
46
|
+
this.callStack = [];
|
|
47
|
+
// Store file reference in nodes
|
|
48
|
+
if (file) {
|
|
49
|
+
for (const node of this.currentAST) {
|
|
50
|
+
node.file = file;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { success: true };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Set a breakpoint at a line
|
|
57
|
+
*/
|
|
58
|
+
setBreakpoint(line, options = {}) {
|
|
59
|
+
const id = `bp_${++this.breakpointIdCounter}`;
|
|
60
|
+
const breakpoint = {
|
|
61
|
+
id,
|
|
62
|
+
line,
|
|
63
|
+
column: options.column,
|
|
64
|
+
condition: options.condition,
|
|
65
|
+
hitCount: 0,
|
|
66
|
+
enabled: options.enabled !== false,
|
|
67
|
+
file: options.file,
|
|
68
|
+
};
|
|
69
|
+
this.breakpoints.set(id, breakpoint);
|
|
70
|
+
this.updateBreakpointList();
|
|
71
|
+
return breakpoint;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Remove a breakpoint by ID
|
|
75
|
+
*/
|
|
76
|
+
removeBreakpoint(id) {
|
|
77
|
+
const removed = this.breakpoints.delete(id);
|
|
78
|
+
if (removed) {
|
|
79
|
+
this.updateBreakpointList();
|
|
80
|
+
}
|
|
81
|
+
return removed;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Remove all breakpoints at a line
|
|
85
|
+
*/
|
|
86
|
+
removeBreakpointsAtLine(line) {
|
|
87
|
+
let removed = 0;
|
|
88
|
+
for (const [id, bp] of this.breakpoints) {
|
|
89
|
+
if (bp.line === line) {
|
|
90
|
+
this.breakpoints.delete(id);
|
|
91
|
+
removed++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (removed > 0) {
|
|
95
|
+
this.updateBreakpointList();
|
|
96
|
+
}
|
|
97
|
+
return removed;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Toggle breakpoint enabled state
|
|
101
|
+
*/
|
|
102
|
+
toggleBreakpoint(id) {
|
|
103
|
+
const bp = this.breakpoints.get(id);
|
|
104
|
+
if (bp) {
|
|
105
|
+
bp.enabled = !bp.enabled;
|
|
106
|
+
this.updateBreakpointList();
|
|
107
|
+
return bp.enabled;
|
|
108
|
+
}
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Get all breakpoints
|
|
113
|
+
*/
|
|
114
|
+
getBreakpoints() {
|
|
115
|
+
return Array.from(this.breakpoints.values());
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Clear all breakpoints
|
|
119
|
+
*/
|
|
120
|
+
clearBreakpoints() {
|
|
121
|
+
this.breakpoints.clear();
|
|
122
|
+
this.updateBreakpointList();
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Start debugging from the beginning
|
|
126
|
+
*/
|
|
127
|
+
async start() {
|
|
128
|
+
this.currentNodeIndex = 0;
|
|
129
|
+
this.callStack = [];
|
|
130
|
+
this.frameIdCounter = 0;
|
|
131
|
+
this.runtime.reset();
|
|
132
|
+
this.state.status = 'running';
|
|
133
|
+
await this.runUntilBreakpoint();
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Continue execution until next breakpoint or end
|
|
137
|
+
*/
|
|
138
|
+
async continue() {
|
|
139
|
+
if (this.state.status !== 'paused')
|
|
140
|
+
return;
|
|
141
|
+
this.state.status = 'running';
|
|
142
|
+
this.currentNodeIndex++;
|
|
143
|
+
await this.runUntilBreakpoint();
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Step into the next node
|
|
147
|
+
*/
|
|
148
|
+
async stepInto() {
|
|
149
|
+
if (this.state.status !== 'paused')
|
|
150
|
+
return;
|
|
151
|
+
this.state.status = 'stepping';
|
|
152
|
+
await this.executeStep('into');
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Step over the current node
|
|
156
|
+
*/
|
|
157
|
+
async stepOver() {
|
|
158
|
+
if (this.state.status !== 'paused')
|
|
159
|
+
return;
|
|
160
|
+
this.state.status = 'stepping';
|
|
161
|
+
await this.executeStep('over');
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Step out of the current function
|
|
165
|
+
*/
|
|
166
|
+
async stepOut() {
|
|
167
|
+
if (this.state.status !== 'paused')
|
|
168
|
+
return;
|
|
169
|
+
this.state.status = 'stepping';
|
|
170
|
+
await this.executeStep('out');
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Stop debugging
|
|
174
|
+
*/
|
|
175
|
+
stop() {
|
|
176
|
+
this.state.status = 'stopped';
|
|
177
|
+
this.callStack = [];
|
|
178
|
+
this.currentNodeIndex = 0;
|
|
179
|
+
this.emitEvent({ type: 'state-change', data: { status: 'stopped' } });
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Pause execution
|
|
183
|
+
*/
|
|
184
|
+
pause() {
|
|
185
|
+
if (this.state.status === 'running') {
|
|
186
|
+
this.state.status = 'paused';
|
|
187
|
+
this.emitEvent({ type: 'state-change', data: { status: 'paused' } });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Get current debug state
|
|
192
|
+
*/
|
|
193
|
+
getState() {
|
|
194
|
+
return { ...this.state };
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Get call stack
|
|
198
|
+
*/
|
|
199
|
+
getCallStack() {
|
|
200
|
+
return [...this.callStack];
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get variables at a specific stack frame
|
|
204
|
+
*/
|
|
205
|
+
getVariables(frameId) {
|
|
206
|
+
if (frameId !== undefined) {
|
|
207
|
+
const frame = this.callStack.find(f => f.id === frameId);
|
|
208
|
+
return frame?.variables || new Map();
|
|
209
|
+
}
|
|
210
|
+
// Return all variables from runtime context
|
|
211
|
+
return new Map(this.runtime.getContext().variables);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Evaluate an expression in the current context
|
|
215
|
+
*/
|
|
216
|
+
async evaluate(expression) {
|
|
217
|
+
try {
|
|
218
|
+
// Try to evaluate as a variable lookup first
|
|
219
|
+
const varValue = this.runtime.getVariable(expression);
|
|
220
|
+
if (varValue !== undefined) {
|
|
221
|
+
return { value: varValue };
|
|
222
|
+
}
|
|
223
|
+
// Try to parse and execute as an expression
|
|
224
|
+
const parseResult = this.parser.parse(expression);
|
|
225
|
+
if (!parseResult.success) {
|
|
226
|
+
return { value: undefined, error: parseResult.errors[0]?.message };
|
|
227
|
+
}
|
|
228
|
+
// Execute the expression
|
|
229
|
+
const results = await this.runtime.executeProgram(parseResult.ast);
|
|
230
|
+
const lastResult = results[results.length - 1];
|
|
231
|
+
return { value: lastResult?.output };
|
|
232
|
+
}
|
|
233
|
+
catch (error) {
|
|
234
|
+
return { value: undefined, error: String(error) };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Set a watch expression
|
|
239
|
+
*/
|
|
240
|
+
watch(expression) {
|
|
241
|
+
const id = `watch_${Date.now()}`;
|
|
242
|
+
return { id, expression };
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Register an event handler
|
|
246
|
+
*/
|
|
247
|
+
on(event, handler) {
|
|
248
|
+
if (!this.eventHandlers.has(event)) {
|
|
249
|
+
this.eventHandlers.set(event, []);
|
|
250
|
+
}
|
|
251
|
+
this.eventHandlers.get(event).push(handler);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Remove an event handler
|
|
255
|
+
*/
|
|
256
|
+
off(event, handler) {
|
|
257
|
+
const handlers = this.eventHandlers.get(event);
|
|
258
|
+
if (handlers) {
|
|
259
|
+
const index = handlers.indexOf(handler);
|
|
260
|
+
if (index !== -1) {
|
|
261
|
+
handlers.splice(index, 1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Get the underlying runtime
|
|
267
|
+
*/
|
|
268
|
+
getRuntime() {
|
|
269
|
+
return this.runtime;
|
|
270
|
+
}
|
|
271
|
+
// Private methods
|
|
272
|
+
async runUntilBreakpoint() {
|
|
273
|
+
while (this.currentNodeIndex < this.currentAST.length) {
|
|
274
|
+
if (this.state.status === 'stopped')
|
|
275
|
+
break;
|
|
276
|
+
const node = this.currentAST[this.currentNodeIndex];
|
|
277
|
+
// Check for breakpoint
|
|
278
|
+
if (this.shouldBreakAt(node)) {
|
|
279
|
+
this.state.status = 'paused';
|
|
280
|
+
this.updateCurrentState(node);
|
|
281
|
+
const bp = this.findBreakpointAtLine(node.line ?? 0);
|
|
282
|
+
if (bp) {
|
|
283
|
+
bp.hitCount++;
|
|
284
|
+
this.emitEvent({
|
|
285
|
+
type: 'breakpoint-hit',
|
|
286
|
+
data: { breakpoint: bp, node, line: node.line ?? 0 },
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
// Execute the node
|
|
292
|
+
await this.executeNode(node);
|
|
293
|
+
this.currentNodeIndex++;
|
|
294
|
+
}
|
|
295
|
+
// Execution complete
|
|
296
|
+
this.state.status = 'stopped';
|
|
297
|
+
this.emitEvent({ type: 'state-change', data: { status: 'stopped', reason: 'complete' } });
|
|
298
|
+
}
|
|
299
|
+
async executeStep(mode) {
|
|
300
|
+
const startStackDepth = this.callStack.length;
|
|
301
|
+
if (this.currentNodeIndex >= this.currentAST.length) {
|
|
302
|
+
this.state.status = 'stopped';
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
const node = this.currentAST[this.currentNodeIndex];
|
|
306
|
+
switch (mode) {
|
|
307
|
+
case 'into':
|
|
308
|
+
// Execute one node, stepping into function calls
|
|
309
|
+
await this.executeNode(node);
|
|
310
|
+
this.currentNodeIndex++;
|
|
311
|
+
break;
|
|
312
|
+
case 'over':
|
|
313
|
+
// Execute one node, treating function calls as single steps
|
|
314
|
+
await this.executeNode(node);
|
|
315
|
+
this.currentNodeIndex++;
|
|
316
|
+
break;
|
|
317
|
+
case 'out':
|
|
318
|
+
// Execute until we leave the current stack frame
|
|
319
|
+
while (this.currentNodeIndex < this.currentAST.length) {
|
|
320
|
+
await this.executeNode(this.currentAST[this.currentNodeIndex]);
|
|
321
|
+
this.currentNodeIndex++;
|
|
322
|
+
if (this.callStack.length < startStackDepth) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
// Update state after step
|
|
329
|
+
if (this.currentNodeIndex < this.currentAST.length) {
|
|
330
|
+
this.state.status = 'paused';
|
|
331
|
+
this.updateCurrentState(this.currentAST[this.currentNodeIndex]);
|
|
332
|
+
this.emitEvent({
|
|
333
|
+
type: 'step-complete',
|
|
334
|
+
data: { mode, node: this.currentAST[this.currentNodeIndex] },
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this.state.status = 'stopped';
|
|
339
|
+
this.emitEvent({ type: 'state-change', data: { status: 'stopped', reason: 'complete' } });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
async executeNode(node) {
|
|
343
|
+
// Push stack frame for functions
|
|
344
|
+
if (node.type === 'function') {
|
|
345
|
+
const funcNode = node;
|
|
346
|
+
this.pushStackFrame(funcNode.name, node);
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
// Execute via runtime
|
|
350
|
+
const result = await this.runtime.executeNode(node);
|
|
351
|
+
if (!result.success && result.error) {
|
|
352
|
+
this.emitEvent({
|
|
353
|
+
type: 'exception',
|
|
354
|
+
data: { error: result.error, node, line: node.line },
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (result.output !== undefined) {
|
|
358
|
+
this.emitEvent({
|
|
359
|
+
type: 'output',
|
|
360
|
+
data: { output: result.output, node },
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
this.emitEvent({
|
|
366
|
+
type: 'exception',
|
|
367
|
+
data: { error: String(error), node, line: node.line },
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
// Pop stack frame when function completes
|
|
371
|
+
if (node.type === 'function') {
|
|
372
|
+
this.popStackFrame();
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
shouldBreakAt(node) {
|
|
376
|
+
const line = node.line;
|
|
377
|
+
for (const bp of this.breakpoints.values()) {
|
|
378
|
+
if (!bp.enabled)
|
|
379
|
+
continue;
|
|
380
|
+
if (bp.line !== line)
|
|
381
|
+
continue;
|
|
382
|
+
// Check condition if present
|
|
383
|
+
if (bp.condition) {
|
|
384
|
+
try {
|
|
385
|
+
const value = this.runtime.getVariable(bp.condition);
|
|
386
|
+
if (!value)
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
catch {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
findBreakpointAtLine(line) {
|
|
398
|
+
for (const bp of this.breakpoints.values()) {
|
|
399
|
+
if (bp.line === line && bp.enabled) {
|
|
400
|
+
return bp;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
pushStackFrame(name, node) {
|
|
406
|
+
const frame = {
|
|
407
|
+
id: ++this.frameIdCounter,
|
|
408
|
+
name,
|
|
409
|
+
file: node.file,
|
|
410
|
+
line: node.line ?? 0,
|
|
411
|
+
column: node.column ?? 0,
|
|
412
|
+
variables: new Map(this.runtime.getContext().variables),
|
|
413
|
+
node,
|
|
414
|
+
};
|
|
415
|
+
this.callStack.push(frame);
|
|
416
|
+
this.state.callStack = [...this.callStack];
|
|
417
|
+
}
|
|
418
|
+
popStackFrame() {
|
|
419
|
+
const frame = this.callStack.pop();
|
|
420
|
+
this.state.callStack = [...this.callStack];
|
|
421
|
+
return frame;
|
|
422
|
+
}
|
|
423
|
+
updateCurrentState(node) {
|
|
424
|
+
this.state.currentLine = node.line ?? 0;
|
|
425
|
+
this.state.currentColumn = node.column ?? 0;
|
|
426
|
+
this.state.currentNode = node;
|
|
427
|
+
}
|
|
428
|
+
updateBreakpointList() {
|
|
429
|
+
this.state.breakpoints = Array.from(this.breakpoints.values());
|
|
430
|
+
}
|
|
431
|
+
emitEvent(event) {
|
|
432
|
+
const handlers = this.eventHandlers.get(event.type) || [];
|
|
433
|
+
for (const handler of handlers) {
|
|
434
|
+
try {
|
|
435
|
+
handler(event);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
console.error('Debug event handler error:', error);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// Also emit to 'all' handlers
|
|
442
|
+
const allHandlers = this.eventHandlers.get('all') || [];
|
|
443
|
+
for (const handler of allHandlers) {
|
|
444
|
+
try {
|
|
445
|
+
handler(event);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
console.error('Debug event handler error:', error);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Create a debugger instance
|
|
455
|
+
*/
|
|
456
|
+
export function createDebugger(runtime) {
|
|
457
|
+
return new HoloScriptDebugger(runtime);
|
|
458
|
+
}
|