Class BytecodeExecutor

java.lang.Object
org.ek9lang.compiler.backend.jvm.BytecodeExecutor

public final class BytecodeExecutor extends Object
Executes generated EK9 bytecode within the current JVM. This avoids spawning external JVM processes for each execution, significantly improving performance.

The executor uses a URLClassLoader to load generated .class files from the compilation output directory. The ek9-lang runtime classes are loaded from the classpath (parent-first delegation).

Output capture is achieved using thread-local PrintStreams. Each thread captures its own stdout and stderr independently, enabling parallel-safe execution. EK9 programs use Stdout/Stderr which write to System.out/System.err, but we redirect both to thread-local delegating streams.

Used by REPL, bytecode tests, and the EK9 test runner.

  • Method Details

    • redirectUncaptured

      public static BytecodeExecutor.OutputRedirection redirectUncaptured(PrintStream out, PrintStream err)
      Route everything the per-thread capture does NOT take to the given streams, and return a handle that restores the previous routing.

      Call this instead of System.setOut/System.setErr around a run whose stray output must be captured — an embedding host such as the MCP server, whose stdout carries JSON-RPC. A plain setOut REPLACES the delegating stream installed below, and the static initialiser that installed it never fires again, so from the second run onward in a long-lived process every per-case capture silently returns nothing and every expected-output comparison sees empty actual output. That is invisible: the aggregate buffer still fills, so the run looks like it produced output while each case looks like it produced none.

      Re-installs the delegating streams if something has displaced them, so this is also the repair path, not only the polite one.

      Parameters:
      out - where uncaptured stdout should go
      err - where uncaptured stderr should go
      Returns:
      a handle whose close restores the previous fallbacks
    • execute

      public static String execute(Path bytecodeDir, String programClassName, String... args) throws Exception
      Executes a generated EK9 program and returns its captured stdout output.

      This is the backward-compatible method for REPL and simple bytecode tests. No env var overrides, no stdin fixtures, no stderr capture returned.

      Parameters:
      bytecodeDir - Directory containing generated .class files
      programClassName - Fully qualified class name (e.g., "bytecode.test.MyProgram")
      args - String arguments to pass to _main() method
      Returns:
      The captured stdout output from the program
      Throws:
      Exception - if execution fails
    • executeWithFixtures

      public static BytecodeExecutor.ExecutionResult executeWithFixtures(Path bytecodeDir, String programClassName, String[] args, Map<String,String> envVars, String stdinContent)
      Executes a generated EK9 program with full test isolation fixtures.

      Provides per-thread isolation for all four I/O channels:

      • stdout - captured via thread-local PrintStream
      • stderr - captured via thread-local PrintStream
      • env vars - thread-local override via EnvVars._setThreadEnv()
      • stdin - thread-local override via Stdin._setThreadInput()
      Parameters:
      bytecodeDir - Directory containing generated .class files
      programClassName - Fully qualified class name
      args - String arguments to pass to _main() method
      envVars - Thread-local env var overrides (null = no override)
      stdinContent - Content to provide as stdin (null = no override)
      Returns:
      Execution result with captured stdout, stderr, exit code, and duration