use std::collections::{HashMap, HashSet};
use crate::analysis::registry::TypeRegistry;
use crate::analysis::types::RustNames;
use crate::ast::logic::{LogicExpr, NumberKind, Term};
use crate::ast::stmt::{BinaryOpKind, Expr, Literal, Stmt, TypeExpr};
use crate::formatter::RustFormatter;
use crate::intern::{Interner, Symbol};
use crate::registry::SymbolRegistry;
use super::context::RefinementContext;
use super::detection::{collect_mutable_vars, expr_debug_prefix, parse_aos_tag};
use super::i64_map::is_logos_map_type;
use super::types::{codegen_type_expr, infer_logos_type, infer_numeric_type};
use super::{
codegen_stmt, get_root_identifier, has_copy_element_type, has_copy_value_type, is_copy_type,
};
use std::sync::LazyLock;
static EMPTY_SYMS: LazyLock<HashSet<Symbol>> = LazyLock::new(HashSet::new);
static EMPTY_TYPES: LazyLock<HashMap<Symbol, String>> = LazyLock::new(HashMap::new);
static EMPTY_BOXED_FIELDS: LazyLock<HashSet<(String, String, String)>> =
LazyLock::new(HashSet::new);
static EMPTY_REGISTRY: LazyLock<TypeRegistry> = LazyLock::new(TypeRegistry::new);
#[derive(Clone, Copy)]
pub(crate) struct ExprCtx<'a> {
pub interner: &'a Interner,
pub synced_vars: &'a HashSet<Symbol>,
pub boxed_fields: &'a HashSet<(String, String, String)>,
pub registry: &'a TypeRegistry,
pub async_functions: &'a HashSet<Symbol>,
pub boxed_bindings: &'a HashSet<Symbol>,
pub string_vars: &'a HashSet<Symbol>,
pub variable_types: &'a HashMap<Symbol, String>,
pub fast_div: &'a HashMap<Symbol, String>,
pub oracle: Option<&'a crate::optimize::OracleFacts>,
pub int_exact_tolerant: bool,
pub int_index_context: bool,
pub int_wrapping: bool,
}
impl<'a> ExprCtx<'a> {
pub(crate) fn bare(interner: &'a Interner, synced_vars: &'a HashSet<Symbol>) -> Self {
ExprCtx {
interner,
synced_vars,
boxed_fields: &EMPTY_BOXED_FIELDS,
registry: &EMPTY_REGISTRY,
async_functions: &EMPTY_SYMS,
boxed_bindings: &EMPTY_SYMS,
string_vars: &EMPTY_SYMS,
variable_types: &EMPTY_TYPES,
fast_div: &EMPTY_TYPES,
oracle: None,
int_exact_tolerant: false,
int_index_context: false,
int_wrapping: false,
}
}
}
thread_local! {
static BIGINT_RETURNING_FNS: std::cell::RefCell<HashSet<Symbol>> =
std::cell::RefCell::new(HashSet::new());
}
pub(crate) fn set_bigint_returning_fns(fns: &HashSet<Symbol>) {
BIGINT_RETURNING_FNS.with(|c| *c.borrow_mut() = fns.clone());
}
fn is_bigint_returning_call(function: Symbol) -> bool {
BIGINT_RETURNING_FNS.with(|c| c.borrow().contains(&function))
}
fn mentions_bigint_var(e: &Expr, variable_types: &HashMap<Symbol, String>) -> bool {
match e {
Expr::Identifier(s) => variable_types.get(s).map_or(false, |t| t.contains("__bigint")),
Expr::Call { function, .. } => is_bigint_returning_call(*function),
Expr::BinaryOp { left, right, .. } => {
mentions_bigint_var(left, variable_types) || mentions_bigint_var(right, variable_types)
}
Expr::Not { operand } => mentions_bigint_var(operand, variable_types),
_ => false,
}
}
enum ConstExact {
InRange,
Promoted(String),
}
fn const_exact_int(op: BinaryOpKind, a: i64, b: i64) -> Option<ConstExact> {
let exact: i128 = match op {
BinaryOpKind::Add => a as i128 + b as i128,
BinaryOpKind::Subtract => a as i128 - b as i128,
BinaryOpKind::Multiply => a as i128 * b as i128,
BinaryOpKind::Divide => {
if b == 0 {
return None;
}
a as i128 / b as i128
}
BinaryOpKind::Modulo => {
if b == 0 {
return None;
}
a as i128 % b as i128
}
_ => return None,
};
Some(if i64::try_from(exact).is_ok() {
ConstExact::InRange
} else {
ConstExact::Promoted(format!("LogosInt::from_literal(\"{}\")", exact))
})
}
fn oracle_proves_int_op_in_range(
ecx: &ExprCtx,
whole: &Expr,
op: BinaryOpKind,
left: &Expr,
right: &Expr,
) -> bool {
if crate::optimize::expr_proven_raw_int_op(whole) {
return true;
}
let Some(o) = ecx.oracle else { return false };
if let Some((lo, hi)) = o.expr_int_range(whole) {
let _ = (lo, hi); return true;
}
let (Some((la, lb)), Some((ra, rb))) = (o.expr_int_range(left), o.expr_int_range(right))
else {
return false;
};
let (la, lb, ra, rb) = (la as i128, lb as i128, ra as i128, rb as i128);
let (lo, hi) = match op {
BinaryOpKind::Add => (la + ra, lb + rb),
BinaryOpKind::Subtract => (la - rb, lb - ra),
BinaryOpKind::Multiply => {
let c = [la * ra, la * rb, lb * ra, lb * rb];
(*c.iter().min().unwrap(), *c.iter().max().unwrap())
}
_ => return false,
};
lo >= i64::MIN as i128 && hi <= i64::MAX as i128
}
fn is_exact_arith_node(e: &Expr) -> bool {
matches!(
e,
Expr::BinaryOp {
op: BinaryOpKind::Add
| BinaryOpKind::Subtract
| BinaryOpKind::Multiply
| BinaryOpKind::Divide
| BinaryOpKind::Modulo,
..
}
)
}
fn exact_chain_max_magnitude(e: &Expr, ecx: &ExprCtx) -> Option<u128> {
if mentions_bigint_var(e, ecx.variable_types) {
return None;
}
match e {
Expr::Literal(Literal::Number(n)) => Some(n.unsigned_abs() as u128),
Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
let l = exact_chain_max_magnitude(left, ecx)?;
let r = exact_chain_max_magnitude(right, ecx)?;
match op {
BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r),
BinaryOpKind::Multiply => l.checked_mul(r),
BinaryOpKind::Divide => Some(l),
BinaryOpKind::Modulo => Some(r.min(l)),
_ => unreachable!("is_exact_arith_node covers exactly these five"),
}
}
_ => (infer_numeric_type(e, ecx.interner, ecx.variable_types) == "i64")
.then_some(1u128 << 63),
}
}
fn emit_exact_chain_i128(e: &Expr, ecx: &ExprCtx) -> String {
match e {
Expr::Literal(Literal::Number(n)) => format!("{}i128", n),
Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
let l = emit_exact_chain_i128(left, ecx);
let r = emit_exact_chain_i128(right, ecx);
let nonzero_lit = matches!(&**right, Expr::Literal(Literal::Number(d)) if *d != 0);
match op {
BinaryOpKind::Add => format!("({} + {})", l, r),
BinaryOpKind::Subtract => format!("({} - {})", l, r),
BinaryOpKind::Multiply => format!("({} * {})", l, r),
BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", l, r),
BinaryOpKind::Divide => format!("logos_div_i128({}, {})", l, r),
BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", l, r),
BinaryOpKind::Modulo => format!("logos_rem_i128({}, {})", l, r),
_ => unreachable!("is_exact_arith_node covers exactly these five"),
}
}
_ => {
let plain = ExprCtx { int_exact_tolerant: false, ..*ecx };
format!("(({}) as i128)", codegen_expr_ctx(e, &plain))
}
}
}
fn try_fuse_narrowed_int_op(
op: BinaryOpKind,
left: &Expr,
right: &Expr,
ecx: &ExprCtx,
) -> Option<String> {
let plain = ExprCtx { int_exact_tolerant: false, ..*ecx };
if !is_exact_arith_node(left) && !is_exact_arith_node(right) {
let helper = match op {
BinaryOpKind::Add => "logos_add_i64",
BinaryOpKind::Subtract => "logos_sub_i64",
BinaryOpKind::Multiply => "logos_mul_i64",
BinaryOpKind::Divide => "logos_div_i64",
BinaryOpKind::Modulo => "logos_rem_i64",
_ => return None,
};
let l = codegen_expr_ctx(left, &plain);
let r = codegen_expr_ctx(right, &plain);
return Some(format!("{}({}, {})", helper, l, r));
}
None
}
fn try_i128_chain(
op: BinaryOpKind,
left: &Expr,
right: &Expr,
ecx: &ExprCtx,
) -> Option<String> {
let l = exact_chain_max_magnitude(left, ecx)?;
let r = exact_chain_max_magnitude(right, ecx)?;
let root_magnitude = match op {
BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r)?,
BinaryOpKind::Multiply => l.checked_mul(r)?,
BinaryOpKind::Divide => l,
BinaryOpKind::Modulo => r.min(l),
_ => return None,
};
if root_magnitude > (i128::MAX as u128) {
return None;
}
let le = emit_exact_chain_i128(left, ecx);
let re = emit_exact_chain_i128(right, ecx);
let nonzero_lit = matches!(right, Expr::Literal(Literal::Number(d)) if *d != 0);
let chain = match op {
BinaryOpKind::Add => format!("({} + {})", le, re),
BinaryOpKind::Subtract => format!("({} - {})", le, re),
BinaryOpKind::Multiply => format!("({} * {})", le, re),
BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", le, re),
BinaryOpKind::Divide => format!("logos_div_i128({}, {})", le, re),
BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", le, re),
BinaryOpKind::Modulo => format!("logos_rem_i128({}, {})", le, re),
_ => unreachable!("root op matched above"),
};
Some(format!("logos_narrow_i128({})", chain))
}
fn chain_magnitude_with_leaf_bound(e: &Expr, bound: u128) -> Option<u128> {
match e {
Expr::Literal(Literal::Number(n)) => Some(n.unsigned_abs() as u128),
Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
let l = chain_magnitude_with_leaf_bound(left, bound)?;
let r = chain_magnitude_with_leaf_bound(right, bound)?;
match op {
BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r),
BinaryOpKind::Multiply => l.checked_mul(r),
BinaryOpKind::Divide => Some(l),
BinaryOpKind::Modulo => Some(r.min(l)),
_ => unreachable!("is_exact_arith_node covers exactly these five"),
}
}
_ => Some(bound),
}
}
fn emit_chain_with_bound_leaves(
e: &Expr,
names: &HashMap<usize, String>,
raw: bool,
) -> String {
match e {
Expr::Literal(Literal::Number(n)) => format!("{}i64", n),
Expr::BinaryOp { op, left, right } if is_exact_arith_node(e) => {
let l = emit_chain_with_bound_leaves(left, names, raw);
let r = emit_chain_with_bound_leaves(right, names, raw);
let nonzero_lit = matches!(&**right, Expr::Literal(Literal::Number(d)) if *d != 0);
if raw {
match op {
BinaryOpKind::Add => format!("({} + {})", l, r),
BinaryOpKind::Subtract => format!("({} - {})", l, r),
BinaryOpKind::Multiply => format!("({} * {})", l, r),
BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", l, r),
BinaryOpKind::Divide => format!("logos_div_i64({}, {})", l, r),
BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", l, r),
BinaryOpKind::Modulo => format!("logos_rem_i64({}, {})", l, r),
_ => unreachable!("is_exact_arith_node covers exactly these five"),
}
} else {
let helper = match op {
BinaryOpKind::Add => "logos_add_exact",
BinaryOpKind::Subtract => "logos_sub_exact",
BinaryOpKind::Multiply => "logos_mul_exact",
BinaryOpKind::Divide => "logos_div_exact",
BinaryOpKind::Modulo => "logos_rem_exact",
_ => unreachable!("is_exact_arith_node covers exactly these five"),
};
format!("{}({}, {})", helper, l, r)
}
}
Expr::Identifier(s) => names[&(usize::MAX - s.index())].clone(),
_ => names[&(e as *const Expr as usize)].clone(),
}
}
fn try_guarded_dual_chain<'e>(
op: BinaryOpKind,
left: &'e Expr<'e>,
right: &'e Expr<'e>,
whole_ecx: &ExprCtx,
) -> Option<String> {
fn walk<'e>(
e: &'e Expr<'e>,
ecx: &ExprCtx,
addrs: &mut Vec<usize>,
exprs: &mut Vec<&'e Expr<'e>>,
seen: &mut std::collections::HashSet<usize>,
) -> Option<()> {
if mentions_bigint_var(e, ecx.variable_types) {
return None;
}
match e {
Expr::Literal(Literal::Number(_)) => Some(()),
Expr::BinaryOp { left, right, .. } if is_exact_arith_node(e) => {
walk(left, ecx, addrs, exprs, seen)?;
walk(right, ecx, addrs, exprs, seen)
}
_ => {
if infer_numeric_type(e, ecx.interner, ecx.variable_types) != "i64" {
return None;
}
let addr = match e {
Expr::Identifier(s) => usize::MAX - s.index(),
_ => e as *const Expr as usize,
};
if seen.insert(addr) {
addrs.push(addr);
exprs.push(e);
}
Some(())
}
}
}
let mut leaves: Vec<usize> = Vec::new();
let mut seen = std::collections::HashSet::new();
let mut leaf_exprs: Vec<&Expr> = Vec::new();
walk(left, whole_ecx, &mut leaves, &mut leaf_exprs, &mut seen)?;
walk(right, whole_ecx, &mut leaves, &mut leaf_exprs, &mut seen)?;
if leaves.is_empty() || leaves.len() > 6 {
return None;
}
let root_mag = |b: u128| -> Option<u128> {
let l = chain_magnitude_with_leaf_bound(left, b)?;
let r = chain_magnitude_with_leaf_bound(right, b)?;
match op {
BinaryOpKind::Add | BinaryOpKind::Subtract => l.checked_add(r),
BinaryOpKind::Multiply => l.checked_mul(r),
BinaryOpKind::Divide => Some(l),
BinaryOpKind::Modulo => Some(r.min(l)),
_ => None,
}
};
let mut bound: u128 = 0;
for exp in (16..=62).rev() {
let b = 1u128 << exp;
if root_mag(b).is_some_and(|m| m <= i64::MAX as u128) {
bound = b;
break;
}
}
if bound == 0 {
return None;
}
let plain = ExprCtx { int_exact_tolerant: false, ..*whole_ecx };
let mut names: HashMap<usize, String> = HashMap::new();
let mut binds = String::new();
for (k, (addr, le)) in leaves.iter().zip(leaf_exprs.iter()).enumerate() {
if matches!(le, Expr::Identifier(_)) {
names.insert(*addr, codegen_expr_ctx(le, &plain));
continue;
}
let name = format!("__chain_l{}", k);
binds.push_str(&format!("let {} = {}; ", name, codegen_expr_ctx(le, &plain)));
names.insert(*addr, name);
}
let guard = leaves
.iter()
.zip(leaf_exprs.iter())
.map(|(addr, le)| {
let n = &names[addr];
let b = bound as i64;
let nonneg = whole_ecx
.oracle
.and_then(|o| o.expr_int_range(le))
.is_some_and(|(lo, _)| lo >= 0);
if nonneg {
format!("({n} <= {b}i64)")
} else {
format!("({n} >= -{b}i64 && {n} <= {b}i64)")
}
})
.collect::<Vec<_>>()
.join(" && ");
let root = |raw: bool| -> String {
let l = emit_chain_with_bound_leaves(left, &names, raw);
let r = emit_chain_with_bound_leaves(right, &names, raw);
let nonzero_lit = matches!(right, Expr::Literal(Literal::Number(d)) if *d != 0);
if raw {
match op {
BinaryOpKind::Add => format!("({} + {})", l, r),
BinaryOpKind::Subtract => format!("({} - {})", l, r),
BinaryOpKind::Multiply => format!("({} * {})", l, r),
BinaryOpKind::Divide if nonzero_lit => format!("({} / {})", l, r),
BinaryOpKind::Divide => format!("logos_div_i64({}, {})", l, r),
BinaryOpKind::Modulo if nonzero_lit => format!("({} % {})", l, r),
BinaryOpKind::Modulo => format!("logos_rem_i64({}, {})", l, r),
_ => unreachable!(),
}
} else {
let helper = match op {
BinaryOpKind::Add => "logos_add_exact",
BinaryOpKind::Subtract => "logos_sub_exact",
BinaryOpKind::Multiply => "logos_mul_exact",
BinaryOpKind::Divide => "logos_div_exact",
BinaryOpKind::Modulo => "logos_rem_exact",
_ => unreachable!(),
};
format!("{}({}, {}).expect_i64(\"Int\")", helper, l, r)
}
};
Some(format!(
"{{ {binds}if {guard} {{ {fast} }} else {{ {slow} }} }}",
fast = root(true),
slow = root(false),
))
}
fn oracle_proves_index(ecx: &ExprCtx, collection: &Expr, index: &Expr) -> bool {
if !crate::optimize::active_config().is_on(crate::optimization::Opt::Unchecked) {
return false;
}
let proven = ecx.oracle.map_or(false, |o| o.index_provably_in_bounds(collection, index));
if proven {
crate::optimize::mark_fired(crate::optimization::Opt::Unchecked);
}
proven
}
pub(crate) fn codegen_expr_with_async_oracle<'a>(
expr: &Expr,
interner: &'a Interner,
synced_vars: &'a HashSet<Symbol>,
async_functions: &'a HashSet<Symbol>,
variable_types: &'a HashMap<Symbol, String>,
oracle: Option<&'a crate::optimize::OracleFacts>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx { async_functions, variable_types, oracle, ..ExprCtx::bare(interner, synced_vars) },
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn codegen_expr_boxed_with_types_oracle<'a>(
expr: &Expr,
interner: &'a Interner,
synced_vars: &'a HashSet<Symbol>,
boxed_fields: &'a HashSet<(String, String, String)>,
registry: &'a TypeRegistry,
async_functions: &'a HashSet<Symbol>,
string_vars: &'a HashSet<Symbol>,
variable_types: &'a HashMap<Symbol, String>,
fast_div: &'a HashMap<Symbol, String>,
oracle: Option<&'a crate::optimize::OracleFacts>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx {
boxed_fields,
registry,
async_functions,
string_vars,
variable_types,
fast_div,
oracle,
..ExprCtx::bare(interner, synced_vars)
},
)
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn codegen_expr_boxed_with_types_oracle_tolerant<'a>(
expr: &Expr,
interner: &'a Interner,
synced_vars: &'a HashSet<Symbol>,
boxed_fields: &'a HashSet<(String, String, String)>,
registry: &'a TypeRegistry,
async_functions: &'a HashSet<Symbol>,
string_vars: &'a HashSet<Symbol>,
variable_types: &'a HashMap<Symbol, String>,
fast_div: &'a HashMap<Symbol, String>,
oracle: Option<&'a crate::optimize::OracleFacts>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx {
boxed_fields,
registry,
async_functions,
string_vars,
variable_types,
fast_div,
oracle,
int_exact_tolerant: true,
..ExprCtx::bare(interner, synced_vars)
},
)
}
pub fn codegen_expr(expr: &Expr, interner: &Interner, synced_vars: &HashSet<Symbol>) -> String {
codegen_expr_ctx(expr, &ExprCtx::bare(interner, synced_vars))
}
pub(crate) fn codegen_expr_with_async(
expr: &Expr,
interner: &Interner,
synced_vars: &HashSet<Symbol>,
async_functions: &HashSet<Symbol>,
variable_types: &HashMap<Symbol, String>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx { async_functions, variable_types, ..ExprCtx::bare(interner, synced_vars) },
)
}
pub(crate) fn codegen_expr_with_async_and_strings(
expr: &Expr,
interner: &Interner,
synced_vars: &HashSet<Symbol>,
async_functions: &HashSet<Symbol>,
string_vars: &HashSet<Symbol>,
variable_types: &HashMap<Symbol, String>,
fast_div: &HashMap<Symbol, String>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx {
async_functions,
string_vars,
variable_types,
fast_div,
int_exact_tolerant: true,
..ExprCtx::bare(interner, synced_vars)
},
)
}
pub(crate) fn is_definitely_numeric_expr(expr: &Expr) -> bool {
match expr {
Expr::Literal(Literal::Number(_)) => true,
Expr::Literal(Literal::Float(_)) => true,
Expr::Literal(Literal::Duration(_)) => true,
Expr::Identifier(_) => true,
Expr::BinaryOp { op: BinaryOpKind::Subtract, .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Multiply, .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Divide, .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Modulo, .. } => true,
Expr::Length { .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
is_definitely_numeric_expr(left) && is_definitely_numeric_expr(right)
}
Expr::Call { .. } => true,
Expr::Index { .. } => true,
_ => true,
}
}
pub(crate) fn is_definitely_string_expr_with_vars(expr: &Expr, string_vars: &HashSet<Symbol>) -> bool {
match expr {
Expr::Literal(Literal::Text(_)) => true,
Expr::Identifier(sym) => string_vars.contains(sym),
Expr::BinaryOp { op: BinaryOpKind::Concat, .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
is_definitely_string_expr_with_vars(left, string_vars)
|| is_definitely_string_expr_with_vars(right, string_vars)
}
Expr::WithCapacity { value, .. } => is_definitely_string_expr_with_vars(value, string_vars),
Expr::InterpolatedString(_) => true,
_ => false,
}
}
pub(crate) fn is_definitely_string_expr(expr: &Expr) -> bool {
let empty = HashSet::new();
is_definitely_string_expr_with_vars(expr, &empty)
}
fn is_definitely_seq_expr(
expr: &Expr,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> bool {
match expr {
Expr::List(_) | Expr::Slice { .. } => true,
Expr::Identifier(sym) => variable_types
.get(sym)
.map(|t| {
let t = t.split("|__hl:").next().unwrap_or(t.as_str());
t.starts_with("LogosSeq") || t.starts_with("Vec<") || t.starts_with("&[")
})
.unwrap_or(false),
Expr::BinaryOp { op: BinaryOpKind::SeqConcat, .. } => true,
Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Multiply, left, right } => {
is_definitely_seq_expr(left, variable_types, interner)
|| is_definitely_seq_expr(right, variable_types, interner)
}
Expr::Call { function, .. } => interner.resolve(*function) == "repeatSeq",
Expr::New { type_name, .. } => interner.resolve(*type_name) == "Seq",
_ => false,
}
}
pub(crate) fn collect_string_concat_operands<'a, 'b>(
expr: &'b Expr<'a>,
string_vars: &HashSet<Symbol>,
operands: &mut Vec<&'b Expr<'a>>,
) {
match expr {
Expr::BinaryOp { op: BinaryOpKind::Concat, left, right } => {
collect_string_concat_operands(left, string_vars, operands);
collect_string_concat_operands(right, string_vars, operands);
}
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
let has_string = is_definitely_string_expr_with_vars(left, string_vars)
|| is_definitely_string_expr_with_vars(right, string_vars);
if has_string {
collect_string_concat_operands(left, string_vars, operands);
collect_string_concat_operands(right, string_vars, operands);
} else {
operands.push(expr);
}
}
_ => {
operands.push(expr);
}
}
}
pub(crate) fn codegen_expr_boxed(
expr: &Expr,
interner: &Interner,
synced_vars: &HashSet<Symbol>,
boxed_fields: &HashSet<(String, String, String)>, registry: &TypeRegistry, async_functions: &HashSet<Symbol>, ) -> String {
codegen_expr_ctx(
expr,
&ExprCtx {
boxed_fields,
registry,
async_functions,
..ExprCtx::bare(interner, synced_vars)
},
)
}
pub(crate) fn codegen_expr_boxed_with_strings(
expr: &Expr,
interner: &Interner,
synced_vars: &HashSet<Symbol>,
boxed_fields: &HashSet<(String, String, String)>,
registry: &TypeRegistry,
async_functions: &HashSet<Symbol>,
string_vars: &HashSet<Symbol>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx {
boxed_fields,
registry,
async_functions,
string_vars,
..ExprCtx::bare(interner, synced_vars)
},
)
}
pub(crate) fn codegen_expr_boxed_with_types(
expr: &Expr,
interner: &Interner,
synced_vars: &HashSet<Symbol>,
boxed_fields: &HashSet<(String, String, String)>,
registry: &TypeRegistry,
async_functions: &HashSet<Symbol>,
string_vars: &HashSet<Symbol>,
variable_types: &HashMap<Symbol, String>,
fast_div: &HashMap<Symbol, String>,
) -> String {
codegen_expr_ctx(
expr,
&ExprCtx {
boxed_fields,
registry,
async_functions,
string_vars,
variable_types,
fast_div,
..ExprCtx::bare(interner, synced_vars)
},
)
}
fn affine_array_coeff_offset(ty: Option<&String>) -> Option<(i64, i64)> {
let rest = ty?.strip_prefix("__affine_array:")?;
let mut parts = rest.splitn(3, ':');
let coeff: i64 = parts.next()?.parse().ok()?;
let offset: i64 = parts.next()?.parse().ok()?;
Some((coeff, offset))
}
fn affine_array_trip(ty: Option<&String>) -> Option<String> {
let rest = ty?.strip_prefix("__affine_array:")?;
let mut parts = rest.splitn(3, ':');
parts.next()?; parts.next()?; Some(parts.next()?.to_string())
}
pub(super) fn is_rational_expr(expr: &Expr, variable_types: &HashMap<Symbol, String>) -> bool {
match expr {
Expr::BinaryOp { op: BinaryOpKind::ExactDivide, .. } => true,
Expr::BinaryOp {
op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply,
left,
right,
} => is_rational_expr(left, variable_types) || is_rational_expr(right, variable_types),
Expr::Identifier(sym) => {
variable_types.get(sym).map_or(false, |t| t == "LogosRational")
}
_ => false,
}
}
fn rational_operand(expr: &Expr, code: &str, variable_types: &HashMap<Symbol, String>) -> String {
if is_rational_expr(expr, variable_types) {
code.to_string()
} else {
format!("LogosRational::from_i64(({}) as i64)", code)
}
}
pub(super) fn is_decimal_expr(
expr: &Expr,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> bool {
match expr {
Expr::Call { function, .. } => interner.resolve(*function) == "decimal",
Expr::BinaryOp {
op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply,
left,
right,
} => {
is_decimal_expr(left, variable_types, interner)
|| is_decimal_expr(right, variable_types, interner)
}
Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosDecimal"),
_ => false,
}
}
fn decimal_operand(
expr: &Expr,
code: &str,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> String {
if is_decimal_expr(expr, variable_types, interner) {
code.to_string()
} else {
format!("LogosDecimal::from_i64(({}) as i64)", code)
}
}
pub(super) fn is_complex_expr(
expr: &Expr,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> bool {
match expr {
Expr::Call { function, .. } => interner.resolve(*function) == "complex",
Expr::BinaryOp {
op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide,
left,
right,
} => {
is_complex_expr(left, variable_types, interner)
|| is_complex_expr(right, variable_types, interner)
}
Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosComplex"),
_ => false,
}
}
fn complex_operand(
expr: &Expr,
code: &str,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> String {
if is_complex_expr(expr, variable_types, interner) {
code.to_string()
} else {
format!("LogosComplex::from_i64(({}) as i64)", code)
}
}
pub(super) fn is_modular_expr(
expr: &Expr,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> bool {
match expr {
Expr::Call { function, .. } => interner.resolve(*function) == "modular",
Expr::BinaryOp {
op: BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide,
left,
right,
} => {
is_modular_expr(left, variable_types, interner)
&& is_modular_expr(right, variable_types, interner)
}
Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosModular"),
_ => false,
}
}
pub(super) fn is_quantity_expr(
expr: &Expr,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> bool {
match expr {
Expr::Call { function, .. } => {
matches!(interner.resolve(*function), "quantity" | "convert")
}
Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Subtract, left, right } => {
is_quantity_expr(left, variable_types, interner)
&& is_quantity_expr(right, variable_types, interner)
}
Expr::BinaryOp { op: BinaryOpKind::Multiply | BinaryOpKind::Divide, left, right } => {
is_quantity_expr(left, variable_types, interner)
|| is_quantity_expr(right, variable_types, interner)
}
Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosQuantity"),
_ => false,
}
}
pub(super) fn is_money_expr(
expr: &Expr,
variable_types: &HashMap<Symbol, String>,
interner: &Interner,
) -> bool {
match expr {
Expr::Call { function, .. } => interner.resolve(*function) == "money",
Expr::BinaryOp { op: BinaryOpKind::Add | BinaryOpKind::Subtract, left, right } => {
is_money_expr(left, variable_types, interner)
&& is_money_expr(right, variable_types, interner)
}
Expr::BinaryOp { op: BinaryOpKind::Multiply | BinaryOpKind::Divide, left, right } => {
is_money_expr(left, variable_types, interner)
|| is_money_expr(right, variable_types, interner)
}
Expr::Identifier(sym) => variable_types.get(sym).map_or(false, |t| t == "LogosMoney"),
_ => false,
}
}
fn codegen_expr_ctx(expr: &Expr, ecx: &ExprCtx) -> String {
let ExprCtx {
interner,
synced_vars,
boxed_fields,
async_functions,
boxed_bindings,
string_vars,
variable_types,
fast_div,
..
} = *ecx;
let names = RustNames::new(interner);
macro_rules! recurse {
($e:expr) => {
codegen_expr_ctx($e, ecx)
};
}
macro_rules! irecurse {
($e:expr) => {
codegen_expr_ctx($e, &ExprCtx { int_index_context: true, ..*ecx })
};
}
match expr {
Expr::Literal(lit) => codegen_literal(lit, interner),
Expr::Identifier(sym) => {
let name = names.ident(*sym);
let base = if boxed_bindings.contains(sym) {
format!("(*{})", name)
} else {
name
};
if variable_types.get(sym).map_or(false, |t| t.contains("__bigint")) {
if ecx.int_index_context {
format!("{}.expect_i64(\"Int\")", base)
} else {
format!("{}.clone()", base)
}
} else {
base
}
}
Expr::BinaryOp { op, left, right } => {
if matches!(op, BinaryOpKind::Modulo | BinaryOpKind::Divide)
&& !mentions_bigint_var(left, variable_types)
{
if let Expr::Identifier(n) = right {
if let Some(helper) = fast_div.get(n) {
let dividend = recurse!(left);
let method = if matches!(op, BinaryOpKind::Modulo) { "rem" } else { "div" };
return format!("({}.{}(({}) as u64) as i64)", helper, method, dividend);
}
}
}
let is_string_concat = matches!(op, BinaryOpKind::Concat)
|| (matches!(op, BinaryOpKind::Add)
&& (is_definitely_string_expr_with_vars(left, string_vars)
|| is_definitely_string_expr_with_vars(right, string_vars)));
if is_string_concat {
let mut operands = Vec::new();
collect_string_concat_operands(expr, string_vars, &mut operands);
let placeholders: String = operands.iter().map(|_| "{}").collect::<Vec<_>>().join("");
let values: Vec<String> = operands.iter().map(|e| {
if let Expr::Literal(Literal::Text(sym)) = e {
format!("\"{}\"", interner.resolve(*sym))
} else {
recurse!(e)
}
}).collect();
return format!("format!(\"{}\", {})", placeholders, values.join(", "));
}
if matches!(op, BinaryOpKind::SeqConcat) {
let l = recurse!(left);
let r = recurse!(right);
return format!(
"LogosSeq::from_vec({{ let mut __sc = ({}).to_vec(); __sc.extend(({}).to_vec()); __sc }})",
l, r
);
}
if matches!(op, BinaryOpKind::ApproxEq) {
let l = recurse!(left);
let r = recurse!(right);
return format!("logos_approx_eq(({}) as f64, ({}) as f64)", l, r);
}
if matches!(op, BinaryOpKind::BitAnd | BinaryOpKind::BitOr | BinaryOpKind::BitXor | BinaryOpKind::Subtract) {
let set_typed = |e: &Expr| -> bool {
matches!(e, Expr::Identifier(sym)
if variable_types.get(sym).is_some_and(|t| t.starts_with("Set<") || t.starts_with("FxHashSet<")))
|| matches!(e, Expr::Call { function, .. } if names.raw(*function) == "setOf")
};
if set_typed(left) || set_typed(right) {
let l = recurse!(left);
let r = recurse!(right);
let method = match op {
BinaryOpKind::BitAnd => "intersection",
BinaryOpKind::BitOr => "union",
BinaryOpKind::BitXor => "symmetric_difference",
_ => "difference",
};
return format!("({}).{}(&({})).cloned().collect::<Set<_>>()", l, method, r);
}
}
if matches!(op, BinaryOpKind::Add)
&& (is_definitely_seq_expr(left, variable_types, interner)
|| is_definitely_seq_expr(right, variable_types, interner))
{
let l = recurse!(left);
let r = recurse!(right);
return format!(
"LogosSeq::from_vec({{ let mut __sc = ({}).to_vec(); __sc.extend(({}).to_vec()); __sc }})",
l, r
);
}
if matches!(op, BinaryOpKind::Multiply) {
let seq_first = is_definitely_seq_expr(left, variable_types, interner);
let seq_second = is_definitely_seq_expr(right, variable_types, interner);
if seq_first || seq_second {
let (seq_e, n_e) = if seq_first { (left, right) } else { (right, left) };
let s = recurse!(seq_e);
let n = recurse!(n_e);
return format!(
"{{ let __rp_src = ({}).to_vec(); let __rp_n = (({}) as i64).max(0) as usize; \
let mut __rp = Vec::with_capacity(__rp_src.len() * __rp_n); \
for _ in 0..__rp_n {{ __rp.extend(__rp_src.iter().map(|__e| __e.fill_clone())); }} \
LogosSeq::from_vec(__rp) }}",
s, n
);
}
}
if matches!(op, BinaryOpKind::Eq | BinaryOpKind::NotEq) {
let neg = matches!(op, BinaryOpKind::NotEq);
if let Expr::Index { collection, index } = left {
if let Expr::Identifier(sym) = collection {
if let Some(t) = variable_types.get(sym) {
if is_logos_map_type(t) {
let coll_str = recurse!(collection);
let key_str = irecurse!(index);
let val_str = recurse!(right);
let cmp = if neg { "!=" } else { "==" };
return format!("({}.get(&({})) {} Some({}))", coll_str, key_str, cmp, val_str);
} else if t.starts_with("std::collections::HashMap") || t.starts_with("HashMap") || t.starts_with("rustc_hash::FxHashMap") || t.starts_with("FxHashMap") {
let coll_str = recurse!(collection);
let key_str = irecurse!(index);
let val_str = recurse!(right);
let cmp = if neg { "!=" } else { "==" };
if has_copy_value_type(t) {
return format!("({}.get(&({})).copied() {} Some({}))", coll_str, key_str, cmp, val_str);
} else {
return format!("({}.get(&({})) {} Some(&({})))", coll_str, key_str, cmp, val_str);
}
}
}
}
}
if let Expr::Index { collection, index } = right {
if let Expr::Identifier(sym) = collection {
if let Some(t) = variable_types.get(sym) {
if is_logos_map_type(t) {
let coll_str = recurse!(collection);
let key_str = irecurse!(index);
let val_str = recurse!(left);
let cmp = if neg { "!=" } else { "==" };
return format!("(Some({}) {} {}.get(&({})))", val_str, cmp, coll_str, key_str);
} else if t.starts_with("std::collections::HashMap") || t.starts_with("HashMap") || t.starts_with("rustc_hash::FxHashMap") || t.starts_with("FxHashMap") {
let coll_str = recurse!(collection);
let key_str = irecurse!(index);
let val_str = recurse!(left);
let cmp = if neg { "!=" } else { "==" };
if has_copy_value_type(t) {
return format!("(Some({}) {} {}.get(&({})).copied())", val_str, cmp, coll_str, key_str);
} else {
return format!("(Some(&({})) {} {}.get(&({})))", val_str, cmp, coll_str, key_str);
}
}
}
}
}
if let (Expr::Index { collection: left_coll, index: left_idx },
Expr::Index { collection: right_coll, index: right_idx }) = (left, right) {
let left_is_string = if let Expr::Identifier(sym) = left_coll {
string_vars.contains(sym) || variable_types.get(sym).map_or(false, |t| t == "String")
} else { false };
let right_is_string = if let Expr::Identifier(sym) = right_coll {
string_vars.contains(sym) || variable_types.get(sym).map_or(false, |t| t == "String")
} else { false };
if left_is_string && right_is_string {
let cmp = if neg { "!=" } else { "==" };
let left_coll_str = recurse!(left_coll);
let right_coll_str = recurse!(right_coll);
let left_idx_simplified = super::peephole::simplify_1based_index(left_idx, interner, true, variable_types);
let right_idx_simplified = super::peephole::simplify_1based_index(right_idx, interner, true, variable_types);
return format!("({}.as_bytes()[{}] {} {}.as_bytes()[{}])",
left_coll_str, left_idx_simplified, cmp, right_coll_str, right_idx_simplified);
}
}
let is_string_index = |expr: &Expr| -> bool {
if let Expr::Index { collection, .. } = expr {
if let Expr::Identifier(sym) = collection {
return string_vars.contains(sym) || variable_types.get(sym).map_or(false, |t| t == "String");
}
}
false
};
let single_char_literal = |expr: &Expr| -> Option<char> {
if let Expr::Literal(Literal::Text(sym)) = expr {
let s = interner.resolve(*sym);
let mut chars = s.chars();
if let Some(c) = chars.next() {
if chars.next().is_none() {
return Some(c);
}
}
}
None
};
if is_string_index(left) {
if let Some(ch) = single_char_literal(right) {
if let Expr::Index { collection, index } = left {
let coll_str = recurse!(collection);
let idx_str = irecurse!(index);
let cmp = if neg { "!=" } else { "==" };
let ch_escaped = match ch {
'\'' => "\\'".to_string(),
'\\' => "\\\\".to_string(),
'\n' => "\\n".to_string(),
'\t' => "\\t".to_string(),
'\r' => "\\r".to_string(),
_ => ch.to_string(),
};
return format!("({}.logos_get_char({}) {} '{}')",
coll_str, idx_str, cmp, ch_escaped);
}
}
}
if is_string_index(right) {
if let Some(ch) = single_char_literal(left) {
if let Expr::Index { collection, index } = right {
let coll_str = recurse!(collection);
let idx_str = irecurse!(index);
let cmp = if neg { "!=" } else { "==" };
let ch_escaped = match ch {
'\'' => "\\'".to_string(),
'\\' => "\\\\".to_string(),
'\n' => "\\n".to_string(),
'\t' => "\\t".to_string(),
'\r' => "\\r".to_string(),
_ => ch.to_string(),
};
return format!("('{}' {} {}.logos_get_char({}))",
ch_escaped, cmp, coll_str, idx_str);
}
}
}
}
if matches!(op, BinaryOpKind::Lt | BinaryOpKind::LtEq | BinaryOpKind::Gt
| BinaryOpKind::GtEq | BinaryOpKind::Eq | BinaryOpKind::NotEq)
{
let left_zb = if let Expr::Identifier(sym) = left {
variable_types.get(sym).map_or(false, |t| t == "__zero_based_i64")
} else { false };
let right_zb = if let Expr::Identifier(sym) = right {
variable_types.get(sym).map_or(false, |t| t == "__zero_based_i64")
} else { false };
if left_zb || right_zb {
let left_str = if left_zb {
format!("({} + 1)", recurse!(left))
} else { recurse!(left) };
let right_str = if right_zb {
format!("({} + 1)", recurse!(right))
} else { recurse!(right) };
let op_str = match op {
BinaryOpKind::Lt => "<", BinaryOpKind::LtEq => "<=",
BinaryOpKind::Gt => ">", BinaryOpKind::GtEq => ">=",
BinaryOpKind::Eq => "==", BinaryOpKind::NotEq => "!=",
_ => unreachable!(),
};
return format!("({} {} {})", left_str, op_str, right_str);
}
}
let left_str = recurse!(left);
let right_str = recurse!(right);
if matches!(op, BinaryOpKind::And | BinaryOpKind::Or) {
let op_str = match op {
BinaryOpKind::And => "&&",
BinaryOpKind::Or => "||",
_ => unreachable!(),
};
let bools = matches!(
infer_logos_type(left, interner, variable_types),
crate::analysis::types::LogosType::Bool
) && matches!(
infer_logos_type(right, interner, variable_types),
crate::analysis::types::LogosType::Bool
);
return if bools {
format!("({} {} {})", left_str, op_str, right_str)
} else {
format!(
"(logos_truthy(&({})) {} logos_truthy(&({})))",
left_str, op_str, right_str
)
};
}
if matches!(
op,
BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
| BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq
| BinaryOpKind::Eq | BinaryOpKind::NotEq
) && (is_quantity_expr(left, variable_types, interner)
|| is_quantity_expr(right, variable_types, interner))
{
let l_q = is_quantity_expr(left, variable_types, interner);
let r_q = is_quantity_expr(right, variable_types, interner);
let code: Option<std::string::String> = match (op, l_q, r_q) {
(BinaryOpKind::Add, true, true) => Some(format!("{}.add(&{})", left_str, right_str)),
(BinaryOpKind::Subtract, true, true) => Some(format!("{}.sub(&{})", left_str, right_str)),
(BinaryOpKind::Multiply, true, true) => Some(format!("{}.mul(&{})", left_str, right_str)),
(BinaryOpKind::Divide, true, true) => Some(format!("{}.div_exact(&{})", left_str, right_str)),
(BinaryOpKind::Multiply, true, false) => Some(format!("{}.scale_int({})", left_str, right_str)),
(BinaryOpKind::Multiply, false, true) => Some(format!("{}.scale_int({})", right_str, left_str)),
(BinaryOpKind::Divide, true, false) => Some(format!("{}.div_int({})", left_str, right_str)),
(BinaryOpKind::Lt, true, true) => Some(format!("({} < {})", left_str, right_str)),
(BinaryOpKind::Gt, true, true) => Some(format!("({} > {})", left_str, right_str)),
(BinaryOpKind::LtEq, true, true) => Some(format!("({} <= {})", left_str, right_str)),
(BinaryOpKind::GtEq, true, true) => Some(format!("({} >= {})", left_str, right_str)),
(BinaryOpKind::Eq, true, true) => Some(format!("({} == {})", left_str, right_str)),
(BinaryOpKind::NotEq, true, true) => Some(format!("({} != {})", left_str, right_str)),
_ => None,
};
if let Some(c) = code {
return c;
}
}
if matches!(
op,
BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
| BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq
| BinaryOpKind::Eq | BinaryOpKind::NotEq
) && (is_money_expr(left, variable_types, interner)
|| is_money_expr(right, variable_types, interner))
{
let l_m = is_money_expr(left, variable_types, interner);
let r_m = is_money_expr(right, variable_types, interner);
let code: Option<std::string::String> = match (op, l_m, r_m) {
(BinaryOpKind::Add, true, true) => Some(format!("{}.add(&{})", left_str, right_str)),
(BinaryOpKind::Subtract, true, true) => Some(format!("{}.sub(&{})", left_str, right_str)),
(BinaryOpKind::Divide, true, true) => Some(format!("{}.ratio(&{})", left_str, right_str)),
(BinaryOpKind::Multiply, true, false) => Some(format!("{}.scale_int({})", left_str, right_str)),
(BinaryOpKind::Multiply, false, true) => Some(format!("{}.scale_int({})", right_str, left_str)),
(BinaryOpKind::Divide, true, false) => Some(format!("{}.div_int({})", left_str, right_str)),
(BinaryOpKind::Lt, true, true) => Some(format!("({} < {})", left_str, right_str)),
(BinaryOpKind::Gt, true, true) => Some(format!("({} > {})", left_str, right_str)),
(BinaryOpKind::LtEq, true, true) => Some(format!("({} <= {})", left_str, right_str)),
(BinaryOpKind::GtEq, true, true) => Some(format!("({} >= {})", left_str, right_str)),
(BinaryOpKind::Eq, true, true) => Some(format!("({} == {})", left_str, right_str)),
(BinaryOpKind::NotEq, true, true) => Some(format!("({} != {})", left_str, right_str)),
_ => None,
};
if let Some(c) = code {
return c;
}
}
if matches!(
op,
BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
) && is_modular_expr(left, variable_types, interner)
&& is_modular_expr(right, variable_types, interner)
{
let method = match op {
BinaryOpKind::Add => "add",
BinaryOpKind::Subtract => "sub",
BinaryOpKind::Multiply => "mul",
BinaryOpKind::Divide => "div_exact",
_ => unreachable!(),
};
return format!("{}.{}(&{})", left_str, method, right_str);
}
if matches!(
op,
BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply | BinaryOpKind::Divide
) && (is_complex_expr(left, variable_types, interner)
|| is_complex_expr(right, variable_types, interner))
{
let l = complex_operand(left, &left_str, variable_types, interner);
let r = complex_operand(right, &right_str, variable_types, interner);
let method = match op {
BinaryOpKind::Add => "add",
BinaryOpKind::Subtract => "sub",
BinaryOpKind::Multiply => "mul",
BinaryOpKind::Divide => "div_exact",
_ => unreachable!(),
};
return format!("{}.{}(&{})", l, method, r);
}
if matches!(op, BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply)
&& (is_decimal_expr(left, variable_types, interner)
|| is_decimal_expr(right, variable_types, interner))
{
let l = decimal_operand(left, &left_str, variable_types, interner);
let r = decimal_operand(right, &right_str, variable_types, interner);
let method = match op {
BinaryOpKind::Add => "add",
BinaryOpKind::Subtract => "sub",
BinaryOpKind::Multiply => "mul",
_ => unreachable!(),
};
return format!("{}.{}(&{})", l, method, r);
}
if matches!(op, BinaryOpKind::Divide)
&& (is_decimal_expr(left, variable_types, interner)
|| is_decimal_expr(right, variable_types, interner))
{
let l = decimal_operand(left, &left_str, variable_types, interner);
let r = decimal_operand(right, &right_str, variable_types, interner);
return format!("{}.to_rational().div_exact(&{}.to_rational())", l, r);
}
if matches!(
op,
BinaryOpKind::Lt | BinaryOpKind::Gt | BinaryOpKind::LtEq | BinaryOpKind::GtEq
| BinaryOpKind::Eq | BinaryOpKind::NotEq
) && (is_decimal_expr(left, variable_types, interner)
|| is_decimal_expr(right, variable_types, interner))
{
let l = decimal_operand(left, &left_str, variable_types, interner);
let r = decimal_operand(right, &right_str, variable_types, interner);
let op_str = match op {
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::LtEq => "<=",
BinaryOpKind::GtEq => ">=",
BinaryOpKind::Eq => "==",
BinaryOpKind::NotEq => "!=",
_ => unreachable!(),
};
return format!("({} {} {})", l, op_str, r);
}
if matches!(op, BinaryOpKind::Eq | BinaryOpKind::NotEq)
&& (is_complex_expr(left, variable_types, interner)
|| is_complex_expr(right, variable_types, interner))
{
let l = complex_operand(left, &left_str, variable_types, interner);
let r = complex_operand(right, &right_str, variable_types, interner);
let op_str = if matches!(op, BinaryOpKind::Eq) { "==" } else { "!=" };
return format!("({} {} {})", l, op_str, r);
}
if matches!(op, BinaryOpKind::ExactDivide)
|| (matches!(
op,
BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply
) && (is_rational_expr(left, variable_types)
|| is_rational_expr(right, variable_types)))
{
let l = rational_operand(left, &left_str, variable_types);
let r = rational_operand(right, &right_str, variable_types);
let method = match op {
BinaryOpKind::Add => "add",
BinaryOpKind::Subtract => "sub",
BinaryOpKind::Multiply => "mul",
BinaryOpKind::ExactDivide => "div_exact",
_ => unreachable!(),
};
return format!("{}.{}(&{})", l, method, r);
}
if matches!(op, BinaryOpKind::Pow) {
let lt = infer_numeric_type(left, interner, variable_types);
let rt = infer_numeric_type(right, interner, variable_types);
if lt == "f64" || rt == "f64" {
let l = if lt == "f64" { left_str.clone() } else { format!("(({}) as f64)", left_str) };
let r = if rt == "f64" { right_str.clone() } else { format!("(({}) as f64)", right_str) };
return format!("({}).powf({})", l, r);
}
if let (Expr::Literal(Literal::Number(a)), Expr::Literal(Literal::Number(b))) = (left, right) {
if let Ok(e) = u32::try_from(*b) {
if let Some(v) = a.checked_pow(e) {
return format!("{}i64", v);
}
}
}
let tol = ExprCtx { int_exact_tolerant: true, ..*ecx };
let l = codegen_expr_ctx(left, &tol);
let r = codegen_expr_ctx(right, &tol);
let l = if matches!(left, Expr::Literal(Literal::Number(_))) { format!("{}i64", l) } else { l };
let r = if matches!(right, Expr::Literal(Literal::Number(_))) { format!("{}i64", r) } else { r };
let inner = format!("logos_pow_exact({}, {})", l, r);
return if ecx.int_exact_tolerant {
inner
} else {
format!("{}.expect_i64(\"Int\")", inner)
};
}
if matches!(op, BinaryOpKind::FloorDivide) {
let lt = infer_numeric_type(left, interner, variable_types);
let rt = infer_numeric_type(right, interner, variable_types);
if lt == "f64" || rt == "f64" {
let l = if lt == "f64" { left_str.clone() } else { format!("(({}) as f64)", left_str) };
let r = if rt == "f64" { right_str.clone() } else { format!("(({}) as f64)", right_str) };
return format!("({} / {}).floor()", l, r);
}
if let (Expr::Literal(Literal::Number(a)), Expr::Literal(Literal::Number(b))) = (left, right) {
if *b != 0 {
if let (Some(q), Some(r)) = (a.checked_div(*b), a.checked_rem(*b)) {
let floored = if r != 0 && (r < 0) != (*b < 0) { q - 1 } else { q };
return format!("{}i64", floored);
}
}
}
let tol = ExprCtx { int_exact_tolerant: true, ..*ecx };
let l = codegen_expr_ctx(left, &tol);
let r = codegen_expr_ctx(right, &tol);
let l = if matches!(left, Expr::Literal(Literal::Number(_))) { format!("{}i64", l) } else { l };
let r = if matches!(right, Expr::Literal(Literal::Number(_))) { format!("{}i64", r) } else { r };
let inner = format!("logos_floordiv_exact({}, {})", l, r);
return if ecx.int_exact_tolerant {
inner
} else {
format!("{}.expect_i64(\"Int\")", inner)
};
}
let op_str = match op {
BinaryOpKind::Add => "+",
BinaryOpKind::Subtract => "-",
BinaryOpKind::Multiply => "*",
BinaryOpKind::Divide | BinaryOpKind::ExactDivide => "/",
BinaryOpKind::Modulo => "%",
BinaryOpKind::Eq => "==",
BinaryOpKind::NotEq => "!=",
BinaryOpKind::Lt => "<",
BinaryOpKind::Gt => ">",
BinaryOpKind::LtEq => "<=",
BinaryOpKind::GtEq => ">=",
BinaryOpKind::And | BinaryOpKind::Or => unreachable!(), BinaryOpKind::Concat => unreachable!(), BinaryOpKind::SeqConcat => unreachable!(), BinaryOpKind::ApproxEq => unreachable!(), BinaryOpKind::Pow => unreachable!(), BinaryOpKind::FloorDivide => unreachable!(), BinaryOpKind::BitXor => "^",
BinaryOpKind::BitAnd => "&",
BinaryOpKind::BitOr => "|",
BinaryOpKind::Shl => "<<",
BinaryOpKind::Shr => ">>",
};
if !matches!(op, BinaryOpKind::And | BinaryOpKind::Or) {
let left_type = infer_numeric_type(left, interner, variable_types);
let right_type = infer_numeric_type(right, interner, variable_types);
let is_cmp = matches!(
op,
BinaryOpKind::Eq
| BinaryOpKind::NotEq
| BinaryOpKind::Lt
| BinaryOpKind::Gt
| BinaryOpKind::LtEq
| BinaryOpKind::GtEq
);
let mixed_cmp = if is_cmp && left_type == "f64" && right_type == "i64" {
Some((right_str.clone(), left_str.clone(), true))
} else if is_cmp && left_type == "i64" && right_type == "f64" {
Some((left_str.clone(), right_str.clone(), false))
} else {
None
};
if let Some((int_e, float_e, flip)) = mixed_cmp {
let pat = match (op, flip) {
(BinaryOpKind::Lt, false) | (BinaryOpKind::Gt, true) => "Some(core::cmp::Ordering::Less)",
(BinaryOpKind::Gt, false) | (BinaryOpKind::Lt, true) => "Some(core::cmp::Ordering::Greater)",
(BinaryOpKind::LtEq, false) | (BinaryOpKind::GtEq, true) => {
"Some(core::cmp::Ordering::Less | core::cmp::Ordering::Equal)"
}
(BinaryOpKind::GtEq, false) | (BinaryOpKind::LtEq, true) => {
"Some(core::cmp::Ordering::Greater | core::cmp::Ordering::Equal)"
}
(BinaryOpKind::Eq, _) => {
return format!("logos_i64_eq_f64({}, {})", int_e, float_e);
}
(BinaryOpKind::NotEq, _) => {
return format!("(!logos_i64_eq_f64({}, {}))", int_e, float_e);
}
_ => unreachable!("is_cmp covers exactly the six comparison ops"),
};
return format!("matches!(logos_cmp_i64_f64({}, {}), {})", int_e, float_e, pat);
}
if left_type == "f64" && right_type != "f64" {
return format!("({} {} (({}) as f64))", left_str, op_str, right_str);
} else if right_type == "f64" && left_type != "f64" {
return format!("((({}) as f64) {} {})", left_str, op_str, right_str);
}
if ecx.int_wrapping
&& left_type == "i64"
&& right_type == "i64"
&& !ecx.int_index_context
&& matches!(op, BinaryOpKind::Add | BinaryOpKind::Subtract | BinaryOpKind::Multiply)
{
let method = match op {
BinaryOpKind::Add => "wrapping_add",
BinaryOpKind::Subtract => "wrapping_sub",
BinaryOpKind::Multiply => "wrapping_mul",
_ => unreachable!("matches! guards exactly these three"),
};
let l = codegen_expr_ctx(left, ecx);
let r = codegen_expr_ctx(right, ecx);
return format!("(({}) as i64).{}({})", l, method, r);
}
let is_exactable = matches!(
op,
BinaryOpKind::Add
| BinaryOpKind::Subtract
| BinaryOpKind::Multiply
| BinaryOpKind::Divide
| BinaryOpKind::Modulo
);
let has_bigint_operand = mentions_bigint_var(left, variable_types)
|| mentions_bigint_var(right, variable_types);
if is_exactable
&& (left_type == "i64" && right_type == "i64" || has_bigint_operand)
&& !ecx.int_index_context
{
let narrow = |e: String| {
if ecx.int_exact_tolerant {
e
} else {
format!("{}.expect_i64(\"Int\")", e)
}
};
if let Expr::Literal(Literal::Number(d)) = right {
let safe_div = matches!(op, BinaryOpKind::Divide) && *d != 0 && *d != -1;
let safe_mod = matches!(op, BinaryOpKind::Modulo) && *d != 0;
if (safe_div || safe_mod) && !has_bigint_operand {
let l = if ecx.int_exact_tolerant {
codegen_expr_ctx(left, &ExprCtx { int_exact_tolerant: false, ..*ecx })
} else {
left_str.clone()
};
return format!("({} {} {})", l, op_str, right_str);
}
}
if let (Expr::Literal(Literal::Number(a)), Expr::Literal(Literal::Number(b))) =
(left, right)
{
match const_exact_int(*op, *a, *b) {
Some(ConstExact::InRange) => {
return format!("({} {} {})", left_str, op_str, right_str);
}
Some(ConstExact::Promoted(lit)) => return narrow(lit),
None => {}
}
} else if !has_bigint_operand && oracle_proves_int_op_in_range(ecx, expr, *op, left, right) {
return format!("({} {} {})", left_str, op_str, right_str);
}
if !ecx.int_exact_tolerant && !has_bigint_operand {
if let Some(fused) = try_fuse_narrowed_int_op(*op, left, right, ecx) {
return fused;
}
if let Some(dual) = try_guarded_dual_chain(*op, left, right, ecx) {
return dual;
}
if let Some(chain) = try_i128_chain(*op, left, right, ecx) {
return chain;
}
}
let helper = match op {
BinaryOpKind::Add => "logos_add_exact",
BinaryOpKind::Subtract => "logos_sub_exact",
BinaryOpKind::Multiply => "logos_mul_exact",
BinaryOpKind::Divide => "logos_div_exact",
BinaryOpKind::Modulo => "logos_rem_exact",
_ => unreachable!("is_exactable covers exactly these five"),
};
let tol = ExprCtx { int_exact_tolerant: true, ..*ecx };
let l = codegen_expr_ctx(left, &tol);
let r = codegen_expr_ctx(right, &tol);
return narrow(format!("{}({}, {})", helper, l, r));
}
}
format!("({} {} {})", left_str, op_str, right_str)
}
Expr::Call { function, args } => {
let func_name = names.ident(*function);
let raw_name = names.raw(*function);
let callee_slot = variable_types.get(function);
let callee_borrow_indices: HashSet<usize> =
super::fn_role_indices(callee_slot, super::FnRole::Borrow);
let callee_value_mutable: HashSet<usize> =
super::fn_role_indices(callee_slot, super::FnRole::ValueMutable);
let arg_wrapping = matches!(raw_name, "word8" | "word16" | "word32" | "word64");
let args_str: Vec<String> = args.iter()
.enumerate()
.map(|(i, a)| {
let s = codegen_expr_ctx(a, &ExprCtx { int_wrapping: arg_wrapping, ..*ecx });
if callee_value_mutable.contains(&i) {
if let Expr::Identifier(sym) = a {
if variable_types.get(sym).is_some_and(|t| t.starts_with('&')) {
return s;
}
}
return format!("&{}", s);
}
if callee_borrow_indices.contains(&i) {
if let Expr::Identifier(sym) = a {
if let Some(ty) = variable_types.get(sym) {
let ty = ty.split("|__hl:").next().unwrap_or(ty.as_str());
if ty.starts_with("&[") || ty.starts_with("&mut [") {
return s; }
if ty.starts_with("Vec<") {
return format!("&{}", s); }
if ty.starts_with('[') {
return format!("&{}", s); }
if ty.starts_with("LogosSeq") {
return format!("&*{}.borrow()", s);
}
}
return format!("&*{}.borrow()", s);
}
format!("&*{}.borrow()", s)
} else {
if let Expr::Identifier(sym) = a {
if variable_types.get(sym).map_or(false, |t| t.contains("__bigint")) {
return format!("{}.expect_i64(\"Int\")", names.ident(*sym));
}
if let Some(ty) = variable_types.get(sym) {
if !is_copy_type(ty) {
return format!("{}.clone()", s);
}
} else {
return format!("{}.clone()", s);
}
}
s
}
})
.collect();
match raw_name {
"sqrt" if args_str.len() == 1 => {
format!("(({}) as f64).sqrt()", args_str[0])
}
"mapOf" if !args_str.is_empty() && args_str.len() % 2 == 0 => {
let mut s = String::from("{ let __map_lit = LogosMap::new(); ");
for pair in args_str.chunks(2) {
s.push_str(&format!("__map_lit.insert({}, {}); ", pair[0], pair[1]));
}
s.push_str("__map_lit }");
s
}
"repeatSeq" if args_str.len() == 2 => {
format!(
"{{ let __rp_x = ({}); let __rp_n = (({}) as i64).max(0) as usize; \
let mut __rp = Vec::with_capacity(__rp_n); \
for _ in 0..__rp_n {{ __rp.push(__rp_x.fill_clone()); }} \
LogosSeq::from_vec(__rp) }}",
args_str[0], args_str[1]
)
}
"setOf" if !args_str.is_empty() => {
let mut s = String::from("{ let mut __set_lit = Set::default(); ");
for a in &args_str {
s.push_str(&format!("__set_lit.insert({}); ", a));
}
s.push_str("__set_lit }");
s
}
"decimal" if args_str.len() == 1 => {
format!("LogosDecimal::parse(&({}).to_string())", args_str[0])
}
"complex" if args_str.len() == 2 => {
let re = rational_operand(&args[0], &args_str[0], variable_types);
let im = rational_operand(&args[1], &args_str[1], variable_types);
format!("LogosComplex::new({}, {})", re, im)
}
"modular" if args_str.len() == 2 => {
format!("LogosModular::new(({}) as i64, ({}) as i64)", args_str[0], args_str[1])
}
"quantity" if args_str.len() == 2 => {
let mag = rational_operand(&args[0], &args_str[0], variable_types);
format!("LogosQuantity::from_rational({}, &({}).to_string())", mag, args_str[1])
}
"convert" if args_str.len() == 2 => {
format!("({}).convert(&({}).to_string())", args_str[0], args_str[1])
}
"money" if args_str.len() == 2 => {
if is_decimal_expr(&args[0], variable_types, interner) {
let amt = decimal_operand(&args[0], &args_str[0], variable_types, interner);
format!("LogosMoney::of({}, &({}).to_string())", amt, args_str[1])
} else {
format!("LogosMoney::from_i64({}, &({}).to_string())", args_str[0], args_str[1])
}
}
"uuid" if args_str.len() == 1 => {
format!("LogosUuid::parse(&({}).to_string())", args_str[0])
}
"uuid_nil" if args_str.is_empty() => "LogosUuid::nil()".to_string(),
"uuid_max" if args_str.is_empty() => "LogosUuid::max()".to_string(),
"uuid_dns" if args_str.is_empty() => "LogosUuid::namespace_dns()".to_string(),
"uuid_url" if args_str.is_empty() => "LogosUuid::namespace_url()".to_string(),
"uuid_oid" if args_str.is_empty() => "LogosUuid::namespace_oid()".to_string(),
"uuid_x500" if args_str.is_empty() => "LogosUuid::namespace_x500()".to_string(),
"uuid_version" if args_str.len() == 1 => format!("({}).version()", args_str[0]),
"text_bytes" if args_str.len() == 1 => format!("text_bytes(&({}))", args_str[0]),
"text_from_bytes" if args_str.len() == 1 => format!("text_from_bytes(&({}))", args_str[0]),
"readWireProgram" if args_str.is_empty() => {
"{ use std::io::Read as _; let mut __len = [0u8; 4]; if std::io::stdin().read_exact(&mut __len).is_err() { std::process::exit(0); } let __n = u32::from_le_bytes(__len) as usize; let mut __wb = vec![0u8; __n]; std::io::stdin().read_exact(&mut __wb).expect(\"readWireProgram: frame\"); <CProgram as logicaffeine_data::wire::WireDecode>::wire_decode(&__wb, &mut 0usize).expect(\"readWireProgram: decode\") }".to_string()
}
"writeWireResidual" if args_str.len() == 1 => {
format!("{{ use std::io::Write as _; let __s: String = ({}).into(); let __b = __s.as_bytes(); let __o = std::io::stdout(); let mut __h = __o.lock(); __h.write_all(&(__b.len() as u32).to_le_bytes()).unwrap(); __h.write_all(__b).unwrap(); __h.flush().unwrap(); __b.len() as i64 }}", args_str[0])
}
"uuid_bytes" if args_str.len() == 1 => format!("({}).byte_seq()", args_str[0]),
"uuid_from_bytes" if args_str.len() == 1 => {
format!("LogosUuid::from_byte_seq(&({}))", args_str[0])
}
"chr" if args_str.len() == 1 => {
format!("logicaffeine_system::text::chr(({}) as i64)", args_str[0])
}
"parse_timestamp" if args_str.len() == 1 => {
format!("LogosMoment::parse_rfc3339(&({}).to_string())", args_str[0])
}
"format_timestamp" if args_str.len() == 1 => {
format!("({}).format_rfc3339()", args_str[0])
}
"year_of" if args_str.len() == 1 => format!("({}).year()", args_str[0]),
"month_of" if args_str.len() == 1 => format!("({}).month()", args_str[0]),
"day_of" if args_str.len() == 1 => format!("({}).day()", args_str[0]),
"weekday_of" if args_str.len() == 1 => format!("({}).weekday()", args_str[0]),
"hour_of" if args_str.len() == 1 => format!("({}).hour()", args_str[0]),
"minute_of" if args_str.len() == 1 => format!("({}).minute()", args_str[0]),
"second_of" if args_str.len() == 1 => format!("({}).second()", args_str[0]),
"week_of" if args_str.len() == 1 => format!("({}).iso_week()", args_str[0]),
"quarter_of" if args_str.len() == 1 => format!("({}).quarter()", args_str[0]),
"date_of" if args_str.len() == 1 => format!("({}).date()", args_str[0]),
"time_of" if args_str.len() == 1 => format!("({}).time_of_day()", args_str[0]),
"seconds_between" if args_str.len() == 2 => {
format!("({}).seconds_until(&({}))", args_str[0], args_str[1])
}
"months_between" if args_str.len() == 2 => {
format!("({}).months_until(&({}))", args_str[0], args_str[1])
}
"years_between" if args_str.len() == 2 => {
format!("({}).years_until(&({}))", args_str[0], args_str[1])
}
"add_seconds" if args_str.len() == 2 => {
format!("({}).add_seconds({})", args_str[0], args_str[1])
}
"in_zone" if args_str.len() == 2 => {
format!("({}).in_zone(&({}).to_string())", args_str[0], args_str[1])
}
"local_instant" if args_str.len() == 2 => {
format!("({}).local_instant(&({}).to_string())", args_str[0], args_str[1])
}
"lanes4Word32" if args_str.len() == 1 => {
format!("lanes4_word32(&({}))", args_str[0])
}
"lanes4Of" if args_str.len() == 4 => {
format!("lanes4_of({}, {}, {}, {})", args_str[0], args_str[1], args_str[2], args_str[3])
}
"lanes16Word8" if args_str.len() == 1 => format!("lanes16_word8(&({}))", args_str[0]),
"seqOfLanes16W8" if args_str.len() == 1 => format!("seq_of_lanes16w8({})", args_str[0]),
"splat16Word8" if args_str.len() == 1 => format!("splat16_word8({})", args_str[0]),
"shuffle16" if args_str.len() == 2 => {
format!("shuffle16({}, {})", args_str[0], args_str[1])
}
"shrBytes16" if args_str.len() == 2 => {
format!("shr_bytes16({}, {})", args_str[0], args_str[1])
}
"interleaveLo16" if args_str.len() == 2 => {
format!("interleave_lo16({}, {})", args_str[0], args_str[1])
}
"interleaveHi16" if args_str.len() == 2 => {
format!("interleave_hi16({}, {})", args_str[0], args_str[1])
}
"byteAdd16" if args_str.len() == 2 => {
format!("byte_add16({}, {})", args_str[0], args_str[1])
}
"maddubs16" if args_str.len() == 2 => {
format!("maddubs16({}, {})", args_str[0], args_str[1])
}
"packus16" if args_str.len() == 2 => {
format!("packus16({}, {})", args_str[0], args_str[1])
}
"seqOfLanes4W32" if args_str.len() == 1 => {
format!("seq_of_lanes4w32({})", args_str[0])
}
"sha1rnds4" if args_str.len() == 3 => {
format!("sha1rnds4({}, {}, {})", args_str[0], args_str[1], args_str[2])
}
"sha1msg1" if args_str.len() == 2 => {
format!("sha1msg1({}, {})", args_str[0], args_str[1])
}
"sha1msg2" if args_str.len() == 2 => {
format!("sha1msg2({}, {})", args_str[0], args_str[1])
}
"sha1nexte" if args_str.len() == 2 => {
format!("sha1nexte({}, {})", args_str[0], args_str[1])
}
"lanes8Word32" if args_str.len() == 1 => {
format!("lanes8_word32(&({}))", args_str[0])
}
"seqOfLanes8" if args_str.len() == 1 => {
format!("seq_of_lanes8({})", args_str[0])
}
"splat8Word32" if args_str.len() == 1 => {
format!("splat8_word32({})", args_str[0])
}
"intOfWord32" if args_str.len() == 1 => {
format!("int_of_word32({})", args_str[0])
}
"intOfWord64" if args_str.len() == 1 => {
format!("int_of_word64({})", args_str[0])
}
"word64Shl" if args_str.len() == 2 => {
format!("word64_shl({}, {})", args_str[0], args_str[1])
}
"word64Shr" if args_str.len() == 2 => {
format!("word64_shr({}, {})", args_str[0], args_str[1])
}
"word32Shr" if args_str.len() == 2 => {
format!("word32_shr({}, {})", args_str[0], args_str[1])
}
"word64And" if args_str.len() == 2 => {
format!("word64_and({}, {})", args_str[0], args_str[1])
}
"word16" if args_str.len() == 1 => {
format!("word16({})", args_str[0])
}
"intOfWord16" if args_str.len() == 1 => {
format!("int_of_word16({})", args_str[0])
}
"lanes4Word64" if args_str.len() == 1 => {
format!("lanes4_word64(&({}))", args_str[0])
}
"seqOfLanes4" if args_str.len() == 1 => {
format!("seq_of_lanes4({})", args_str[0])
}
"mul32x32to64" if args_str.len() == 2 => {
format!("mul32x32to64({}, {})", args_str[0], args_str[1])
}
"hsumLanes4" if args_str.len() == 1 => {
format!("hsum_lanes4({})", args_str[0])
}
"splat4Word64" if args_str.len() == 1 => {
format!("splat4_word64({})", args_str[0])
}
"andNot4" if args_str.len() == 2 => {
format!("and_not4({}, {})", args_str[0], args_str[1])
}
"lanes16Word16" if args_str.len() == 1 => {
format!("lanes16_word16(&({}))", args_str[0])
}
"seqOfLanes16" if args_str.len() == 1 => {
format!("seq_of_lanes16({})", args_str[0])
}
"splat16Word16" if args_str.len() == 1 => {
format!("splat16_word16({})", args_str[0])
}
"mulhi16" if args_str.len() == 2 => {
format!("mulhi16({}, {})", args_str[0], args_str[1])
}
"montmul32" if args_str.len() == 4 => {
format!("montmul32({}, {}, {}, {})", args_str[0], args_str[1], args_str[2], args_str[3])
}
"nttBcastLo" if args_str.len() == 2 => {
format!("({}).ntt_bcast_lo(({}) as usize)", args_str[0], args_str[1])
}
"nttBcastHi" if args_str.len() == 2 => {
format!("({}).ntt_bcast_hi(({}) as usize)", args_str[0], args_str[1])
}
"nttBlend" if args_str.len() == 3 => {
format!("({}).ntt_blend({}, ({}) as usize)", args_str[0], args_str[1], args_str[2])
}
"pow" if args_str.len() == 2 && is_modular_expr(&args[0], variable_types, interner) => {
format!("({}).pow(({}) as u64)", args_str[0], args_str[1])
}
"abs" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
format!("({}).abs()", args_str[0])
}
"floor" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
format!("({}).floor()", args_str[0])
}
"ceil" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
format!("({}).ceil()", args_str[0])
}
"round" if args_str.len() == 1 && is_rational_expr(&args[0], variable_types) => {
format!("({}).round()", args_str[0])
}
"abs" if args_str.len() == 1 => {
let arg_type = infer_numeric_type(&args[0], interner, variable_types);
if arg_type == "f64" {
format!("(({}) as f64).abs()", args_str[0])
} else {
format!("(({}) as i64).abs()", args_str[0])
}
}
"floor" if args_str.len() == 1 => {
format!("((({}) as f64).floor() as i64)", args_str[0])
}
"ceil" if args_str.len() == 1 => {
format!("((({}) as f64).ceil() as i64)", args_str[0])
}
"round" if args_str.len() == 1 => {
format!("((({}) as f64).round() as i64)", args_str[0])
}
"pow" if args_str.len() == 2 => {
format!("((({}) as f64).powf(({}) as f64))", args_str[0], args_str[1])
}
"min" if args_str.len() == 2 => {
format!("({}).min({})", args_str[0], args_str[1])
}
"max" if args_str.len() == 2 => {
format!("({}).max({})", args_str[0], args_str[1])
}
_ => {
if async_functions.contains(function) {
format!("{}({}).await", func_name, args_str.join(", "))
} else {
format!("{}({})", func_name, args_str.join(", "))
}
}
}
}
Expr::Index { collection: Expr::Identifier(sym), index }
if affine_array_coeff_offset(variable_types.get(sym)).is_some() =>
{
let (coeff, offset) = affine_array_coeff_offset(variable_types.get(sym)).unwrap();
enum Pos {
Const(i64),
Expr(String),
}
let pos = match index {
Expr::Literal(Literal::Number(n)) => Pos::Const(n - 1),
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => match (left, right) {
(_, Expr::Literal(Literal::Number(1))) => Pos::Expr(recurse!(left)),
(Expr::Literal(Literal::Number(1)), _) => Pos::Expr(recurse!(right)),
(_, Expr::Literal(Literal::Number(k))) if *k > 1 => {
Pos::Expr(format!("({} + {})", recurse!(left), k - 1))
}
(Expr::Literal(Literal::Number(k)), _) if *k > 1 => {
Pos::Expr(format!("({} + {})", recurse!(right), k - 1))
}
_ => Pos::Expr(format!("(({}) - 1)", irecurse!(index))),
},
_ => Pos::Expr(format!("(({}) - 1)", irecurse!(index))),
};
match pos {
Pos::Const(p) => format!("{}i64", coeff.wrapping_mul(p).wrapping_add(offset)),
Pos::Expr(_) if coeff == 0 => format!("{}i64", offset),
Pos::Expr(p) => {
let base = if coeff == 1 { p } else { format!("(({}) * {})", p, coeff) };
if offset == 0 {
base
} else {
format!("({} + {})", base, offset)
}
}
}
}
Expr::Index { collection: Expr::Identifier(sym), index }
if parse_aos_tag(variable_types.get(sym)).is_some() =>
{
let tag = parse_aos_tag(variable_types.get(sym)).unwrap();
let row = match index {
Expr::Literal(Literal::Number(1)) => "0".to_string(),
Expr::Literal(Literal::Number(n)) => format!("{}", n - 1),
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => match (left, right) {
(_, Expr::Literal(Literal::Number(1))) => format!("({}) as usize", recurse!(left)),
(Expr::Literal(Literal::Number(1)), _) => format!("({}) as usize", recurse!(right)),
_ => format!("(({}) - 1) as usize", irecurse!(index)),
},
_ => format!("(({}) - 1) as usize", irecurse!(index)),
};
format!("{}[{}][{}]", tag.backing, row, tag.col)
}
Expr::Index { collection, index } => {
let coll_str = recurse!(collection);
let known_type = if let Expr::Identifier(sym) = collection {
variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
} else {
None
};
if let Expr::Literal(Literal::Number(n)) = index {
if *n < 0 {
if let Some(t) = known_type {
let idx_capable = t.starts_with("LogosSeq")
|| t.starts_with("Vec")
|| t.starts_with("&[")
|| t.starts_with("&mut [")
|| t.starts_with('[')
|| t == "String";
if idx_capable {
let read = format!(
"logicaffeine_data::LogosIndex::logos_get(&{}, {}i64)",
coll_str, n
);
return if t == "Vec<i32>" {
format!("(({}) as i64)", read)
} else {
read
};
}
}
}
}
match known_type {
Some(t) if t.starts_with("LogosSeq") || t.starts_with("Vec") => {
let is_logos_seq = t.starts_with("LogosSeq");
let suffix = if has_copy_element_type(t) { "" } else { ".clone()" };
let is_zero_based_counter = if let Expr::Identifier(idx_sym) = index {
variable_types.get(idx_sym).map_or(false, |t| t == "__zero_based_i64")
} else {
false
};
let index_part = if is_zero_based_counter {
let idx_name = irecurse!(index);
format!("{} as usize", idx_name)
} else { match index {
Expr::Literal(Literal::Number(1)) => "0".to_string(),
Expr::Literal(Literal::Number(n)) => format!("{}", n - 1),
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
match (left, right) {
(_, Expr::Literal(Literal::Number(1))) => {
let left_str = irecurse!(left);
if matches!(left, Expr::Identifier(_)) {
format!("{} as usize", left_str)
} else {
format!("({}) as usize", left_str)
}
}
(Expr::Literal(Literal::Number(1)), _) => {
let right_str = irecurse!(right);
if matches!(right, Expr::Identifier(_)) {
format!("{} as usize", right_str)
} else {
format!("({}) as usize", right_str)
}
}
(_, Expr::Literal(Literal::Number(k))) if *k > 1 => {
format!("({} + {}) as usize", irecurse!(left), k - 1)
}
(Expr::Literal(Literal::Number(k)), _) if *k > 1 => {
format!("({} + {}) as usize", irecurse!(right), k - 1)
}
_ => {
format!("({} - 1) as usize", irecurse!(index))
}
}
}
_ => {
format!("({} - 1) as usize", irecurse!(index))
}
} };
let read = if oracle_proves_index(ecx, collection, index) {
match (is_logos_seq, suffix.is_empty()) {
(true, true) => format!("unsafe {{ *{}.borrow().get_unchecked({}) }}", coll_str, index_part),
(true, false) => format!("unsafe {{ {}.borrow().get_unchecked({}){} }}", coll_str, index_part, suffix),
(false, true) => format!("unsafe {{ *{}.get_unchecked({}) }}", coll_str, index_part),
(false, false) => format!("unsafe {{ {}.get_unchecked({}){} }}", coll_str, index_part, suffix),
}
} else if is_logos_seq {
format!("{}.borrow()[{}]{}", coll_str, index_part, suffix)
} else {
format!("{}[{}]{}", coll_str, index_part, suffix)
};
if t == "Vec<i32>" {
format!("(({}) as i64)", read)
} else {
read
}
}
Some(t) if t.starts_with("&[") || t.starts_with("&mut [") || t.starts_with('[') => {
let elem = if t.starts_with('[') {
t.trim_start_matches('[').split("; ").next().unwrap_or("_")
} else {
t.strip_prefix("&mut [")
.or_else(|| t.strip_prefix("&["))
.and_then(|s| s.strip_suffix(']'))
.unwrap_or("_")
};
let suffix = if is_copy_type(elem) { "" } else { ".clone()" };
let is_zero_based_counter = if let Expr::Identifier(idx_sym) = index {
variable_types.get(idx_sym).map_or(false, |t| t == "__zero_based_i64")
} else {
false
};
let index_part = if is_zero_based_counter {
let idx_name = irecurse!(index);
format!("{} as usize", idx_name)
} else { match index {
Expr::Literal(Literal::Number(1)) => "0".to_string(),
Expr::Literal(Literal::Number(n)) => format!("{}", n - 1),
Expr::BinaryOp { op: BinaryOpKind::Add, left, right } => {
match (left, right) {
(_, Expr::Literal(Literal::Number(1))) => {
let left_str = irecurse!(left);
if matches!(left, Expr::Identifier(_)) {
format!("{} as usize", left_str)
} else {
format!("({}) as usize", left_str)
}
}
(Expr::Literal(Literal::Number(1)), _) => {
let right_str = irecurse!(right);
if matches!(right, Expr::Identifier(_)) {
format!("{} as usize", right_str)
} else {
format!("({}) as usize", right_str)
}
}
(_, Expr::Literal(Literal::Number(k))) if *k > 1 => {
format!("({} + {}) as usize", irecurse!(left), k - 1)
}
(Expr::Literal(Literal::Number(k)), _) if *k > 1 => {
format!("({} + {}) as usize", irecurse!(right), k - 1)
}
_ => {
format!("({} - 1) as usize", irecurse!(index))
}
}
}
_ => {
format!("({} - 1) as usize", irecurse!(index))
}
} };
let read = if oracle_proves_index(ecx, collection, index) {
if suffix.is_empty() {
format!("unsafe {{ *{}.get_unchecked({}) }}", coll_str, index_part)
} else {
format!("unsafe {{ {}.get_unchecked({}){} }}", coll_str, index_part, suffix)
}
} else {
format!("{}[{}]{}", coll_str, index_part, suffix)
};
if elem == "i32" {
format!("(({}) as i64)", read)
} else {
read
}
}
Some(t) if is_logos_map_type(t) => {
let index_str = irecurse!(index);
format!("{}.get(&({})).expect(\"Key not found in map\")", coll_str, index_str)
}
Some(t) if t.starts_with("std::collections::HashMap") || t.starts_with("HashMap") || t.starts_with("rustc_hash::FxHashMap") || t.starts_with("FxHashMap") => {
let index_str = irecurse!(index);
let suffix = if has_copy_value_type(t) { "" } else { ".clone()" };
format!("{}[&({})]{}", coll_str, index_str, suffix)
}
Some("String") => {
let index_str = irecurse!(index);
format!("LogosIndex::logos_get(&{}, {})", coll_str, index_str)
}
_ => {
let index_str = irecurse!(index);
format!("LogosIndex::logos_get(&{}, {})", coll_str, index_str)
}
}
}
Expr::Slice { collection, start, end } => {
let coll_str = recurse!(collection);
let start_str = recurse!(start);
let end_str = recurse!(end);
let known_type = if let Expr::Identifier(sym) = collection {
variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
} else {
None
};
if matches!(known_type, Some(t) if t.starts_with("LogosSeq")) {
format!("&{}.borrow()[({} - 1) as usize..{} as usize]", coll_str, start_str, end_str)
} else {
format!("&{}[({} - 1) as usize..{} as usize]", coll_str, start_str, end_str)
}
}
Expr::Copy { expr: inner } => {
if let Expr::Slice { collection, start, end } = inner {
let coll_str = recurse!(collection);
let start_str = recurse!(start);
let end_str = recurse!(end);
let known_type = if let Expr::Identifier(sym) = collection {
variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
} else {
None
};
if matches!(known_type, Some(t) if t.starts_with("LogosSeq")) {
format!("LogosSeq::from_vec({}.borrow()[({} - 1) as usize..{} as usize].to_vec())", coll_str, start_str, end_str)
} else if matches!(known_type, Some(t) if t.starts_with("&[") || t.starts_with("Vec<")) {
format!("LogosSeq::from_vec({}[({} - 1) as usize..{} as usize].to_vec())", coll_str, start_str, end_str)
} else {
format!("{}[({} - 1) as usize..{} as usize].to_vec()", coll_str, start_str, end_str)
}
} else {
let known_type = if let Expr::Identifier(sym) = inner {
variable_types.get(sym).map(|s| s.split("|__hl:").next().unwrap_or(s.as_str()))
} else {
None
};
let expr_str = recurse!(inner);
if matches!(known_type, Some(t) if t.starts_with("Vec<")) {
format!("LogosSeq::from_vec({}.clone())", expr_str)
} else if matches!(known_type, Some(t) if t.starts_with("&[")) {
format!("LogosSeq::from_vec({}.to_vec())", expr_str)
} else if matches!(known_type, Some(t) if t.starts_with("LogosSeq") || t.starts_with("LogosMap")) {
format!("{}.deep_clone()", expr_str)
} else {
format!("{}.to_owned()", expr_str)
}
}
}
Expr::Give { value } => {
recurse!(value)
}
Expr::Length { collection: Expr::Identifier(sym) }
if affine_array_trip(variable_types.get(sym)).is_some() =>
{
format!("(({}) as i64)", affine_array_trip(variable_types.get(sym)).unwrap())
}
Expr::Length { collection: Expr::Identifier(sym) }
if parse_aos_tag(variable_types.get(sym)).is_some() =>
{
format!("{}i64", parse_aos_tag(variable_types.get(sym)).unwrap().len)
}
Expr::Length { collection } => {
if let Expr::Identifier(sym) = collection {
if let Some(t) = variable_types.get(sym) {
if let Some(pos) = t.find("|__hl:") {
return t[pos + "|__hl:".len()..].to_string();
}
}
}
let coll_str = recurse!(collection);
format!("({}.len() as i64)", coll_str)
}
Expr::Contains { collection, value } => {
let coll_str = recurse!(collection);
let val_str = recurse!(value);
if let crate::analysis::types::LogosType::Map(k, _) =
super::types::infer_logos_type(collection, interner, variable_types)
{
if matches!(*k, crate::analysis::types::LogosType::Int)
&& super::types::infer_numeric_type(value, interner, variable_types) == "f64"
{
return format!(
"logicaffeine_data::logos_i64_key_of_f64({}).map_or(false, |__k| {}.logos_contains(&__k))",
val_str, coll_str
);
}
}
format!("{}.logos_contains(&{})", coll_str, val_str)
}
Expr::Union { left, right } => {
let left_str = recurse!(left);
let right_str = recurse!(right);
format!("{}.union(&{}).cloned().collect::<Set<_>>()", left_str, right_str)
}
Expr::Intersection { left, right } => {
let left_str = recurse!(left);
let right_str = recurse!(right);
format!("{}.intersection(&{}).cloned().collect::<Set<_>>()", left_str, right_str)
}
Expr::ManifestOf { zone } => {
let zone_str = recurse!(zone);
format!("logicaffeine_system::network::FileSipper::from_zone(&{}).manifest()", zone_str)
}
Expr::ChunkAt { index, zone } => {
let zone_str = recurse!(zone);
let index_str = irecurse!(index);
format!("logicaffeine_system::network::FileSipper::from_zone(&{}).get_chunk(({} - 1) as usize)", zone_str, index_str)
}
Expr::List(ref items) => {
let item_strs: Vec<String> = items.iter()
.map(|i| recurse!(i))
.collect();
format!("LogosSeq::from_vec(vec![{}])", item_strs.join(", "))
}
Expr::Tuple(ref items) => {
let item_strs: Vec<String> = items.iter()
.map(|i| format!("Value::from({})", recurse!(i)))
.collect();
format!("vec![{}]", item_strs.join(", "))
}
Expr::Range { start, end } => {
let start_str = recurse!(start);
let end_str = recurse!(end);
format!("({}..={})", start_str, end_str)
}
Expr::FieldAccess { object, field } => {
let field_name = interner.resolve(*field);
let root_sym = get_root_identifier(object);
if let Some(sym) = root_sym {
if synced_vars.contains(&sym) {
let obj_name = interner.resolve(sym);
return format!("{}.get().await.{}", obj_name, field_name);
}
}
let obj_str = recurse!(object);
format!("{}.{}", obj_str, field_name)
}
Expr::New { type_name, type_args, init_fields } => {
let type_str = interner.resolve(*type_name);
if !init_fields.is_empty() {
let fields_str = init_fields.iter()
.map(|(name, value)| {
let field_name = interner.resolve(*name);
let value_str = recurse!(value);
format!("{}: {}", field_name, value_str)
})
.collect::<Vec<_>>()
.join(", ");
format!("{} {{ {}, ..Default::default() }}", type_str, fields_str)
} else if type_args.is_empty() {
format!("{}::default()", type_str)
} else {
let args_str = type_args.iter()
.map(|t| codegen_type_expr(t, interner))
.collect::<Vec<_>>()
.join(", ");
format!("{}::<{}>::default()", type_str, args_str)
}
}
Expr::NewVariant { enum_name, variant, fields } => {
let enum_str = interner.resolve(*enum_name);
let variant_str = interner.resolve(*variant);
if fields.is_empty() {
format!("{}::{}", enum_str, variant_str)
} else {
let mut identifier_counts: HashMap<Symbol, usize> = HashMap::new();
for (_, value) in fields.iter() {
if let Expr::Identifier(sym) = value {
*identifier_counts.entry(*sym).or_insert(0) += 1;
}
}
let mut remaining_uses: HashMap<Symbol, usize> = identifier_counts.clone();
let fields_str: Vec<String> = fields.iter()
.map(|(field_name, value)| {
let name = interner.resolve(*field_name);
let val = if let Expr::Identifier(sym) = value {
let total = identifier_counts.get(sym).copied().unwrap_or(0);
let remaining = remaining_uses.get_mut(sym);
let base_name = if boxed_bindings.contains(sym) {
format!("(*{})", interner.resolve(*sym))
} else {
interner.resolve(*sym).to_string()
};
if total > 1 {
if let Some(r) = remaining {
*r -= 1;
if *r > 0 {
format!("{}.clone()", base_name)
} else {
base_name
}
} else {
base_name
}
} else {
base_name
}
} else {
recurse!(value)
};
let key = (enum_str.to_string(), variant_str.to_string(), name.to_string());
if boxed_fields.contains(&key) {
format!("{}: Box::new({})", name, val)
} else {
format!("{}: {}", name, val)
}
})
.collect();
format!("{}::{} {{ {} }}", enum_str, variant_str, fields_str.join(", "))
}
}
Expr::OptionSome { value } => {
format!("Some({})", recurse!(value))
}
Expr::OptionNone => {
"None".to_string()
}
Expr::Escape { code, .. } => {
let raw_code = interner.resolve(*code);
let mut block = String::from("{\n");
for line in raw_code.lines() {
block.push_str(" ");
block.push_str(line);
block.push('\n');
}
block.push('}');
block
}
Expr::WithCapacity { value, capacity } => {
let cap_str = recurse!(capacity);
match value {
Expr::Literal(Literal::Text(sym)) if interner.resolve(*sym).is_empty() => {
format!("String::with_capacity(({}) as usize)", cap_str)
}
Expr::Literal(Literal::Text(sym)) => {
let text = interner.resolve(*sym);
format!("{{ let mut __s = String::with_capacity(({}) as usize); __s.push_str(\"{}\"); __s }}", cap_str, text)
}
Expr::New { type_name, type_args, .. } => {
let type_str = interner.resolve(*type_name);
match type_str {
"Seq" | "List" | "Vec" => {
let elem = if !type_args.is_empty() {
codegen_type_expr(&type_args[0], interner)
} else { "()".to_string() };
format!("LogosSeq::<{}>::with_capacity(({}) as usize)", elem, cap_str)
}
"Map" | "HashMap" => {
let (k, v) = if type_args.len() >= 2 {
(codegen_type_expr(&type_args[0], interner),
codegen_type_expr(&type_args[1], interner))
} else { ("String".to_string(), "String".to_string()) };
format!("LogosMap::<{}, {}>::with_capacity(({}) as usize)", k, v, cap_str)
}
"Set" | "HashSet" => {
let elem = if !type_args.is_empty() {
codegen_type_expr(&type_args[0], interner)
} else { "()".to_string() };
format!("{{ let __s: Set<{}> = Set::with_capacity_and_hasher(({}) as usize, Default::default()); __s }}", elem, cap_str)
}
_ => recurse!(value) }
}
_ => recurse!(value)
}
}
Expr::Closure { params, body, .. } => {
use crate::ast::stmt::ClosureBody;
let params_str: Vec<String> = params.iter()
.map(|(name, ty)| {
let param_name = names.ident(*name);
let param_type = codegen_type_expr(ty, interner);
format!("{}: {}", param_name, param_type)
})
.collect();
match body {
ClosureBody::Expression(expr) => {
let body_str = recurse!(expr);
format!("move |{}| {{ {} }}", params_str.join(", "), body_str)
}
ClosureBody::Block(stmts) => {
let mut body_str = String::new();
let mut ctx = RefinementContext::new();
let empty_mutable = collect_mutable_vars(stmts);
let empty_lww = HashSet::new();
let empty_mv = HashSet::new();
let mut empty_synced = HashSet::new();
let empty_caps = HashMap::new();
let empty_pipes = HashSet::new();
let empty_boxed = HashSet::new();
let empty_registry = TypeRegistry::new();
let type_env = crate::analysis::types::TypeEnv::new();
for stmt in stmts.iter() {
body_str.push_str(&codegen_stmt(
stmt, interner, 2, &empty_mutable, &mut ctx,
&empty_lww, &empty_mv, &mut empty_synced, &empty_caps,
async_functions, &empty_pipes, &empty_boxed, &empty_registry,
&type_env,
));
}
format!("move |{}| {{\n{}{}}}", params_str.join(", "), body_str, " ")
}
}
}
Expr::CallExpr { callee, args } => {
let callee_str = recurse!(callee);
let args_str: Vec<String> = args.iter().map(|a| recurse!(a)).collect();
format!("({})({})", callee_str, args_str.join(", "))
}
Expr::InterpolatedString(parts) => {
codegen_interpolated_string(parts, ecx)
}
Expr::Not { operand } => {
let operand_str = recurse!(operand);
if matches!(
infer_logos_type(operand, interner, variable_types),
crate::analysis::types::LogosType::Bool
) {
format!("!({})", operand_str)
} else {
format!("(!logos_truthy(&({})))", operand_str)
}
}
}
}
pub(crate) fn codegen_interpolated_string(
parts: &[crate::ast::stmt::StringPart],
ecx: &ExprCtx,
) -> String {
use crate::ast::stmt::StringPart;
let interner = ecx.interner;
let mut fmt_str = String::new();
let mut args = Vec::new();
for part in parts {
match part {
StringPart::Literal(sym) => {
let text = interner.resolve(*sym);
for ch in text.chars() {
match ch {
'{' => fmt_str.push_str("{{"),
'}' => fmt_str.push_str("}}"),
'\n' => fmt_str.push_str("\\n"),
'\t' => fmt_str.push_str("\\t"),
'\r' => fmt_str.push_str("\\r"),
_ => fmt_str.push(ch),
}
}
}
StringPart::Expr { value, format_spec, debug } => {
if *debug {
let debug_prefix = expr_debug_prefix(value, interner);
for ch in debug_prefix.chars() {
match ch {
'{' => fmt_str.push_str("{{"),
'}' => fmt_str.push_str("}}"),
_ => fmt_str.push(ch),
}
}
fmt_str.push('=');
}
let needs_float_cast = if let Some(spec) = format_spec {
let spec_str = interner.resolve(*spec);
if spec_str == "$" {
fmt_str.push('$');
fmt_str.push_str("{:.2}");
true
} else if spec_str.starts_with('.') {
fmt_str.push_str(&format!("{{:{}}}", spec_str));
true
} else {
fmt_str.push_str(&format!("{{:{}}}", spec_str));
false
}
} else {
fmt_str.push_str("{}");
false
};
let arg_str = codegen_expr_ctx(value, ecx);
if needs_float_cast {
args.push(format!("{} as f64", arg_str));
} else {
args.push(arg_str);
}
}
}
}
if args.is_empty() {
let mut raw = String::new();
for part in parts {
if let StringPart::Literal(sym) = part {
let text = interner.resolve(*sym);
for ch in text.chars() {
match ch {
'\n' => raw.push_str("\\n"),
'\t' => raw.push_str("\\t"),
'\r' => raw.push_str("\\r"),
'"' => raw.push_str("\\\""),
'\\' => raw.push_str("\\\\"),
_ => raw.push(ch),
}
}
}
}
format!("String::from(\"{}\")", raw)
} else {
format!("format!(\"{}\"{})", fmt_str, args.iter().map(|a| format!(", {}", a)).collect::<String>())
}
}
pub(crate) fn codegen_literal(lit: &Literal, interner: &Interner) -> String {
match lit {
Literal::Number(n) => {
if *n > i32::MAX as i64 || *n < i32::MIN as i64 {
format!("{}_i64", n)
} else {
n.to_string()
}
}
Literal::Float(f) if f.is_nan() => "f64::NAN".to_string(),
Literal::Float(f) if f.is_infinite() && *f > 0.0 => "f64::INFINITY".to_string(),
Literal::Float(f) if f.is_infinite() => "f64::NEG_INFINITY".to_string(),
Literal::Float(f) => format!("{}f64", f),
Literal::Text(sym) => {
let raw = interner.resolve(*sym);
let escaped: String = raw.chars().map(|c| match c {
'\n' => "\\n".to_string(),
'\r' => "\\r".to_string(),
'\t' => "\\t".to_string(),
'\\' => "\\\\".to_string(),
'"' => "\\\"".to_string(),
other => other.to_string(),
}).collect();
format!("String::from(\"{}\")", escaped)
}
Literal::Boolean(b) => b.to_string(),
Literal::Nothing => "()".to_string(),
Literal::Char(c) => {
match c {
'\n' => "'\\n'".to_string(),
'\t' => "'\\t'".to_string(),
'\r' => "'\\r'".to_string(),
'\\' => "'\\\\'".to_string(),
'\'' => "'\\''".to_string(),
'\0' => "'\\0'".to_string(),
c => format!("'{}'", c),
}
}
Literal::Duration(nanos) => format!("std::time::Duration::from_nanos({}u64)", nanos),
Literal::Date(days) => format!("LogosDate({})", days),
Literal::Moment(nanos) => format!("LogosMoment({})", nanos),
Literal::Span { months, days } => format!("LogosSpan::new({}, {})", months, days),
Literal::Time(nanos) => format!("LogosTime({})", nanos),
}
}
pub fn codegen_assertion(expr: &LogicExpr, interner: &Interner) -> String {
let mut registry = SymbolRegistry::new();
let formatter = RustFormatter;
let mut buf = String::new();
match expr.write_logic(&mut buf, &mut registry, interner, &formatter) {
Ok(_) => buf,
Err(_) => "/* error generating assertion */ false".to_string(),
}
}
pub fn codegen_term(term: &Term, interner: &Interner) -> String {
match term {
Term::Constant(sym) => interner.resolve(*sym).to_string(),
Term::Variable(sym) => interner.resolve(*sym).to_string(),
Term::Value { kind, .. } => match kind {
NumberKind::Integer(n) => n.to_string(),
NumberKind::Real(f) => f.to_string(),
NumberKind::Symbolic(sym) => interner.resolve(*sym).to_string(),
},
Term::Function(name, args) => {
let args_str: Vec<String> = args.iter()
.map(|a| codegen_term(a, interner))
.collect();
format!("{}({})", interner.resolve(*name), args_str.join(", "))
}
Term::Possessed { possessor, possessed } => {
let poss_str = codegen_term(possessor, interner);
format!("{}.{}", poss_str, interner.resolve(*possessed))
}
Term::Group(members) => {
let members_str: Vec<String> = members.iter()
.map(|m| codegen_term(m, interner))
.collect();
format!("({})", members_str.join(", "))
}
_ => "/* unsupported Term */".to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_literal_number() {
let interner = Interner::new();
let synced_vars = HashSet::new();
let expr = Expr::Literal(Literal::Number(42));
assert_eq!(codegen_expr(&expr, &interner, &synced_vars), "42");
}
#[test]
fn test_literal_boolean() {
let interner = Interner::new();
let synced_vars = HashSet::new();
assert_eq!(codegen_expr(&Expr::Literal(Literal::Boolean(true)), &interner, &synced_vars), "true");
assert_eq!(codegen_expr(&Expr::Literal(Literal::Boolean(false)), &interner, &synced_vars), "false");
}
#[test]
fn test_literal_nothing() {
let interner = Interner::new();
let synced_vars = HashSet::new();
assert_eq!(codegen_expr(&Expr::Literal(Literal::Nothing), &interner, &synced_vars), "()");
}
}