///|
/// Public interpreter APIs.

///|
fn function_stub(name : String) -> Value {
  Value::Function(FunctionValue::{
    name,
    params: [],
    defaults: [],
    body: [],
    is_generator: false,
    is_async: false,
    closure: [],
  })
}

///|
pub fn Config::default() -> Config {
  Config::{
    // Keep this lower than CPython's default (1000) to make the CPython Lib/test
    // suite practical under an interpreter.
    max_recursion: 250,
    traceback_limit: 20,
    track_spans: true,
    bytecode_strict: false,
    allow_filesystem_imports: false,
    import_paths: [],
    argv: [],
    main_path: None,
  }
}

///|
pub fn Config::for_cli(
  import_paths : Array[String],
  main_path : String?,
  argv : Array[String],
) -> Config {
  let base = Config::default()
  Config::{
    max_recursion: base.max_recursion,
    traceback_limit: base.traceback_limit,
    track_spans: base.track_spans,
    bytecode_strict: base.bytecode_strict,
    allow_filesystem_imports: true,
    import_paths,
    argv,
    main_path,
  }
}

///|
pub fn Config::with_bytecode_strict(self : Config, strict : Bool) -> Config {
  Config::{
    max_recursion: self.max_recursion,
    traceback_limit: self.traceback_limit,
    track_spans: self.track_spans,
    bytecode_strict: strict,
    allow_filesystem_imports: self.allow_filesystem_imports,
    import_paths: self.import_paths,
    argv: self.argv,
    main_path: self.main_path,
  }
}

///|
pub fn MockIO::new(stdin : Array[String]) -> MockIO {
  MockIO::{
    stdin,
    stdin_pos: { val: 0, },
    stdout: StringBuilder(),
    stderr: StringBuilder(),
  }
}

///|
pub fn MockIO::read_line(self : MockIO) -> String? {
  if self.stdin_pos.val >= self.stdin.length() {
    None
  } else {
    let line = self.stdin[self.stdin_pos.val]
    self.stdin_pos.val += 1
    Some(line)
  }
}

///|
pub fn MockIO::write_stdout(self : MockIO, text : String) -> Unit {
  self.stdout.write_string(text)
}

///|
pub fn MockIO::write_stderr(self : MockIO, text : String) -> Unit {
  self.stderr.write_string(text)
}

///|
pub fn MockIO::take_stdout(self : MockIO) -> String {
  self.stdout.to_string()
}

///|
pub fn MockIO::take_stderr(self : MockIO) -> String {
  self.stderr.to_string()
}

///|
pub fn Interpreter::new() -> Interpreter {
  let config = Config::{
    max_recursion: 250,
    traceback_limit: 20,
    track_spans: true,
    bytecode_strict: false,
    allow_filesystem_imports: true,
    import_paths: [],
    argv: [],
    main_path: None,
  }
  Interpreter::with_io(config, MockIO::new([]))
}

///|
pub fn Interpreter::with_config(config : Config) -> Interpreter {
  Interpreter::with_io(config, MockIO::new([]))
}

