Skip to main content

sim_lib_lang_lua/
number.rs

1use sim_kernel::{Cx, Error, Expr, NumberLiteral, Result, Symbol, Value};
2
3use crate::operator::LuaOp;
4
5/// Lua numeric subtype used by the core operator layer.
6#[derive(Clone, Copy, Debug, PartialEq)]
7pub enum LuaNumber {
8    /// Integer subtype.
9    Integer(i64),
10    /// Floating-point subtype.
11    Float(f64),
12}
13
14impl LuaNumber {
15    fn as_f64(self) -> f64 {
16        match self {
17            Self::Integer(value) => value as f64,
18            Self::Float(value) => value,
19        }
20    }
21
22    fn as_i64(self) -> Option<i64> {
23        match self {
24            Self::Integer(value) => Some(value),
25            Self::Float(value) if value.fract() == 0.0 => {
26                if value >= i64::MIN as f64 && value <= i64::MAX as f64 {
27                    Some(value as i64)
28                } else {
29                    None
30                }
31            }
32            Self::Float(_) => None,
33        }
34    }
35}
36
37/// Converts a SIM value to a Lua number, including Lua string-to-number coercion.
38pub fn lua_number_from_value(cx: &mut Cx, value: &Value) -> Result<Option<LuaNumber>> {
39    match value.object().as_expr(cx)? {
40        Expr::Number(number) => Ok(number_from_literal(&number)),
41        Expr::String(text) => Ok(number_from_text(&text)),
42        _ => Ok(None),
43    }
44}
45
46/// Builds a runtime value from a Lua integer.
47pub fn lua_integer_value(cx: &mut Cx, value: i64) -> Result<Value> {
48    cx.factory()
49        .number_literal(lua_integer_domain(), value.to_string())
50}
51
52/// Builds a runtime value from a Lua float.
53pub fn lua_float_value(cx: &mut Cx, value: f64) -> Result<Value> {
54    if !value.is_finite() {
55        return Err(Error::Eval("lua number result is not finite".to_owned()));
56    }
57    cx.factory()
58        .number_literal(lua_float_domain(), canonical_float(value))
59}
60
61/// Applies Lua arithmetic, bitwise, comparison, and coercion rules.
62pub fn lua_arith_or_compare(cx: &mut Cx, op: LuaOp, l: Value, r: Value) -> Result<Value> {
63    match op {
64        LuaOp::Concat => lua_concat(cx, &l, &r),
65        LuaOp::Eq => cx.factory().bool(l == r),
66        LuaOp::Lt | LuaOp::Le => lua_compare(cx, op, &l, &r),
67        _ => {
68            let left = required_number(cx, &l, op)?;
69            let right = required_number(cx, &r, op)?;
70            match op {
71                LuaOp::Add => numeric_result(cx, left, right, |a, b| a + b, |a, b| a + b),
72                LuaOp::Sub => numeric_result(cx, left, right, |a, b| a - b, |a, b| a - b),
73                LuaOp::Mul => numeric_result(cx, left, right, |a, b| a * b, |a, b| a * b),
74                LuaOp::FloatDiv => lua_float_value(cx, left.as_f64() / right.as_f64()),
75                LuaOp::FloorDiv => floor_div(cx, left, right),
76                LuaOp::Mod => modulo(cx, left, right),
77                LuaOp::Pow => lua_float_value(cx, left.as_f64().powf(right.as_f64())),
78                LuaOp::Band => bitwise(cx, left, right, |a, b| a & b),
79                LuaOp::Bor => bitwise(cx, left, right, |a, b| a | b),
80                LuaOp::Bxor => bitwise(cx, left, right, |a, b| a ^ b),
81                LuaOp::Shl => bitwise(cx, left, right, |a, b| a.wrapping_shl(shift_count(b))),
82                LuaOp::Shr => bitwise(cx, left, right, |a, b| a.wrapping_shr(shift_count(b))),
83                LuaOp::Concat | LuaOp::Len | LuaOp::Eq | LuaOp::Lt | LuaOp::Le => unreachable!(),
84            }
85        }
86    }
87}
88
89fn lua_compare(cx: &mut Cx, op: LuaOp, left: &Value, right: &Value) -> Result<Value> {
90    if let (Some(left), Some(right)) = (
91        lua_number_from_value(cx, left)?,
92        lua_number_from_value(cx, right)?,
93    ) {
94        return cx.factory().bool(match op {
95            LuaOp::Lt => left.as_f64() < right.as_f64(),
96            LuaOp::Le => left.as_f64() <= right.as_f64(),
97            _ => unreachable!(),
98        });
99    }
100    let left = string_coercion(cx, left)?;
101    let right = string_coercion(cx, right)?;
102    cx.factory().bool(match op {
103        LuaOp::Lt => left < right,
104        LuaOp::Le => left <= right,
105        _ => unreachable!(),
106    })
107}
108
109fn lua_concat(cx: &mut Cx, left: &Value, right: &Value) -> Result<Value> {
110    let left = string_coercion(cx, left)?;
111    let right = string_coercion(cx, right)?;
112    cx.factory().string(format!("{left}{right}"))
113}
114
115fn required_number(cx: &mut Cx, value: &Value, op: LuaOp) -> Result<LuaNumber> {
116    lua_number_from_value(cx, value)?.ok_or_else(|| {
117        Error::Eval(format!(
118            "lua operator {} requires numeric operands",
119            op.name()
120        ))
121    })
122}
123
124fn numeric_result(
125    cx: &mut Cx,
126    left: LuaNumber,
127    right: LuaNumber,
128    integer_op: fn(i64, i64) -> i64,
129    float_op: fn(f64, f64) -> f64,
130) -> Result<Value> {
131    match (left, right) {
132        (LuaNumber::Integer(left), LuaNumber::Integer(right)) => {
133            lua_integer_value(cx, integer_op(left, right))
134        }
135        _ => lua_float_value(cx, float_op(left.as_f64(), right.as_f64())),
136    }
137}
138
139fn floor_div(cx: &mut Cx, left: LuaNumber, right: LuaNumber) -> Result<Value> {
140    if right.as_f64() == 0.0 {
141        return Err(Error::Eval("lua floor division by zero".to_owned()));
142    }
143    match (left, right) {
144        (LuaNumber::Integer(left), LuaNumber::Integer(right)) => {
145            lua_integer_value(cx, (left as f64 / right as f64).floor() as i64)
146        }
147        _ => lua_float_value(cx, (left.as_f64() / right.as_f64()).floor()),
148    }
149}
150
151fn modulo(cx: &mut Cx, left: LuaNumber, right: LuaNumber) -> Result<Value> {
152    if right.as_f64() == 0.0 {
153        return Err(Error::Eval("lua modulo by zero".to_owned()));
154    }
155    match (left, right) {
156        (LuaNumber::Integer(left), LuaNumber::Integer(right)) => {
157            lua_integer_value(cx, left.rem_euclid(right))
158        }
159        _ => {
160            let divisor = right.as_f64();
161            lua_float_value(
162                cx,
163                left.as_f64() - (left.as_f64() / divisor).floor() * divisor,
164            )
165        }
166    }
167}
168
169fn bitwise(
170    cx: &mut Cx,
171    left: LuaNumber,
172    right: LuaNumber,
173    op: fn(i64, i64) -> i64,
174) -> Result<Value> {
175    let left = left
176        .as_i64()
177        .ok_or_else(|| Error::Eval("lua bitwise operand must be an integer".to_owned()))?;
178    let right = right
179        .as_i64()
180        .ok_or_else(|| Error::Eval("lua bitwise operand must be an integer".to_owned()))?;
181    lua_integer_value(cx, op(left, right))
182}
183
184fn shift_count(value: i64) -> u32 {
185    value.clamp(0, 63) as u32
186}
187
188fn string_coercion(cx: &mut Cx, value: &Value) -> Result<String> {
189    match value.object().as_expr(cx)? {
190        Expr::String(text) => Ok(text),
191        Expr::Number(number) => Ok(number.canonical),
192        other => Err(Error::Eval(format!(
193            "lua operator cannot coerce {other:?} to string"
194        ))),
195    }
196}
197
198fn number_from_literal(number: &NumberLiteral) -> Option<LuaNumber> {
199    if let Ok(value) = number.canonical.parse::<i64>() {
200        return Some(LuaNumber::Integer(value));
201    }
202    let value = number.canonical.parse::<f64>().ok()?;
203    value.is_finite().then_some(LuaNumber::Float(value))
204}
205
206fn number_from_text(text: &str) -> Option<LuaNumber> {
207    let text = text.trim();
208    if let Ok(value) = text.parse::<i64>() {
209        return Some(LuaNumber::Integer(value));
210    }
211    let value = text.parse::<f64>().ok()?;
212    value.is_finite().then_some(LuaNumber::Float(value))
213}
214
215fn canonical_float(value: f64) -> String {
216    if value.fract() == 0.0 {
217        format!("{value:.1}")
218    } else {
219        value.to_string()
220    }
221}
222
223fn lua_integer_domain() -> Symbol {
224    sim_lib_numbers_i64::number_domain()
225}
226
227fn lua_float_domain() -> Symbol {
228    sim_lib_numbers_f64::number_domain()
229}