Skip to content
Draft
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
11 changes: 10 additions & 1 deletion crates/monty/src/builtins/chr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,16 @@ pub fn builtin_chr(vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -
Value::Int(n) => {
if *n < 0 || *n > 0x0010_FFFF {
Err(SimpleException::new_msg(ExcType::ValueError, "chr() arg not in range(0x110000)").into())
} else if let Some(c) = char::from_u32(u32::try_from(*n).expect("chr() range check failed")) {
} else if let Some(c) = char::from_u32(
#[expect(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
reason = "range of n already validated in the first if condition"
)]
{
*n as u32
},
) {
Ok(allocate_char(c, vm.heap)?)
} else {
// This shouldn't happen for valid Unicode range, but handle it
Expand Down
14 changes: 5 additions & 9 deletions crates/monty/src/builtins/pow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,16 @@ pub fn builtin_pow(vm: &mut VM<'_, '_, impl ResourceTracker>, args: ArgValues) -
(Value::Int(b), Value::Int(e), Value::Int(m_val)) => {
if *m_val == 0 {
Err(SimpleException::new_msg(ExcType::ValueError, "pow() 3rd argument cannot be 0").into())
} else if *e < 0 {
} else if let Ok(e) = u64::try_from(*e) {
// Use modular exponentiation
Ok(Value::Int(mod_pow(*b, e, *m_val)))
} else {
debug_assert!(*e < 0, "i64 -> u64 succeeds for all non-negative values");
Err(SimpleException::new_msg(
ExcType::ValueError,
"pow() 2nd argument cannot be negative when 3rd argument specified",
)
.into())
} else {
// Use modular exponentiation
let result = mod_pow(
*b,
u64::try_from(*e).expect("pow exponent >= 0 but failed u64 conversion"),
*m_val,
);
Ok(Value::Int(result))
}
}
_ => Err(SimpleException::new_msg(
Expand Down
8 changes: 8 additions & 0 deletions crates/monty/src/bytecode/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ impl CodeBuilder {
// pops obj + args, pushes result: 1 - (1 + arg_count) = -arg_count
self.adjust_stack(-i16::from(operand2));
}
Opcode::CallAttrExtended => {
// Pops obj + args_tuple (+ kwargs_dict if flag set), pushes result.
// Flag=0 (no kwargs): pops 2, pushes 1 -> -1. Flag=1 (kwargs): pops 3, pushes 1 -> -2.
self.adjust_stack(-1 - i16::from(operand2 & 1));
}
_ => {
if let Some(effect) = op.stack_effect() {
self.adjust_stack(effect);
Expand Down Expand Up @@ -528,6 +533,9 @@ impl CodeBuilder {
let effect: i16 = match op {
// CallFunction pops (callable + args), pushes result: -(1 + arg_count) + 1 = -arg_count
Opcode::CallFunction => -i16::from(operand),
// CallFunctionExtended pops callable + args_tuple (+ kwargs_dict if flag set), pushes result.
// Flag=0 (no kwargs): pops 2, pushes 1 -> -1. Flag=1 (kwargs): pops 3, pushes 1 -> -2.
Opcode::CallFunctionExtended => -1 - i16::from(operand & 1),
// UnpackSequence pops 1, pushes n: n - 1
Opcode::UnpackSequence => i16::from(operand) - 1,
// ListAppend/SetAdd pop value: -1 (depth operand doesn't affect stack count)
Expand Down
24 changes: 18 additions & 6 deletions crates/monty/src/bytecode/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ use crate::{
AssignTarget, Callable, CmpOperator, Comprehension, DictItem, Expr, ExprLoc, Identifier, Literal, NameScope,
Node, Operator, PreparedFunctionDef, PreparedNode, SequenceItem, UnpackTarget,
},
fstring::{ConversionFlag, FStringPart, FormatSpec, ParsedFormatSpec, encode_format_spec},
fstring::{
ConversionFlag, FStringPart, FormatSpec, MAX_ENCODED_PRECISION, MAX_ENCODED_WIDTH, ParsedFormatSpec,
encode_format_spec,
},
function::Function,
intern::{Interns, StringId},
modules::StandardLib,
Expand Down Expand Up @@ -2744,7 +2747,7 @@ impl<'a> Compiler<'a> {
// Static format spec - push a marker constant with the parsed spec info
// We store this as a special format spec value in the constant pool
// The VM will recognize this and use the pre-parsed spec
let const_idx = self.add_format_spec_const(parsed);
let const_idx = self.add_format_spec_const(parsed)?;
self.code.emit_u16(Opcode::LoadConst, const_idx);
Ok(conv_bits | 0x04) // has format spec on stack
}
Expand All @@ -2764,14 +2767,23 @@ impl<'a> Compiler<'a> {
/// Adds a format spec to the constant pool as an encoded integer.
///
/// Uses the encoding from `fstring::encode_format_spec` and stores it as
/// a negative integer to distinguish from regular ints.
fn add_format_spec_const(&mut self, spec: &ParsedFormatSpec) -> u16 {
let encoded = encode_format_spec(spec);
/// a negative integer to distinguish from regular ints. Returns a
/// `CompileError` if the width or precision is larger than the compact
/// encoding can represent — far beyond any realistic format spec.
fn add_format_spec_const(&mut self, spec: &ParsedFormatSpec) -> Result<u16, CompileError> {
let encoded = encode_format_spec(spec).ok_or_else(|| {
CompileError::new(
format!(
"format specifier width or precision exceeds supported limits (max width {MAX_ENCODED_WIDTH}, max precision {MAX_ENCODED_PRECISION})"
),
CodeRange::default(),
)
})?;
// Use negative to distinguish from regular ints (format spec marker)
// We negate and subtract 1 to ensure it's negative and recoverable
let encoded_i64 = i64::try_from(encoded).expect("format spec encoding exceeds i64::MAX");
let marker = -(encoded_i64 + 1);
self.code.add_const(Value::Int(marker))
Ok(self.code.add_const(Value::Int(marker)))
}

// ========================================================================
Expand Down
5 changes: 2 additions & 3 deletions crates/monty/src/bytecode/vm/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,8 @@ impl<T: ResourceTracker> VM<'_, '_, T> {
let target_stack_depth = frame.stack_base + frame.locals_count as usize + entry.stack_depth() as usize;

// Unwind stack to target depth (drop excess values)
while this.stack.len() > target_stack_depth {
let value = this.stack.pop().unwrap();
value.drop_with_heap(this);
for value in this.stack.drain(target_stack_depth..).rev() {
value.drop_with_heap(this.heap);
}

// Push exception value onto stack (handler expects it)
Expand Down
Loading