use crate::prelude::*;
built_in! {self, _func, scope, arithmetic;
abs => {"abs", COMMON, Exact(1), {
match _func.arg()? {
Pos { val: Int(int), .. } => {
scope.extend(&mov_int(Rax, int));
scope.extend(&[Custom(CQO), LogicRR(Xor, Rax, Rdx), SubRR(Rax, Rdx)]);
Ok(Int(Var(scope.ret(Rax)?)))
}
Pos { val: Float(float), .. } => {
scope.extend(&mov_float_reg(Rax, float));
scope.push(Custom(BTR_RAX_63));
Ok(Float(Var(scope.ret(Rax)?)))
}
other => Err(_func.args_err(vec![IntT, BoolT], other.map_ref(Json::as_type)))
}
}},
calc_add => {"+", COMMON, AtLeast(2), {
self.arithmetic_op(
&(AddRR(Rax, Rcx), Add),
(&i64::checked_add, &i64::checked_add),
0, (&|| Ok(None), &|| Some(IncR(Rax))),
None, _func, scope
)
}},
calc_div => {"/", COMMON, AtLeast(2), {
let func_pos = _func.pos;
self.arithmetic_op(
&(IDivR(Rcx), Div),
(&i64::checked_div, &i64::checked_mul),
1, (&|| err!(func_pos, ZeroDivision), &|| None),
Some(&Jsonpiler::check_zero_cqo), _func, scope
)
}},
calc_minus => {"-", COMMON, AtLeast(1), {
if _func.val.len == 1 {
match _func.arg()? {
Pos { val: Int(int), .. } => {
scope.extend(&mov_int(Rax, int));
scope.push(UnaryR(Neg, Rax));
Ok(Int(Var(scope.ret(Rax)?)))
}
Pos { val: Float(float), .. } => {
scope.extend(&mov_float_reg(Rax, float));
scope.push(Custom(BTC_RAX_63));
Ok(Float(Var(scope.ret(Rax)?)))
}
other => Err(_func.args_err(vec![IntT, BoolT], other.map_ref(Json::as_type)))
}
} else {
self.arithmetic_op(
&(SubRR(Rax, Rcx), Sub),
(&i64::checked_sub, &i64::checked_add),
0, (&|| Ok(None), &|| Some(DecR(Rax))),
None, _func, scope
)
}
}},
calc_mul => {"*", COMMON, AtLeast(2), {
self.arithmetic_op(
&(IMulRR(Rax, Rcx), Mul),
(&i64::checked_mul, &i64::checked_mul),
1, (&|| Ok(Some(0)), &|| None),
None, _func, scope
)
}},
float => {"Float", COMMON, Exact(1), {
scope.extend(&mov_int(Rax, arg!(_func, (Int(x)) => x).val));
scope.push(CvtSi2Sd(Rax, Rax));
scope.ret_xmm(Rax)
}},
int => {"Int", COMMON, Exact(1), {
scope.extend(&self.mov_float_xmm(Rax, Rax, arg!(_func, (Float(x)) => x).val)?);
scope.push(CvtTSd2Si(Rax, Rax));
Ok(Int(Var(scope.ret(Rax)?)))
}},
random => {"random", COMMON, Exact(0), {
scope.push(Call(self.get_random(scope.id)?));
Ok(Int(Var(scope.ret(Rax)?)))
}},
rem => {"%", COMMON, Exact(2), {
let lhs = arg!(_func, (Int(x)) => x).val;
let Pos { val: rhs, pos } = arg!(_func, (Int(x)) => x);
if matches!(rhs, Lit(0)) {
return err!(pos, ZeroDivision);
}
if let (Lit(lit1), Lit(lit2)) = (&lhs, &rhs) {
return Ok(Int(Lit(lit1.wrapping_rem(*lit2))));
}
scope.extend(&mov_int(Rax, lhs));
scope.extend(&mov_int(Rcx, rhs));
scope.extend(&self.check_zero_cqo(pos, scope.id)?);
scope.push(IDivR(Rcx));
Ok(Int(Var(scope.ret(Rdx)?)))
}},
shift_left => {"<<", COMMON, Exact(2), { self.shift(Shl, _func, scope) }},
shift_right => {">>", COMMON, Exact(2), { self.shift(Shr, _func, scope) }},
sqrt => {"sqrt", COMMON, Exact(1), {
scope.extend(&self.mov_float_xmm(Rax, Rax, arg!(_func, (Float(x)) => x).val)?);
scope.push(SqrtSd(Rax, Rax));
scope.ret_xmm(Rax)
}},
}
type Op = dyn Fn(i64, i64) -> Option<i64>;
type CheckFn = dyn Fn(&mut Jsonpiler, Position, LabelId) -> ErrOR<Vec<Inst>>;
impl Jsonpiler {
#[expect(clippy::too_many_arguments)]
fn arithmetic_op(
&mut self,
op_inst: &(Inst, ArithSdKind),
ops: (&Op, &Op),
ident_elem: i64,
when: (&impl Fn() -> ErrOR<Option<i64>>, &impl Fn() -> Option<Inst>),
check_opt: Option<&CheckFn>,
func: &mut Pos<BuiltIn>,
scope: &mut Scope,
) -> ErrOR<Json> {
match func.arg()? {
Pos { val: Int(int), pos } => {
let mut rest = vec![];
for _ in 1..func.val.len {
rest.push(arg!(func, (Int(x)) => x));
}
let (first, vars, acc) = constant_fold(pos.with(int), rest, ops, ident_elem, &when.0)?;
if first.is_none() && vars.is_empty() {
return Ok(Int(Lit(acc)));
}
if let Some(memory) = first {
if acc == 0
&& let Some(ret_val) = when.0()?
{
return Ok(Int(Lit(ret_val)));
}
scope.extend(&mov_int(Rax, Var(memory)));
if acc != 0 {
if acc == 1 {
if let Some(inst) = when.1() {
scope.push(inst);
if !self.release {
scope.push(JCc(O, self.custom_err(RuntimeOverflow, None, func.pos, scope.id)?));
}
}
} else {
self.rest_op(Lit(acc), check_opt, op_inst.0, func, scope)?;
}
}
} else {
scope.extend(&mov_int(Rax, Lit(acc)));
}
for memory in vars {
self.rest_op(Var(memory.val), check_opt, op_inst.0, func, scope)?;
}
Ok(Int(Var(scope.ret(Rax)?)))
}
Pos { val: Float(float), .. } => {
scope.extend(&self.mov_float_xmm(Rax, Rax, float)?);
for _ in 1..func.val.len {
scope.extend(&self.mov_float_xmm(Rcx, Rax, arg!(func, (Float(x)) => x).val)?);
scope.push(ArithSd(op_inst.1, Rax, Rcx));
}
scope.ret_xmm(Rax)
}
Pos { val: Str(string), .. } if func.val.name == "+" => {
self.concat_strings(string, func, scope)
}
other => Err(func.args_err(
if func.val.name == "+" { vec![IntT, BoolT, StrT] } else { vec![IntT, BoolT] },
other.map_ref(Json::as_type),
)),
}
}
pub(crate) fn check_zero_cqo(&mut self, pos: Position, caller: LabelId) -> ErrOR<Vec<Inst>> {
let zero_division = self.custom_err(RuntimeZeroDivision, None, pos, caller)?;
Ok(vec![LogicRR(Test, Rcx, Rcx), JCc(E, zero_division), Custom(CQO)])
}
fn rest_op(
&mut self,
bind: Bind<i64>,
check_opt: Option<&CheckFn>,
int_inst: Inst,
func: &mut Pos<BuiltIn>,
scope: &mut Scope,
) -> ErrOR<()> {
scope.extend(&mov_int(Rcx, bind));
if let Some(check) = check_opt {
scope.extend(&check(self, func.pos, scope.id)?);
}
scope.push(int_inst);
if !self.release {
scope.push(JCc(O, self.custom_err(RuntimeOverflow, None, func.pos, scope.id)?));
}
Ok(())
}
fn shift(
&mut self,
direction: ShiftDirection,
func: &mut Pos<BuiltIn>,
scope: &mut Scope,
) -> ErrOR<Json> {
let lhs = arg!(func, (Int(x)) => x).val;
let Pos { val: rhs, pos } = arg!(func, (Int(x)) => x);
if let (Lit(lit1), Lit(lit2)) = (&lhs, &rhs) {
let Ok(rhs_u32) = u32::try_from(*lit2) else { return err!(pos, TooLargeShift) };
return Ok(Int(Lit(
if direction == Shl { lit1.checked_shl(rhs_u32) } else { lit1.checked_shr(rhs_u32) }
.ok_or(Compilation(TooLargeShift, vec![pos]))?,
)));
}
scope.extend(&mov_int(Rax, lhs));
if let Lit(lit2) = rhs {
if let Ok(rhs_u8) = u8::try_from(lit2)
&& lit2 < 64
{
scope.push(ShiftR(direction, Rax, Shift::Ib(rhs_u8)));
} else {
return err!(pos, TooLargeShift);
}
} else {
let too_large_shift = self.custom_err(RuntimeTooLargeShift, None, pos, scope.id)?;
scope.extend(&mov_int(Rcx, rhs));
scope.extend(&[
mov_d(Rdx, 64),
LogicRR(Cmp, Rcx, Rdx),
JCc(Ge, too_large_shift),
ShiftR(direction, Rax, Shift::Cl),
]);
}
Ok(Int(Var(scope.ret(Rax)?)))
}
}
fn constant_fold(
first: Pos<Bind<i64>>,
rest: Vec<Pos<Bind<i64>>>,
ops: (&Op, &Op),
ident_elem: i64,
when0: &impl Fn() -> ErrOR<Option<i64>>,
) -> ErrOR<(Option<Memory>, Vec<Pos<Memory>>, i64)> {
let mut vars = vec![];
let mut acc = ident_elem;
for bind in rest {
match bind.val {
Lit(lit) => acc = ops.1(acc, lit).ok_or(Compilation(Overflow, vec![bind.pos]))?,
Var(memory) => vars.push(bind.pos.with(memory)),
}
}
match first.val {
Lit(lit) => Ok((
None,
vars,
if acc == 0
&& let Some(ret_val) = when0()?
{
ret_val
} else {
ops.0(lit, acc).ok_or(Compilation(Overflow, vec![first.pos]))?
},
)),
Var(memory) => Ok((Some(memory), vars, acc)),
}
}