use super::cast::{c_int, c_long, d2ui, imod, my_nint, nint};
use super::cvt;
use super::error::CalcError;
use super::opcodes::{CoreOp, Opcode, StringOp};
use super::random::local_random;
use super::value::{SCALC_STRING_SIZE, ScalcString, StackValue};
use super::{CompiledExpr, StringInputs};
pub fn eval(expr: &CompiledExpr, inputs: &mut StringInputs) -> Result<StackValue, CalcError> {
if expr.is_empty() {
return Err(CalcError::EmptyProgram);
}
expr.check_until_ceiling()?;
let mut stack: Vec<StackValue> = Vec::with_capacity(20);
let code = &expr.code;
let mut pc = 0;
let mut until_marks: Vec<(usize, usize)> = Vec::new();
let mut loops_done: i32 = 0;
let string_path = expr.uses_string;
while pc < code.len() {
let op = &code[pc];
pc += 1;
match op {
Opcode::Core(core) => match core {
CoreOp::End => break,
CoreOp::PushConst(v) => stack.push(StackValue::Double(*v)),
CoreOp::PushVar(idx) => {
let v = inputs.num_arg(*idx as usize).unwrap_or(0.0);
stack.push(StackValue::Double(v));
}
CoreOp::PushDoubleVar(idx) => {
let s = inputs.str_arg(*idx as usize).cloned().unwrap_or_default();
stack.push(StackValue::Str(s));
}
CoreOp::Pi => stack.push(StackValue::Double(std::f64::consts::PI)),
CoreOp::D2R => {
stack.push(StackValue::Double(std::f64::consts::PI / 180.0));
}
CoreOp::R2D => {
stack.push(StackValue::Double(180.0 / std::f64::consts::PI));
}
CoreOp::S2R => {
stack.push(StackValue::Double(std::f64::consts::PI / (180.0 * 3600.0)));
}
CoreOp::R2S => {
stack.push(StackValue::Double((180.0 * 3600.0) / std::f64::consts::PI));
}
CoreOp::Random => {
stack.push(StackValue::Double(local_random()));
}
CoreOp::NormalRandom => {
let u1 = local_random();
let u2 = local_random();
let n = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
stack.push(StackValue::Double(n));
}
CoreOp::FetchVal => {
stack.push(StackValue::Double(inputs.prev_val));
}
CoreOp::FetchSval => {
stack.push(StackValue::Str(inputs.prev_sval.clone()));
}
CoreOp::Add => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
stack.push(match Pair::of(a, b) {
Pair::Numeric(x, y) => StackValue::Double(x + y),
Pair::Strings(x, y) => {
StackValue::str([x.as_bytes(), y.as_bytes()].concat())
}
});
}
CoreOp::Sub => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
stack.push(match Pair::of(a, b) {
Pair::Numeric(x, y) => StackValue::Double(x - y),
Pair::Strings(x, y) => {
let mut out = x.into_bytes();
if let Some(pos) = find_sub(&out, y.as_bytes()) {
out.drain(pos..pos + y.len());
}
StackValue::str(out)
}
});
}
CoreOp::Mul => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double(a * b));
}
CoreOp::Div => {
let (a, b) = pop2_f64(&mut stack)?;
if b == 0.0 {
return Err(CalcError::DivisionByZero);
}
stack.push(StackValue::Double(a / b));
}
CoreOp::Mod => {
let (a, b) = pop2_f64(&mut stack)?;
match imod(a, b, c_long) {
Some(r) => stack.push(StackValue::Double(r)),
None => return Err(CalcError::DivisionByZero),
}
}
CoreOp::Neg => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(-a));
}
CoreOp::Power => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double(a.powf(b)));
}
CoreOp::Eq => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
let result = match Pair::of(a, b) {
Pair::Numeric(x, y) => (x - y).abs() < SMALL,
Pair::Strings(x, y) => x == y,
};
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::Ne => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
let result = match Pair::of(a, b) {
Pair::Numeric(x, y) => (x - y).abs() > SMALL,
Pair::Strings(x, y) => x != y,
};
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::Lt => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
let result = match Pair::of(a, b) {
Pair::Numeric(x, y) => (y - x) > SMALL,
Pair::Strings(x, y) => x < y,
};
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::Le => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
let result = match Pair::of(a, b) {
Pair::Numeric(x, y) => (x - y).abs() < SMALL || x < y,
Pair::Strings(x, y) => x <= y,
};
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::Gt => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
let result = match Pair::of(a, b) {
Pair::Numeric(x, y) => (x - y) > SMALL,
Pair::Strings(x, y) => x > y,
};
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::Ge => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
let result = match Pair::of(a, b) {
Pair::Numeric(x, y) => (x - y).abs() < SMALL || x > y,
Pair::Strings(x, y) => x >= y,
};
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::And => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double(if a != 0.0 && b != 0.0 {
1.0
} else {
0.0
}));
}
CoreOp::Or => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double(if a != 0.0 || b != 0.0 {
1.0
} else {
0.0
}));
}
CoreOp::Not => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(if a == 0.0 { 1.0 } else { 0.0 }));
}
CoreOp::BitAnd => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double((c_long(a) & c_long(b)) as f64));
}
CoreOp::BitOr => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double((c_long(a) | c_long(b)) as f64));
}
CoreOp::BitXor => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double((c_long(a) ^ c_long(b)) as f64));
}
CoreOp::BitNot => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(if string_path {
!c_int(a) as f64
} else {
!c_long(a) as f64
}));
}
CoreOp::Shl | CoreOp::Shr => {
let right = core == &CoreOp::Shr;
let count = pop1(&mut stack)?.to_double();
let subject = pop1(&mut stack)?;
stack.push(match subject {
StackValue::Str(s) if string_path => {
StackValue::str(shift_chars(s.as_bytes(), count, right))
}
v => {
let a = v.to_double();
let out = if string_path {
let (x, n) = (c_int(a), c_int(count) as u32);
if right {
x.wrapping_shr(n) as f64
} else {
x.wrapping_shl(n) as f64
}
} else {
let (x, n) = (c_long(a), c_long(count) as u32);
if right {
x.wrapping_shr(n) as f64
} else {
x.wrapping_shl(n) as f64
}
};
StackValue::Double(out)
}
});
}
CoreOp::ShrLogical => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double((d2ui(a) >> (d2ui(b) & 31)) as f64));
}
CoreOp::CondIf => {
let cond = pop1_f64(&mut stack)?;
if cond == 0.0 {
pc = cond_search(code, pc, true)?;
}
}
CoreOp::CondElse => {
pc = cond_search(code, pc, false)?;
}
CoreOp::CondEnd => {}
CoreOp::Abs => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(super::abs_val(a)));
}
CoreOp::Sqrt => {
let a = pop1_f64(&mut stack)?;
if a < 0.0 {
return Err(CalcError::DomainError);
}
stack.push(StackValue::Double(a.sqrt()));
}
CoreOp::Exp => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.exp()));
}
CoreOp::Log10 => {
let a = pop1_f64(&mut stack)?;
if a < 0.0 {
return Err(CalcError::DomainError);
}
stack.push(StackValue::Double(a.log10()));
}
CoreOp::LogE => {
let a = pop1_f64(&mut stack)?;
if a < 0.0 {
return Err(CalcError::DomainError);
}
stack.push(StackValue::Double(a.ln()));
}
CoreOp::Sin => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.sin()));
}
CoreOp::Cos => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.cos()));
}
CoreOp::Tan => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.tan()));
}
CoreOp::Asin => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.asin()));
}
CoreOp::Acos => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.acos()));
}
CoreOp::Atan => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.atan()));
}
CoreOp::Sinh => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.sinh()));
}
CoreOp::Cosh => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.cosh()));
}
CoreOp::Tanh => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.tanh()));
}
CoreOp::Ceil => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.ceil()));
}
CoreOp::Floor => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(a.floor()));
}
CoreOp::Nint => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(nint(a, c_long)));
}
CoreOp::IsNan(nargs) => {
let n = *nargs as usize;
if stack.len() < n {
return Err(CalcError::Underflow);
}
let mut result = false;
for _ in 0..n {
let v = pop1_f64(&mut stack)?;
result = result || v.is_nan();
}
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::IsInf => {
let a = pop1_f64(&mut stack)?;
stack.push(StackValue::Double(super::isinf(a)));
}
CoreOp::Finite(nargs) => {
let n = *nargs as usize;
if stack.len() < n {
return Err(CalcError::Underflow);
}
let mut result = true;
for _ in 0..n {
let v = pop1_f64(&mut stack)?;
result = result && v.is_finite();
}
stack.push(StackValue::Double(if result { 1.0 } else { 0.0 }));
}
CoreOp::Atan2 => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double(b.atan2(a)));
}
CoreOp::Fmod => {
let (a, b) = pop2_f64(&mut stack)?;
stack.push(StackValue::Double(a % b));
}
CoreOp::Max(nargs) => {
let args = pop_n(&mut stack, *nargs as usize)?;
stack.push(Extremum::Max.fold(Operands::of(args)));
}
CoreOp::Min(nargs) => {
let args = pop_n(&mut stack, *nargs as usize)?;
stack.push(Extremum::Min.fold(Operands::of(args)));
}
CoreOp::MaxVal | CoreOp::MinVal => {
let which = match core {
CoreOp::MaxVal => Extremum::Max,
_ => Extremum::Min,
};
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
stack.push(match Pair::of(a, b) {
Pair::Numeric(a, b) => StackValue::Double(which.pick(a, b)),
Pair::Strings(a, b) => StackValue::Str(which.pick(a, b)),
});
}
CoreOp::StoreVar(idx) => {
let v = pop1_f64(&mut stack)?;
if let Some(slot) = inputs.num_arg_mut(*idx as usize) {
*slot = v;
}
}
CoreOp::StoreDoubleVar(idx) => {
let v = pop1(&mut stack)?;
if let Some(slot) = inputs.str_arg_mut(*idx as usize) {
*slot = v.into_string_value();
}
}
},
Opcode::String(StringOp::SubLast | StringOp::ToDouble | StringOp::Byte)
if !string_path => {}
Opcode::String(sop) => match sop {
StringOp::PushString(s) => {
stack.push(StackValue::str(s));
}
StringOp::ToString => {
let v = pop1(&mut stack)?;
stack.push(StackValue::Str(v.into_string_value()));
}
StringOp::ToDouble => {
let v = pop1(&mut stack)?;
stack.push(StackValue::Double(match &v {
StackValue::Double(d) => *d,
StackValue::Str(s) => hunt_double(s.as_bytes()),
}));
}
StringOp::Len => {
let v = pop1(&mut stack)?;
stack.push(StackValue::Double(v.into_string_value().len() as f64));
}
StringOp::Byte => {
let v = pop1(&mut stack)?;
stack.push(StackValue::Double(match &v {
StackValue::Double(d) => *d,
StackValue::Str(s) => s.as_bytes().first().map_or(0.0, |b| *b as i8 as f64),
}));
}
StringOp::TrEsc => {
let v = pop1(&mut stack)?;
stack.push(match v {
StackValue::Double(d) => StackValue::Double(d),
StackValue::Str(s) => StackValue::str_ncpy(raw_from_escaped(s.as_bytes())),
});
}
StringOp::Esc => {
let v = pop1(&mut stack)?;
stack.push(match v {
StackValue::Double(d) => StackValue::Double(d),
StackValue::Str(s) => StackValue::str_ncpy(escaped_from_raw(s.as_bytes())),
});
}
StringOp::Printf => {
let val = pop1(&mut stack)?;
let fmt = pop1(&mut stack)?;
let result = simple_printf(fmt.as_bytes()?, &val)?;
stack.push(StackValue::str_ncpy(result));
}
StringOp::Sscanf => {
let fmt = pop1(&mut stack)?;
let input = pop1(&mut stack)?;
let result = super::scanf::sscanf(input.as_bytes()?, fmt.as_bytes()?)?;
stack.push(result);
}
StringOp::BinRead => {
let fmt = pop1(&mut stack)?;
let subject = pop1(&mut stack)?;
let value = bin_read(subject.as_bytes()?, fmt.as_bytes()?)?;
stack.push(StackValue::Double(value));
}
StringOp::BinWrite => {
let val = pop1(&mut stack)?;
let fmt = pop1(&mut stack)?;
let result = bin_write(fmt.as_bytes()?, &val)?;
stack.push(StackValue::str_ncpy(result));
}
StringOp::Crc16 => {
let v = pop1(&mut stack)?;
stack.push(checksum_op(v, crc16_escaped, Combine::Replace));
}
StringOp::Crc16Append => {
let v = pop1(&mut stack)?;
stack.push(checksum_op(v, crc16_escaped, Combine::Append));
}
StringOp::Lrc => {
let v = pop1(&mut stack)?;
stack.push(checksum_op(v, super::checksum::lrc, Combine::Replace));
}
StringOp::LrcAppend => {
let v = pop1(&mut stack)?;
stack.push(checksum_op(v, super::checksum::lrc, Combine::AsciiFrame));
}
StringOp::Xor8 => {
let v = pop1(&mut stack)?;
stack.push(checksum_op(v, xor8_escaped, Combine::Replace));
}
StringOp::Xor8Append => {
let v = pop1(&mut stack)?;
stack.push(checksum_op(v, xor8_escaped, Combine::Append));
}
StringOp::Subrange => {
let end_val = pop1(&mut stack)?;
let start_val = pop1(&mut stack)?;
let subject = pop1(&mut stack)?.into_string_value();
let s = subject.as_bytes();
let k = s.len() as i64;
let (i, j) = subrange_bounds(s, &start_val, &end_val);
let out = if j < i {
&[][..]
} else {
&s[i as usize..(j + 1).min(k) as usize]
};
stack.push(StackValue::str(out));
}
StringOp::Replace => {
let replace = pop1(&mut stack)?.into_string_value();
let find = pop1(&mut stack)?.into_string_value();
let subject = pop1(&mut stack)?.into_string_value();
let s = subject.as_bytes();
let mut result = s.to_vec();
if let Some(pos) = find_sub(s, find.as_bytes()) {
result.splice(pos..pos + find.len(), replace.as_bytes().iter().copied());
}
stack.push(StackValue::str(result));
}
StringOp::SubLast => {
let b = pop1(&mut stack)?;
let a = pop1(&mut stack)?;
stack.push(match Pair::of(a, b) {
Pair::Numeric(x, y) => StackValue::Double(x - y),
Pair::Strings(x, y) => {
let mut out = x.into_bytes();
if let Some(pos) = rfind_sub(&out, y.as_bytes()) {
out.drain(pos..pos + y.len());
}
StackValue::str(out)
}
});
}
StringOp::DynFetch => {
let idx = my_nint(pop1(&mut stack)?.to_double());
let v = c_int_to_index(idx)
.and_then(|i| inputs.num_arg(i))
.unwrap_or(0.0);
stack.push(StackValue::Double(v));
}
StringOp::DynSFetch => {
let idx = my_nint(pop1(&mut stack)?.to_double());
let s = c_int_to_index(idx)
.and_then(|i| inputs.str_arg(i))
.cloned()
.unwrap_or_default();
stack.push(StackValue::Str(s));
}
StringOp::DynStore => {
let value = pop1(&mut stack)?.to_double();
let idx = my_nint(pop1(&mut stack)?.to_double());
if let Some(slot) = c_int_to_index(idx).and_then(|i| inputs.num_arg_mut(i)) {
*slot = value;
}
}
StringOp::DynSStore => {
let value = pop1(&mut stack)?.into_string_value();
let idx = my_nint(pop1(&mut stack)?.to_double());
if let Some(slot) = c_int_to_index(idx).and_then(|i| inputs.str_arg_mut(i)) {
*slot = value;
}
}
},
Opcode::Control(ctrl) => match ctrl {
super::opcodes::ControlOp::Until(_end_pc) => {
let until_pc = pc - 1;
match until_marks.iter_mut().find(|(k, _)| *k == until_pc) {
Some((_, depth)) => *depth = stack.len(),
None => until_marks.push((until_pc, stack.len())),
}
}
super::opcodes::ControlOp::UntilEnd(start_pc) => {
loops_done += 1;
if loops_done > scalc_loop_max() {
continue;
}
let cond = match stack.last() {
Some(v) => v.to_double(),
None => return Err(CalcError::Underflow),
};
if cond == 0.0 {
let Some((_, depth)) = until_marks.iter().find(|(k, _)| k == start_pc)
else {
return Err(CalcError::Internal);
};
stack.truncate(*depth);
pc = *start_pc + 1;
}
}
},
#[allow(unreachable_patterns)]
_ => return Err(CalcError::Internal),
}
}
let result = match <[StackValue; 1]>::try_from(stack) {
Ok([result]) => result,
Err(_) => return Err(CalcError::StackLeak),
};
Ok(result)
}
#[derive(Clone, Copy, PartialEq)]
enum Combine {
Replace,
Append,
AsciiFrame,
}
fn checksum_op(
v: StackValue,
digest: impl FnOnce(&[u8]) -> Option<String>,
combine: Combine,
) -> StackValue {
let StackValue::Str(s) = v else {
return v; };
let Some(text) = digest(s.as_bytes()) else {
return StackValue::Str(s); };
match combine {
Combine::Replace => StackValue::str_ncpy(text),
Combine::Append => StackValue::str([s.as_bytes(), text.as_bytes()].concat()),
Combine::AsciiFrame => StackValue::str([b":", s.as_bytes(), text.as_bytes()].concat()),
}
}
fn crc16_escaped(operand: &[u8]) -> Option<String> {
let raw = raw_from_escaped(operand);
if raw.is_empty() {
return None;
}
let crc = super::checksum::crc16(&raw);
Some(format!("\\x{:02x}\\x{:02x}", crc & 0xff, (crc >> 8) & 0xff))
}
fn xor8_escaped(operand: &[u8]) -> Option<String> {
let raw = raw_from_escaped(operand);
if raw.is_empty() {
return None;
}
Some(format!("\\x{:02x}", super::checksum::xor8(&raw)))
}
fn hunt_double(s: &[u8]) -> f64 {
let Some(mut i) = s.iter().position(|c| c.is_ascii_digit()) else {
return 0.0;
};
if i > 0 && s[i - 1] == b'.' {
i -= 1;
}
if i > 0 && s[i - 1] == b'-' {
i -= 1;
}
super::strtod::strtod(&s[i..]).value
}
const SMALL: f64 = 1e-11;
static SCALC_LOOP_MAX: std::sync::atomic::AtomicI32 = std::sync::atomic::AtomicI32::new(1000);
pub fn scalc_loop_max() -> i32 {
SCALC_LOOP_MAX.load(std::sync::atomic::Ordering::Relaxed)
}
pub fn set_scalc_loop_max(n: i32) {
SCALC_LOOP_MAX.store(n, std::sync::atomic::Ordering::Relaxed);
}
fn simple_printf(fmt: &[u8], val: &StackValue) -> Result<Vec<u8>, CalcError> {
match conversion_index(fmt) {
None => Ok(fmt.to_vec()),
Some(i) if fmt[i] == b'*' => Err(CalcError::InvalidFormat),
Some(_) => Ok(c_snprintf(fmt, val)),
}
}
fn conversion_index(fmt: &[u8]) -> Option<usize> {
let mut s = 0usize;
while let Some(p) = find_sub(&fmt[s..], b"%%") {
s += p + 2;
}
let pct = find_byte(&fmt[s..], b'%')? + s;
let conv = fmt[pct + 1..]
.iter()
.position(|b| b"*cdeEfgGiousxX".contains(b))?;
Some(pct + 1 + conv)
}
struct Spec {
minus: bool,
plus: bool,
space: bool,
zero: bool,
alt: bool,
width: usize,
precision: Option<usize>,
conv: u8,
end: usize,
}
fn parse_spec(fmt: &[u8], i: usize) -> Option<Spec> {
let mut j = i + 1;
let (mut minus, mut plus, mut space, mut zero, mut alt) = (false, false, false, false, false);
while let Some(&c) = fmt.get(j) {
match c {
b'-' => minus = true,
b'+' => plus = true,
b' ' => space = true,
b'0' => zero = true,
b'#' => alt = true,
_ => break,
}
j += 1;
}
let mut width = 0usize;
while let Some(&c) = fmt.get(j) {
if !c.is_ascii_digit() {
break;
}
width = width * 10 + (c - b'0') as usize;
j += 1;
}
let mut precision = None;
if fmt.get(j) == Some(&b'.') {
j += 1;
let mut p = 0usize;
while let Some(&c) = fmt.get(j) {
if !c.is_ascii_digit() {
break;
}
p = p * 10 + (c - b'0') as usize;
j += 1;
}
precision = Some(p);
}
while matches!(
fmt.get(j),
Some(b'h' | b'l' | b'L' | b'q' | b'j' | b'z' | b't')
) {
j += 1;
}
let conv = *fmt.get(j)?;
if !b"cdiouxXeEfgGs".contains(&conv) {
return None;
}
Some(Spec {
minus,
plus,
space,
zero,
alt,
width,
precision,
conv,
end: j + 1,
})
}
fn c_snprintf(fmt: &[u8], val: &StackValue) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
let mut i = 0usize;
let mut consumed = false;
while i < fmt.len() {
if fmt[i] != b'%' {
out.push(fmt[i]);
i += 1;
continue;
}
if fmt.get(i + 1) == Some(&b'%') {
out.push(b'%');
i += 2;
continue;
}
match parse_spec(fmt, i) {
Some(spec) if !consumed => {
consumed = true;
out.extend_from_slice(&render_spec(&spec, val));
i = spec.end;
}
Some(spec) => {
out.extend_from_slice(&fmt[i..spec.end]);
i = spec.end;
}
None => {
out.push(fmt[i]);
i += 1;
}
}
}
out
}
fn render_spec(spec: &Spec, val: &StackValue) -> Vec<u8> {
let body = match spec.conv {
b's' => {
let text = val.clone().into_string_value();
let mut b = text.as_bytes().to_vec();
if let Some(p) = spec.precision {
b.truncate(p);
}
return pad(b, spec.width, spec.minus, false);
}
b'c' | b'd' | b'i' | b'o' | b'u' | b'x' | b'X' => {
let l = i64::from(my_nint(val.to_double()));
return pad(render_int(spec, l), spec.width, spec.minus, spec.zero);
}
b'e' | b'E' => cvt::fmt_e(
val.to_double(),
spec.precision.unwrap_or(6),
spec.conv == b'E',
),
b'f' => cvt::fmt_f(val.to_double(), spec.precision.unwrap_or(6)),
b'g' | b'G' => cvt::fmt_g(
val.to_double(),
spec.precision.unwrap_or(6),
spec.conv == b'G',
spec.alt,
),
_ => unreachable!("parse_spec only returns C's conversion characters"),
};
let d = val.to_double();
let mut body = body;
if spec.alt && !body.contains('.') && d.is_finite() {
body.push('.');
}
if d.is_sign_positive() && !body.starts_with('+') {
if spec.plus {
body.insert(0, '+');
} else if spec.space {
body.insert(0, ' ');
}
}
pad(
body.into_bytes(),
spec.width,
spec.minus,
spec.zero && d.is_finite(),
)
}
fn render_int(spec: &Spec, l: i64) -> Vec<u8> {
if spec.conv == b'c' {
return vec![l as u8];
}
let (mut prefix, digits) = match spec.conv {
b'd' | b'i' => {
let v = l as i32;
let sign = if v < 0 {
"-".to_string()
} else if spec.plus {
"+".to_string()
} else if spec.space {
" ".to_string()
} else {
String::new()
};
(sign, v.unsigned_abs().to_string())
}
b'u' => (String::new(), (l as u32).to_string()),
b'o' => {
let d = format!("{:o}", l as u32);
let p = if spec.alt && !d.starts_with('0') {
"0".to_string()
} else {
String::new()
};
(p, d)
}
b'x' | b'X' => {
let v = l as u32;
let d = if spec.conv == b'x' {
format!("{v:x}")
} else {
format!("{v:X}")
};
let p = match (spec.alt, v, spec.conv) {
(true, 0, _) => String::new(),
(true, _, b'x') => "0x".to_string(),
(true, _, _) => "0X".to_string(),
_ => String::new(),
};
(p, d)
}
_ => unreachable!("render_int only sees C's integer conversions"),
};
let digits = match spec.precision {
Some(p) if digits.len() < p => format!("{}{digits}", "0".repeat(p - digits.len())),
_ => digits,
};
prefix.push_str(&digits);
prefix.into_bytes()
}
fn pad(body: Vec<u8>, width: usize, minus: bool, zero: bool) -> Vec<u8> {
if body.len() >= width {
return body;
}
let fill = width - body.len();
if minus {
let mut out = body;
out.extend(std::iter::repeat_n(b' ', fill));
return out;
}
if !zero {
let mut out = vec![b' '; fill];
out.extend(body);
return out;
}
let skip = match body.first() {
Some(b'-' | b'+' | b' ') => 1,
_ if body.starts_with(b"0x") || body.starts_with(b"0X") => 2,
_ => 0,
};
let mut out = body[..skip].to_vec();
out.extend(std::iter::repeat_n(b'0', fill));
out.extend_from_slice(&body[skip..]);
out
}
fn c_int_to_index(i: i32) -> Option<usize> {
usize::try_from(i).ok()
}
#[derive(Clone, Copy, PartialEq)]
enum BinField {
Int(usize), Uint(usize), Float(usize), Char, }
impl BinField {
fn parse(conv: u8, prev: Option<u8>) -> Option<BinField> {
Some(match conv {
b'd' | b'i' => BinField::Int(if prev == Some(b'h') { 2 } else { 4 }),
b'o' | b'u' | b'x' | b'X' => BinField::Uint(if prev == Some(b'h') { 2 } else { 4 }),
b'e' | b'E' | b'f' | b'g' | b'G' => {
BinField::Float(if prev == Some(b'l') { 8 } else { 4 })
}
b'c' => BinField::Char,
_ => return None,
})
}
fn width(self) -> usize {
match self {
BinField::Int(w) | BinField::Uint(w) | BinField::Float(w) => w,
BinField::Char => 1,
}
}
}
fn bin_write(f: &[u8], val: &StackValue) -> Result<String, CalcError> {
let conv = conversion_index(f).ok_or(CalcError::InvalidFormat)?;
let field = BinField::parse(f[conv], f.get(conv.wrapping_sub(1)).copied())
.ok_or(CalcError::InvalidFormat)?;
let d = val.to_double();
let n = my_nint(d);
let raw: Vec<u8> = match field {
BinField::Char => vec![n as u8],
BinField::Int(w) | BinField::Uint(w) => n.to_le_bytes()[..w].to_vec(),
BinField::Float(4) => (d as f32).to_le_bytes().to_vec(),
BinField::Float(_) => d.to_le_bytes().to_vec(),
};
Ok(escaped_from_raw(&raw))
}
fn bin_read(subject: &[u8], f: &[u8]) -> Result<f64, CalcError> {
let conv = super::scanf::find_conversion_indicator(f).ok_or(CalcError::InvalidFormat)?;
let field = BinField::parse(f[conv], f.get(conv.wrapping_sub(1)).copied())
.ok_or(CalcError::InvalidFormat)?;
let raw = raw_from_escaped(subject);
let skip = match find_byte(f, b'*') {
Some(star) if star < conv => suppressed_skip_bytes(&f[star + 1..]),
_ => 0,
};
let w = field.width();
let bytes = raw.get(skip..skip + w).ok_or(CalcError::InvalidFormat)?;
Ok(match field {
BinField::Char => bytes[0] as i8 as f64,
BinField::Int(2) => i16::from_le_bytes([bytes[0], bytes[1]]) as f64,
BinField::Int(_) | BinField::Uint(4) => {
u32::from_le_bytes(bytes.try_into().unwrap()) as f64
}
BinField::Uint(_) => u16::from_le_bytes([bytes[0], bytes[1]]) as f64,
BinField::Float(4) => f32::from_le_bytes(bytes.try_into().unwrap()) as f64,
BinField::Float(_) => f64::from_le_bytes(bytes.try_into().unwrap()),
})
}
fn suppressed_skip_bytes(tail: &[u8]) -> usize {
let digits = tail.iter().take_while(|b| b.is_ascii_digit()).count();
let count: usize = std::str::from_utf8(&tail[..digits])
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(1);
let rest = &tail[digits..];
match rest.first() {
Some(b'h') => count * 2,
Some(b'l') => {
if rest.iter().any(|b| b"diouxX".contains(b)) {
count * 4
} else {
count * 8
}
}
Some(b'd' | b'i' | b'o' | b'u' | b'x' | b'X') => count * 4,
Some(b'e' | b'E' | b'f' | b'g' | b'G') => count * 4,
_ => count,
}
}
fn find_byte(h: &[u8], n: u8) -> Option<usize> {
h.iter().position(|b| *b == n)
}
fn find_sub(h: &[u8], n: &[u8]) -> Option<usize> {
if n.is_empty() {
return Some(0);
}
if n.len() > h.len() {
return None;
}
h.windows(n.len()).position(|w| w == n)
}
fn rfind_sub(h: &[u8], n: &[u8]) -> Option<usize> {
if n.is_empty() {
return Some(h.len());
}
if n.len() > h.len() {
return None;
}
h.windows(n.len()).rposition(|w| w == n)
}
fn shift_chars(bytes: &[u8], count: f64, right: bool) -> Vec<u8> {
const N: usize = SCALC_STRING_SIZE;
let j = my_nint(count).clamp(0, N as i32) as usize;
let mut buf = [0u8; N];
for (slot, b) in buf.iter_mut().zip(bytes.iter().take(N)) {
*slot = *b;
}
let mut out = buf;
if right {
for i in (0..N).rev() {
out[i] = if i >= j { buf[i - j] } else { b' ' };
}
out[N - 1] = 0;
} else if j == N {
out[0] = 0;
} else {
out[..N - j].copy_from_slice(&buf[j..]);
}
let end = find_byte(&out, 0).unwrap_or(N);
out[..end].to_vec()
}
fn escaped_from_raw(src: &[u8]) -> String {
let mut out = String::new();
for &c in src {
match c {
0x07 => out.push_str("\\a"),
0x08 => out.push_str("\\b"),
0x0c => out.push_str("\\f"),
b'\n' => out.push_str("\\n"),
b'\r' => out.push_str("\\r"),
b'\t' => out.push_str("\\t"),
0x0b => out.push_str("\\v"),
b'\\' => out.push_str("\\\\"),
b'\'' => out.push_str("\\'"),
b'"' => out.push_str("\\\""),
0 => out.push_str("\\0"),
0x20..=0x7e => out.push(c as char),
_ => out.push_str(&format!("\\x{c:02x}")),
}
}
out
}
fn raw_from_escaped(s: &[u8]) -> Vec<u8> {
fn nibble(c: u8) -> u8 {
(c as char).to_digit(16).expect("checked is_ascii_hexdigit") as u8
}
let mut out = Vec::new();
let mut i = 0;
'next: while i < s.len() {
let mut c = s[i];
i += 1;
loop {
if c != b'\\' {
out.push(c);
continue 'next;
}
let Some(&e) = s.get(i) else { break 'next };
i += 1;
match e {
b'a' => out.push(0x07),
b'b' => out.push(0x08),
b'f' => out.push(0x0c),
b'n' => out.push(b'\n'),
b'r' => out.push(b'\r'),
b't' => out.push(b'\t'),
b'v' => out.push(0x0b),
b'\\' => out.push(b'\\'),
b'\'' => out.push(b'\''),
b'"' => out.push(b'"'),
b'0' => out.push(0),
b'x' => {
let Some(&c1) = s.get(i) else { return out };
i += 1;
if !c1.is_ascii_hexdigit() {
c = c1; continue;
}
let u = nibble(c1);
let Some(&c2) = s.get(i) else {
out.push(u);
return out;
};
i += 1;
if !c2.is_ascii_hexdigit() {
out.push(u);
c = c2; continue;
}
out.push((u << 4) | nibble(c2));
}
other => out.push(other),
}
continue 'next;
}
}
out
}
#[derive(Debug, Clone, PartialEq)]
pub struct ScalcResult {
pub val: f64,
pub sval: ScalcString,
pub non_finite: bool,
}
pub fn epilogue(expr: &CompiledExpr, top: &StackValue, precision: i16) -> ScalcResult {
let val = top.to_double();
let non_finite = !val.is_finite();
if expr.uses_string {
ScalcResult {
val,
sval: top.clone().into_string_value(),
non_finite,
}
} else {
let sval = if val.is_nan() {
ScalcString::from_c("NaN")
} else {
ScalcString::from_c(super::cvt::cvt_double_to_string(val, precision as u16))
};
ScalcResult {
val,
sval,
non_finite,
}
}
}
fn pop1(stack: &mut Vec<StackValue>) -> Result<StackValue, CalcError> {
stack.pop().ok_or(CalcError::Underflow)
}
fn pop_n(stack: &mut Vec<StackValue>, n: usize) -> Result<Vec<StackValue>, CalcError> {
if n == 0 || stack.len() < n {
return Err(CalcError::Underflow);
}
Ok(stack.split_off(stack.len() - n).into_iter().rev().collect())
}
enum Pair {
Numeric(f64, f64),
Strings(ScalcString, ScalcString),
}
impl Pair {
fn of(a: StackValue, b: StackValue) -> Pair {
match (a, b) {
(StackValue::Str(x), StackValue::Str(y)) => Pair::Strings(x, y),
(a, b) => Pair::Numeric(a.to_double(), b.to_double()),
}
}
}
enum Operands {
Numeric(Vec<f64>),
Strings(Vec<ScalcString>),
}
impl Operands {
fn of(args: Vec<StackValue>) -> Operands {
if args.iter().any(StackValue::is_double) {
Operands::Numeric(args.iter().map(StackValue::to_double).collect())
} else {
Operands::Strings(
args.into_iter()
.map(|v| match v {
StackValue::Str(s) => s,
StackValue::Double(_) => unreachable!("no arg is a double here"),
})
.collect(),
)
}
}
}
#[derive(Clone, Copy)]
enum Extremum {
Max,
Min,
}
impl Extremum {
fn pick<T: PartialOrd>(self, a: T, b: T) -> T {
let right_wins = match self {
Extremum::Max => a < b,
Extremum::Min => a > b,
};
if right_wins { b } else { a }
}
fn fold(self, args: Operands) -> StackValue {
match args {
Operands::Numeric(vals) => {
let mut running = vals[0];
for &cur in &vals[1..] {
let keep_running = match self {
Extremum::Max => cur < running,
Extremum::Min => cur > running,
};
if !(keep_running || running.is_nan()) {
running = cur;
}
}
StackValue::Double(running)
}
Operands::Strings(vals) => {
let mut running = vals[0].clone();
for cur in &vals[1..] {
let keep_running = match self {
Extremum::Max => cur.as_bytes() < running.as_bytes(),
Extremum::Min => cur.as_bytes() > running.as_bytes(),
};
if !keep_running {
running = cur.clone();
}
}
StackValue::Str(running)
}
}
}
}
fn subrange_bounds(subject: &[u8], start: &StackValue, end: &StackValue) -> (i64, i64) {
let k = subject.len() as i64;
let i = match start {
StackValue::Double(d) => {
let i = i64::from(c_int(*d));
if i < 0 { i + k } else { i }
}
StackValue::Str(needle) => {
find_sub(subject, needle.as_bytes()).map_or(0, |p| (p + needle.len()) as i64)
}
};
let j = match end {
StackValue::Double(d) => {
let j = i64::from(c_int(*d));
if j < 0 { j + k } else { j }
}
StackValue::Str(needle) if needle.is_empty() => k,
StackValue::Str(needle) => find_sub(subject, needle.as_bytes()).map_or(k, |p| p as i64 - 1),
};
(i.clamp(0, k), j.min(k))
}
fn pop1_f64(stack: &mut Vec<StackValue>) -> Result<f64, CalcError> {
let v = stack.pop().ok_or(CalcError::Underflow)?;
Ok(v.to_double())
}
fn pop2_f64(stack: &mut Vec<StackValue>) -> Result<(f64, f64), CalcError> {
let b = pop1_f64(stack)?;
let a = pop1_f64(stack)?;
Ok((a, b))
}
fn cond_search(code: &[Opcode], start: usize, find_else: bool) -> Result<usize, CalcError> {
let mut count: i32 = 1;
let mut pc = start;
while pc < code.len() {
let op = &code[pc];
if matches!(op, Opcode::Core(CoreOp::End)) {
break;
}
let is_match = match op {
Opcode::Core(CoreOp::CondElse) => find_else,
Opcode::Core(CoreOp::CondEnd) => !find_else,
_ => false,
};
if is_match {
count -= 1;
if count == 0 {
return Ok(pc + 1);
}
}
if matches!(op, Opcode::Core(CoreOp::CondIf)) {
count += 1;
}
pc += 1;
}
Err(CalcError::Conditional)
}
#[cfg(test)]
mod parity_tests {
use crate::calc::{StackValue, StringInputs, scalc};
fn run_num(expr: &str) -> f64 {
let mut inp = StringInputs::new();
match scalc(expr, &mut inp).unwrap() {
StackValue::Double(v) => v,
StackValue::Str(s) => panic!("expected double, got string {s:?}"),
}
}
#[test]
fn h6_eq_is_within_small() {
assert_eq!(run_num("1e-12 == 0"), 1.0); assert_eq!(run_num("1e-12 # 0"), 0.0); assert_eq!(run_num("0 == 0"), 1.0); assert_eq!(run_num("0.1+0.2 == 0.3"), 1.0); }
#[test]
fn h6_inequalities_are_within_small() {
assert_eq!(run_num("1e-12 < 1e-11"), 0.0);
assert_eq!(run_num("1 >= 1"), 1.0); assert_eq!(run_num("2 > 1"), 1.0); }
#[test]
fn h7_div_by_zero_fails_the_perform() {
let mut inp = StringInputs::new();
assert!(scalc("1/0", &mut inp).is_err()); assert!(scalc("-1/0", &mut inp).is_err()); assert!(scalc("0/0", &mut inp).is_err()); }
}
#[cfg(test)]
mod stack_depth_invariant {
use super::*;
use crate::calc::engine::ExprKind;
fn run(code: Vec<Opcode>) -> Result<StackValue, CalcError> {
let expr = CompiledExpr {
code,
..CompiledExpr::empty(ExprKind::String)
};
eval(&expr, &mut StringInputs::new())
}
fn run_string(code: Vec<Opcode>) -> Result<StackValue, CalcError> {
let expr = CompiledExpr {
code,
uses_string: true,
..CompiledExpr::empty(ExprKind::String)
};
eval(&expr, &mut StringInputs::new())
}
#[test]
fn a_leaked_operand_is_an_error_not_the_top_of_stack() {
let leaked = vec![
Opcode::Core(CoreOp::PushConst(1.0)),
Opcode::Core(CoreOp::PushConst(2.0)),
Opcode::Core(CoreOp::End),
];
assert_eq!(run(leaked), Err(CalcError::StackLeak));
}
#[test]
fn an_empty_stack_is_an_error_not_a_zero() {
let consumed = vec![
Opcode::Core(CoreOp::PushConst(1.0)),
Opcode::Core(CoreOp::StoreVar(0)),
Opcode::Core(CoreOp::End),
];
assert_eq!(run(consumed), Err(CalcError::StackLeak));
}
#[test]
fn a_leaked_string_operand_is_an_error_too() {
let leaked = vec![
Opcode::String(StringOp::PushString(b"a".to_vec())),
Opcode::String(StringOp::PushString(b"b".to_vec())),
Opcode::Core(CoreOp::End),
];
assert_eq!(run_string(leaked), Err(CalcError::StackLeak));
}
#[test]
fn exactly_one_value_is_the_result() {
let balanced = vec![
Opcode::Core(CoreOp::PushConst(1.0)),
Opcode::Core(CoreOp::PushConst(2.0)),
Opcode::Core(CoreOp::Add),
Opcode::Core(CoreOp::End),
];
assert_eq!(run(balanced), Ok(StackValue::Double(3.0)));
}
}