Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/monty-js/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ pub fn monty_to_js<'e>(obj: &MontyObject, env: &'e Env) -> Result<JsMontyObject<
// Function objects are internal to the name lookup protocol and should not normally
// appear as final output values. If they do, represent as a string with the function name.
MontyObject::Function { name, .. } => env.create_string(name)?.into_unknown(env)?,
MontyObject::FileData { path, mode, .. } => {
let repr = format!("<_io.TextIOWrapper name='{path}' mode='{mode}'>");
env.create_string(&repr)?.into_unknown(env)?
}
};
Ok(JsMontyObject(unknown))
}
Expand Down
7 changes: 7 additions & 0 deletions crates/monty-python/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ pub fn monty_to_py(py: Python<'_>, obj: &MontyObject, dc_registry: &DcRegistry)
// Function objects are internal to the name lookup protocol and should not normally
// appear as final output values. If they do, represent as a string with the function name.
MontyObject::Function { name, .. } => Ok(PyString::new(py, name).into_any().unbind()),
// FileData is input-only (from host to VM) and should not appear as output.
MontyObject::FileData { path, mode, .. } => Ok(PyString::new(
py,
&format!("<_io.TextIOWrapper name='{path}' mode='{mode}'>"),
)
.into_any()
.unbind()),
}
}

Expand Down
29 changes: 23 additions & 6 deletions crates/monty/src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ mod map;
mod min_max; // min and max share implementation
mod next;
mod oct;
mod open;
mod ord;
mod pow;
mod print;
Expand All @@ -38,7 +39,7 @@ use strum::{Display, EnumString, FromRepr, IntoStaticStr};

use crate::{
args::ArgValues,
bytecode::VM,
bytecode::{CallResult, VM},
exception_private::{ExcType, RunResult},
resource::ResourceTracker,
types::Type,
Expand All @@ -61,11 +62,14 @@ pub(crate) enum Builtins {

impl Builtins {
/// Calls this builtin with the given arguments.
pub fn call(self, vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<Value> {
///
/// Returns `CallResult` to support builtins like `open()` that need to
/// yield `OsCall` to the host rather than returning a value directly.
pub fn call(self, vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<CallResult> {
match self {
Self::Function(b) => b.call(vm, args),
Self::ExcType(exc) => exc.call(vm, args),
Self::Type(t) => t.call(vm, args),
Self::ExcType(exc) => exc.call(vm, args).map(CallResult::Value),
Self::Type(t) => t.call(vm, args).map(CallResult::Value),
}
}

Expand Down Expand Up @@ -181,7 +185,7 @@ pub enum BuiltinsFunctions {
Next,
// object - handled by Type enum
Oct,
// Open,
Open,
Ord,
Pow,
Print,
Expand Down Expand Up @@ -210,7 +214,19 @@ impl BuiltinsFunctions {
///
/// All builtins receive the full VM context, which provides access to the heap,
/// interned strings, and print output.
pub(crate) fn call(self, vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<Value> {
///
/// Returns `CallResult` to support builtins like `open()` that need to yield
/// `OsCall` to the host rather than returning a value directly.
pub(crate) fn call(self, vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<CallResult> {
match self {
Self::Open => open::builtin_open(vm, args),
// All other builtins return a Value directly
other => other.call_value(vm, args).map(CallResult::Value),
}
}

/// Executes builtins that always return a `Value` directly.
fn call_value(self, vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<Value> {
match self {
Self::Abs => abs::builtin_abs(vm, args),
Self::All => all::builtin_all(vm, args),
Expand Down Expand Up @@ -241,6 +257,7 @@ impl BuiltinsFunctions {
Self::Sum => sum::builtin_sum(vm, args),
Self::Type => type_::builtin_type(vm, args),
Self::Zip => zip::builtin_zip(vm, args),
Self::Open => unreachable!("Open is handled in call()"),
}
}
}
76 changes: 76 additions & 0 deletions crates/monty/src/builtins/open.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Implementation of the `open()` builtin function.
//!
//! `open()` yields an `OsCall(FileOpen)` to the host, which reads or prepares the file
//! and returns `MontyObject::FileData`. The VM then creates a `FileObject` on the heap.

use crate::{
ResourceTracker,
args::ArgValues,
bytecode::{CallResult, VM},
exception_private::{ExcType, RunResult, SimpleException},
heap::HeapData,
os::OsFunction,
types::str::allocate_string,
value::Value,
};

/// Implementation of the `open()` builtin function.
///
/// Accepts `open(path)` or `open(path, mode)`. The mode defaults to `'r'` (read).
/// Yields `OsCall(FileOpen)` with path and mode arguments for the host to resolve.
pub fn builtin_open(vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -> RunResult<CallResult> {
let (path_value, mode_value) = args.get_one_two_args("open", vm.heap)?;

// Extract strings to owned copies before dropping values
let path_str = extract_str(&path_value, vm, "open", "file")?.to_owned();
let mode_str = if let Some(ref mode_val) = mode_value {
extract_str(mode_val, vm, "open", "mode")?.to_owned()
} else {
"r".to_owned()
};

// Clean up the original arguments
path_value.drop_with_heap(vm);
if let Some(m) = mode_value {
m.drop_with_heap(vm);
}

// Validate mode
if mode_str != "r" && mode_str != "w" {
return Err(SimpleException::new_msg(ExcType::ValueError, format!("invalid mode: '{mode_str}'")).into());
}

// Build the OsCall arguments: path string and mode string as Values
let os_path = allocate_string(path_str, vm.heap)?;
let os_mode = allocate_string(mode_str, vm.heap)?;

Ok(CallResult::OsCall(
OsFunction::FileOpen,
ArgValues::Two(os_path, os_mode),
))
}

/// Extracts a `&str` from a `Value`, raising `TypeError` if it's not a string.
fn extract_str<'a>(
value: &'a Value,
vm: &'a VM<'_, '_, impl ResourceTracker>,
func_name: &str,
arg_name: &str,
) -> RunResult<&'a str> {
match value {
Value::InternString(id) => Ok(vm.interns.get_str(*id)),
Value::Ref(id) => match vm.heap.get(*id) {
HeapData::Str(s) => Ok(s.as_str()),
_ => Err(SimpleException::new_msg(
ExcType::TypeError,
format!("{func_name}() {arg_name} argument must be str"),
)
.into()),
},
_ => Err(SimpleException::new_msg(
ExcType::TypeError,
format!("{func_name}() {arg_name} argument must be str"),
)
.into()),
}
}
Loading
Loading