1use sim_kernel::{Cx, Error, Expr, NumberLiteral, Result, Symbol, Value};
2
3use crate::operator::LuaOp;
4
5#[derive(Clone, Copy, Debug, PartialEq)]
7pub enum LuaNumber {
8 Integer(i64),
10 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
37pub 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
46pub 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
52pub 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
61pub 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}