///|
pub fn Interpreter::new_spec() -> Interpreter {
  let config = Config::default()
  let object_dict : Array[(String, Value)] = []
  object_dict.push(
    (
      "__getattribute__",
      Value::Function(FunctionValue::{
        name: "__getattribute__",
        params: ["self", "name"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__setattr__",
      Value::Function(FunctionValue::{
        name: "__setattr__",
        params: ["self", "name", "value"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__delattr__",
      Value::Function(FunctionValue::{
        name: "__delattr__",
        params: ["self", "name"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__new__",
      Value::Function(FunctionValue::{
        name: "__new__",
        params: ["cls"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__init__",
      Value::Function(FunctionValue::{
        name: "__init__",
        params: ["self", "*args", "**kwargs"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__init_subclass__",
      Value::Function(FunctionValue::{
        name: "__init_subclass__",
        params: ["cls", "**kwargs"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__eq__",
      Value::Function(FunctionValue::{
        name: "__eq__",
        params: ["self", "other"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__ne__",
      Value::Function(FunctionValue::{
        name: "__ne__",
        params: ["self", "other"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__hash__",
      Value::Function(FunctionValue::{
        name: "__hash__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__str__",
      Value::Function(FunctionValue::{
        name: "__str__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  let object_class = ClassValue::{
    name: "object",
    bases: [],
    dict: object_dict,
  }
  let ellipsis_class = ClassValue::{
    name: "ellipsis",
    bases: [Value::Class(object_class)],
    dict: [],
  }
  let not_implemented_class = ClassValue::{
    name: "NotImplementedType",
    bases: [Value::Class(object_class)],
    dict: [],
  }
  let ellipsis_dict : Array[(String, Value)] = []
  ellipsis_dict.push(("hashvalue", Value::Int(fresh_object_hashvalue())))
  let not_implemented_dict : Array[(String, Value)] = []
  not_implemented_dict.push(("hashvalue", Value::Int(fresh_object_hashvalue())))
  let ellipsis_value = Value::Instance(InstanceValue::{
    class: ellipsis_class,
    dict: ellipsis_dict,
  })
  let not_implemented_value = Value::Instance(InstanceValue::{
    class: not_implemented_class,
    dict: not_implemented_dict,
  })
  let base_exception_dict : Array[(String, Value)] = []
  base_exception_dict.push(
    (
      "with_traceback",
      Value::Function(FunctionValue::{
        name: "with_traceback",
        params: ["self", "tb"],
        defaults: [],
        body: [Stmt::Return(Some(Expr::Name("self")))],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "__setstate__",
      Value::Function(FunctionValue::{
        name: "__setstate__",
        params: ["self", "state"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "__reduce_ex__",
      Value::Function(FunctionValue::{
        name: "__reduce_ex__",
        params: ["self", "protocol"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "__reduce__",
      Value::Function(FunctionValue::{
        name: "__reduce__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "add_note",
      Value::Function(FunctionValue::{
        name: "add_note",
        params: ["self", "note"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "__repr__",
      Value::Function(FunctionValue::{
        name: "__repr__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "add_note",
      Value::Function(FunctionValue::{
        name: "add_note",
        params: ["self", "note"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  let base_exception_class = ClassValue::{
    name: "BaseException",
    bases: [Value::Class(object_class)],
    dict: base_exception_dict,
  }
  let exception_class = ClassValue::{
    name: "Exception",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let system_exit_class = ClassValue::{
    name: "SystemExit",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let keyboard_interrupt_class = ClassValue::{
    name: "KeyboardInterrupt",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let generator_exit_class = ClassValue::{
    name: "GeneratorExit",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let base_exception_group_class = ClassValue::{
    name: "BaseExceptionGroup",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let exception_group_class = ClassValue::{
    name: "ExceptionGroup",
    bases: [
      Value::Class(base_exception_group_class),
      Value::Class(exception_class),
    ],
    dict: [],
  }
  let warning_class = ClassValue::{
    name: "Warning",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let user_warning_class = ClassValue::{
    name: "UserWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let deprecation_warning_class = ClassValue::{
    name: "DeprecationWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let pending_deprecation_warning_class = ClassValue::{
    name: "PendingDeprecationWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let syntax_warning_class = ClassValue::{
    name: "SyntaxWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let runtime_warning_class = ClassValue::{
    name: "RuntimeWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let future_warning_class = ClassValue::{
    name: "FutureWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let import_warning_class = ClassValue::{
    name: "ImportWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let unicode_warning_class = ClassValue::{
    name: "UnicodeWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let bytes_warning_class = ClassValue::{
    name: "BytesWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let resource_warning_class = ClassValue::{
    name: "ResourceWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let encoding_warning_class = ClassValue::{
    name: "EncodingWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let system_error_class = ClassValue::{
    name: "SystemError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let os_error_class = ClassValue::{
    name: "OSError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let child_process_error_class = ClassValue::{
    name: "ChildProcessError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let connection_error_class = ClassValue::{
    name: "ConnectionError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let broken_pipe_error_class = ClassValue::{
    name: "BrokenPipeError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let connection_aborted_error_class = ClassValue::{
    name: "ConnectionAbortedError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let connection_refused_error_class = ClassValue::{
    name: "ConnectionRefusedError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let connection_reset_error_class = ClassValue::{
    name: "ConnectionResetError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let interrupted_error_class = ClassValue::{
    name: "InterruptedError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let process_lookup_error_class = ClassValue::{
    name: "ProcessLookupError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let file_not_found_error_class = ClassValue::{
    name: "FileNotFoundError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let file_exists_error_class = ClassValue::{
    name: "FileExistsError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let permission_error_class = ClassValue::{
    name: "PermissionError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let is_a_directory_error_class = ClassValue::{
    name: "IsADirectoryError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let not_a_directory_error_class = ClassValue::{
    name: "NotADirectoryError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let timeout_error_class = ClassValue::{
    name: "TimeoutError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let builtins : Array[(String, Value)] = []
  builtins.push(("__debug__", Value::Bool(true)))
  let builtin_fn_names : Array[String] = [
    "__mpython_run", "__mpython_asyncgenexp_next", "__mpython_asyncgen_next", "__mpython_asyncgen_send",
    "__mpython_asyncgen_throw", "__mpython_asyncgen_close", "__mpython_posix_open",
    "__mpython_posix_read", "__mpython_posix_lseek", "__mpython_posix_write", "__mpython_posix_close",
    "__mpython_posix_fstat", "__mpython_posix_unlink", "__mpython_posix_mkdir", "__mpython_posix_rmdir",
    "__mpython_posix_utime", "__mpython_posix_stat", "__mpython_posix_getcwd", "__mpython_posix_chdir",
    "__mpython_posix_listdir", "__mpython_abc_cache_token", "__mpython_abc_cache_token_bump",
    "print", "len", "range", "str", "bytes", "bytearray", "memoryview", "int", "float",
    "complex", "list", "dict", "set", "frozenset", "tuple", "enumerate", "zip", "map",
    "filter", "any", "all", "sum", "property", "staticmethod", "classmethod", "delattr",
    "compile", "eval", "exec", "ascii", "open", "__import__",
  ]
  for fn_name in builtin_fn_names {
    builtins.push((fn_name, function_stub(fn_name)))
  }
  builtins.push(("object", Value::Class(object_class)))
  builtins.push(("Ellipsis", ellipsis_value))
  builtins.push(("NotImplemented", not_implemented_value))
  builtins.push(("BaseException", Value::Class(base_exception_class)))
  builtins.push(("GeneratorExit", Value::Class(generator_exit_class)))
  builtins.push(("SystemExit", Value::Class(system_exit_class)))
  builtins.push(("KeyboardInterrupt", Value::Class(keyboard_interrupt_class)))
  builtins.push(("Exception", Value::Class(exception_class)))
  builtins.push(
    ("BaseExceptionGroup", Value::Class(base_exception_group_class)),
  )
  builtins.push(("ExceptionGroup", Value::Class(exception_group_class)))
  builtins.push(("Warning", Value::Class(warning_class)))
  builtins.push(("UserWarning", Value::Class(user_warning_class)))
  builtins.push(("DeprecationWarning", Value::Class(deprecation_warning_class)))
  builtins.push(
    (
      "PendingDeprecationWarning",
      Value::Class(pending_deprecation_warning_class),
    ),
  )
  builtins.push(("SyntaxWarning", Value::Class(syntax_warning_class)))
  builtins.push(("RuntimeWarning", Value::Class(runtime_warning_class)))
  builtins.push(("FutureWarning", Value::Class(future_warning_class)))
  builtins.push(("ImportWarning", Value::Class(import_warning_class)))
  builtins.push(("UnicodeWarning", Value::Class(unicode_warning_class)))
  builtins.push(("BytesWarning", Value::Class(bytes_warning_class)))
  builtins.push(("ResourceWarning", Value::Class(resource_warning_class)))
  builtins.push(("EncodingWarning", Value::Class(encoding_warning_class)))
  builtins.push(("SystemError", Value::Class(system_error_class)))
  builtins.push(("OSError", Value::Class(os_error_class)))
  builtins.push(("ChildProcessError", Value::Class(child_process_error_class)))
  builtins.push(("ConnectionError", Value::Class(connection_error_class)))
  builtins.push(("BrokenPipeError", Value::Class(broken_pipe_error_class)))
  builtins.push(
    ("ConnectionAbortedError", Value::Class(connection_aborted_error_class)),
  )
  builtins.push(
    ("ConnectionRefusedError", Value::Class(connection_refused_error_class)),
  )
  builtins.push(
    ("ConnectionResetError", Value::Class(connection_reset_error_class)),
  )
  builtins.push(("InterruptedError", Value::Class(interrupted_error_class)))
  builtins.push(
    ("ProcessLookupError", Value::Class(process_lookup_error_class)),
  )
  builtins.push(("TimeoutError", Value::Class(timeout_error_class)))
  builtins.push(("FileNotFoundError", Value::Class(file_not_found_error_class)))
  builtins.push(("FileExistsError", Value::Class(file_exists_error_class)))
  builtins.push(("PermissionError", Value::Class(permission_error_class)))
  builtins.push(("IsADirectoryError", Value::Class(is_a_directory_error_class)))
  builtins.push(
    ("NotADirectoryError", Value::Class(not_a_directory_error_class)),
  )
  let builtin_type_names : Array[String] = [
    "int", "bool", "float", "complex", "str", "bytes", "bytearray", "memoryview",
    "list", "tuple", "dict", "set", "frozenset", "range", "property", "staticmethod",
    "classmethod",
  ]
  for type_name in builtin_type_names {
    let klass = builtin_class_from_name(type_name, builtins)
    set_named_value(builtins, type_name, Value::Class(klass))
  }
  let interpreter = Interpreter::with_builtins(builtins, config)
  // The reference generator compiles snippets from a module with
  // `from __future__ import annotations`; eval/exec inherit that flag.
  set_named_value(
    interpreter.globals,
    "__mpython_future_annotations__",
    Value::Bool(true),
  )
  interpreter
}

///|
pub fn Interpreter::with_builtins(
  builtins : Array[(String, Value)],
  config : Config,
) -> Interpreter {
  Interpreter::{ config, globals: [], builtins, io: MockIO::new([]), }
}

///|
pub fn Interpreter::with_io(config : Config, io : MockIO) -> Interpreter {
  // Capture the host working directory once so stdlib paths can be made stable
  // even if Python code calls os.chdir() (which we model virtually).
  match @env.current_dir() {
    Some(dir) => {
      set_initial_workdir(dir)
      set_current_workdir(dir)
    }
    None => ()
  }
  let object_dict : Array[(String, Value)] = []
  object_dict.push(
    (
      "__getattribute__",
      Value::Function(FunctionValue::{
        name: "__getattribute__",
        params: ["self", "name"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__setattr__",
      Value::Function(FunctionValue::{
        name: "__setattr__",
        params: ["self", "name", "value"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__delattr__",
      Value::Function(FunctionValue::{
        name: "__delattr__",
        params: ["self", "name"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__new__",
      Value::Function(FunctionValue::{
        name: "__new__",
        params: ["cls"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__init__",
      Value::Function(FunctionValue::{
        name: "__init__",
        params: ["self", "*args", "**kwargs"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__init_subclass__",
      Value::Function(FunctionValue::{
        name: "__init_subclass__",
        params: ["cls", "**kwargs"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__eq__",
      Value::Function(FunctionValue::{
        name: "__eq__",
        params: ["self", "other"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__ne__",
      Value::Function(FunctionValue::{
        name: "__ne__",
        params: ["self", "other"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__hash__",
      Value::Function(FunctionValue::{
        name: "__hash__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  object_dict.push(
    (
      "__str__",
      Value::Function(FunctionValue::{
        name: "__str__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  let object_class = ClassValue::{
    name: "object",
    bases: [],
    dict: object_dict,
  }
  let ellipsis_class = ClassValue::{
    name: "ellipsis",
    bases: [Value::Class(object_class)],
    dict: [],
  }
  let not_implemented_class = ClassValue::{
    name: "NotImplementedType",
    bases: [Value::Class(object_class)],
    dict: [],
  }
  let ellipsis_dict : Array[(String, Value)] = []
  ellipsis_dict.push(("hashvalue", Value::Int(fresh_object_hashvalue())))
  let not_implemented_dict : Array[(String, Value)] = []
  not_implemented_dict.push(("hashvalue", Value::Int(fresh_object_hashvalue())))
  let ellipsis_value = Value::Instance(InstanceValue::{
    class: ellipsis_class,
    dict: ellipsis_dict,
  })
  let not_implemented_value = Value::Instance(InstanceValue::{
    class: not_implemented_class,
    dict: not_implemented_dict,
  })
  let base_exception_dict : Array[(String, Value)] = []
  base_exception_dict.push(
    (
      "with_traceback",
      Value::Function(FunctionValue::{
        name: "with_traceback",
        params: ["self", "tb"],
        defaults: [],
        body: [Stmt::Return(Some(Expr::Name("self")))],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "__setstate__",
      Value::Function(FunctionValue::{
        name: "__setstate__",
        params: ["self", "state"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "__repr__",
      Value::Function(FunctionValue::{
        name: "__repr__",
        params: ["self"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  base_exception_dict.push(
    (
      "add_note",
      Value::Function(FunctionValue::{
        name: "add_note",
        params: ["self", "note"],
        defaults: [],
        body: [],
        is_generator: false,
        is_async: false,
        closure: [],
      }),
    ),
  )
  let base_exception_class = ClassValue::{
    name: "BaseException",
    bases: [Value::Class(object_class)],
    dict: base_exception_dict,
  }
  let exception_class = ClassValue::{
    name: "Exception",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let system_exit_class = ClassValue::{
    name: "SystemExit",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let keyboard_interrupt_class = ClassValue::{
    name: "KeyboardInterrupt",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let generator_exit_class = ClassValue::{
    name: "GeneratorExit",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let base_exception_group_class = ClassValue::{
    name: "BaseExceptionGroup",
    bases: [Value::Class(base_exception_class)],
    dict: [],
  }
  let exception_group_class = ClassValue::{
    name: "ExceptionGroup",
    bases: [
      Value::Class(base_exception_group_class),
      Value::Class(exception_class),
    ],
    dict: [],
  }
  let warning_class = ClassValue::{
    name: "Warning",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let user_warning_class = ClassValue::{
    name: "UserWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let deprecation_warning_class = ClassValue::{
    name: "DeprecationWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let pending_deprecation_warning_class = ClassValue::{
    name: "PendingDeprecationWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let syntax_warning_class = ClassValue::{
    name: "SyntaxWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let runtime_warning_class = ClassValue::{
    name: "RuntimeWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let future_warning_class = ClassValue::{
    name: "FutureWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let import_warning_class = ClassValue::{
    name: "ImportWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let unicode_warning_class = ClassValue::{
    name: "UnicodeWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let bytes_warning_class = ClassValue::{
    name: "BytesWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let resource_warning_class = ClassValue::{
    name: "ResourceWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let encoding_warning_class = ClassValue::{
    name: "EncodingWarning",
    bases: [Value::Class(warning_class)],
    dict: [],
  }
  let system_error_class = ClassValue::{
    name: "SystemError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let name_error_class = ClassValue::{
    name: "NameError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let unbound_local_error_class = ClassValue::{
    name: "UnboundLocalError",
    bases: [Value::Class(name_error_class)],
    dict: [],
  }
  let type_error_class = ClassValue::{
    name: "TypeError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let attribute_error_class = ClassValue::{
    name: "AttributeError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let lookup_error_class = ClassValue::{
    name: "LookupError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let index_error_class = ClassValue::{
    name: "IndexError",
    bases: [Value::Class(lookup_error_class)],
    dict: [],
  }
  let key_error_class = ClassValue::{
    name: "KeyError",
    bases: [Value::Class(lookup_error_class)],
    dict: [],
  }
  let buffer_error_class = ClassValue::{
    name: "BufferError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let reference_error_class = ClassValue::{
    name: "ReferenceError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let arithmetic_error_class = ClassValue::{
    name: "ArithmeticError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let floating_point_error_class = ClassValue::{
    name: "FloatingPointError",
    bases: [Value::Class(arithmetic_error_class)],
    dict: [],
  }
  let zero_division_error_class = ClassValue::{
    name: "ZeroDivisionError",
    bases: [Value::Class(arithmetic_error_class)],
    dict: [],
  }
  let overflow_error_class = ClassValue::{
    name: "OverflowError",
    bases: [Value::Class(arithmetic_error_class)],
    dict: [],
  }
  let value_error_class = ClassValue::{
    name: "ValueError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let unicode_error_class = ClassValue::{
    name: "UnicodeError",
    bases: [Value::Class(value_error_class)],
    dict: [],
  }
  let unicode_decode_error_class = ClassValue::{
    name: "UnicodeDecodeError",
    bases: [Value::Class(unicode_error_class)],
    dict: [],
  }
  let unicode_encode_error_class = ClassValue::{
    name: "UnicodeEncodeError",
    bases: [Value::Class(unicode_error_class)],
    dict: [],
  }
  let unicode_translate_error_class = ClassValue::{
    name: "UnicodeTranslateError",
    bases: [Value::Class(unicode_error_class)],
    dict: [],
  }
  let os_error_class = ClassValue::{
    name: "OSError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let blocking_io_error_class = ClassValue::{
    name: "BlockingIOError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let child_process_error_class = ClassValue::{
    name: "ChildProcessError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let connection_error_class = ClassValue::{
    name: "ConnectionError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let broken_pipe_error_class = ClassValue::{
    name: "BrokenPipeError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let connection_aborted_error_class = ClassValue::{
    name: "ConnectionAbortedError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let connection_refused_error_class = ClassValue::{
    name: "ConnectionRefusedError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let connection_reset_error_class = ClassValue::{
    name: "ConnectionResetError",
    bases: [Value::Class(connection_error_class)],
    dict: [],
  }
  let interrupted_error_class = ClassValue::{
    name: "InterruptedError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let process_lookup_error_class = ClassValue::{
    name: "ProcessLookupError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let file_not_found_error_class = ClassValue::{
    name: "FileNotFoundError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let file_exists_error_class = ClassValue::{
    name: "FileExistsError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let permission_error_class = ClassValue::{
    name: "PermissionError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let is_a_directory_error_class = ClassValue::{
    name: "IsADirectoryError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let not_a_directory_error_class = ClassValue::{
    name: "NotADirectoryError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let timeout_error_class = ClassValue::{
    name: "TimeoutError",
    bases: [Value::Class(os_error_class)],
    dict: [],
  }
  let import_error_class = ClassValue::{
    name: "ImportError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let module_not_found_error_class = ClassValue::{
    name: "ModuleNotFoundError",
    bases: [Value::Class(import_error_class)],
    dict: [],
  }
  let assertion_error_class = ClassValue::{
    name: "AssertionError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let syntax_error_class = ClassValue::{
    name: "SyntaxError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let indentation_error_class = ClassValue::{
    name: "IndentationError",
    bases: [Value::Class(syntax_error_class)],
    dict: [],
  }
  let tab_error_class = ClassValue::{
    name: "TabError",
    bases: [Value::Class(indentation_error_class)],
    dict: [],
  }
  let eof_error_class = ClassValue::{
    name: "EOFError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let stop_iteration_class = ClassValue::{
    name: "StopIteration",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let stop_async_iteration_class = ClassValue::{
    name: "StopAsyncIteration",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let runtime_error_class = ClassValue::{
    name: "RuntimeError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let not_implemented_error_class = ClassValue::{
    name: "NotImplementedError",
    bases: [Value::Class(runtime_error_class)],
    dict: [],
  }
  let recursion_error_class = ClassValue::{
    name: "RecursionError",
    bases: [Value::Class(runtime_error_class)],
    dict: [],
  }
  let memory_error_class = ClassValue::{
    name: "MemoryError",
    bases: [Value::Class(exception_class)],
    dict: [],
  }
  let builtins : Array[(String, Value)] = [
    ("print", function_stub("print")),
    ("input", function_stub("input")),
    ("breakpoint", function_stub("breakpoint")),
    ("open", function_stub("open")),
    ("__debug__", Value::Bool(true)),
    ("__mpython_run", function_stub("__mpython_run")),
    (
      "__mpython_levenshtein_distance",
      function_stub("__mpython_levenshtein_distance"),
    ),
    ("__mpython_asyncgenexp_next", function_stub("__mpython_asyncgenexp_next")),
    ("__mpython_asyncgen_next", function_stub("__mpython_asyncgen_next")),
    ("__mpython_asyncgen_send", function_stub("__mpython_asyncgen_send")),
    ("__mpython_asyncgen_throw", function_stub("__mpython_asyncgen_throw")),
    ("__mpython_asyncgen_close", function_stub("__mpython_asyncgen_close")),
    ("__mpython_posix_open", function_stub("__mpython_posix_open")),
    ("__mpython_posix_read", function_stub("__mpython_posix_read")),
    ("__mpython_posix_lseek", function_stub("__mpython_posix_lseek")),
    ("__mpython_posix_write", function_stub("__mpython_posix_write")),
    ("__mpython_posix_close", function_stub("__mpython_posix_close")),
    ("__mpython_posix_fstat", function_stub("__mpython_posix_fstat")),
    ("__mpython_posix_unlink", function_stub("__mpython_posix_unlink")),
    ("__mpython_posix_mkdir", function_stub("__mpython_posix_mkdir")),
    ("__mpython_posix_rmdir", function_stub("__mpython_posix_rmdir")),
    ("__mpython_posix_utime", function_stub("__mpython_posix_utime")),
    ("__mpython_posix_stat", function_stub("__mpython_posix_stat")),
    ("__mpython_posix_getcwd", function_stub("__mpython_posix_getcwd")),
    ("__mpython_posix_chdir", function_stub("__mpython_posix_chdir")),
    ("__mpython_posix_listdir", function_stub("__mpython_posix_listdir")),
    ("__mpython_abc_cache_token", function_stub("__mpython_abc_cache_token")),
    (
      "__mpython_abc_cache_token_bump",
      function_stub("__mpython_abc_cache_token_bump"),
    ),
    ("str", function_stub("str")),
    ("bytes", function_stub("bytes")),
    ("bytearray", function_stub("bytearray")),
    ("memoryview", function_stub("memoryview")),
    ("dict", function_stub("dict")),
    ("list", function_stub("list")),
    ("tuple", function_stub("tuple")),
    ("slice", function_stub("slice")),
    ("set", function_stub("set")),
    ("frozenset", function_stub("frozenset")),
    ("len", function_stub("len")),
    ("range", function_stub("range")),
    ("sum", function_stub("sum")),
    ("max", function_stub("max")),
    ("min", function_stub("min")),
    ("any", function_stub("any")),
    ("all", function_stub("all")),
    ("enumerate", function_stub("enumerate")),
    ("zip", function_stub("zip")),
    ("map", function_stub("map")),
    ("filter", function_stub("filter")),
    ("reversed", function_stub("reversed")),
    ("sorted", function_stub("sorted")),
    ("int", function_stub("int")),
    ("float", function_stub("float")),
    ("complex", function_stub("complex")),
    ("iter", function_stub("iter")),
    ("next", function_stub("next")),
    ("bool", function_stub("bool")),
    ("property", function_stub("property")),
    ("staticmethod", function_stub("staticmethod")),
    ("classmethod", function_stub("classmethod")),
    ("delattr", function_stub("delattr")),
    ("super", function_stub("super")),
    ("repr", function_stub("repr")),
    ("ascii", function_stub("ascii")),
    ("abs", function_stub("abs")),
    ("round", function_stub("round")),
    ("pow", function_stub("pow")),
    ("divmod", function_stub("divmod")),
    ("chr", function_stub("chr")),
    ("ord", function_stub("ord")),
    ("bin", function_stub("bin")),
    ("oct", function_stub("oct")),
    ("hex", function_stub("hex")),
    ("callable", function_stub("callable")),
    ("isinstance", function_stub("isinstance")),
    ("issubclass", function_stub("issubclass")),
    ("type", function_stub("type")),
    ("id", function_stub("id")),
    ("hash", function_stub("hash")),
    ("globals", function_stub("globals")),
    ("vars", function_stub("vars")),
    ("locals", function_stub("locals")),
    ("compile", function_stub("compile")),
    ("eval", function_stub("eval")),
    ("exec", function_stub("exec")),
    ("__import__", function_stub("__import__")),
    ("dir", function_stub("dir")),
    ("getattr", function_stub("getattr")),
    ("hasattr", function_stub("hasattr")),
    ("setattr", function_stub("setattr")),
    ("object", Value::Class(object_class)),
    ("Ellipsis", ellipsis_value),
    ("NotImplemented", not_implemented_value),
    ("BaseException", Value::Class(base_exception_class)),
    ("GeneratorExit", Value::Class(generator_exit_class)),
    ("SystemExit", Value::Class(system_exit_class)),
    ("KeyboardInterrupt", Value::Class(keyboard_interrupt_class)),
    ("Exception", Value::Class(exception_class)),
    ("BaseExceptionGroup", Value::Class(base_exception_group_class)),
    ("ExceptionGroup", Value::Class(exception_group_class)),
    ("Warning", Value::Class(warning_class)),
    ("UserWarning", Value::Class(user_warning_class)),
    ("DeprecationWarning", Value::Class(deprecation_warning_class)),
    (
      "PendingDeprecationWarning",
      Value::Class(pending_deprecation_warning_class),
    ),
    ("SyntaxWarning", Value::Class(syntax_warning_class)),
    ("RuntimeWarning", Value::Class(runtime_warning_class)),
    ("FutureWarning", Value::Class(future_warning_class)),
    ("ImportWarning", Value::Class(import_warning_class)),
    ("UnicodeWarning", Value::Class(unicode_warning_class)),
    ("BytesWarning", Value::Class(bytes_warning_class)),
    ("ResourceWarning", Value::Class(resource_warning_class)),
    ("EncodingWarning", Value::Class(encoding_warning_class)),
    ("SystemError", Value::Class(system_error_class)),
    ("NameError", Value::Class(name_error_class)),
    ("UnboundLocalError", Value::Class(unbound_local_error_class)),
    ("TypeError", Value::Class(type_error_class)),
    ("AttributeError", Value::Class(attribute_error_class)),
    ("BufferError", Value::Class(buffer_error_class)),
    ("LookupError", Value::Class(lookup_error_class)),
    ("IndexError", Value::Class(index_error_class)),
    ("KeyError", Value::Class(key_error_class)),
    ("NotImplementedError", Value::Class(not_implemented_error_class)),
    ("ArithmeticError", Value::Class(arithmetic_error_class)),
    ("FloatingPointError", Value::Class(floating_point_error_class)),
    ("ZeroDivisionError", Value::Class(zero_division_error_class)),
    ("OverflowError", Value::Class(overflow_error_class)),
    ("ValueError", Value::Class(value_error_class)),
    ("UnicodeError", Value::Class(unicode_error_class)),
    ("UnicodeDecodeError", Value::Class(unicode_decode_error_class)),
    ("UnicodeEncodeError", Value::Class(unicode_encode_error_class)),
    ("UnicodeTranslateError", Value::Class(unicode_translate_error_class)),
    ("OSError", Value::Class(os_error_class)),
    // CPython compatibility: IOError is an alias of OSError.
    ("IOError", Value::Class(os_error_class)),
    ("BlockingIOError", Value::Class(blocking_io_error_class)),
    ("ChildProcessError", Value::Class(child_process_error_class)),
    ("ConnectionError", Value::Class(connection_error_class)),
    ("BrokenPipeError", Value::Class(broken_pipe_error_class)),
    ("ConnectionAbortedError", Value::Class(connection_aborted_error_class)),
    ("ConnectionRefusedError", Value::Class(connection_refused_error_class)),
    ("ConnectionResetError", Value::Class(connection_reset_error_class)),
    ("InterruptedError", Value::Class(interrupted_error_class)),
    ("TimeoutError", Value::Class(timeout_error_class)),
    ("FileNotFoundError", Value::Class(file_not_found_error_class)),
    ("FileExistsError", Value::Class(file_exists_error_class)),
    ("PermissionError", Value::Class(permission_error_class)),
    ("IsADirectoryError", Value::Class(is_a_directory_error_class)),
    ("NotADirectoryError", Value::Class(not_a_directory_error_class)),
    ("ProcessLookupError", Value::Class(process_lookup_error_class)),
    ("ImportError", Value::Class(import_error_class)),
    ("ModuleNotFoundError", Value::Class(module_not_found_error_class)),
    ("AssertionError", Value::Class(assertion_error_class)),
    ("SyntaxError", Value::Class(syntax_error_class)),
    ("IndentationError", Value::Class(indentation_error_class)),
    ("TabError", Value::Class(tab_error_class)),
    ("EOFError", Value::Class(eof_error_class)),
    ("StopIteration", Value::Class(stop_iteration_class)),
    ("StopAsyncIteration", Value::Class(stop_async_iteration_class)),
    ("RuntimeError", Value::Class(runtime_error_class)),
    ("RecursionError", Value::Class(recursion_error_class)),
    ("MemoryError", Value::Class(memory_error_class)),
    ("ReferenceError", Value::Class(reference_error_class)),
  ]
  let builtin_type_names : Array[String] = [
    "int", "bool", "float", "complex", "str", "bytes", "bytearray", "memoryview",
    "list", "tuple", "slice", "dict", "set", "frozenset", "range", "type", "property",
    "staticmethod", "classmethod",
  ]
  for type_name in builtin_type_names {
    let klass = builtin_class_from_name(type_name, builtins)
    set_named_value(builtins, type_name, Value::Class(klass))
  }
  let globals : Array[(String, Value)] = [
    ("hashvalue", Value::Int(fresh_object_hashvalue())),
    ("__name__", Value::Str("__main__")),
    ("__package__", Value::Str("")),
    // Importlib startup tests expect these attributes to exist on every module.
    ("__loader__", Value::None),
    ("__spec__", Value::None),
  ]
  let module_class = ClassValue::{ name: "module", bases: [], dict: [], }
  let main_module = Value::Instance(InstanceValue::{
    class: module_class,
    dict: globals,
  })
  module_cache_set("__main__", main_module)
  // Seed a tiny `pdb` module in `sys.modules` so stdlib helpers like
  // `unittest.mock.patch("pdb.set_trace")` don't import CPython's full pdb
  // implementation (which requires many unsupported modules).
  module_cache_set("pdb", make_pdb_module(builtins))
  Interpreter::{ config, globals, builtins, io, }
}

///|
pub fn Interpreter::exec(
  self : Interpreter,
  program : Module,
) -> Result[RunResult, RuntimeError] {
  ensure_runtime_callbacks_ready()
  let filename = match self.config.main_path {
    Some(path) => path
    None => "".to_string()
  }
  let _ = match ensure_recursion_available() {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  push_traceback_frame("".to_string(), filename)
  set_traceback_frame_env(self.globals, self.globals)
  // Execute via the bytecode pipeline. If compilation fails, surface the error
  // directly so we don't silently fall back and hide bytecode compiler gaps.
  let future_annotations = stmts_have_future_annotations(program.body) ||
    get_global_value(self.globals, "__mpython_future_annotations__")
    is Some(Value::Bool(true))
  let value_result = match
    compile_stmts_to_bc_with_future(
      program.body,
      "",
      filename,
      future_annotations,
    ) {
    Ok(code) => {
      push_active_config(self.config)
      let out = bc_exec(
        code,
        self.globals,
        self.globals,
        self.builtins,
        self.io,
      )
      pop_active_config()
      out
    }
    Err(err) => Err(err)
  }
  pop_traceback_frame()
  let value = match value_result {
    Ok(value) => value
    Err(err) =>
      if err.exc_type == "SystemExit" {
        // Treat `sys.exit(...)` as normal termination: do not print a traceback.
        return Ok(RunResult::{
          value: Value::None,
          stdout: self.io.take_stdout(),
          stderr: self.io.take_stderr(),
          globals: self.globals,
        })
      } else {
        return Err(err)
      }
  }
  match value {
    Value::Function(_) =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "ValueError: unsupported value".to_string(),
        ),
      )
    _ => ()
  }
  Ok(RunResult::{
    value,
    stdout: self.io.take_stdout(),
    stderr: self.io.take_stderr(),
    globals: self.globals,
  })
}

///|
pub fn Interpreter::exec_source(
  self : Interpreter,
  source : String,
) -> Result[RunResult, RuntimeError] {
  let program = match parse(source) {
    Ok(value) => value
    Err(err) => return Err(make_parse_runtime_error(err))
  }
  self.exec(program)
}

///|
pub fn Interpreter::execute_test_dir(
  self : Interpreter,
  path : String,
) -> TestResult raise MpythonError {
  let _ = self
  if !@fs.path_exists(path) {
    let err = make_runtime_error(
      RuntimeErrorKind::Runtime,
      "RuntimeError: test dir not found: " + path,
    )
    raise MpythonError::MpythonError(err)
  }
  TestResult::{ passed: 0, failed: 0, errors: [], }
}

///|
pub fn Interpreter::eval_source(
  self : Interpreter,
  source : String,
) -> Result[RunResult, RuntimeError] {
  let expr = match parse_expr(source) {
    Ok(value) => value
    Err(err) => return Err(make_parse_runtime_error(err))
  }
  ensure_runtime_callbacks_ready()
  let filename = match self.config.main_path {
    Some(path) => path
    None => "".to_string()
  }
  let _ = match ensure_recursion_available() {
    Ok(_) => ()
    Err(err) => return Err(err)
  }
  push_traceback_frame("".to_string(), filename)
  set_traceback_frame_env(self.globals, self.globals)
  push_active_config(self.config)
  let value_result = eval_expr_with_env(
    expr,
    self.globals,
    self.globals,
    self.builtins,
    self.io,
  )
  pop_active_config()
  pop_traceback_frame()
  let value = match value_result {
    Ok(value) => value
    Err(err) => return Err(err)
  }
  match value {
    Value::Function(_) =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "ValueError: unsupported value".to_string(),
        ),
      )
    Value::Class(_) =>
      return Err(
        make_runtime_error(
          RuntimeErrorKind::Runtime,
          "ValueError: unsupported value".to_string(),
        ),
      )
    Value::Instance(inst) =>
      if inst.class.name == "generator" {
        return Err(
          make_runtime_error(
            RuntimeErrorKind::Runtime,
            "ValueError: unsupported value".to_string(),
          ),
        )
      }
    _ => ()
  }
  Ok(RunResult::{
    value,
    stdout: self.io.take_stdout(),
    stderr: self.io.take_stderr(),
    globals: self.globals,
  })
}

///|
pub fn Interpreter::eval(
  self : Interpreter,
  expr : Expr,
) -> Result[Value, RuntimeError] {
  ensure_runtime_callbacks_ready()
  eval_expr_with_env(expr, self.globals, self.globals, self.builtins, self.io)
}

///|
pub fn Interpreter::get_global(self : Interpreter, name : String) -> Value? {
  for pair in self.globals {
    if pair.0 == name {
      return Some(pair.1)
    }
  }
  None
}

///|
pub fn Interpreter::set_global(
  self : Interpreter,
  name : String,
  value : Value,
) -> Unit {
  set_global_value(self.globals, name, value)
}

///|
pub fn Interpreter::set_global_str(
  self : Interpreter,
  name : String,
  value : String,
) -> Unit {
  set_global_value(self.globals, name, Value::Str(value))
}

///|
pub fn Interpreter::set_main_module_spec(
  self : Interpreter,
  module_name : String,
  origin : String,
  parent : String,
  is_package_main : Bool,
) -> Unit {
  // When running with `-m`, CPython populates `__spec__`/`__loader__` on the
  // `__main__` module. Stdlib helpers (e.g. `linecache.lazycache`) rely on a
  // working `__loader__.get_source(__spec__.name)` implementation.
  let loader = make_importlib_loader()
  match loader {
    Value::Instance(loader_inst) =>
      set_named_value(loader_inst.dict, "path", Value::Str(origin))
    _ => ()
  }
  let spec_class = ClassValue::{ name: "ModuleSpec", bases: [], dict: [], }
  let spec_name = if is_package_main {
    module_name + ".__main__"
  } else {
    module_name
  }
  let submodule_search_locations = if is_package_main {
    let pkg_dir = @path.Path(origin).dirname().to_string()
    Value::List([Value::Str(pkg_dir)])
  } else {
    Value::None
  }
  let spec_value = Value::Instance(InstanceValue::{
    class: spec_class,
    dict: [
      ("hashvalue", Value::Int(fresh_object_hashvalue())),
      ("name", Value::Str(spec_name)),
      ("origin", Value::Str(origin)),
      ("loader", loader),
      ("parent", Value::Str(parent)),
      ("submodule_search_locations", submodule_search_locations),
      ("cached", Value::None),
      ("loader_state", Value::None),
      ("_uninitialized_submodules", Value::List([])),
      ("has_location", Value::Bool(true)),
    ],
  })
  set_global_value(self.globals, "__loader__", loader)
  set_global_value(self.globals, "__spec__", spec_value)
  // Make `__loader__.get_source(__spec__.name)` work for the `__main__` module by
  // aliasing it in `sys.modules` under the real module name.
  match module_cache_get("__main__") {
    Some(main_mod) => module_cache_set(spec_name, main_mod)
    None => ()
  }
}