use sim_kernel::{Cx, Error, Expr, NumberLiteral, Result, Symbol, Value};
use crate::operator::LuaOp;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LuaNumber {
Integer(i64),
Float(f64),
}
impl LuaNumber {
fn as_f64(self) -> f64 {
match self {
Self::Integer(value) => value as f64,
Self::Float(value) => value,
}
}
fn as_i64(self) -> Option<i64> {
match self {
Self::Integer(value) => Some(value),
Self::Float(value) if value.fract() == 0.0 => {
if value >= i64::MIN as f64 && value <= i64::MAX as f64 {
Some(value as i64)
} else {
None
}
}
Self::Float(_) => None,
}
}
}
pub fn lua_number_from_value(cx: &mut Cx, value: &Value) -> Result<Option<LuaNumber>> {
match value.object().as_expr(cx)? {
Expr::Number(number) => Ok(number_from_literal(&number)),
Expr::String(text) => Ok(number_from_text(&text)),
_ => Ok(None),
}
}
pub fn lua_integer_value(cx: &mut Cx, value: i64) -> Result<Value> {
cx.factory()
.number_literal(lua_integer_domain(), value.to_string())
}
pub fn lua_float_value(cx: &mut Cx, value: f64) -> Result<Value> {
if !value.is_finite() {
return Err(Error::Eval("lua number result is not finite".to_owned()));
}
cx.factory()
.number_literal(lua_float_domain(), canonical_float(value))
}
pub fn lua_arith_or_compare(cx: &mut Cx, op: LuaOp, l: Value, r: Value) -> Result<Value> {
match op {
LuaOp::Concat => lua_concat(cx, &l, &r),
LuaOp::Eq => cx.factory().bool(l == r),
LuaOp::Lt | LuaOp::Le => lua_compare(cx, op, &l, &r),
_ => {
let left = required_number(cx, &l, op)?;
let right = required_number(cx, &r, op)?;
match op {
LuaOp::Add => numeric_result(cx, left, right, |a, b| a + b, |a, b| a + b),
LuaOp::Sub => numeric_result(cx, left, right, |a, b| a - b, |a, b| a - b),
LuaOp::Mul => numeric_result(cx, left, right, |a, b| a * b, |a, b| a * b),
LuaOp::FloatDiv => lua_float_value(cx, left.as_f64() / right.as_f64()),
LuaOp::FloorDiv => floor_div(cx, left, right),
LuaOp::Mod => modulo(cx, left, right),
LuaOp::Pow => lua_float_value(cx, left.as_f64().powf(right.as_f64())),
LuaOp::Band => bitwise(cx, left, right, |a, b| a & b),
LuaOp::Bor => bitwise(cx, left, right, |a, b| a | b),
LuaOp::Bxor => bitwise(cx, left, right, |a, b| a ^ b),
LuaOp::Shl => bitwise(cx, left, right, |a, b| a.wrapping_shl(shift_count(b))),
LuaOp::Shr => bitwise(cx, left, right, |a, b| a.wrapping_shr(shift_count(b))),
LuaOp::Concat | LuaOp::Len | LuaOp::Eq | LuaOp::Lt | LuaOp::Le => unreachable!(),
}
}
}
}
fn lua_compare(cx: &mut Cx, op: LuaOp, left: &Value, right: &Value) -> Result<Value> {
if let (Some(left), Some(right)) = (
lua_number_from_value(cx, left)?,
lua_number_from_value(cx, right)?,
) {
return cx.factory().bool(match op {
LuaOp::Lt => left.as_f64() < right.as_f64(),
LuaOp::Le => left.as_f64() <= right.as_f64(),
_ => unreachable!(),
});
}
let left = string_coercion(cx, left)?;
let right = string_coercion(cx, right)?;
cx.factory().bool(match op {
LuaOp::Lt => left < right,
LuaOp::Le => left <= right,
_ => unreachable!(),
})
}
fn lua_concat(cx: &mut Cx, left: &Value, right: &Value) -> Result<Value> {
let left = string_coercion(cx, left)?;
let right = string_coercion(cx, right)?;
cx.factory().string(format!("{left}{right}"))
}
fn required_number(cx: &mut Cx, value: &Value, op: LuaOp) -> Result<LuaNumber> {
lua_number_from_value(cx, value)?.ok_or_else(|| {
Error::Eval(format!(
"lua operator {} requires numeric operands",
op.name()
))
})
}
fn numeric_result(
cx: &mut Cx,
left: LuaNumber,
right: LuaNumber,
integer_op: fn(i64, i64) -> i64,
float_op: fn(f64, f64) -> f64,
) -> Result<Value> {
match (left, right) {
(LuaNumber::Integer(left), LuaNumber::Integer(right)) => {
lua_integer_value(cx, integer_op(left, right))
}
_ => lua_float_value(cx, float_op(left.as_f64(), right.as_f64())),
}
}
fn floor_div(cx: &mut Cx, left: LuaNumber, right: LuaNumber) -> Result<Value> {
if right.as_f64() == 0.0 {
return Err(Error::Eval("lua floor division by zero".to_owned()));
}
match (left, right) {
(LuaNumber::Integer(left), LuaNumber::Integer(right)) => {
lua_integer_value(cx, (left as f64 / right as f64).floor() as i64)
}
_ => lua_float_value(cx, (left.as_f64() / right.as_f64()).floor()),
}
}
fn modulo(cx: &mut Cx, left: LuaNumber, right: LuaNumber) -> Result<Value> {
if right.as_f64() == 0.0 {
return Err(Error::Eval("lua modulo by zero".to_owned()));
}
match (left, right) {
(LuaNumber::Integer(left), LuaNumber::Integer(right)) => {
lua_integer_value(cx, left.rem_euclid(right))
}
_ => {
let divisor = right.as_f64();
lua_float_value(
cx,
left.as_f64() - (left.as_f64() / divisor).floor() * divisor,
)
}
}
}
fn bitwise(
cx: &mut Cx,
left: LuaNumber,
right: LuaNumber,
op: fn(i64, i64) -> i64,
) -> Result<Value> {
let left = left
.as_i64()
.ok_or_else(|| Error::Eval("lua bitwise operand must be an integer".to_owned()))?;
let right = right
.as_i64()
.ok_or_else(|| Error::Eval("lua bitwise operand must be an integer".to_owned()))?;
lua_integer_value(cx, op(left, right))
}
fn shift_count(value: i64) -> u32 {
value.clamp(0, 63) as u32
}
fn string_coercion(cx: &mut Cx, value: &Value) -> Result<String> {
match value.object().as_expr(cx)? {
Expr::String(text) => Ok(text),
Expr::Number(number) => Ok(number.canonical),
other => Err(Error::Eval(format!(
"lua operator cannot coerce {other:?} to string"
))),
}
}
fn number_from_literal(number: &NumberLiteral) -> Option<LuaNumber> {
if let Ok(value) = number.canonical.parse::<i64>() {
return Some(LuaNumber::Integer(value));
}
let value = number.canonical.parse::<f64>().ok()?;
value.is_finite().then_some(LuaNumber::Float(value))
}
fn number_from_text(text: &str) -> Option<LuaNumber> {
let text = text.trim();
if let Ok(value) = text.parse::<i64>() {
return Some(LuaNumber::Integer(value));
}
let value = text.parse::<f64>().ok()?;
value.is_finite().then_some(LuaNumber::Float(value))
}
fn canonical_float(value: f64) -> String {
if value.fract() == 0.0 {
format!("{value:.1}")
} else {
value.to_string()
}
}
fn lua_integer_domain() -> Symbol {
sim_lib_numbers_i64::number_domain()
}
fn lua_float_domain() -> Symbol {
sim_lib_numbers_f64::number_domain()
}