288 lines
8.3 KiB
Rust
288 lines
8.3 KiB
Rust
//! Laufzeitwerte der TBVM: Tagged Enum (Entwurf docs/tbvm-design.md),
|
||
//! `Rc` statt GC (der Dialekt kennt keine Zyklen), Arrays/Records als
|
||
//! geteilte Handles, Referenzwerte für BYREF-Parameter.
|
||
//!
|
||
//! Hier lebt außerdem die **Zahlenkonvertierungs-Matrix** (Banker's
|
||
//! Rounding, Überlauf → Fehler 6) — die einzige Implementierung der in
|
||
//! docs/tbvm-design.md dokumentierten Semantik.
|
||
|
||
use crate::errors::RuntimeError;
|
||
use std::cell::RefCell;
|
||
use std::rc::Rc;
|
||
|
||
#[derive(Debug, Clone)]
|
||
pub enum Value {
|
||
Int(i16),
|
||
Lng(i32),
|
||
Sng(f32),
|
||
Dbl(f64),
|
||
Cur(i64),
|
||
Str(Rc<str>),
|
||
/// Formular-/Control-Objekt mit optionalem Index eines Control-Arrays.
|
||
Obj(u16, Option<i32>),
|
||
Arr(Rc<RefCell<ArrayObj>>),
|
||
Rec(Rc<RefCell<RecordObj>>),
|
||
/// Referenz (BYREF-Parameter-Slot).
|
||
Ref(VarRef),
|
||
/// Nicht-initialisiertes Array-/Record-Handle (Auto-DIM bei Zugriff).
|
||
Empty,
|
||
}
|
||
|
||
/// Referenzziel eines BYREF-Parameters.
|
||
#[derive(Debug, Clone)]
|
||
pub enum VarRef {
|
||
Global(u16),
|
||
/// Absoluter Index in den Locals-Stack der VM.
|
||
Stack(u32),
|
||
/// Arrayelement (flacher Index).
|
||
Elem(Rc<RefCell<ArrayObj>>, u32),
|
||
/// Record-Feldpfad.
|
||
Field(Rc<RefCell<RecordObj>>, Vec<u16>),
|
||
}
|
||
|
||
/// Initialisierungstyp eines Slots/Elements (serialisierbar im `.tbc`).
|
||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||
pub enum TypeInit {
|
||
Int,
|
||
Lng,
|
||
Sng,
|
||
Dbl,
|
||
Cur,
|
||
Str,
|
||
FixedStr(u32),
|
||
Udt(u16),
|
||
/// Array-/Record-Slot ohne Vorbelegung (Auto-DIM).
|
||
Empty,
|
||
}
|
||
|
||
/// UDT-Layout für die Default-Erzeugung.
|
||
#[derive(Debug, Clone)]
|
||
pub struct UdtLayout {
|
||
pub name: String,
|
||
pub fields: Vec<TypeInit>,
|
||
}
|
||
|
||
pub fn default_value(init: &TypeInit, udts: &[UdtLayout]) -> Value {
|
||
match init {
|
||
TypeInit::Int => Value::Int(0),
|
||
TypeInit::Lng => Value::Lng(0),
|
||
TypeInit::Sng => Value::Sng(0.0),
|
||
TypeInit::Dbl => Value::Dbl(0.0),
|
||
TypeInit::Cur => Value::Cur(0),
|
||
TypeInit::Str => Value::Str(Rc::from("")),
|
||
TypeInit::FixedStr(n) => Value::Str(Rc::from(" ".repeat(*n as usize).as_str())),
|
||
TypeInit::Udt(id) => {
|
||
let layout = &udts[*id as usize];
|
||
let fields = layout
|
||
.fields
|
||
.iter()
|
||
.map(|f| default_value(f, udts))
|
||
.collect();
|
||
Value::Rec(Rc::new(RefCell::new(RecordObj { fields })))
|
||
}
|
||
TypeInit::Empty => Value::Empty,
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub struct ArrayObj {
|
||
pub elem: TypeInit,
|
||
/// (Untergrenze, Obergrenze) je Dimension.
|
||
pub dims: Vec<(i32, i32)>,
|
||
pub data: Vec<Value>,
|
||
}
|
||
|
||
impl ArrayObj {
|
||
pub fn new(
|
||
elem: TypeInit,
|
||
dims: Vec<(i32, i32)>,
|
||
udts: &[UdtLayout],
|
||
) -> Result<Self, RuntimeError> {
|
||
let mut len: usize = 1;
|
||
for (lo, hi) in &dims {
|
||
if hi < lo {
|
||
return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
|
||
}
|
||
let n = (*hi as i64 - *lo as i64 + 1) as usize;
|
||
len = len.checked_mul(n).ok_or(RuntimeError::OUT_OF_MEMORY)?;
|
||
if len > 64 * 1024 * 1024 {
|
||
return Err(RuntimeError::OUT_OF_MEMORY);
|
||
}
|
||
}
|
||
let mut data = Vec::with_capacity(len);
|
||
for _ in 0..len {
|
||
data.push(default_value(&elem, udts));
|
||
}
|
||
Ok(ArrayObj { elem, dims, data })
|
||
}
|
||
|
||
/// Flacher Index (zeilenweise, letzte Dimension läuft am schnellsten);
|
||
/// Bereichsprüfung → Fehler 9.
|
||
pub fn flat_index(&self, idx: &[i32]) -> Result<u32, RuntimeError> {
|
||
if idx.len() != self.dims.len() {
|
||
return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
|
||
}
|
||
let mut flat: u64 = 0;
|
||
for (i, (lo, hi)) in idx.iter().zip(self.dims.iter()) {
|
||
if i < lo || i > hi {
|
||
return Err(RuntimeError::SUBSCRIPT_OUT_OF_RANGE);
|
||
}
|
||
let span = (*hi as i64 - *lo as i64 + 1) as u64;
|
||
flat = flat * span + (*i as i64 - *lo as i64) as u64;
|
||
}
|
||
Ok(flat as u32)
|
||
}
|
||
}
|
||
|
||
#[derive(Debug)]
|
||
pub struct RecordObj {
|
||
pub fields: Vec<Value>,
|
||
}
|
||
|
||
// ---- Konvertierungsmatrix ---------------------------------------------------
|
||
|
||
/// Kaufmännische Rundung zur nächsten geraden Zahl (Banker's Rounding,
|
||
/// Verhalten von CINT/CLNG des Vorbilds).
|
||
pub fn banker_round(x: f64) -> f64 {
|
||
let floor = x.floor();
|
||
let diff = x - floor;
|
||
if diff > 0.5 {
|
||
floor + 1.0
|
||
} else if diff < 0.5 {
|
||
floor
|
||
} else {
|
||
// exakt .5 → zur geraden Zahl
|
||
if (floor as i64) % 2 == 0 {
|
||
floor
|
||
} else {
|
||
floor + 1.0
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn f64_to_i16(x: f64) -> Result<i16, RuntimeError> {
|
||
let r = banker_round(x);
|
||
if !(i16::MIN as f64..=i16::MAX as f64).contains(&r) {
|
||
return Err(RuntimeError::OVERFLOW);
|
||
}
|
||
Ok(r as i16)
|
||
}
|
||
|
||
pub fn f64_to_i32(x: f64) -> Result<i32, RuntimeError> {
|
||
let r = banker_round(x);
|
||
if !(i32::MIN as f64..=i32::MAX as f64).contains(&r) {
|
||
return Err(RuntimeError::OVERFLOW);
|
||
}
|
||
Ok(r as i32)
|
||
}
|
||
|
||
pub fn f64_to_f32(x: f64) -> Result<f32, RuntimeError> {
|
||
if x.is_finite() && x.abs() > f32::MAX as f64 {
|
||
return Err(RuntimeError::OVERFLOW);
|
||
}
|
||
Ok(x as f32)
|
||
}
|
||
|
||
/// f64 → CURRENCY (Festkomma ×10 000, Banker's auf der 4. Nachkommastelle).
|
||
pub fn f64_to_cur(x: f64) -> Result<i64, RuntimeError> {
|
||
let scaled = banker_round(x * 10_000.0);
|
||
if !((i64::MIN as f64) < scaled && scaled < (i64::MAX as f64)) {
|
||
return Err(RuntimeError::OVERFLOW);
|
||
}
|
||
Ok(scaled as i64)
|
||
}
|
||
|
||
/// CURRENCY → Ganzzahl (÷10 000, Banker's; ganzzahlig exakt).
|
||
pub fn cur_to_i64(c: i64) -> i64 {
|
||
let q = c.div_euclid(10_000);
|
||
let r = c.rem_euclid(10_000);
|
||
// r in 0..10000; runde halb-zu-gerade
|
||
if r > 5_000 {
|
||
q + 1
|
||
} else if r < 5_000 || q % 2 == 0 {
|
||
q
|
||
} else {
|
||
q + 1
|
||
}
|
||
}
|
||
|
||
pub fn cur_to_i16(c: i64) -> Result<i16, RuntimeError> {
|
||
let v = cur_to_i64(c);
|
||
i16::try_from(v).map_err(|_| RuntimeError::OVERFLOW)
|
||
}
|
||
|
||
pub fn cur_to_i32(c: i64) -> Result<i32, RuntimeError> {
|
||
let v = cur_to_i64(c);
|
||
i32::try_from(v).map_err(|_| RuntimeError::OVERFLOW)
|
||
}
|
||
|
||
pub fn cur_to_f64(c: i64) -> f64 {
|
||
c as f64 / 10_000.0
|
||
}
|
||
|
||
/// i32 → i16 mit Bereichsprüfung.
|
||
pub fn i32_to_i16(v: i32) -> Result<i16, RuntimeError> {
|
||
i16::try_from(v).map_err(|_| RuntimeError::OVERFLOW)
|
||
}
|
||
|
||
/// Numerischer Wert als f64 (für Builtins, die per Tag dispatchen).
|
||
pub fn as_f64(v: &Value) -> f64 {
|
||
match v {
|
||
Value::Int(x) => *x as f64,
|
||
Value::Lng(x) => *x as f64,
|
||
Value::Sng(x) => *x as f64,
|
||
Value::Dbl(x) => *x,
|
||
Value::Cur(x) => cur_to_f64(*x),
|
||
_ => 0.0,
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn banker_rounding() {
|
||
assert_eq!(banker_round(0.5), 0.0);
|
||
assert_eq!(banker_round(1.5), 2.0);
|
||
assert_eq!(banker_round(2.5), 2.0);
|
||
assert_eq!(banker_round(-0.5), 0.0);
|
||
assert_eq!(banker_round(-1.5), -2.0);
|
||
assert_eq!(banker_round(2.4), 2.0);
|
||
assert_eq!(banker_round(2.6), 3.0);
|
||
}
|
||
|
||
#[test]
|
||
fn overflow_bei_konvertierung() {
|
||
assert!(f64_to_i16(40_000.0).is_err());
|
||
assert!(f64_to_i16(32_767.4).is_ok());
|
||
assert!(f64_to_i32(3e9).is_err());
|
||
assert!(f64_to_f32(1e39).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn currency_rundung() {
|
||
assert_eq!(f64_to_cur(1.5).unwrap(), 15_000);
|
||
assert_eq!(f64_to_cur(-2.25).unwrap(), -22_500);
|
||
// Halb-zu-gerade nur bei exakt darstellbarem .5-Fall (Skalierung
|
||
// 10000.5 ist binär exakt, wenn der Ausgangswert es hergibt):
|
||
assert_eq!(banker_round(10_000.5), 10_000.0);
|
||
assert_eq!(cur_to_i64(15_000), 2); // 1.5 → 2
|
||
assert_eq!(cur_to_i64(25_000), 2); // 2.5 → 2
|
||
assert_eq!(cur_to_i64(-15_000), -2);
|
||
}
|
||
|
||
#[test]
|
||
fn array_indexpruefung() {
|
||
let a = ArrayObj::new(TypeInit::Int, vec![(0, 10)], &[]).unwrap();
|
||
assert_eq!(a.flat_index(&[0]).unwrap(), 0);
|
||
assert_eq!(a.flat_index(&[10]).unwrap(), 10);
|
||
assert!(a.flat_index(&[11]).is_err());
|
||
assert!(a.flat_index(&[-1]).is_err());
|
||
let b = ArrayObj::new(TypeInit::Int, vec![(1, 3), (1, 2)], &[]).unwrap();
|
||
assert_eq!(b.flat_index(&[1, 1]).unwrap(), 0);
|
||
assert_eq!(b.flat_index(&[1, 2]).unwrap(), 1);
|
||
assert_eq!(b.flat_index(&[2, 1]).unwrap(), 2);
|
||
}
|
||
}
|