55using SharpTS . Runtime . BuiltIns ;
66using SharpTS . Runtime . BuiltIns . Modules ;
77using SharpTS . Runtime . BuiltIns . Modules . Interpreter ;
8+ using SharpTS . Runtime . EventLoop ;
89using SharpTS . Runtime . Exceptions ;
910using SharpTS . Runtime . Types ;
1011using SharpTS . TypeSystem ;
@@ -129,6 +130,9 @@ public Interpreter()
129130 // Track all pending timers for cleanup on disposal
130131 private readonly System . Collections . Concurrent . ConcurrentBag < Runtime . Types . SharpTSTimeout > _pendingTimers = new ( ) ;
131132
133+ // Event loop for managing async handles (servers, timers, etc.)
134+ private readonly EventLoop _eventLoop = new ( ) ;
135+
132136 // Virtual timer system - timers are checked and executed on the main thread during loop iterations.
133137 // This avoids thread scheduling issues on macOS where background threads may not get CPU time.
134138 // Uses PriorityQueue for O(log n) insert and O(log n) extraction of due timers.
@@ -195,6 +199,52 @@ internal VirtualTimer ScheduleTimer(int delayMs, int intervalMs, Action callback
195199 return timer ;
196200 }
197201
202+ /// <summary>
203+ /// Registers an async handle with the interpreter's event loop.
204+ /// The interpreter will keep running while this handle is active.
205+ /// </summary>
206+ internal void RegisterHandle ( IAsyncHandle handle )
207+ {
208+ _eventLoop . Register ( handle ) ;
209+ }
210+
211+ /// <summary>
212+ /// Unregisters an async handle from the interpreter's event loop.
213+ /// </summary>
214+ internal void UnregisterHandle ( IAsyncHandle handle )
215+ {
216+ _eventLoop . Unregister ( handle ) ;
217+ }
218+
219+ /// <summary>
220+ /// Runs the event loop, processing timers and keeping the process alive while there are active handles.
221+ /// Uses efficient waiting via ManualResetEventSlim instead of polling.
222+ /// </summary>
223+ private void RunEventLoop ( )
224+ {
225+ // Check if there are scheduled timers - they also count as active handles
226+ bool HasTimersOrHandles ( ) => _hasScheduledTimers || _eventLoop . HasActiveHandles ( ) ;
227+
228+ while ( ! _isDisposed && HasTimersOrHandles ( ) )
229+ {
230+ // Process any pending timer callbacks
231+ ProcessPendingCallbacks ( ) ;
232+
233+ // If only timers are active (no server handles), we need to continue the loop
234+ // If there are active handles, the event loop will wait efficiently
235+ if ( _eventLoop . HasActiveHandles ( ) )
236+ {
237+ // Let the event loop wait for state changes (with timeout for timer processing)
238+ _eventLoop . Run ( ProcessPendingCallbacks ) ;
239+ }
240+ else if ( _hasScheduledTimers )
241+ {
242+ // Only timers active - sleep briefly then check again
243+ Thread . Sleep ( 10 ) ;
244+ }
245+ }
246+ }
247+
198248 /// <summary>
199249 /// Processes all due virtual timers. Called during loop iterations to execute
200250 /// timer callbacks without relying on background thread scheduling.
@@ -270,6 +320,9 @@ public void Dispose()
270320 {
271321 _isDisposed = true ;
272322
323+ // Dispose the event loop first to stop any waiting
324+ _eventLoop . Dispose ( ) ;
325+
273326 // Cancel all pending timers to release resources immediately
274327 while ( _pendingTimers . TryTake ( out var timer ) )
275328 {
@@ -377,6 +430,9 @@ public void Interpret(List<Stmt> statements, TypeMap? typeMap = null)
377430
378431 // After executing all statements, check for a main() function and call it
379432 TryCallMainWithExitCode ( statements ) ;
433+
434+ // Always run the event loop - servers/timers may have been registered
435+ RunEventLoop ( ) ;
380436 }
381437 catch ( Exception error )
382438 {
@@ -414,10 +470,17 @@ public void InterpretModules(List<ParsedModule> modules, ModuleResolver resolver
414470 }
415471
416472 // After executing all modules, check for main() in the entry module (last one)
473+ // Note: main() may have already been called during module execution if there's
474+ // a top-level main() call. TryCallMainWithExitCode handles exit codes but
475+ // the event loop should run regardless of main().
417476 if ( modules . Count > 0 )
418477 {
419478 TryCallMainWithExitCode ( modules [ ^ 1 ] . Statements ) ;
420479 }
480+
481+ // Always run the event loop at the end - servers/timers may have been
482+ // registered during module execution (even without a main function)
483+ RunEventLoop ( ) ;
421484 }
422485 catch ( Exception error )
423486 {
@@ -488,8 +551,9 @@ private void TryCallMainWithExitCode(List<Stmt> statements)
488551 {
489552 if ( stmt is Stmt . Function func && func . Name . Lexeme == "main" && func . Body != null )
490553 {
491- // Check signature: exactly one parameter (args: string[])
492- if ( func . Parameters . Count == 1 && func . Parameters [ 0 ] . Type == "string[]" )
554+ // Accept signatures: main() or main(args: string[])
555+ var paramCount = func . Parameters . Count ;
556+ if ( paramCount == 0 || ( paramCount == 1 && func . Parameters [ 0 ] . Type == "string[]" ) )
493557 {
494558 // Accept return types: void, null (implicit), number, Promise<void>, Promise<number>
495559 var rt = func . ReturnType ;
@@ -513,12 +577,15 @@ private void TryCallMainWithExitCode(List<Stmt> statements)
513577 if ( mainValue is not SharpTSFunction mainFn )
514578 return ;
515579
516- // Call main with process.argv
580+ // Call main with process.argv (pass args even if main() doesn't take them - JS allows this)
517581 var argv = ProcessBuiltIns . GetArgv ( ) ;
518582 object ? result ;
519583 try
520584 {
521- result = mainFn . Call ( this , [ argv ] ) ;
585+ // Pass argv only if main expects it
586+ result = mainFunc . Parameters . Count == 0
587+ ? mainFn . Call ( this , [ ] )
588+ : mainFn . Call ( this , [ argv ] ) ;
522589 }
523590 catch ( Runtime . Exceptions . ReturnException ret )
524591 {
@@ -536,6 +603,10 @@ private void TryCallMainWithExitCode(List<Stmt> statements)
536603 {
537604 System . Environment . Exit ( ( int ) exitCode ) ;
538605 }
606+
607+ // Note: RunEventLoop is called by the caller (Interpret or InterpretModules)
608+ // after this method returns, so handles registered during main() or module
609+ // execution will keep the process alive.
539610 }
540611
541612 /// <summary>
@@ -1021,31 +1092,37 @@ internal ExecutionResult VisitDoWhile(Stmt.DoWhile doWhileStmt)
10211092
10221093 internal ExecutionResult VisitFor ( Stmt . For forStmt )
10231094 {
1024- // Execute initializer once
1025- if ( forStmt . Initializer != null )
1026- Execute ( forStmt . Initializer ) ;
1027- // Loop with proper continue handling - increment always runs
1028- while ( forStmt . Condition == null || IsTruthy ( Evaluate ( forStmt . Condition ) ) )
1095+ // Create scope for loop variables (ES6 let/const block scoping)
1096+ // Variables declared with let/const in the initializer are scoped to the loop
1097+ RuntimeEnvironment loopEnv = new ( _environment ) ;
1098+ using ( PushScope ( loopEnv ) )
10291099 {
1030- var result = Execute ( forStmt . Body ) ;
1031- if ( result . Type == ExecutionResult . ResultType . Break && result . TargetLabel == null ) break ;
1032- // On continue, execute increment then continue the loop
1033- if ( result . Type == ExecutionResult . ResultType . Continue && result . TargetLabel == null )
1100+ // Execute initializer once (defines loop variable in loopEnv)
1101+ if ( forStmt . Initializer != null )
1102+ Execute ( forStmt . Initializer ) ;
1103+ // Loop with proper continue handling - increment always runs
1104+ while ( forStmt . Condition == null || IsTruthy ( Evaluate ( forStmt . Condition ) ) )
10341105 {
1106+ var result = Execute ( forStmt . Body ) ;
1107+ if ( result . Type == ExecutionResult . ResultType . Break && result . TargetLabel == null ) break ;
1108+ // On continue, execute increment then continue the loop
1109+ if ( result . Type == ExecutionResult . ResultType . Continue && result . TargetLabel == null )
1110+ {
1111+ if ( forStmt . Increment != null )
1112+ Evaluate ( forStmt . Increment ) ;
1113+ // Yield to allow timer callbacks and other threads to execute
1114+ Thread . Sleep ( 0 ) ;
1115+ continue ;
1116+ }
1117+ if ( result . IsAbrupt ) return result ;
1118+ // Normal completion: execute increment
10351119 if ( forStmt . Increment != null )
10361120 Evaluate ( forStmt . Increment ) ;
1037- // Yield to allow timer callbacks and other threads to execute
1038- Thread . Sleep ( 0 ) ;
1039- continue ;
1121+ // Process any pending timer callbacks
1122+ ProcessPendingCallbacks ( ) ;
10401123 }
1041- if ( result . IsAbrupt ) return result ;
1042- // Normal completion: execute increment
1043- if ( forStmt . Increment != null )
1044- Evaluate ( forStmt . Increment ) ;
1045- // Process any pending timer callbacks
1046- ProcessPendingCallbacks ( ) ;
1124+ return ExecutionResult . Success ( ) ;
10471125 }
1048- return ExecutionResult . Success ( ) ;
10491126 }
10501127
10511128 internal ExecutionResult VisitForOf ( Stmt . ForOf forOf ) => ExecuteForOf ( forOf ) ;
0 commit comments