-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathCInterpreter.cls
More file actions
6291 lines (5146 loc) · 201 KB
/
Copy pathCInterpreter.cls
File metadata and controls
6291 lines (5146 loc) · 201 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
Persistable = 0 'NotPersistable
DataBindingBehavior = 0 'vbNone
DataSourceBehavior = 0 'vbNone
MTSTransactionMode = 0 'NotAnMTSObject
END
Attribute VB_Name = "CInterpreter"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = True
'Author: David Zimmer <dzzie@yahoo.com>
'AI: Claude.ai
'Site: http://sandsprite.com
'License: MIT
Option Explicit
Public HeadLessDebugging As Boolean
Private m_globalScope As CScope
Private m_currentScope As CScope
Private m_parser As CParser
Private m_output As String
Private m_shouldBreak As Boolean
Private m_shouldContinue As Boolean
Private m_returnValue As CValue
Private m_hasReturned As Boolean
Private m_functions As Collection ' Store function declarations
' Module-level variables
Private m_thisContext As CValue ' Current 'this' binding
Private m_requestedNextLine As Long
Private m_hasRequestedNextLine As Boolean
' Store the parsed AST (for debugging and validation)
Private m_programAST As CNode
' Track what's already been executed
Private m_executedCode As String
' COM INTEGRATION
Public UseSafeSubset As Boolean
Private m_comObjects As Collection
' Debugger state
Private m_debugMode As Boolean
Private m_debugPaused As Boolean
Private m_debugStepMode As DebugStepMode
Private m_breakpoints As Collection
Private m_callStack As Collection
Private m_currentLine As Long
Private m_sourceCode As String
Private m_stepOutDepth As Long
Private m_AbortExec As Boolean
Public Enum DebugStepMode
smNone = 0 ' Run until breakpoint
smStepInto = 1 ' Step into functions
smStepOver = 2 ' Step over functions
smStepOut = 3 ' Run until return from current function
End Enum
Public Enum enumAuditEvents
aeEval = 0
aeDecode = 1
aeBracketAccess = 2
aeCOMCall = 3
aeActiveX = 4
aeXOR = 5
aeParse = 6
aeFunctionConstructor = 7
End Enum
Public AuditMode As Boolean
Public Event AuditEvent(category As enumAuditEvents, description As String, ByRef cancel As Boolean)
' Debugger events
Public Event OnBreakpoint(ByVal LineNumber As Long, ByVal sourceCode As String)
Public Event OnStep(ByVal LineNumber As Long, ByVal sourceCode As String)
Public Event OnCallStackChanged()
Public Event OnError(ByVal ErrorMessage As String, ByVal LineNumber As Long, ByVal Source As String, ByVal col As Long)
Public Event OnExecutionComplete()
Public Event OnVariablesChanged()
Public Event ConsoleLog(ByVal msg As String)
Const debug_mode As Boolean = False
Property Let DynProxyDebug(X As Boolean)
If ensureDynProxy() Then IPCDebugMode IIf(X, 1, 0)
End Property
Sub DynProxySend(ByVal msg As String)
If ensureDynProxy() Then SendDbgMsg msg
End Sub
Sub ShowAbout()
frmAbout.LaunchForm
End Sub
'example low level api to add a global:
'Dim config As CValue
'Set config = interp.CreateJSObject()
'
'Dim str As CValue
'Set str = New CValue
'str.vType = vtString
'str.strVal = "debug mode"
'
'Call config.SetProperty("mode", str)
'interp.SetVariable "appConfig", config
Public Function CreateJSObject() As CValue
Dim result As New CValue
result.vType = vtObject
Set result.objectProps = New Collection
Set result.objectKeys = New Collection
Set CreateJSObject = result
End Function
Public Function CreateJSArray() As CValue
Dim result As New CValue
result.vType = vtArray
Set result.arrayVal = New Collection
Set CreateJSArray = result
End Function
Function AuditEventToStr(ae As enumAuditEvents) As String
Dim categoryName As String
Select Case ae
Case aeEval: categoryName = "EVAL"
Case aeDecode: categoryName = "DECODE"
Case aeBracketAccess: categoryName = "BRACKET"
Case aeCOMCall: categoryName = "COM_CALL"
Case aeActiveX: categoryName = "ACTIVEX"
Case aeXOR: categoryName = "XOR"
Case aeParse: categoryName = "PARSE"
Case aeFunctionConstructor: categoryName = "FUNC_CTOR"
End Select
AuditEventToStr = categoryName
End Function
' FOR TESTING ONLY - Setup minimal debug state
Friend Sub TestSetupDebugState(code As String, currentLine As Long)
m_sourceCode = code
m_currentLine = currentLine
m_debugMode = True
m_debugPaused = True
' Parse code
Set m_programAST = m_parser.ParseScript(code)
End Sub
Property Get GlobalScope() As CScope
Set GlobalScope = m_globalScope
End Property
Property Get CurrentScope() As CScope
Set CurrentScope = m_currentScope
End Property
Sub Abort()
m_AbortExec = True
StopDebug
End Sub
Private Sub RaiseAudit(ByVal category As enumAuditEvents, ByVal description As String)
Dim cancel As Boolean
If AuditMode Then
RaiseEvent AuditEvent(category, description, cancel)
If cancel Then
m_AbortExec = True
Err.Raise vbObjectError + 1000, "CInterpreter.RaiseAudit", _
"Execution cancelled by user audit: " & description
End If
End If
End Sub
Private Sub Class_Initialize()
Set m_parser = New CParser
Set m_globalScope = New CScope
Set m_currentScope = m_globalScope
Set m_functions = New Collection
Set m_comObjects = New Collection ' NEW
' Initialize debugger
Set m_breakpoints = New Collection
Set m_callStack = New Collection
m_debugMode = False
m_debugStepMode = smNone
m_currentLine = 0
m_debugPaused = False
m_AbortExec = False
UseSafeSubset = True ' Safe by default
AddBuiltins ' Add built-in console object
End Sub
Private Sub InitDebug()
m_debugMode = True
m_debugPaused = True 'False
m_debugStepMode = smStepInto
m_currentLine = 0
m_stepOutDepth = 0
m_AbortExec = False
Set m_callStack = New Collection
If m_breakpoints Is Nothing Then
Set m_breakpoints = New Collection
End If
' Add global frame
Dim frame As New CDebugCallFrame
frame.FunctionName = "<global>"
frame.LineNumber = 0
Set frame.scope = m_globalScope
frame.ArgumentCount = 0
m_callStack.add frame
RaiseEvent OnCallStackChanged
End Sub
Public Sub StopDebug()
m_debugMode = False
m_debugPaused = False
Set m_callStack = New Collection
Set m_breakpoints = New Collection
End Sub
' Set/clear breakpoint
Public Sub SetBreakpoint(LineNumber As Long)
On Error Resume Next
m_breakpoints.Remove CStr(LineNumber)
On Error GoTo 0
m_breakpoints.add LineNumber, CStr(LineNumber)
End Sub
Public Sub ClearBreakpoint(LineNumber As Long)
On Error Resume Next
m_breakpoints.Remove CStr(LineNumber)
On Error GoTo 0
End Sub
Public Sub ClearAllBreakpoints()
Set m_breakpoints = New Collection
End Sub
Public Function hasBreakpoint(LineNumber As Long) As Boolean
On Error Resume Next
Dim X As Long
X = m_breakpoints(CStr(LineNumber))
hasBreakpoint = (Err.Number = 0)
End Function
' Step commands
Public Sub StepInto()
m_debugStepMode = smStepInto
m_debugPaused = False
End Sub
Public Sub StepOver()
m_debugStepMode = smStepOver
m_debugPaused = False
m_stepOutDepth = m_callStack.count
End Sub
Public Sub StepOut()
m_debugStepMode = smStepOut
m_debugPaused = False
m_stepOutDepth = m_callStack.count - 1
End Sub
Public Sub Run()
m_debugStepMode = smNone
m_debugPaused = False
m_AbortExec = False
End Sub
Public Sub PauseExecution()
If Not m_debugMode Then Exit Sub
m_debugPaused = True
m_debugStepMode = smStepInto ' Switch to step mode when manually paused
End Sub
' Get current state
Public Property Get currentLine() As Long
currentLine = m_currentLine
End Property
Public Property Get IsPaused() As Boolean
IsPaused = m_debugPaused
End Property
Public Property Get IsDebugging() As Boolean
IsDebugging = m_debugMode
End Property
' Get call stack as array of strings
Public Function GetCallStackStrings() As String()
Dim result() As String
Dim i As Long
Dim frame As CDebugCallFrame
If m_callStack.count = 0 Then
ReDim result(0)
result(0) = "<no stack>"
GetCallStackStrings = result
Exit Function
End If
ReDim result(m_callStack.count - 1)
For i = 1 To m_callStack.count
Set frame = m_callStack(i)
result(i - 1) = frame.ToString()
Next
GetCallStackStrings = result
End Function
' Get variables in current scope
Public Function GetCurrentVariables() As Collection
If m_callStack.count = 0 Then
Set GetCurrentVariables = New Collection
Exit Function
End If
Dim frame As CDebugCallFrame
frame = m_callStack(m_callStack.count)
' For now, return a collection of strings
' Format: "varName = value"
Dim vars As New Collection
' We need to enumerate scope variables
' This is tricky because Collection doesn't expose keys
' We'll add a helper method to CScope
Set GetCurrentVariables = vars
End Function
' Get variables from current scope
Public Function GetCurrentScopeVariables() As Collection
Set GetCurrentScopeVariables = New Collection
If Not m_debugMode Then Exit Function
' Get the current scope (function scope or global)
Dim CurrentScope As CScope
If m_callStack.count > 0 Then
' We're in a function - get the function's scope from top of stack
Dim frame As CDebugCallFrame
Set frame = m_callStack.Item(m_callStack.count)
Set CurrentScope = frame.scope
Else
' Global scope
Set CurrentScope = m_globalScope
End If
If CurrentScope Is Nothing Then
Set CurrentScope = m_globalScope
End If
If CurrentScope Is Nothing Then Exit Function
' Use CScope's GetAllVariables method
Set GetCurrentScopeVariables = CurrentScope.GetAllVariables()
End Function
' ============================================
' DEBUGGER INTERNAL HOOKS
' ============================================
' Called before each statement execution
Private Sub DebugCheckBreak(Node As CNode)
Dim atBreakpoint As Boolean
Dim shouldBreak As Boolean
If Not m_debugMode Then Exit Sub
If Node Is Nothing Then Exit Sub
m_currentLine = Node.LineNumber
atBreakpoint = hasBreakpoint(m_currentLine) ' Check for breakpoint
shouldBreak = False ' Determine if we should break
Select Case m_debugStepMode
Case smStepInto
shouldBreak = True
Case smStepOver
' Break if at same or shallower depth
If m_callStack.count <= m_stepOutDepth Then
shouldBreak = True
End If
Case smStepOut
' Break when we've returned to shallower depth
If m_callStack.count <= m_stepOutDepth Then
shouldBreak = True
m_debugStepMode = smStepInto
End If
Case smNone
' Only break on explicit breakpoints
shouldBreak = atBreakpoint
End Select
If shouldBreak Or atBreakpoint Then
m_debugPaused = True
If atBreakpoint Then
RaiseEvent OnBreakpoint(m_currentLine, m_sourceCode)
Else
RaiseEvent OnStep(m_currentLine, m_sourceCode)
End If
RaiseEvent OnVariablesChanged
If Not HeadLessDebugging Then
' PAUSE EXECUTION - wait for user
Do While m_debugPaused And m_debugMode
DoEvents ' This processes the Break button click
Sleep 15
Loop
End If
End If
End Sub
' Called when entering a function
Private Sub DebugPushFrame(funcName As String, argCount As Long)
If Not m_debugMode Then Exit Sub
Dim frame As New CDebugCallFrame
frame.FunctionName = funcName
frame.LineNumber = m_currentLine
Set frame.scope = m_currentScope
frame.ArgumentCount = argCount
m_callStack.add frame
RaiseEvent OnCallStackChanged
End Sub
' Called when exiting a function
Private Sub DebugPopFrame()
If Not m_debugMode Then Exit Sub
If m_callStack.count > 1 Then
m_callStack.Remove m_callStack.count
RaiseEvent OnCallStackChanged
End If
End Sub
Public Sub AddCode(code As String)
On Error GoTo ErrorHandler
' Skip empty code
If Len(Trim$(code)) = 0 Then Exit Sub
' Parse and execute just the new code
Dim ast As CNode
Set ast = m_parser.ParseScript(code)
ExecuteProgram ast
' Remember we executed this
If Len(m_executedCode) > 0 Then
m_executedCode = m_executedCode & vbCrLf
End If
m_executedCode = m_executedCode & code
' Raise completion event if debugging
If m_debugMode Then
RaiseEvent OnExecutionComplete
End If
Exit Sub
ErrorHandler:
m_output = m_output & vbCrLf & "ERROR in AddCode: " & Err.description
Debug.Print "AddCode Error: " & Err.description & " - " & Err.Source
Dim col As Long
' For parser errors during execution, extract line from error message
If m_currentLine = 0 And Err.Number = vbObjectError + 2000 Then
m_currentLine = ExtractLineFromError(Err.description)
col = ExtractColumnFromError(Err.description)
End If
RaiseEvent OnError(Err.description, m_currentLine, Err.Source, col)
End Sub
Public Sub ClearContext()
m_executedCode = ""
' Reset the interpreter state
Set m_globalScope = New CScope
Set m_currentScope = m_globalScope
Set m_functions = New Collection
Set m_comObjects = New Collection
m_output = ""
m_shouldBreak = False
m_shouldContinue = False
Set m_returnValue = Nothing
m_hasReturned = False
AddBuiltins ' Re-add console, etc.
End Sub
' Evaluate expression in CURRENT context (no re-parsing old code)
Public Function Eval(code As String) As CValue
On Error GoTo ErrorHandler
Dim ast As CNode
Dim result As CValue
' Parse ONLY the new code
Set ast = m_parser.ParseScript(code)
' Should be a single expression statement
If ast.Body.count > 0 Then
Dim stmt As CNode
Set stmt = ast.Body(1)
If stmt.tType = ExpressionStatement_Node Then
' Evaluate the expression in the current context
Set result = EvaluateExpression(stmt.Test)
Else
' Execute the statement
ExecuteStatement stmt
Set result = New CValue
result.vType = vtUndefined
End If
Else
Set result = New CValue
result.vType = vtUndefined
End If
Set Eval = result
Exit Function
ErrorHandler:
m_output = m_output & vbCrLf & "ERROR: " & Err.description
Debug.Print "Eval Error: " & Err.description & " " & Err.Source
Dim col As Long
' For parser errors during execution, extract line from error message
If m_currentLine = 0 And Err.Number = vbObjectError + 2000 Then
m_currentLine = ExtractLineFromError(Err.description)
col = ExtractColumnFromError(Err.description)
End If
RaiseEvent OnError(Err.description, m_currentLine, Err.Source, col)
Dim undef As New CValue
undef.vType = vtUndefined
Set Eval = undef
End Function
' Add this helper function to your interpreter class
Private Function ExtractLineFromError(errDescription As String) As Long
' Extract line number from parser error message
' Format: "Unexpected token: } at line 10, column 3"
Dim linePos As Long
linePos = InStr(errDescription, "at line ")
If linePos > 0 Then
Dim numStr As String
numStr = Mid$(errDescription, linePos + 8) ' Skip "at line "
' Stop at comma (before column info)
Dim commaPos As Long
commaPos = InStr(numStr, ",")
If commaPos > 0 Then
numStr = Left$(numStr, commaPos - 1)
End If
ExtractLineFromError = CLng(numStr)
Else
ExtractLineFromError = 0
End If
End Function
Private Function ExtractColumnFromError(errDescription As String) As Long
Dim colPos As Long
colPos = InStr(errDescription, "column ")
If colPos > 0 Then
ExtractColumnFromError = CLng(Mid$(errDescription, colPos + 7))
Else
ExtractColumnFromError = 0
End If
End Function
' Execute statement(s) in current context
Public Sub EvalStatement(code As String)
On Error GoTo ErrorHandler
Dim ast As CNode
Set ast = m_parser.ParseScript(code)
' Execute in the EXISTING context
ExecuteProgram ast
Exit Sub
ErrorHandler:
m_output = m_output & vbCrLf & "ERROR: " & Err.description & " - " & Err.Source
Debug.Print "EvalStatement Error: " & Err.description & " - " & Err.Source
Dim col As Long
' For parser errors during execution, extract line from error message
If m_currentLine = 0 And Err.Number = vbObjectError + 2000 Then
m_currentLine = ExtractLineFromError(Err.description)
col = ExtractColumnFromError(Err.description)
End If
RaiseEvent OnError(Err.description, m_currentLine, Err.Source, col)
End Sub
' Get a JavaScript variable from VB6
Public Function GetVariable(Name As String) As CValue
Set GetVariable = m_globalScope.GetVar(Name)
End Function
' Set a JavaScript variable from VB6
Public Sub SetVariable(Name As String, Value As Variant)
Dim val As New CValue
Set val = VariantToCValue(Value)
m_globalScope.DefineVar Name, val
End Sub
' Check if a JavaScript variable exists
Public Function HasVariable(Name As String) As Boolean
HasVariable = m_globalScope.HasVar(Name)
End Function
' BONUS: Get variable as VB6 Variant (convenience method)
Public Function GetVariableAsVariant(Name As String) As Variant
Dim val As CValue
Set val = m_globalScope.GetVar(Name)
GetVariableAsVariant = CValueToVariant(val)
End Function
Private Sub AddBuiltins()
' Create console.log as a special marker
' (We'll handle it specially in EvaluateCall)
' NEW: Create Math object
Dim mathObj As New CValue
mathObj.vType = vtObject
Set mathObj.objectProps = New Collection
' Add Math constants
Dim pi As New CValue
pi.vType = vtNumber
pi.numVal = 3.14159265358979
mathObj.objectProps.add pi, "PI"
Dim e As New CValue
e.vType = vtNumber
e.numVal = 2.71828182845905
mathObj.objectProps.add e, "E"
Dim ln2 As New CValue
ln2.vType = vtNumber
ln2.numVal = 0.693147180559945
mathObj.objectProps.add ln2, "LN2"
Dim ln10 As New CValue
ln10.vType = vtNumber
ln10.numVal = 2.30258509299405
mathObj.objectProps.add ln10, "LN10"
Dim log2e As New CValue
log2e.vType = vtNumber
log2e.numVal = 1.44269504088896
mathObj.objectProps.add log2e, "LOG2E"
Dim log10e As New CValue
log10e.vType = vtNumber
log10e.numVal = 0.434294481903252
mathObj.objectProps.add log10e, "LOG10E"
Dim sqrt2 As New CValue
sqrt2.vType = vtNumber
sqrt2.numVal = 1.4142135623731
mathObj.objectProps.add sqrt2, "SQRT2"
Dim sqrt1_2 As New CValue
sqrt1_2.vType = vtNumber
sqrt1_2.numVal = 0.707106781186548
mathObj.objectProps.add sqrt1_2, "SQRT1_2"
' Define Math in global scope
m_globalScope.DefineVar "Math", mathObj
' NEW: Create JSON object
Dim jsonObj As New CValue
jsonObj.vType = vtObject
Set jsonObj.objectProps = New Collection
' Define JSON in global scope
m_globalScope.DefineVar "JSON", jsonObj
End Sub
' Public API: Let host add COM objects
Public Sub AddCOMObject(Name As String, obj As Object)
' Create a CValue wrapper for the COM object
Dim val As New CValue
val.vType = vtCOMObject
Set val.objVal = obj
' Define in global scope
m_globalScope.DefineVar Name, val
' Also track in collection for enumeration
On Error Resume Next
m_comObjects.Remove Name
On Error GoTo 0
m_comObjects.add obj, Name
End Sub
' Attach a JS helper function to a COM object
' comObjName: Name of the COM object in global scope (e.g., "WScript")
' methodName: Name of the helper method to attach (e.g., "safeRun")
' jsFunction: JS function source code (e.g., "function(x) { return this.Run(x); }")
Public Sub AttachCOMHelper(comObjName As String, methodName As String, jsFunction As String)
On Error GoTo errHandler
Static counter As Long ' Persistent across calls
counter = counter + 1
' Get the COM object wrapper from global scope
Dim comWrapper As CValue
Set comWrapper = m_globalScope.GetVar(comObjName)
If comWrapper Is Nothing Then
Err.Raise vbObjectError + 1, "AttachCOMHelper", _
"Variable not found: " & comObjName
End If
If comWrapper.vType <> vtCOMObject Then
Err.Raise vbObjectError + 2, "AttachCOMHelper", _
"Not a COM object: " & comObjName & " (type=" & comWrapper.vType & ")"
End If
' Parse and execute the function definition
Dim tempVarName As String
tempVarName = "__helper_" & methodName & "_" & counter
Dim code As String
code = "var " & tempVarName & " = " & jsFunction & ";"
Execute code
' Get the created function
Dim helperFunc As CValue
Set helperFunc = m_globalScope.GetVar(tempVarName)
If helperFunc Is Nothing Or helperFunc.vType <> vtfunction Then
Err.Raise vbObjectError + 3, "AttachCOMHelper", _
"Failed to create function from: " & jsFunction
End If
' Attach it to the COM object wrapper
comWrapper.SetProperty methodName, helperFunc
' Clean up temp variable
m_globalScope.DeleteVar tempVarName
Exit Sub
errHandler:
' Re-raise with context
Err.Raise Err.Number, "AttachCOMHelper", _
"Failed to attach " & methodName & " to " & comObjName & ": " & Err.description
End Sub
' Public API: Remove a COM object
Public Sub RemoveCOMObject(Name As String)
' Remove from global scope
Dim undef As New CValue
undef.vType = vtUndefined
m_currentScope.SetVar Name, undef
' Remove from tracking
On Error Resume Next
m_comObjects.Remove Name
On Error GoTo 0
End Sub
' Main entry point
Public Sub Execute(code As String, Optional withDebugger As Boolean = False)
On Error GoTo ErrorHandler
' Parse the code
Dim ast As CNode
Set ast = m_parser.ParseScript(code)
If withDebugger Then
Set m_programAST = ast
End If
If withDebugger Then InitDebug
' Execute the program
ExecuteProgram ast
If m_debugMode Then
RaiseEvent OnExecutionComplete
End If
Exit Sub
ErrorHandler:
m_output = m_output & vbCrLf & "ERROR: " & Err.description
Debug.Print "Interpreter Error: " & Err.description & " - " & Err.Source
Dim col As Long
' For parser errors during execution, extract line from error message
If m_currentLine = 0 And Err.Number = vbObjectError + 2000 Then
m_currentLine = ExtractLineFromError(Err.description)
col = ExtractColumnFromError(Err.description)
End If
RaiseEvent OnError(Err.description, m_currentLine, Err.Source, col)
End Sub
' Get accumulated output (for testing)
Public Function GetOutput() As String
GetOutput = m_output
End Function
Public Sub ClearOutput()
m_output = ""
End Sub
'updated to allow set next line
Private Sub ExecuteProgram(programNode As CNode)
If programNode Is Nothing Then Exit Sub
If programNode.tType <> Program_Node Then Exit Sub
Dim statements As Collection
Set statements = programNode.Body
If statements Is Nothing Then Exit Sub
' HOIST GLOBAL FUNCTIONS FIRST
HoistFunctions statements, m_globalScope ' m_currentScope
' Execute each statement
Dim stmt As CNode
Dim stmtIndex As Long
stmtIndex = 1
m_AbortExec = False
Do While stmtIndex <= statements.count
If stmtIndex Mod 25 = 0 Then 'in case of a endless loop allow UI to breathe and user can click abort
DoEvents
Sleep 15
End If
If m_AbortExec = True Then
RaiseEvent OnError("Execution Aborted", m_currentLine, "ExecuteProgram", 0)
StopDebug
Exit Do
End If
Set stmt = statements(stmtIndex)
'Debug.Print ">>> ExecuteProgram: About to execute statement " & stmtIndex & " (line " & stmt.LineNumber & ")"
If Not stmt Is Nothing Then
m_currentLine = stmt.LineNumber
'If m_debugMode Then 'now causing duplicate hits..
' DebugCheckBreak stmt
'End If
End If
' Check if user requested a jump to different line
If m_hasRequestedNextLine Then
'Debug.Print ">>> ExecuteProgram: Jump requested! Current stmtIndex=" & stmtIndex
m_hasRequestedNextLine = False
' Find the statement at the requested line
Dim targetIndex As Long
targetIndex = FindStatementIndexAtLine(statements, m_requestedNextLine)
If targetIndex > 0 Then
'Debug.Print ">>> Jumping: statement " & stmtIndex & " (line " & stmt.LineNumber & ") -> statement " & targetIndex & " (line " & m_requestedNextLine & ")"
stmtIndex = targetIndex
Set stmt = statements(stmtIndex)
Else
'Debug.Print ">>> Jump failed: could not find statement at line " & m_requestedNextLine
End If
End If
' Execute the statement
ExecuteStatement stmt
' Check for return
If m_hasReturned Then Exit Sub
stmtIndex = stmtIndex + 1
Loop
End Sub
' Execute a statement
Private Sub ExecuteStatement(Node As CNode)
DoEvents
If m_AbortExec Then Exit Sub
If Not Node Is Nothing Then
m_currentLine = Node.LineNumber
If m_debugMode Then
If Node.tType <> BlockStatement_Node Then DebugCheckBreak Node
End If
End If
Select Case Node.tType
Case ExpressionStatement_Node:
' Evaluate and discard result
Dim result As CValue
Set result = EvaluateExpression(Node.Test)
Case VariableDeclaration_Node:
ExecuteVarDeclaration Node
Case BlockStatement_Node:
ExecuteBlock Node
Case EmptyStatement_Node:
' Do nothing
Case IfStatement_Node:
ExecuteIfStatement Node
Case WhileStatement_Node:
ExecuteWhileStatement Node
Case DoWhileStatement_Node:
ExecuteDoWhileStatement Node
Case ForStatement_Node:
ExecuteForStatement Node
Case BreakStatement_Node:
' Set break flag (we'll implement this)
m_shouldBreak = True
Case ContinueStatement_Node:
' Set continue flag
m_shouldContinue = True
Case ReturnStatement_Node:
ExecuteReturnStatement Node
Case FunctionDeclaration_Node:
'ExecuteFunctionDeclaration Node
' Skip - already hoisted in ExecuteBlock/ExecuteProgram
Case ThrowStatement_Node:
ExecuteThrowStatement Node
Case TryStatement_Node:
ExecuteTryStatement Node
Case SwitchStatement_Node:
ExecuteSwitchStatement Node
Case Else:
Err.Raise vbObjectError + 1000, "CInterpreter", _
"Unsupported statement type: " & Node.tType
End Select
End Sub