///|
/// Minimal module objects + module loading for `import`.
///|
let sys_modules_ref : Ref[Array[(Value, Value)]] = { val: [], }
///|
fn module_cache_get(name : String) -> Value? {
for pair in sys_modules_ref.val {
match pair.0 {
Value::Str(text) => if text == name { return Some(pair.1) }
_ => ()
}
}
None
}
///|
fn module_cache_set(name : String, module_value : Value) -> Unit {
for j = 0; j < sys_modules_ref.val.length(); j = j + 1 {
match sys_modules_ref.val[j].0 {
Value::Str(text) =>
if text == name {
sys_modules_ref.val[j] = (Value::Str(name), module_value)
return
}
_ => ()
}
}
sys_modules_ref.val.push((Value::Str(name), module_value))
}
///|
fn module_cache_delete(name : String) -> Unit {
let mut j = sys_modules_ref.val.length()
while j > 0 {
j = j - 1
match sys_modules_ref.val[j].0 {
Value::Str(text) =>
if text == name {
let _ = sys_modules_ref.val.remove(j)
}
_ => ()
}
}
}
///|
fn module_name_parts(name : String) -> Array[String] {
let out : Array[String] = []
for part in name.split(".") {
let text = part.to_owned()
if text.length() > 0 {
out.push(text)
}
}
out
}
///|
fn module_top_name(name : String) -> String {
let parts = module_name_parts(name)
if parts.length() == 0 {
name
} else {
parts[0]
}
}
///|
fn module_rel_path(name : String) -> String {
let parts = module_name_parts(name)
if parts.length() == 0 {
name
} else {
let mut joined = parts[0]
for i = 1; i < parts.length(); i = i + 1 {
joined = joined + "/" + parts[i]
}
joined
}
}
///|
fn module_join_parts(parts : Array[String], count : Int32) -> String {
if count <= 0 || parts.length() == 0 {
"".to_string()
} else {
let mut joined = parts[0]
let mut i = 1
while i < count && i < parts.length() {
joined = joined + "." + parts[i]
i += 1
}
joined
}
}
///|
fn module_parent_name(name : String) -> String {
let parts = module_name_parts(name)
if parts.length() <= 1 {
"".to_string()
} else {
module_join_parts(parts, parts.length() - 1)
}
}
///|
fn resolve_relative_module_name(
module_name : String,
globals : Array[(String, Value)],
) -> Result[String, RuntimeError] {
if module_name.length() > 0 && !module_name.has_prefix(".") {
return Ok(module_name)
}
let mut level = 1
let mut rest = "".to_string()
if module_name.length() > 0 {
let chars = module_name.to_array()
level = 0
while level < chars.length() && chars[level] == '.' {
level += 1
}
rest = substring(module_name, level, module_name.length())
}
let package_name = match get_named_value(globals, "__package__") {
Some(Value::Str(name)) => name
_ => "".to_string()
}
if package_name.length() == 0 {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: attempted relative import with no known parent package",
),
)
}
let parts = module_name_parts(package_name)
let base_len = parts.length() - (level - 1)
if base_len <= 0 {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: attempted relative import beyond top-level package",
),
)
}
let base = module_join_parts(parts, base_len)
if rest.length() == 0 {
Ok(base)
} else {
Ok(base + "." + rest)
}
}
///|
fn set_module_attr(module_value : Value, name : String, value : Value) -> Unit {
match module_value {
Value::Instance(inst) => set_named_value(inst.dict, name, value)
_ => ()
}
}
///|
fn ensure_module_entry(name : String) -> Value {
match module_cache_get(name) {
Some(value) => value
None => {
let module_value = make_module_instance(name, [])
module_cache_set(name, module_value)
module_value
}
}
}
///|
fn register_module_hierarchy(
module_name : String,
module_value : Value,
) -> Unit {
let parts = module_name_parts(module_name)
if parts.length() == 0 {
return
}
module_cache_set(module_name, module_value)
if parts.length() == 1 {
return
}
let mut prefix = parts[0]
let mut parent = ensure_module_entry(prefix)
for i = 1; i < parts.length(); i = i + 1 {
let part = parts[i]
let current_name = prefix + "." + part
let current = if i == parts.length() - 1 {
module_value
} else {
ensure_module_entry(current_name)
}
set_module_attr(parent, part, current)
parent = current
prefix = current_name
}
}
///|
fn module_function_stub(name : String) -> Value {
Value::Function(FunctionValue::{
name,
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
})
}
///|
fn make_importlib_loader() -> Value {
// Loader used by the stdlib `importlib` machinery. Methods are intrinsics
// implemented in `call_callable_with_env`.
let loader_class = ClassValue::{
name: "_MpythonLoader",
bases: [],
dict: [
(
"create_module",
Value::Function(FunctionValue::{
name: "__mpython_create_module",
params: ["self", "spec"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"exec_module",
Value::Function(FunctionValue::{
name: "__mpython_exec_module",
params: ["self", "module"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"get_source",
Value::Function(FunctionValue::{
name: "__mpython_get_source",
params: ["self", "fullname"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
Value::Instance(InstanceValue::{
class: loader_class,
// `importlib.machinery.SourceFileLoader` exposes `.path`. Keep an attribute
// with the same name on our shim loader so stdlib tests can introspect it.
dict: [("path", Value::None)],
})
}
///|
fn make_importlib_finder() -> Value {
// A minimal `sys.meta_path` finder to make CPython's pure-Python importlib
// usable. The actual search logic lives in the intrinsic implementation.
let finder_class = ClassValue::{
name: "_MpythonPathFinder",
bases: [],
dict: [
(
"find_spec",
Value::Function(FunctionValue::{
name: "__mpython_find_spec",
params: ["self", "fullname", "path", "target"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"invalidate_caches",
Value::Function(FunctionValue::{
name: "__mpython_invalidate_caches",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
Value::Instance(InstanceValue::{ class: finder_class, dict: [], })
}
///|
fn make_module_instance(name : String, dict : Array[(String, Value)]) -> Value {
let module_class = ClassValue::{ name: "module", bases: [], dict: [], }
let spec_class = ClassValue::{ name: "ModuleSpec", bases: [], dict: [], }
// For builtin/host-provided modules we use a tiny placeholder loader so
// `importlib.machinery.BuiltinImporter.find_spec(name)` implies a non-None
// `module.__loader__` in the stdlib tests.
let loader_class = ClassValue::{
name: "BuiltinImporter",
bases: [],
dict: [],
}
let loader_value = Value::Instance(InstanceValue::{
class: loader_class,
dict: [("hashvalue", Value::Int(fresh_object_hashvalue()))],
})
let spec_value = Value::Instance(InstanceValue::{
class: spec_class,
dict: [
("hashvalue", Value::Int(fresh_object_hashvalue())),
("name", Value::Str(name)),
("origin", Value::Str("")),
("loader", loader_value),
("parent", Value::Str("")),
("submodule_search_locations", Value::None),
("cached", Value::None),
("loader_state", Value::None),
("_uninitialized_submodules", Value::List([])),
("has_location", Value::Bool(false)),
],
})
Value::Instance(InstanceValue::{
class: module_class,
// Keep a minimum set of attributes expected by CPython's importlib and
// stdlib test harness. These are intentionally shallow shims.
dict: [
("hashvalue", Value::Int(fresh_object_hashvalue())),
("__name__", Value::Str(name)),
("__package__", Value::Str("")),
("__loader__", loader_value),
("__spec__", spec_value),
] +
dict,
})
}
///|
fn make_math_module() -> Value {
make_module_instance("math", [
("pi", Value::Float(3.141592653589793)),
("e", Value::Float(2.718281828459045)),
("tau", Value::Float(6.283185307179586)),
("copysign", module_function_stub("math.copysign")),
("sqrt", module_function_stub("math.sqrt")),
("pow", module_function_stub("math.pow")),
("log", module_function_stub("math.log")),
("log2", module_function_stub("math.log2")),
("exp", module_function_stub("math.exp")),
("ceil", module_function_stub("math.ceil")),
("floor", module_function_stub("math.floor")),
("trunc", module_function_stub("math.trunc")),
("modf", module_function_stub("math.modf")),
("frexp", module_function_stub("math.frexp")),
("ldexp", module_function_stub("math.ldexp")),
("acos", module_function_stub("math.acos")),
("cos", module_function_stub("math.cos")),
("sin", module_function_stub("math.sin")),
("hypot", module_function_stub("math.hypot")),
("fabs", module_function_stub("math.fabs")),
("erf", module_function_stub("math.erf")),
("lgamma", module_function_stub("math.lgamma")),
("fsum", module_function_stub("math.fsum")),
("sumprod", module_function_stub("math.sumprod")),
("isfinite", module_function_stub("math.isfinite")),
("isnan", module_function_stub("math.isnan")),
("isinf", module_function_stub("math.isinf")),
("isclose", module_function_stub("math.isclose")),
("gcd", module_function_stub("math.gcd")),
("isqrt", module_function_stub("math.isqrt")),
])
}
///|
fn make_time_module(builtins : Array[(String, Value)]) -> Value {
let tuple_class = builtin_class_from_name("tuple", builtins)
make_module_instance("time", [
("time", module_function_stub("time.time")),
("ctime", module_function_stub("time.ctime")),
("monotonic", module_function_stub("time.monotonic")),
("perf_counter", module_function_stub("time.perf_counter")),
("perf_counter_ns", module_function_stub("time.perf_counter_ns")),
("process_time", module_function_stub("time.process_time")),
("process_time_ns", module_function_stub("time.process_time_ns")),
("get_clock_info", module_function_stub("time.get_clock_info")),
("localtime", module_function_stub("time.localtime")),
("gmtime", module_function_stub("time.gmtime")),
("sleep", module_function_stub("time.sleep")),
("strftime", module_function_stub("time.strftime")),
// Minimal compatibility: our `time.localtime()` returns a plain tuple.
// Expose `struct_time` as an alias to `tuple` so stdlib code can reference it.
("struct_time", Value::Class(tuple_class)),
])
}
///|
fn make_platform_module() -> Value {
// Minimal `platform` shim. The real CPython `platform` module relies on
// tuple subclassing (namedtuple) which moonpython does not fully support yet.
make_module_instance("platform", [
(
"system",
Value::Function(FunctionValue::{
name: "platform.system",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"machine",
Value::Function(FunctionValue::{
name: "platform.machine",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
])
}
///|
fn make_enum_module(builtins : Array[(String, Value)]) -> Value {
// Minimal enum shim: enough for stdlib modules like `calendar` and basic
// `IntEnum`/`IntFlag` usage. This is *not* a full implementation of
// CPython's enum module.
let object_class = builtin_class_from_name("object", builtins)
let int_class = builtin_class_from_name("int", builtins)
let str_class = builtin_class_from_name("str", builtins)
let enum_class = ClassValue::{
name: "Enum",
bases: [Value::Class(object_class)],
dict: [],
}
let int_enum_class = ClassValue::{
name: "IntEnum",
bases: [Value::Class(int_class), Value::Class(enum_class)],
dict: [],
}
let str_enum_class = ClassValue::{
name: "StrEnum",
bases: [Value::Class(str_class), Value::Class(enum_class)],
dict: [],
}
let int_flag_class = ClassValue::{
name: "IntFlag",
bases: [Value::Class(int_class), Value::Class(enum_class)],
dict: [],
}
let decorator_class = ClassValue::{
name: "_SimpleEnumDecorator",
bases: [],
dict: [
(
"__call__",
Value::Function(FunctionValue::{
name: "enum._simple_enum_decorator_call",
params: ["self", "cls"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
make_module_instance("enum", [
("Enum", Value::Class(enum_class)),
("IntEnum", Value::Class(int_enum_class)),
("StrEnum", Value::Class(str_enum_class)),
("IntFlag", Value::Class(int_flag_class)),
("auto", module_function_stub("enum.auto")),
("global_enum", module_function_stub("enum.global_enum")),
("_simple_enum", module_function_stub("enum._simple_enum")),
("_SimpleEnumDecorator", Value::Class(decorator_class)),
])
}
///|
fn make_faulthandler_module() -> Value {
// CPython exposes faulthandler as a C extension; for moonpython we provide a
// minimal no-op shim so the stdlib test harness can import and call it.
make_module_instance("faulthandler", [
("enable", module_function_stub("faulthandler.enable")),
("disable", module_function_stub("faulthandler.disable")),
("register", module_function_stub("faulthandler.register")),
("unregister", module_function_stub("faulthandler.unregister")),
("dump_traceback", module_function_stub("faulthandler.dump_traceback")),
(
"dump_traceback_later",
module_function_stub("faulthandler.dump_traceback_later"),
),
(
"cancel_dump_traceback_later",
module_function_stub("faulthandler.cancel_dump_traceback_later"),
),
("is_enabled", module_function_stub("faulthandler.is_enabled")),
])
}
///|
fn make_atexit_module() -> Value {
// Minimal `atexit` shim: used by stdlib modules like `logging` to register
// shutdown hooks. We don't execute hooks yet, but `register()` must exist.
make_module_instance("atexit", [
("register", module_function_stub("atexit.register")),
])
}
///|
let socket_socket_class_ref : Ref[ClassValue?] = { val: None, }
///|
fn socket_socket_class(builtins : Array[(String, Value)]) -> ClassValue {
match socket_socket_class_ref.val {
Some(klass) => return klass
None => ()
}
let object_class = builtin_class_from_name("object", builtins)
let dict : Array[(String, Value)] = [
(
"__init__",
Value::Function(FunctionValue::{
name: "socket.socket.__init__",
params: ["self", "*args", "**kwargs"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"connect",
Value::Function(FunctionValue::{
name: "socket.socket.connect",
params: ["self", "address"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"close",
Value::Function(FunctionValue::{
name: "socket.socket.close",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"send",
Value::Function(FunctionValue::{
name: "socket.socket.send",
params: ["self", "data"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"sendall",
Value::Function(FunctionValue::{
name: "socket.socket.sendall",
params: ["self", "data"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"sendto",
Value::Function(FunctionValue::{
name: "socket.socket.sendto",
params: ["self", "data", "address"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"recv",
Value::Function(FunctionValue::{
name: "socket.socket.recv",
params: ["self", "bufsize"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"settimeout",
Value::Function(FunctionValue::{
name: "socket.socket.settimeout",
params: ["self", "timeout"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
]
let klass = ClassValue::{
name: "socket",
bases: [Value::Class(object_class)],
dict,
}
socket_socket_class_ref.val = Some(klass)
klass
}
///|
fn make_socket_module(builtins : Array[(String, Value)]) -> Value {
// Minimal `socket` shim: enough for importing `logging.handlers` and running
// non-networking stdlib tests. This does not implement real networking.
make_module_instance("socket", [
("AF_UNSPEC", Value::Int(0N)),
("AF_UNIX", Value::Int(1N)),
("AF_INET", Value::Int(2N)),
("AF_INET6", Value::Int(10N)),
("AI_PASSIVE", Value::Int(1N)),
("SOCK_STREAM", Value::Int(1N)),
("SOCK_DGRAM", Value::Int(2N)),
("has_ipv6", Value::Bool(false)),
("socket", Value::Class(socket_socket_class(builtins))),
("create_connection", module_function_stub("socket.create_connection")),
("getaddrinfo", module_function_stub("socket.getaddrinfo")),
])
}
///|
fn make_select_module() -> Value {
make_module_instance("select", [
("select", module_function_stub("select.select")),
])
}
///|
fn make_struct_module(builtins : Array[(String, Value)]) -> Value {
let exc_base = match get_named_value(builtins, "Exception") {
Some(Value::Class(c)) => Value::Class(c)
_ => Value::None
}
let struct_error_class = ClassValue::{
name: "error",
bases: if exc_base is Value::Class(_) {
[exc_base]
} else {
[]
},
dict: [],
}
let struct_class = ClassValue::{
name: "Struct",
bases: [],
dict: [
(
"__init__",
Value::Function(FunctionValue::{
name: "_struct.Struct.__init__",
params: ["self", "format"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"pack",
Value::Function(FunctionValue::{
name: "_struct.Struct.pack",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"unpack",
Value::Function(FunctionValue::{
name: "_struct.Struct.unpack",
params: ["self", "buffer"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"calcsize",
Value::Function(FunctionValue::{
name: "_struct.Struct.calcsize",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
make_module_instance("_struct", [
("calcsize", module_function_stub("_struct.calcsize")),
("pack", module_function_stub("_struct.pack")),
("pack_into", module_function_stub("_struct.pack_into")),
("unpack", module_function_stub("_struct.unpack")),
("unpack_from", module_function_stub("_struct.unpack_from")),
("iter_unpack", module_function_stub("_struct.iter_unpack")),
("Struct", Value::Class(struct_class)),
("error", Value::Class(struct_error_class)),
("_clearcache", module_function_stub("_struct._clearcache")),
("__doc__", Value::Str("Minimal _struct shim for moonpython")),
])
}
///|
fn make_gc_module() -> Value {
// CPython exposes `gc` as a C extension. For moonpython we provide a minimal
// shim that is sufficient for importing/running parts of the stdlib tests.
make_module_instance("gc", [
// Used by regrtest and some tests. We never populate it (no real GC), but
// it must exist and be mutable.
("garbage", Value::List([])),
("callbacks", Value::List([])),
("enable", module_function_stub("gc.enable")),
("disable", module_function_stub("gc.disable")),
("isenabled", module_function_stub("gc.isenabled")),
("collect", module_function_stub("gc.collect")),
("get_threshold", module_function_stub("gc.get_threshold")),
("set_threshold", module_function_stub("gc.set_threshold")),
("get_count", module_function_stub("gc.get_count")),
])
}
///|
fn make_binascii_module(builtins : Array[(String, Value)]) -> Value {
// CPython exposes `binascii` as a C extension. moonpython doesn't support C
// extensions, so provide a small pure-runtime subset used by the stdlib.
let exc_base : Array[Value] = []
match get_named_value(builtins, "ValueError") {
Some(Value::Class(exc)) => exc_base.push(Value::Class(exc))
_ => ()
}
if exc_base.length() == 0 {
match get_named_value(builtins, "Exception") {
Some(Value::Class(exc)) => exc_base.push(Value::Class(exc))
_ => ()
}
}
let error_class = ClassValue::{ name: "Error", bases: exc_base, dict: [], }
let incomplete_class = ClassValue::{
name: "Incomplete",
bases: [Value::Class(error_class)],
dict: [],
}
make_module_instance("binascii", [
("Error", Value::Class(error_class)),
("Incomplete", Value::Class(incomplete_class)),
("a2b_base64", module_function_stub("binascii.a2b_base64")),
("b2a_base64", module_function_stub("binascii.b2a_base64")),
("a2b_qp", module_function_stub("binascii.a2b_qp")),
("b2a_qp", module_function_stub("binascii.b2a_qp")),
("a2b_uu", module_function_stub("binascii.a2b_uu")),
("b2a_uu", module_function_stub("binascii.b2a_uu")),
("hexlify", module_function_stub("binascii.hexlify")),
("unhexlify", module_function_stub("binascii.unhexlify")),
("a2b_hex", module_function_stub("binascii.a2b_hex")),
("b2a_hex", module_function_stub("binascii.b2a_hex")),
("crc32", module_function_stub("binascii.crc32")),
("crc_hqx", module_function_stub("binascii.crc_hqx")),
("__doc__", Value::Str("Minimal binascii shim for moonpython")),
])
}
///|
fn make_asyncio_module() -> Value {
make_module_instance("asyncio", [
("run", module_function_stub("asyncio.run")),
("gather", module_function_stub("asyncio.gather")),
(
"iscoroutinefunction",
Value::Function(FunctionValue::{
name: "asyncio.iscoroutinefunction",
params: ["func"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
])
}
///|
let rlock_class_ref : Ref[ClassValue?] = { val: None, }
///|
fn thread_rlock_class(builtins : Array[(String, Value)]) -> ClassValue {
match rlock_class_ref.val {
Some(value) => return value
None => ()
}
let bases : Array[Value] = []
match get_named_value(builtins, "object") {
Some(Value::Class(object_class)) => bases.push(Value::Class(object_class))
_ => ()
}
let dict : Array[(String, Value)] = [
(
"__enter__",
Value::Function(FunctionValue::{
name: "_thread.RLock.__enter__",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"__exit__",
Value::Function(FunctionValue::{
name: "_thread.RLock.__exit__",
params: ["self", "exc_type", "exc", "tb"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"acquire",
Value::Function(FunctionValue::{
name: "_thread.RLock.acquire",
params: ["self", "blocking", "timeout"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"release",
Value::Function(FunctionValue::{
name: "_thread.RLock.release",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"locked",
Value::Function(FunctionValue::{
name: "_thread.RLock.locked",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"_at_fork_reinit",
Value::Function(FunctionValue::{
name: "_thread.RLock._at_fork_reinit",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
]
let klass = ClassValue::{ name: "RLock", bases, dict, }
rlock_class_ref.val = Some(klass)
klass
}
///|
fn make_thread_module(builtins : Array[(String, Value)]) -> Value {
let rlock_class = thread_rlock_class(builtins)
let exc_bases : Array[Value] = []
match get_named_value(builtins, "Exception") {
Some(Value::Class(exception_class)) =>
exc_bases.push(Value::Class(exception_class))
_ => ()
}
let error_class = ClassValue::{ name: "error", bases: exc_bases, dict: [], }
make_module_instance("_thread", [
("get_ident", module_function_stub("_thread.get_ident")),
("allocate_lock", module_function_stub("_thread.allocate_lock")),
("start_new_thread", module_function_stub("_thread.start_new_thread")),
("daemon_threads_allowed", Value::Bool(true)),
("_set_sentinel", module_function_stub("_thread._set_sentinel")),
("_count", module_function_stub("_thread._count")),
("stack_size", module_function_stub("_thread.stack_size")),
("TIMEOUT_MAX", Value::Float(9223372036.0)),
("error", Value::Class(error_class)),
("RLock", Value::Class(rlock_class)),
])
}
///|
fn warnings_function_stub(name : String) -> Value {
Value::Function(FunctionValue::{
name,
params: ["*args", "**kwargs"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
})
}
///|
fn make_warnings_module() -> Value {
make_module_instance("_warnings", [
("filters", Value::List([])),
("_defaultaction", Value::Str("default")),
("_onceregistry", Value::Dict([])),
("_filters_mutated", warnings_function_stub("_warnings._filters_mutated")),
("warn", warnings_function_stub("_warnings.warn")),
("warn_explicit", warnings_function_stub("_warnings.warn_explicit")),
])
}
///|
fn make_string_cmodule() -> Value {
make_module_instance("_string", [
("formatter_parser", module_function_stub("_string.formatter_parser")),
(
"formatter_field_name_split",
module_function_stub("_string.formatter_field_name_split"),
),
])
}
///|
fn make_itertools_chain_factory() -> Value {
let dict : Array[(String, Value)] = [
("__call__", module_function_stub("itertools.chain")),
("from_iterable", module_function_stub("itertools.chain.from_iterable")),
]
let klass = ClassValue::{ name: "chain_factory", bases: [], dict, }
Value::Instance(InstanceValue::{ class: klass, dict: [], })
}
///|
fn make_itertools_module() -> Value {
make_module_instance("itertools", [
("chain", make_itertools_chain_factory()),
("accumulate", module_function_stub("itertools.accumulate")),
("filterfalse", module_function_stub("itertools.filterfalse")),
("groupby", module_function_stub("itertools.groupby")),
("count", module_function_stub("itertools.count")),
("cycle", module_function_stub("itertools.cycle")),
("repeat", module_function_stub("itertools.repeat")),
("starmap", module_function_stub("itertools.starmap")),
("islice", module_function_stub("itertools.islice")),
("permutations", module_function_stub("itertools.permutations")),
("combinations", module_function_stub("itertools.combinations")),
("product", module_function_stub("itertools.product")),
])
}
///|
fn make_pdb_module(builtins : Array[(String, Value)]) -> Value {
// Minimal debugger shim. CPython's `pdb` pulls in a large dependency chain
// (inspect/tokenize/io) that isn't available yet in moonpython. For the
// standard library tests we only need `pdb.set_trace` to exist and be
// patchable by unittest.mock.
let bases : Array[Value] = []
match get_named_value(builtins, "object") {
Some(Value::Class(object_class)) => bases.push(Value::Class(object_class))
_ => ()
}
let pdb_class = ClassValue::{ name: "Pdb", bases, dict: [], }
make_module_instance("pdb", [
("set_trace", module_function_stub("pdb.set_trace")),
("Pdb", Value::Class(pdb_class)),
])
}
///|
fn make_weakref_module(builtins : Array[(String, Value)]) -> Value {
let bases : Array[Value] = []
match get_named_value(builtins, "object") {
Some(Value::Class(object_class)) => bases.push(Value::Class(object_class))
_ => ()
}
let weakref_dict : Array[(String, Value)] = [
("__new__", module_function_stub("_weakref.ref")),
("__hash__", module_function_stub("weakref.__hash__")),
]
let reference_type_class = ClassValue::{
name: "weakref",
bases,
dict: weakref_dict,
}
let proxy_type_class = ClassValue::{ name: "ProxyType", bases, dict: [], }
let callable_proxy_type_class = ClassValue::{
name: "CallableProxyType",
bases,
dict: [],
}
make_module_instance("_weakref", [
("ref", Value::Class(reference_type_class)),
("proxy", module_function_stub("_weakref.proxy")),
("getweakrefcount", module_function_stub("_weakref.getweakrefcount")),
("getweakrefs", module_function_stub("_weakref.getweakrefs")),
(
"_remove_dead_weakref",
module_function_stub("_weakref._remove_dead_weakref"),
),
("ReferenceType", Value::Class(reference_type_class)),
("ProxyType", Value::Class(proxy_type_class)),
("CallableProxyType", Value::Class(callable_proxy_type_class)),
])
}
///|
fn make_builtins_module(builtins : Array[(String, Value)]) -> Value {
let dict : Array[(String, Value)] = []
for pair in builtins {
dict.push(pair)
}
make_module_instance("builtins", dict)
}
///|
fn make_sys_module(builtins : Array[(String, Value)], config : Config) -> Value {
let _ = builtins
let builtin_module_names = Value::Tuple([
Value::Str("builtins"),
Value::Str("sys"),
Value::Str("posix"),
Value::Str("_struct"),
Value::Str("gc"),
Value::Str("binascii"),
Value::Str("faulthandler"),
Value::Str("select"),
Value::Str("_thread"),
Value::Str("_warnings"),
Value::Str("_weakref"),
Value::Str("_string"),
Value::Str("itertools"),
Value::Str("math"),
Value::Str("time"),
Value::Str("asyncio"),
])
let version_info = Value::Tuple([
Value::Int(3N),
Value::Int(12N),
Value::Int(0N),
Value::Str("final"),
Value::Int(0N),
])
let impl_class = ClassValue::{ name: "implementation", bases: [], dict: [], }
let impl_value = Value::Instance(InstanceValue::{
class: impl_class,
dict: [
("name", Value::Str("moonpython")),
("version", version_info),
// CPython's importlib uses this to form __pycache__ filenames.
("cache_tag", Value::Str("cpython-312")),
],
})
let float_info_class = ClassValue::{
name: "float_info",
bases: [],
dict: [],
}
let float_info_value = Value::Instance(InstanceValue::{
class: float_info_class,
dict: [
("max", Value::Float(1.7976931348623157e308)),
("max_exp", Value::Int(1024N)),
("max_10_exp", Value::Int(308N)),
("min", Value::Float(2.2250738585072014e-308)),
("min_exp", Value::Int(-1021N)),
("min_10_exp", Value::Int(-307N)),
("digits", Value::Int(15N)),
("mant_dig", Value::Int(53N)),
("epsilon", Value::Float(2.220446049250313e-16)),
("radix", Value::Int(2N)),
("rounds", Value::Int(1N)),
],
})
let hash_info_class = ClassValue::{ name: "hash_info", bases: [], dict: [], }
let hash_info_value = Value::Instance(InstanceValue::{
class: hash_info_class,
dict: [
("width", Value::Int(64N)),
("modulus", Value::Int(2305843009213693951N)),
("inf", Value::Int(314159N)),
("nan", Value::Int(0N)),
("imag", Value::Int(1000003N)),
("algorithm", Value::Str("siphash13")),
("hash_bits", Value::Int(64N)),
("seed_bits", Value::Int(128N)),
("cutoff", Value::Int(0N)),
],
})
let flags_class = ClassValue::{ name: "flags", bases: [], dict: [], }
let flags_value = Value::Instance(InstanceValue::{
class: flags_class,
dict: [
("debug", Value::Int(0N)),
("inspect", Value::Int(0N)),
("interactive", Value::Int(0N)),
("optimize", Value::Int(0N)),
("dont_write_bytecode", Value::Int(0N)),
("no_user_site", Value::Int(0N)),
("no_site", Value::Int(0N)),
("ignore_environment", Value::Int(0N)),
("verbose", Value::Int(0N)),
("bytes_warning", Value::Int(0N)),
("quiet", Value::Int(0N)),
("hash_randomization", Value::Int(0N)),
("isolated", Value::Int(0N)),
("dev_mode", Value::Int(0N)),
("utf8_mode", Value::Int(0N)),
("warn_default_encoding", Value::Int(0N)),
],
})
let stdin_class = ClassValue::{
name: "stdin",
bases: [],
dict: [
(
"readline",
Value::Function(FunctionValue::{
name: "sys.stdin.readline",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"isatty",
Value::Function(FunctionValue::{
name: "sys.stdin.isatty",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
let stdout_class = ClassValue::{
name: "stdout",
bases: [],
dict: [
(
"write",
Value::Function(FunctionValue::{
name: "sys.stdout.write",
params: ["self", "text"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"flush",
Value::Function(FunctionValue::{
name: "sys.stdout.flush",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"reconfigure",
Value::Function(FunctionValue::{
name: "sys.stdout.reconfigure",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"isatty",
Value::Function(FunctionValue::{
name: "sys.stdout.isatty",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
let stderr_class = ClassValue::{
name: "stderr",
bases: [],
dict: [
(
"write",
Value::Function(FunctionValue::{
name: "sys.stderr.write",
params: ["self", "text"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"flush",
Value::Function(FunctionValue::{
name: "sys.stderr.flush",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"reconfigure",
Value::Function(FunctionValue::{
name: "sys.stderr.reconfigure",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"isatty",
Value::Function(FunctionValue::{
name: "sys.stderr.isatty",
params: ["self"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
let stdin_value = Value::Instance(InstanceValue::{
class: stdin_class,
dict: [
("hashvalue", Value::Int(fresh_object_hashvalue())),
("encoding", Value::Str("utf-8")),
("errors", Value::Str("strict")),
("newline", Value::None),
],
})
let stdout_value = Value::Instance(InstanceValue::{
class: stdout_class,
dict: [
("hashvalue", Value::Int(fresh_object_hashvalue())),
("encoding", Value::Str("utf-8")),
("errors", Value::Str("strict")),
("newline", Value::None),
],
})
let stderr_value = Value::Instance(InstanceValue::{
class: stderr_class,
dict: [
("hashvalue", Value::Int(fresh_object_hashvalue())),
("encoding", Value::Str("utf-8")),
("errors", Value::Str("strict")),
("newline", Value::None),
],
})
let breakpointhook_value = module_function_stub("sys.__breakpointhook__")
let prefix_value = Value::Str("")
// `sys.argv` and `sys.path` are heavily relied upon by the CPython stdlib
// (e.g. `unittest` assumes `argv[0]` exists).
let argv_values : Array[Value] = []
for arg in config.argv {
argv_values.push(Value::Str(arg))
}
let path_values : Array[Value] = []
let mut script_dir = "".to_string()
match config.main_path {
Some(main_path) => {
let base = @path.Path(main_path).dirname().to_string()
let base_abs = resolve_path_from_initial_cwd(base)
if base_abs.length() > 0 && base_abs != "." {
script_dir = base_abs
}
}
None => ()
}
path_values.push(Value::Str(script_dir))
if script_dir.length() > 0 {
path_values.push(Value::Str(""))
}
for root in config.import_paths {
path_values.push(Value::Str(resolve_path_from_initial_cwd(root)))
}
make_module_instance("sys", [
("maxsize", Value::Int(9223372036854775807N)),
("version", Value::Str("3.12.0 (#0, Jan 01 1970, 00:00:00) [moonpython]")),
("version_info", version_info),
("byteorder", Value::Str("little")),
// Use a CPython-recognized platform tag so the stdlib can take appropriate
// feature-gating branches (no subprocess/fork on WASI).
("platform", Value::Str("wasi")),
("platlibdir", Value::Str("lib")),
("abiflags", Value::Str("")),
("executable", Value::Str("moonpython")),
("implementation", impl_value),
("float_info", float_info_value),
("hash_info", hash_info_value),
("flags", flags_value),
("dont_write_bytecode", Value::Bool(false)),
("prefix", prefix_value),
("base_prefix", prefix_value),
("exec_prefix", prefix_value),
("base_exec_prefix", prefix_value),
("builtin_module_names", builtin_module_names),
// Keep this minimal; used by traceback.py for NameError import suggestions.
("stdlib_module_names", Value::Set([Value::Str("io"), Value::Str("_io")])),
("pycache_prefix", Value::None),
("modules", Value::Dict(sys_modules_ref.val)),
("path", Value::List(path_values)),
("path_hooks", Value::List([])),
("path_importer_cache", Value::Dict([])),
("meta_path", Value::List([make_importlib_finder()])),
("argv", Value::List(argv_values)),
("warnoptions", Value::List([])),
("stdout", stdout_value),
("stderr", stderr_value),
("stdin", stdin_value),
("__stdout__", stdout_value),
("__stderr__", stderr_value),
("__stdin__", stdin_value),
("__breakpointhook__", breakpointhook_value),
("breakpointhook", breakpointhook_value),
(
"unraisablehook",
Value::Function(FunctionValue::{
name: "sys.unraisablehook",
params: ["unraisable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"__unraisablehook__",
Value::Function(FunctionValue::{
name: "sys.unraisablehook",
params: ["unraisable"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
("audit", module_function_stub("sys.audit")),
(
"exit",
Value::Function(FunctionValue::{
name: "sys.exit",
params: ["status"],
defaults: [Value::None],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"exc_info",
Value::Function(FunctionValue::{
name: "sys.exc_info",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"getrefcount",
Value::Function(FunctionValue::{
name: "sys.getrefcount",
params: ["object"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"_getframe",
Value::Function(FunctionValue::{
name: "sys._getframe",
params: ["depth"],
defaults: [Value::Int(0N)],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"exception",
Value::Function(FunctionValue::{
name: "sys.exception",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"intern",
Value::Function(FunctionValue::{
name: "sys.intern",
params: ["s"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"getdefaultencoding",
Value::Function(FunctionValue::{
name: "sys.getdefaultencoding",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"getfilesystemencoding",
Value::Function(FunctionValue::{
name: "sys.getfilesystemencoding",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"getfilesystemencodeerrors",
Value::Function(FunctionValue::{
name: "sys.getfilesystemencodeerrors",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"getrecursionlimit",
Value::Function(FunctionValue::{
name: "sys.getrecursionlimit",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"gettrace",
Value::Function(FunctionValue::{
name: "sys.gettrace",
params: [],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"setrecursionlimit",
Value::Function(FunctionValue::{
name: "sys.setrecursionlimit",
params: ["limit"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"settrace",
Value::Function(FunctionValue::{
name: "sys.settrace",
params: ["func"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"excepthook",
Value::Function(FunctionValue::{
name: "sys.excepthook",
params: ["exctype", "value", "traceback"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
(
"__excepthook__",
Value::Function(FunctionValue::{
name: "sys.excepthook",
params: ["exctype", "value", "traceback"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
])
}
///|
fn make_sysconfigdata_module(name : String) -> Value {
// CPython generates this at build-time; we provide a minimal stub so the
// vendored stdlib (sysconfig + test.support) can import and proceed.
make_module_instance(name, [("build_time_vars", Value::Dict([]))])
}
///|
fn make_python_module(
name : String,
source : String,
builtins : Array[(String, Value)],
io : MockIO,
config : Config,
package_name~ : String = "",
filename~ : String = "",
is_package~ : Bool = false,
package_path~ : String = "",
) -> Result[Value, RuntimeError] {
let filename_abs = resolve_path_from_initial_cwd(filename)
let package_path_abs = if is_package {
resolve_path_from_initial_cwd(package_path)
} else {
package_path
}
let program = match parse(source) {
Ok(v) => v
Err(err) => {
// Include filename for stdlib/import syntax errors to make debugging and
// conformance triage practical.
let base = make_parse_runtime_error(err)
let message = filename_abs + ": " + base.message
return Err(RuntimeError::{
kind: base.kind,
message,
span: base.span,
traceback: base.traceback,
traceback_envs: base.traceback_envs,
exc_type: base.exc_type,
exc_args: [message],
exc_value: base.exc_value,
exc_cause: base.exc_cause,
exc_context: base.exc_context,
exc_suppress_context: base.exc_suppress_context,
})
}
}
fn unwrap_stmt(node : Stmt) -> Stmt {
match node {
Stmt::WithSpan(span~, stmt~) => {
let _ = span
unwrap_stmt(stmt)
}
other => other
}
}
let doc_value = if program.body.length() > 0 {
match unwrap_stmt(program.body[0]) {
Stmt::ExprStmt(Expr::Literal(Literal::Str(text))) => Value::Str(text)
_ => Value::None
}
} else {
Value::None
}
let spec_class = ClassValue::{ name: "ModuleSpec", bases: [], dict: [], }
// A tiny placeholder loader object so CPython's importlib bootstrap doesn't
// treat file-backed modules as namespace packages (loader=None), which would
// currently raise NotImplementedError during `_init_module_attrs`.
let loader_class = ClassValue::{
name: "SourceFileLoader",
bases: [],
dict: [
(
"get_source",
Value::Function(FunctionValue::{
name: "__mpython_get_source",
params: ["self", "fullname"],
defaults: [],
body: [],
is_generator: false,
is_async: false,
closure: [],
}),
),
],
}
let loader_value = Value::Instance(InstanceValue::{
class: loader_class,
dict: [("name", Value::Str(name)), ("path", Value::Str(filename_abs))],
})
let submodule_locations = if is_package {
Value::List([Value::Str(package_path_abs)])
} else {
Value::None
}
let spec_value = Value::Instance(InstanceValue::{
class: spec_class,
dict: [
("name", Value::Str(name)),
("origin", Value::Str(filename_abs)),
("loader", loader_value),
("parent", Value::Str(package_name)),
("submodule_search_locations", submodule_locations),
("cached", Value::None),
("loader_state", Value::None),
("_uninitialized_submodules", Value::List([])),
("has_location", Value::Bool(true)),
],
})
let module_globals : Array[(String, Value)] = [
("hashvalue", Value::Int(fresh_object_hashvalue())),
("__name__", Value::Str(name)),
("__package__", Value::Str(package_name)),
("__file__", Value::Str(filename_abs)),
("__loader__", loader_value),
("__spec__", spec_value),
("__doc__", doc_value),
]
let module_class = ClassValue::{ name: "module", bases: [], dict: [], }
let module_value = Value::Instance(InstanceValue::{
class: module_class,
dict: module_globals,
})
module_cache_set(name, module_value)
// Module-level execution must not inherit the caller's closure/scope stacks
// (e.g. `import x` from inside a function should not make module code see
// that function's locals as "closure" candidates).
let saved_closure_env_stack = closure_env_stack.val
let saved_scope_decls_stack = scope_decls_stack.val
closure_env_stack.val = []
scope_decls_stack.val = []
let _ = match ensure_recursion_available() {
Ok(_) => ()
Err(err) => return Err(err)
}
push_traceback_frame("".to_string(), filename)
set_traceback_frame_env(module_globals, module_globals)
let _ = match
eval_block(
program.body,
module_globals,
module_globals,
builtins,
io,
config,
) {
Ok(_) => ()
Err(err) => {
pop_traceback_frame()
closure_env_stack.val = saved_closure_env_stack
scope_decls_stack.val = saved_scope_decls_stack
// Imports that raise during module execution should not leave a partially
// initialized module in the cache. This prevents follow-up imports from
// silently "succeeding" with missing attributes (e.g. `ssl` when `_ssl`
// is absent).
module_cache_delete(name)
return Err(err)
}
}
pop_traceback_frame()
closure_env_stack.val = saved_closure_env_stack
scope_decls_stack.val = saved_scope_decls_stack
Ok(module_value)
}
///|
fn candidate_module_paths(
module_name : String,
globals : Array[(String, Value)],
config : Config,
) -> Array[String] {
fn sys_path_roots() -> Array[String] {
let roots : Array[String] = []
match module_cache_get("sys") {
Some(Value::Instance(inst)) =>
match get_named_value(inst.dict, "path") {
Some(Value::List(items)) =>
for item in items {
match item {
Value::Str(root) => roots.push(root)
_ => ()
}
}
_ => ()
}
_ => ()
}
roots
}
let rel = module_rel_path(module_name) + ".py"
let paths : Array[String] = []
match config.main_path {
Some(main_path) => {
// Use x/path.dirname to get containing directory.
let base = @path.Path(main_path).dirname().to_string()
if base.length() > 0 {
paths.push(@path.Path(base).join(@path.Path(rel)).to_string())
}
}
None => ()
}
// Current working directory (honors `os.chdir()` via runtime workdir tracking).
let cwd = resolve_path_from_cwd(rel)
paths.push(cwd)
// Additional import roots.
for root in config.import_paths {
paths.push(@path.Path(root).join(@path.Path(rel)).to_string())
}
// `sys.path` is mutable at runtime (e.g. tempfile-based tests). Honor it for
// filesystem imports when available.
for root in sys_path_roots() {
paths.push(@path.Path(root).join(@path.Path(rel)).to_string())
}
// Avoid unused warnings.
let _ = globals
paths
}
///|
fn candidate_package_paths(
module_name : String,
globals : Array[(String, Value)],
config : Config,
) -> Array[String] {
fn sys_path_roots() -> Array[String] {
let roots : Array[String] = []
match module_cache_get("sys") {
Some(Value::Instance(inst)) =>
match get_named_value(inst.dict, "path") {
Some(Value::List(items)) =>
for item in items {
match item {
Value::Str(root) => roots.push(root)
_ => ()
}
}
_ => ()
}
_ => ()
}
roots
}
let rel = module_rel_path(module_name)
let paths : Array[String] = []
match config.main_path {
Some(main_path) => {
let base = @path.Path(main_path).dirname().to_string()
if base.length() > 0 {
paths.push(@path.Path(base).join(@path.Path(rel)).to_string())
}
}
None => ()
}
paths.push(resolve_path_from_cwd(rel))
for root in config.import_paths {
paths.push(@path.Path(root).join(@path.Path(rel)).to_string())
}
for root in sys_path_roots() {
paths.push(@path.Path(root).join(@path.Path(rel)).to_string())
}
let _ = globals
paths
}
///|
fn load_module_from_filesystem(
module_name : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
config : Config,
) -> Result[Value, RuntimeError] {
let paths = candidate_module_paths(module_name, globals, config)
for path in paths {
if @fs.path_exists(path) {
let source = @fs.read_file_to_string(path) catch {
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: cannot read file: " + path,
),
)
}
return make_python_module(
module_name,
source,
builtins,
io,
config,
package_name=module_parent_name(module_name),
filename=path,
)
}
}
let package_paths = candidate_package_paths(module_name, globals, config)
for path in package_paths {
let init_path = @path.Path(path).join(@path.Path("__init__.py")).to_string()
if @fs.path_exists(init_path) {
let source = @fs.read_file_to_string(init_path) catch {
_ =>
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: cannot read file: " + init_path,
),
)
}
let module_value = make_python_module(
module_name,
source,
builtins,
io,
config,
package_name=module_name,
filename=init_path,
is_package=true,
package_path=path,
)
match module_value {
Ok(Value::Instance(inst)) =>
// Mark as a package so stdlib importlib can resolve submodules.
set_named_value(
inst.dict,
"__path__",
Value::List([Value::Str(resolve_path_from_initial_cwd(path))]),
)
_ => ()
}
return module_value
}
if @fs.path_exists(path) {
let module_value = make_module_instance(module_name, [])
match module_value {
Value::Instance(inst) =>
set_named_value(
inst.dict,
"__path__",
Value::List([Value::Str(resolve_path_from_initial_cwd(path))]),
)
_ => ()
}
return Ok(module_value)
}
}
Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ModuleNotFoundError: No module named '" + module_name + "'",
),
)
}
///|
fn import_module(
module_name : String,
globals : Array[(String, Value)],
builtins : Array[(String, Value)],
io : MockIO,
config : Config,
) -> Result[Value, RuntimeError] {
if module_name.length() == 0 {
if !config.allow_filesystem_imports {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: module is not allowed",
),
)
}
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: cannot import empty module name",
),
)
}
if !config.allow_filesystem_imports &&
module_name != "builtins" &&
module_name != "math" {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: module " + module_name + " is not allowed",
),
)
}
match module_cache_get(module_name) {
Some(v) => {
register_module_hierarchy(module_name, v)
return Ok(v)
}
None => ()
}
if module_name.contains(".") {
let parent = module_parent_name(module_name)
if parent.length() > 0 {
let _ = match import_module(parent, globals, builtins, io, config) {
Ok(v) => v
Err(err) => return Err(err)
}
}
match module_cache_get(module_name) {
Some(v) => {
register_module_hierarchy(module_name, v)
return Ok(v)
}
None => ()
}
}
let module_value = if module_name == "builtins" {
make_builtins_module(builtins)
} else if module_name == "sys" {
make_sys_module(builtins, config)
} else if module_name == "platform" {
make_platform_module()
} else if module_name.has_prefix("_sysconfigdata_") {
make_sysconfigdata_module(module_name)
} else if module_name == "_struct" {
make_struct_module(builtins)
} else if module_name == "gc" {
make_gc_module()
} else if module_name == "binascii" {
make_binascii_module(builtins)
} else if module_name == "faulthandler" {
make_faulthandler_module()
} else if module_name == "atexit" {
make_atexit_module()
} else if module_name == "socket" {
make_socket_module(builtins)
} else if module_name == "select" {
make_select_module()
} else if module_name == "_thread" {
make_thread_module(builtins)
} else if module_name == "_warnings" {
make_warnings_module()
} else if module_name == "_string" {
make_string_cmodule()
} else if module_name == "_weakref" {
make_weakref_module(builtins)
} else if module_name == "itertools" {
make_itertools_module()
} else if module_name == "pdb" {
make_pdb_module(builtins)
} else if module_name == "math" {
make_math_module()
} else if module_name == "time" {
make_time_module(builtins)
} else if module_name == "enum" {
make_enum_module(builtins)
} else if module_name == "asyncio" {
// Intentionally prefer a tiny builtin shim over CPython's `asyncio`.
//
// CPython's `asyncio` pulls in a large dependency graph (typing, sockets,
// selectors, threading) that moonpython does not currently aim to emulate.
// The shim is enough for parts of the stdlib (e.g. `unittest.mock`) that
// only need a few helpers like `iscoroutinefunction`.
make_asyncio_module()
} else {
if !config.allow_filesystem_imports {
return Err(
make_runtime_error(
RuntimeErrorKind::Runtime,
"ImportError: module " + module_name + " is not allowed",
),
)
}
match
load_module_from_filesystem(module_name, globals, builtins, io, config) {
Ok(v) => v
Err(err) => return Err(err)
}
}
register_module_hierarchy(module_name, module_value)
Ok(module_value)
}