Refactor JoinIR lowerers and boundary
This commit is contained in:
@ -1,922 +0,0 @@
|
||||
//! NormalizedExprLowererBox: Pure expression lowering SSOT (Phase 140 P0)
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! Lower a *pure* AST expression into JoinIR `ValueId` + emitted `JoinInst`s.
|
||||
//!
|
||||
//! ## Scope (Phase 140 P0)
|
||||
//!
|
||||
//! Supported (pure only):
|
||||
//! - Variable: env lookup → ValueId
|
||||
//! - Literals: Integer / Bool
|
||||
//! - Unary: `-` (Neg), `not` (Not)
|
||||
//! - Binary arith (int-only): `+ - * /`
|
||||
//! - Compare (int-only): `== != < <= > >=`
|
||||
//!
|
||||
//! Out of scope (returns `Ok(None)`):
|
||||
//! - Call/MethodCall/FromCall/Array/Map/FieldAccess/Index/New/This/Me/…
|
||||
//! - Literals other than Integer/Bool
|
||||
//! - Operators outside the scope above
|
||||
//!
|
||||
//! ## Contract
|
||||
//!
|
||||
//! - Out-of-scope returns `Ok(None)` (fallback to legacy routing).
|
||||
//! - `Err(_)` is reserved for internal invariants only (should be rare).
|
||||
|
||||
use super::expr_lowering_contract::{ExprLoweringScope, ImpurePolicy, KnownIntrinsic, OutOfScopeReason};
|
||||
use super::known_intrinsics::KnownIntrinsicRegistryBox; // Phase 141 P1.5
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
|
||||
use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeInst, UnaryOp};
|
||||
use crate::mir::types::MirType;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Box-First: Pure expression lowering for Normalized shadow paths
|
||||
pub struct NormalizedExprLowererBox;
|
||||
|
||||
impl NormalizedExprLowererBox {
|
||||
pub fn lower_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
Self::lower_expr_with_scope(ExprLoweringScope::PureOnly, ast, env, body, next_value_id)
|
||||
}
|
||||
|
||||
pub fn lower_expr_with_scope(
|
||||
scope: ExprLoweringScope,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// Phase 146 P1: ANF routing (dev-only, scope-aware)
|
||||
if crate::config::env::anf_dev_enabled() {
|
||||
// P1: Allow ANF for PureOnly if HAKO_ANF_ALLOW_PURE=1
|
||||
let should_try_anf = match scope {
|
||||
ExprLoweringScope::WithImpure(_) => true,
|
||||
ExprLoweringScope::PureOnly => crate::config::env::anf_allow_pure_enabled(),
|
||||
};
|
||||
|
||||
if should_try_anf {
|
||||
use super::super::anf::{AnfPlanBox, AnfExecuteBox};
|
||||
match AnfPlanBox::plan_expr(ast, env) {
|
||||
Ok(Some(plan)) => {
|
||||
match AnfExecuteBox::try_execute(&plan, ast, &mut env.clone(), body, next_value_id)? {
|
||||
Some(vid) => return Ok(Some(vid)), // P1+: ANF succeeded
|
||||
None => {
|
||||
// P0: stub returns None, fallback to legacy
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!("[phase145/debug] ANF plan found but execute returned None (P0 stub)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
// Out-of-scope for ANF, continue with legacy lowering
|
||||
}
|
||||
Err(_reason) => {
|
||||
// Explicitly out-of-scope (ContainsCall/ContainsMethodCall), continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if Self::out_of_scope_reason(scope, ast, env).is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => Ok(env.get(name).copied()),
|
||||
ASTNode::Literal { value, .. } => Self::lower_literal(value, body, next_value_id),
|
||||
ASTNode::UnaryOp { operator, operand, .. } => {
|
||||
Self::lower_unary(operator, operand, env, body, next_value_id)
|
||||
}
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
Self::lower_binary(operator, left, right, env, body, next_value_id)
|
||||
}
|
||||
ASTNode::MethodCall { object, method, arguments, .. } => match scope {
|
||||
ExprLoweringScope::PureOnly => Ok(None),
|
||||
ExprLoweringScope::WithImpure(policy) => {
|
||||
let Some(intrinsic) =
|
||||
Self::match_known_intrinsic_method_call(policy, object, method, arguments, env)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
Self::lower_known_intrinsic_method_call(intrinsic, object, body, next_value_id, env)
|
||||
}
|
||||
},
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Classify out-of-scope reasons for diagnostics/tests without changing the core API.
|
||||
pub fn out_of_scope_reason(
|
||||
scope: ExprLoweringScope,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<OutOfScopeReason> {
|
||||
match scope {
|
||||
ExprLoweringScope::PureOnly => Self::out_of_scope_reason_pure(ast, env),
|
||||
ExprLoweringScope::WithImpure(policy) => Self::out_of_scope_reason_with_impure(policy, ast, env),
|
||||
}
|
||||
}
|
||||
|
||||
fn out_of_scope_reason_pure(ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> Option<OutOfScopeReason> {
|
||||
match ast {
|
||||
ASTNode::FunctionCall { .. } | ASTNode::Call { .. } => Some(OutOfScopeReason::Call),
|
||||
ASTNode::MethodCall { .. } => Some(OutOfScopeReason::MethodCall),
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
if env.contains_key(name) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::MissingEnvVar)
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Integer(_) | LiteralValue::Bool(_) => None,
|
||||
_ => Some(OutOfScopeReason::UnsupportedLiteral),
|
||||
},
|
||||
|
||||
ASTNode::UnaryOp { operator, operand, .. } => match operator {
|
||||
UnaryOperator::Minus => {
|
||||
// Only int operand is supported.
|
||||
if Self::is_supported_int_expr(operand, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
// Only bool operand is supported.
|
||||
if Self::is_supported_bool_expr(operand, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::BitNot => Some(OutOfScopeReason::UnsupportedOperator),
|
||||
UnaryOperator::Weak => Some(OutOfScopeReason::UnsupportedOperator), // Phase 285W-Syntax-0
|
||||
},
|
||||
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
if Self::binary_kind(operator).is_some() {
|
||||
// Phase 140 policy: binary ops require int operands (arith/compare).
|
||||
if Self::is_supported_int_expr(left, env) && Self::is_supported_int_expr(right, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Some(OutOfScopeReason::ImpureExpression),
|
||||
}
|
||||
}
|
||||
|
||||
fn out_of_scope_reason_with_impure(
|
||||
policy: ImpurePolicy,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<OutOfScopeReason> {
|
||||
match ast {
|
||||
ASTNode::FunctionCall { .. } | ASTNode::Call { .. } => Some(OutOfScopeReason::Call),
|
||||
ASTNode::MethodCall { object, method, arguments, .. } => {
|
||||
// Phase 141 P1.5: Use registry for better diagnostics
|
||||
if Self::match_known_intrinsic_method_call(policy, object, method, arguments, env).is_some()
|
||||
{
|
||||
None // In scope (whitelisted)
|
||||
} else {
|
||||
// Check if it's a known intrinsic but not whitelisted
|
||||
let receiver_ok = matches!(object.as_ref(), ASTNode::Variable { name, .. } if env.contains_key(name));
|
||||
if receiver_ok && KnownIntrinsicRegistryBox::lookup(method, arguments.len()).is_some() {
|
||||
// Known intrinsic signature but not in current policy allowlist
|
||||
Some(OutOfScopeReason::IntrinsicNotWhitelisted)
|
||||
} else {
|
||||
// Generic method call not supported
|
||||
Some(OutOfScopeReason::MethodCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
if env.contains_key(name) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::MissingEnvVar)
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Integer(_) | LiteralValue::Bool(_) => None,
|
||||
_ => Some(OutOfScopeReason::UnsupportedLiteral),
|
||||
},
|
||||
|
||||
ASTNode::UnaryOp { operator, operand, .. } => match operator {
|
||||
UnaryOperator::Minus => {
|
||||
if Self::is_supported_int_expr_with_scope(ExprLoweringScope::WithImpure(policy), operand, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
if Self::is_supported_bool_expr_with_scope(ExprLoweringScope::WithImpure(policy), operand, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::BitNot => Some(OutOfScopeReason::UnsupportedOperator),
|
||||
UnaryOperator::Weak => Some(OutOfScopeReason::UnsupportedOperator), // Phase 285W-Syntax-0
|
||||
},
|
||||
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
if Self::binary_kind(operator).is_some() {
|
||||
if Self::is_supported_int_expr_with_scope(ExprLoweringScope::WithImpure(policy), left, env)
|
||||
&& Self::is_supported_int_expr_with_scope(ExprLoweringScope::WithImpure(policy), right, env)
|
||||
{
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Some(OutOfScopeReason::ImpureExpression),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_int_expr(ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> bool {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal { value: LiteralValue::Integer(_), .. } => true,
|
||||
ASTNode::UnaryOp { operator: UnaryOperator::Minus, operand, .. } => {
|
||||
Self::is_supported_int_expr(operand, env)
|
||||
}
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
matches!(Self::binary_kind(operator), Some(BinaryKind::Arith(_)))
|
||||
&& Self::is_supported_int_expr(left, env)
|
||||
&& Self::is_supported_int_expr(right, env)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_bool_expr(ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> bool {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal { value: LiteralValue::Bool(_), .. } => true,
|
||||
ASTNode::UnaryOp { operator: UnaryOperator::Not, operand, .. } => {
|
||||
Self::is_supported_bool_expr(operand, env)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_int_expr_with_scope(scope: ExprLoweringScope, ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> bool {
|
||||
match scope {
|
||||
ExprLoweringScope::PureOnly => Self::is_supported_int_expr(ast, env),
|
||||
ExprLoweringScope::WithImpure(policy) => match ast {
|
||||
ASTNode::MethodCall { object, method, arguments, .. } => {
|
||||
Self::match_known_intrinsic_method_call(policy, object, method, arguments, env).is_some()
|
||||
}
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal { value: LiteralValue::Integer(_), .. } => true,
|
||||
ASTNode::UnaryOp { operator: UnaryOperator::Minus, operand, .. } => {
|
||||
Self::is_supported_int_expr_with_scope(ExprLoweringScope::WithImpure(policy), operand, env)
|
||||
}
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
matches!(Self::binary_kind(operator), Some(BinaryKind::Arith(_)) | Some(BinaryKind::Compare(_)))
|
||||
&& Self::is_supported_int_expr_with_scope(ExprLoweringScope::WithImpure(policy), left, env)
|
||||
&& Self::is_supported_int_expr_with_scope(ExprLoweringScope::WithImpure(policy), right, env)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_bool_expr_with_scope(scope: ExprLoweringScope, ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> bool {
|
||||
match scope {
|
||||
ExprLoweringScope::PureOnly => Self::is_supported_bool_expr(ast, env),
|
||||
ExprLoweringScope::WithImpure(policy) => match ast {
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal { value: LiteralValue::Bool(_), .. } => true,
|
||||
ASTNode::UnaryOp { operator: UnaryOperator::Not, operand, .. } => {
|
||||
Self::is_supported_bool_expr_with_scope(ExprLoweringScope::WithImpure(policy), operand, env)
|
||||
}
|
||||
_ => {
|
||||
let _ = policy;
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
fn lower_literal(
|
||||
value: &LiteralValue,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match value {
|
||||
LiteralValue::Integer(i) => {
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Integer(*i),
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
LiteralValue::Bool(b) => {
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Bool(*b),
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_unary(
|
||||
operator: &UnaryOperator,
|
||||
operand: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match operator {
|
||||
UnaryOperator::Minus => {
|
||||
let operand_vid =
|
||||
match Self::lower_int_expr(operand, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst,
|
||||
op: UnaryOp::Neg,
|
||||
operand: operand_vid,
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
let operand_vid =
|
||||
match Self::lower_bool_expr(operand, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst,
|
||||
op: UnaryOp::Not,
|
||||
operand: operand_vid,
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
UnaryOperator::BitNot => Ok(None),
|
||||
UnaryOperator::Weak => Ok(None), // Phase 285W-Syntax-0: Not supported in normalized lowering
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 141 P1.5: Refactored to use KnownIntrinsicRegistryBox
|
||||
fn match_known_intrinsic_method_call(
|
||||
policy: ImpurePolicy,
|
||||
object: &ASTNode,
|
||||
method: &str,
|
||||
arguments: &[ASTNode],
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<KnownIntrinsic> {
|
||||
match policy {
|
||||
ImpurePolicy::KnownIntrinsicOnly => {
|
||||
let receiver_ok = matches!(object, ASTNode::Variable { name, .. } if env.contains_key(name));
|
||||
if !receiver_ok {
|
||||
return None;
|
||||
}
|
||||
// SSOT: Use registry for lookup (Phase 141 P1.5)
|
||||
KnownIntrinsicRegistryBox::lookup(method, arguments.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 141 P1.5: Refactored to use KnownIntrinsicRegistryBox
|
||||
fn lower_known_intrinsic_method_call(
|
||||
intrinsic: KnownIntrinsic,
|
||||
object: &ASTNode,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let receiver = match object {
|
||||
ASTNode::Variable { name, .. } => match env.get(name).copied() {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
},
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
// SSOT: Get spec from registry (Phase 141 P1.5)
|
||||
let spec = KnownIntrinsicRegistryBox::get_spec(intrinsic);
|
||||
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::MethodCall {
|
||||
dst,
|
||||
receiver,
|
||||
method: spec.method_name.to_string(),
|
||||
args: vec![],
|
||||
// Use type hint from registry (currently all intrinsics return Integer)
|
||||
type_hint: Some(MirType::Integer),
|
||||
});
|
||||
Ok(Some(dst))
|
||||
}
|
||||
|
||||
fn lower_binary(
|
||||
operator: &BinaryOperator,
|
||||
left: &ASTNode,
|
||||
right: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let Some(kind) = Self::binary_kind(operator) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
match kind {
|
||||
BinaryKind::Arith(op) => {
|
||||
let lhs = match Self::lower_int_expr(left, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let rhs = match Self::lower_int_expr(right, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
BinaryKind::Compare(op) => {
|
||||
let lhs = match Self::lower_int_expr(left, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let rhs = match Self::lower_int_expr(right, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_int_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => Ok(env.get(name).copied()),
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Integer(_) => Self::lower_literal(value, body, next_value_id),
|
||||
_ => Ok(None),
|
||||
},
|
||||
ASTNode::UnaryOp { operator: UnaryOperator::Minus, operand, .. } => {
|
||||
Self::lower_unary(&UnaryOperator::Minus, operand, env, body, next_value_id)
|
||||
}
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
let Some(BinaryKind::Arith(_)) = Self::binary_kind(operator) else {
|
||||
return Ok(None);
|
||||
};
|
||||
Self::lower_binary(operator, left, right, env, body, next_value_id)
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn lower_bool_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => Ok(env.get(name).copied()),
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Bool(_) => Self::lower_literal(value, body, next_value_id),
|
||||
_ => Ok(None),
|
||||
},
|
||||
ASTNode::UnaryOp { operator: UnaryOperator::Not, operand, .. } => {
|
||||
Self::lower_unary(&UnaryOperator::Not, operand, env, body, next_value_id)
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn binary_kind(op: &BinaryOperator) -> Option<BinaryKind> {
|
||||
match op {
|
||||
BinaryOperator::Add => Some(BinaryKind::Arith(BinOpKind::Add)),
|
||||
BinaryOperator::Subtract => Some(BinaryKind::Arith(BinOpKind::Sub)),
|
||||
BinaryOperator::Multiply => Some(BinaryKind::Arith(BinOpKind::Mul)),
|
||||
BinaryOperator::Divide => Some(BinaryKind::Arith(BinOpKind::Div)),
|
||||
BinaryOperator::Equal => Some(BinaryKind::Compare(CompareOp::Eq)),
|
||||
BinaryOperator::NotEqual => Some(BinaryKind::Compare(CompareOp::Ne)),
|
||||
BinaryOperator::Less => Some(BinaryKind::Compare(CompareOp::Lt)),
|
||||
BinaryOperator::LessEqual => Some(BinaryKind::Compare(CompareOp::Le)),
|
||||
BinaryOperator::Greater => Some(BinaryKind::Compare(CompareOp::Gt)),
|
||||
BinaryOperator::GreaterEqual => Some(BinaryKind::Compare(CompareOp::Ge)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum BinaryKind {
|
||||
Arith(BinOpKind),
|
||||
Compare(CompareOp),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::Span;
|
||||
|
||||
fn span() -> Span {
|
||||
Span::unknown()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_var_from_env() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("x".to_string(), ValueId(42));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(42)));
|
||||
assert!(body.is_empty());
|
||||
assert_eq!(next, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_scope_var_missing() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Variable {
|
||||
name: "missing".to_string(),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, None);
|
||||
assert!(body.is_empty());
|
||||
assert_eq!(next, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_int_literal_const_emits() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Integer(7),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(100)));
|
||||
assert_eq!(next, 101);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: ValueId(100),
|
||||
value: ConstValue::Integer(7)
|
||||
})]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_bool_literal_const_emits() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(100)));
|
||||
assert_eq!(next, 101);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: ValueId(100),
|
||||
value: ConstValue::Bool(true)
|
||||
})]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_unary_minus_int() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 10;
|
||||
|
||||
let ast = ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Minus,
|
||||
operand: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(3),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(11)));
|
||||
assert_eq!(next, 12);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[
|
||||
JoinInst::Compute(MirLikeInst::Const { .. }),
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp { dst: ValueId(11), op: UnaryOp::Neg, .. })
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_unary_not_bool() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 10;
|
||||
|
||||
let ast = ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(false),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(11)));
|
||||
assert_eq!(next, 12);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[
|
||||
JoinInst::Compute(MirLikeInst::Const { .. }),
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp { dst: ValueId(11), op: UnaryOp::Not, .. })
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_add_sub_mul_div_ints() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("x".to_string(), ValueId(1));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let add = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&add, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(101)));
|
||||
|
||||
let sub = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(5),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(3),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let _ = NormalizedExprLowererBox::lower_expr(&sub, &env, &mut body, &mut next).unwrap();
|
||||
|
||||
let mul = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Multiply,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(6),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(7),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let _ = NormalizedExprLowererBox::lower_expr(&mul, &env, &mut body, &mut next).unwrap();
|
||||
|
||||
let div = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Divide,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(8),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let _ = NormalizedExprLowererBox::lower_expr(&div, &env, &mut body, &mut next).unwrap();
|
||||
|
||||
assert!(body.iter().any(|i| matches!(i, JoinInst::Compute(MirLikeInst::BinOp { op: BinOpKind::Add, .. }))));
|
||||
assert!(body.iter().any(|i| matches!(i, JoinInst::Compute(MirLikeInst::BinOp { op: BinOpKind::Sub, .. }))));
|
||||
assert!(body.iter().any(|i| matches!(i, JoinInst::Compute(MirLikeInst::BinOp { op: BinOpKind::Mul, .. }))));
|
||||
assert!(body.iter().any(|i| matches!(i, JoinInst::Compute(MirLikeInst::BinOp { op: BinOpKind::Div, .. }))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_compare_eq_lt_ints() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("x".to_string(), ValueId(1));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let eq = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&eq, &env, &mut body, &mut next).unwrap();
|
||||
assert!(got.is_some());
|
||||
|
||||
let lt = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(<, &env, &mut body, &mut next).unwrap();
|
||||
assert!(got.is_some());
|
||||
|
||||
assert!(body.iter().any(|i| matches!(i, JoinInst::Compute(MirLikeInst::Compare { op: CompareOp::Eq, .. }))));
|
||||
assert!(body.iter().any(|i| matches!(i, JoinInst::Compute(MirLikeInst::Compare { op: CompareOp::Lt, .. }))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_scope_call() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 1;
|
||||
|
||||
let ast = ASTNode::FunctionCall {
|
||||
name: "f".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, None);
|
||||
assert!(body.is_empty());
|
||||
assert_eq!(next, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_is_out_of_scope_in_pure_only() {
|
||||
let env = BTreeMap::new();
|
||||
let ast = ASTNode::Call {
|
||||
callee: Box::new(ASTNode::Variable {
|
||||
name: "f".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
assert_eq!(
|
||||
NormalizedExprLowererBox::out_of_scope_reason(ExprLoweringScope::PureOnly, &ast, &env),
|
||||
Some(OutOfScopeReason::Call)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn methodcall_is_out_of_scope_in_pure_only() {
|
||||
let env = BTreeMap::new();
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
method: "m".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
assert_eq!(
|
||||
NormalizedExprLowererBox::out_of_scope_reason(ExprLoweringScope::PureOnly, &ast, &env),
|
||||
Some(OutOfScopeReason::MethodCall)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn methodcall_length0_is_in_scope_with_known_intrinsic_only() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("s".to_string(), ValueId(1));
|
||||
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
NormalizedExprLowererBox::out_of_scope_reason(
|
||||
ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly),
|
||||
&ast,
|
||||
&env
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_methodcall_length0_emits_method_call_inst() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("s".to_string(), ValueId(7));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
let got = NormalizedExprLowererBox::lower_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly),
|
||||
&ast,
|
||||
&env,
|
||||
&mut body,
|
||||
&mut next,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(got, Some(ValueId(100)));
|
||||
assert_eq!(next, 101);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[JoinInst::MethodCall {
|
||||
dst: ValueId(100),
|
||||
receiver: ValueId(7),
|
||||
method,
|
||||
args,
|
||||
type_hint: Some(MirType::Integer),
|
||||
}] if method == "length" && args.is_empty()
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
use crate::ast::BinaryOperator;
|
||||
use crate::mir::join_ir::{BinOpKind, CompareOp};
|
||||
|
||||
pub(super) enum BinaryKind {
|
||||
Arith(BinOpKind),
|
||||
Compare(CompareOp),
|
||||
}
|
||||
|
||||
pub(super) fn binary_kind(op: &BinaryOperator) -> Option<BinaryKind> {
|
||||
match op {
|
||||
BinaryOperator::Add => Some(BinaryKind::Arith(BinOpKind::Add)),
|
||||
BinaryOperator::Subtract => Some(BinaryKind::Arith(BinOpKind::Sub)),
|
||||
BinaryOperator::Multiply => Some(BinaryKind::Arith(BinOpKind::Mul)),
|
||||
BinaryOperator::Divide => Some(BinaryKind::Arith(BinOpKind::Div)),
|
||||
BinaryOperator::Equal => Some(BinaryKind::Compare(CompareOp::Eq)),
|
||||
BinaryOperator::NotEqual => Some(BinaryKind::Compare(CompareOp::Ne)),
|
||||
BinaryOperator::Less => Some(BinaryKind::Compare(CompareOp::Lt)),
|
||||
BinaryOperator::LessEqual => Some(BinaryKind::Compare(CompareOp::Le)),
|
||||
BinaryOperator::Greater => Some(BinaryKind::Compare(CompareOp::Gt)),
|
||||
BinaryOperator::GreaterEqual => Some(BinaryKind::Compare(CompareOp::Ge)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
use super::NormalizedExprLowererBox;
|
||||
use super::super::expr_lowering_contract::{ImpurePolicy, KnownIntrinsic};
|
||||
use super::super::known_intrinsics::KnownIntrinsicRegistryBox;
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::types::MirType;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl NormalizedExprLowererBox {
|
||||
/// Phase 141 P1.5: Refactored to use KnownIntrinsicRegistryBox
|
||||
pub(super) fn match_known_intrinsic_method_call(
|
||||
policy: ImpurePolicy,
|
||||
object: &ASTNode,
|
||||
method: &str,
|
||||
arguments: &[ASTNode],
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<KnownIntrinsic> {
|
||||
match policy {
|
||||
ImpurePolicy::KnownIntrinsicOnly => {
|
||||
let receiver_ok =
|
||||
matches!(object, ASTNode::Variable { name, .. } if env.contains_key(name));
|
||||
if !receiver_ok {
|
||||
return None;
|
||||
}
|
||||
KnownIntrinsicRegistryBox::lookup(method, arguments.len())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 141 P1.5: Refactored to use KnownIntrinsicRegistryBox
|
||||
pub(super) fn lower_known_intrinsic_method_call(
|
||||
intrinsic: KnownIntrinsic,
|
||||
object: &ASTNode,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let receiver = match object {
|
||||
ASTNode::Variable { name, .. } => match env.get(name).copied() {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
},
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
let spec = KnownIntrinsicRegistryBox::get_spec(intrinsic);
|
||||
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::MethodCall {
|
||||
dst,
|
||||
receiver,
|
||||
method: spec.method_name.to_string(),
|
||||
args: vec![],
|
||||
type_hint: Some(MirType::Integer),
|
||||
});
|
||||
Ok(Some(dst))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,173 @@
|
||||
use super::binary::{binary_kind, BinaryKind};
|
||||
use super::NormalizedExprLowererBox;
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, UnaryOperator};
|
||||
use crate::mir::join_ir::{ConstValue, JoinInst, MirLikeInst, UnaryOp};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl NormalizedExprLowererBox {
|
||||
pub(super) fn alloc_value_id(next_value_id: &mut u32) -> ValueId {
|
||||
let vid = ValueId(*next_value_id);
|
||||
*next_value_id += 1;
|
||||
vid
|
||||
}
|
||||
|
||||
pub(super) fn lower_literal(
|
||||
value: &LiteralValue,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match value {
|
||||
LiteralValue::Integer(i) => {
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Integer(*i),
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
LiteralValue::Bool(b) => {
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::Const {
|
||||
dst,
|
||||
value: ConstValue::Bool(*b),
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_unary(
|
||||
operator: &UnaryOperator,
|
||||
operand: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match operator {
|
||||
UnaryOperator::Minus => {
|
||||
let operand_vid = match Self::lower_int_expr(operand, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst,
|
||||
op: UnaryOp::Neg,
|
||||
operand: operand_vid,
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
let operand_vid = match Self::lower_bool_expr(operand, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst,
|
||||
op: UnaryOp::Not,
|
||||
operand: operand_vid,
|
||||
}));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
UnaryOperator::BitNot => Ok(None),
|
||||
UnaryOperator::Weak => Ok(None), // Phase 285W-Syntax-0: Not supported in normalized lowering
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_binary(
|
||||
operator: &BinaryOperator,
|
||||
left: &ASTNode,
|
||||
right: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
let Some(kind) = binary_kind(operator) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
match kind {
|
||||
BinaryKind::Arith(op) => {
|
||||
let lhs = match Self::lower_int_expr(left, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let rhs = match Self::lower_int_expr(right, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
BinaryKind::Compare(op) => {
|
||||
let lhs = match Self::lower_int_expr(left, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
let rhs = match Self::lower_int_expr(right, env, body, next_value_id)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
let dst = Self::alloc_value_id(next_value_id);
|
||||
body.push(JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }));
|
||||
Ok(Some(dst))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_int_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => Ok(env.get(name).copied()),
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Integer(_) => Self::lower_literal(value, body, next_value_id),
|
||||
_ => Ok(None),
|
||||
},
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Minus,
|
||||
operand,
|
||||
..
|
||||
} => Self::lower_unary(&UnaryOperator::Minus, operand, env, body, next_value_id),
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, right, ..
|
||||
} => {
|
||||
let Some(BinaryKind::Arith(_)) = binary_kind(operator) else {
|
||||
return Ok(None);
|
||||
};
|
||||
Self::lower_binary(operator, left, right, env, body, next_value_id)
|
||||
}
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn lower_bool_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => Ok(env.get(name).copied()),
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Bool(_) => Self::lower_literal(value, body, next_value_id),
|
||||
_ => Ok(None),
|
||||
},
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} => Self::lower_unary(&UnaryOperator::Not, operand, env, body, next_value_id),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,137 @@
|
||||
//! NormalizedExprLowererBox: Pure expression lowering SSOT (Phase 140 P0)
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! Lower a *pure* AST expression into JoinIR `ValueId` + emitted `JoinInst`s.
|
||||
//!
|
||||
//! ## Scope (Phase 140 P0)
|
||||
//!
|
||||
//! Supported (pure only):
|
||||
//! - Variable: env lookup → ValueId
|
||||
//! - Literals: Integer / Bool
|
||||
//! - Unary: `-` (Neg), `not` (Not)
|
||||
//! - Binary arith (int-only): `+ - * /`
|
||||
//! - Compare (int-only): `== != < <= > >=`
|
||||
//!
|
||||
//! Out of scope (returns `Ok(None)`):
|
||||
//! - Call/MethodCall/FromCall/Array/Map/FieldAccess/Index/New/This/Me/…
|
||||
//! - Literals other than Integer/Bool
|
||||
//! - Operators outside the scope above
|
||||
//!
|
||||
//! ## Contract
|
||||
//!
|
||||
//! - Out-of-scope returns `Ok(None)` (caller routes to non-normalized lowering).
|
||||
//! - `Err(_)` is reserved for internal invariants only (should be rare).
|
||||
|
||||
mod binary;
|
||||
mod intrinsics;
|
||||
mod lowering;
|
||||
mod scope;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use super::expr_lowering_contract::ExprLoweringScope;
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Box-First: Pure expression lowering for Normalized shadow paths
|
||||
pub struct NormalizedExprLowererBox;
|
||||
|
||||
impl NormalizedExprLowererBox {
|
||||
pub fn lower_expr(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
Self::lower_expr_with_scope(ExprLoweringScope::PureOnly, ast, env, body, next_value_id)
|
||||
}
|
||||
|
||||
pub fn lower_expr_with_scope(
|
||||
scope: ExprLoweringScope,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
body: &mut Vec<JoinInst>,
|
||||
next_value_id: &mut u32,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
// Phase 146 P1: ANF routing (dev-only, scope-aware)
|
||||
if crate::config::env::anf_dev_enabled() {
|
||||
// P1: Allow ANF for PureOnly if HAKO_ANF_ALLOW_PURE=1
|
||||
let should_try_anf = match scope {
|
||||
ExprLoweringScope::WithImpure(_) => true,
|
||||
ExprLoweringScope::PureOnly => crate::config::env::anf_allow_pure_enabled(),
|
||||
};
|
||||
|
||||
if should_try_anf {
|
||||
use super::super::anf::{AnfExecuteBox, AnfPlanBox};
|
||||
match AnfPlanBox::plan_expr(ast, env) {
|
||||
Ok(Some(plan)) => {
|
||||
match AnfExecuteBox::try_execute(
|
||||
&plan,
|
||||
ast,
|
||||
&mut env.clone(),
|
||||
body,
|
||||
next_value_id,
|
||||
)? {
|
||||
Some(vid) => return Ok(Some(vid)),
|
||||
None => {
|
||||
if crate::config::env::joinir_dev_enabled() {
|
||||
eprintln!(
|
||||
"[phase145/debug] ANF plan found but execute returned None (P0 stub)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None) => {
|
||||
// Out-of-scope for ANF, continue with normalized lowering
|
||||
}
|
||||
Err(_reason) => {
|
||||
// Explicitly out-of-scope (ContainsCall/ContainsMethodCall), continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if Self::out_of_scope_reason(scope, ast, env).is_some() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => Ok(env.get(name).copied()),
|
||||
ASTNode::Literal { value, .. } => Self::lower_literal(value, body, next_value_id),
|
||||
ASTNode::UnaryOp {
|
||||
operator, operand, ..
|
||||
} => Self::lower_unary(operator, operand, env, body, next_value_id),
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, right, ..
|
||||
} => Self::lower_binary(operator, left, right, env, body, next_value_id),
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} => match scope {
|
||||
ExprLoweringScope::PureOnly => Ok(None),
|
||||
ExprLoweringScope::WithImpure(policy) => {
|
||||
let Some(intrinsic) =
|
||||
Self::match_known_intrinsic_method_call(policy, object, method, arguments, env)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
Self::lower_known_intrinsic_method_call(
|
||||
intrinsic,
|
||||
object,
|
||||
body,
|
||||
next_value_id,
|
||||
env,
|
||||
)
|
||||
}
|
||||
},
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,301 @@
|
||||
use super::binary::{binary_kind, BinaryKind};
|
||||
use super::NormalizedExprLowererBox;
|
||||
use super::super::expr_lowering_contract::{
|
||||
ExprLoweringScope, ImpurePolicy, OutOfScopeReason,
|
||||
};
|
||||
use super::super::known_intrinsics::KnownIntrinsicRegistryBox;
|
||||
use crate::ast::{ASTNode, LiteralValue, UnaryOperator};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl NormalizedExprLowererBox {
|
||||
/// Classify out-of-scope reasons for diagnostics/tests without changing the core API.
|
||||
pub fn out_of_scope_reason(
|
||||
scope: ExprLoweringScope,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<OutOfScopeReason> {
|
||||
match scope {
|
||||
ExprLoweringScope::PureOnly => Self::out_of_scope_reason_pure(ast, env),
|
||||
ExprLoweringScope::WithImpure(policy) => {
|
||||
Self::out_of_scope_reason_with_impure(policy, ast, env)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn out_of_scope_reason_pure(
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<OutOfScopeReason> {
|
||||
match ast {
|
||||
ASTNode::FunctionCall { .. } | ASTNode::Call { .. } => Some(OutOfScopeReason::Call),
|
||||
ASTNode::MethodCall { .. } => Some(OutOfScopeReason::MethodCall),
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
if env.contains_key(name) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::MissingEnvVar)
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Integer(_) | LiteralValue::Bool(_) => None,
|
||||
_ => Some(OutOfScopeReason::UnsupportedLiteral),
|
||||
},
|
||||
|
||||
ASTNode::UnaryOp {
|
||||
operator, operand, ..
|
||||
} => match operator {
|
||||
UnaryOperator::Minus => {
|
||||
if Self::is_supported_int_expr(operand, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
if Self::is_supported_bool_expr(operand, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::BitNot => Some(OutOfScopeReason::UnsupportedOperator),
|
||||
UnaryOperator::Weak => Some(OutOfScopeReason::UnsupportedOperator),
|
||||
},
|
||||
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, right, ..
|
||||
} => {
|
||||
if binary_kind(operator).is_some() {
|
||||
if Self::is_supported_int_expr(left, env) && Self::is_supported_int_expr(right, env) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Some(OutOfScopeReason::ImpureExpression),
|
||||
}
|
||||
}
|
||||
|
||||
fn out_of_scope_reason_with_impure(
|
||||
policy: ImpurePolicy,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> Option<OutOfScopeReason> {
|
||||
match ast {
|
||||
ASTNode::FunctionCall { .. } | ASTNode::Call { .. } => Some(OutOfScopeReason::Call),
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
if Self::match_known_intrinsic_method_call(policy, object, method, arguments, env)
|
||||
.is_some()
|
||||
{
|
||||
None
|
||||
} else {
|
||||
let receiver_ok =
|
||||
matches!(object.as_ref(), ASTNode::Variable { name, .. } if env.contains_key(name));
|
||||
if receiver_ok && KnownIntrinsicRegistryBox::lookup(method, arguments.len()).is_some()
|
||||
{
|
||||
Some(OutOfScopeReason::IntrinsicNotWhitelisted)
|
||||
} else {
|
||||
Some(OutOfScopeReason::MethodCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
if env.contains_key(name) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::MissingEnvVar)
|
||||
}
|
||||
}
|
||||
|
||||
ASTNode::Literal { value, .. } => match value {
|
||||
LiteralValue::Integer(_) | LiteralValue::Bool(_) => None,
|
||||
_ => Some(OutOfScopeReason::UnsupportedLiteral),
|
||||
},
|
||||
|
||||
ASTNode::UnaryOp {
|
||||
operator, operand, ..
|
||||
} => match operator {
|
||||
UnaryOperator::Minus => {
|
||||
if Self::is_supported_int_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
operand,
|
||||
env,
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::Not => {
|
||||
if Self::is_supported_bool_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
operand,
|
||||
env,
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
UnaryOperator::BitNot => Some(OutOfScopeReason::UnsupportedOperator),
|
||||
UnaryOperator::Weak => Some(OutOfScopeReason::UnsupportedOperator),
|
||||
},
|
||||
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, right, ..
|
||||
} => {
|
||||
if binary_kind(operator).is_some() {
|
||||
if Self::is_supported_int_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
left,
|
||||
env,
|
||||
) && Self::is_supported_int_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
right,
|
||||
env,
|
||||
) {
|
||||
None
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
} else {
|
||||
Some(OutOfScopeReason::UnsupportedOperator)
|
||||
}
|
||||
}
|
||||
|
||||
_ => Some(OutOfScopeReason::ImpureExpression),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_int_expr(ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> bool {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(_),
|
||||
..
|
||||
} => true,
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Minus,
|
||||
operand,
|
||||
..
|
||||
} => Self::is_supported_int_expr(operand, env),
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, right, ..
|
||||
} => {
|
||||
matches!(binary_kind(operator), Some(BinaryKind::Arith(_)))
|
||||
&& Self::is_supported_int_expr(left, env)
|
||||
&& Self::is_supported_int_expr(right, env)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_bool_expr(ast: &ASTNode, env: &BTreeMap<String, ValueId>) -> bool {
|
||||
match ast {
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Bool(_),
|
||||
..
|
||||
} => true,
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} => Self::is_supported_bool_expr(operand, env),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_int_expr_with_scope(
|
||||
scope: ExprLoweringScope,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> bool {
|
||||
match scope {
|
||||
ExprLoweringScope::PureOnly => Self::is_supported_int_expr(ast, env),
|
||||
ExprLoweringScope::WithImpure(policy) => match ast {
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} => Self::match_known_intrinsic_method_call(policy, object, method, arguments, env)
|
||||
.is_some(),
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(_),
|
||||
..
|
||||
} => true,
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Minus,
|
||||
operand,
|
||||
..
|
||||
} => Self::is_supported_int_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
operand,
|
||||
env,
|
||||
),
|
||||
ASTNode::BinaryOp {
|
||||
operator, left, right, ..
|
||||
} => {
|
||||
matches!(
|
||||
binary_kind(operator),
|
||||
Some(BinaryKind::Arith(_)) | Some(BinaryKind::Compare(_))
|
||||
) && Self::is_supported_int_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
left,
|
||||
env,
|
||||
) && Self::is_supported_int_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
right,
|
||||
env,
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn is_supported_bool_expr_with_scope(
|
||||
scope: ExprLoweringScope,
|
||||
ast: &ASTNode,
|
||||
env: &BTreeMap<String, ValueId>,
|
||||
) -> bool {
|
||||
match scope {
|
||||
ExprLoweringScope::PureOnly => Self::is_supported_bool_expr(ast, env),
|
||||
ExprLoweringScope::WithImpure(policy) => match ast {
|
||||
ASTNode::Variable { name, .. } => env.contains_key(name),
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Bool(_),
|
||||
..
|
||||
} => true,
|
||||
ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand,
|
||||
..
|
||||
} => Self::is_supported_bool_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(policy),
|
||||
operand,
|
||||
env,
|
||||
),
|
||||
_ => {
|
||||
let _ = policy;
|
||||
false
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,412 @@
|
||||
use super::super::expr_lowering_contract::{ExprLoweringScope, ImpurePolicy, OutOfScopeReason};
|
||||
use super::*;
|
||||
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span, UnaryOperator};
|
||||
use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeInst, UnaryOp};
|
||||
use crate::mir::types::MirType;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn span() -> Span {
|
||||
Span::unknown()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_var_from_env() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("x".to_string(), ValueId(42));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(42)));
|
||||
assert!(body.is_empty());
|
||||
assert_eq!(next, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_scope_var_missing() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Variable {
|
||||
name: "missing".to_string(),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, None);
|
||||
assert!(body.is_empty());
|
||||
assert_eq!(next, 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_int_literal_const_emits() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Integer(7),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(100)));
|
||||
assert_eq!(next, 101);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: ValueId(100),
|
||||
value: ConstValue::Integer(7)
|
||||
})]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_bool_literal_const_emits() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::Literal {
|
||||
value: LiteralValue::Bool(true),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(100)));
|
||||
assert_eq!(next, 101);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[JoinInst::Compute(MirLikeInst::Const {
|
||||
dst: ValueId(100),
|
||||
value: ConstValue::Bool(true)
|
||||
})]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_unary_minus_int() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 10;
|
||||
|
||||
let ast = ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Minus,
|
||||
operand: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(3),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(11)));
|
||||
assert_eq!(next, 12);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[
|
||||
JoinInst::Compute(MirLikeInst::Const { .. }),
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: ValueId(11),
|
||||
op: UnaryOp::Neg,
|
||||
..
|
||||
})
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_unary_not_bool() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 10;
|
||||
|
||||
let ast = ASTNode::UnaryOp {
|
||||
operator: UnaryOperator::Not,
|
||||
operand: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Bool(false),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(11)));
|
||||
assert_eq!(next, 12);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[
|
||||
JoinInst::Compute(MirLikeInst::Const { .. }),
|
||||
JoinInst::Compute(MirLikeInst::UnaryOp {
|
||||
dst: ValueId(11),
|
||||
op: UnaryOp::Not,
|
||||
..
|
||||
})
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_add_sub_mul_div_ints() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("x".to_string(), ValueId(1));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let add = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&add, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, Some(ValueId(101)));
|
||||
|
||||
let sub = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(5),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(3),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let _ = NormalizedExprLowererBox::lower_expr(&sub, &env, &mut body, &mut next).unwrap();
|
||||
|
||||
let mul = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Multiply,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(6),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(7),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let _ = NormalizedExprLowererBox::lower_expr(&mul, &env, &mut body, &mut next).unwrap();
|
||||
|
||||
let div = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Divide,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(8),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(2),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let _ = NormalizedExprLowererBox::lower_expr(&div, &env, &mut body, &mut next).unwrap();
|
||||
|
||||
assert!(body.iter().any(|i| matches!(
|
||||
i,
|
||||
JoinInst::Compute(MirLikeInst::BinOp {
|
||||
op: BinOpKind::Add,
|
||||
..
|
||||
})
|
||||
)));
|
||||
assert!(body.iter().any(|i| matches!(
|
||||
i,
|
||||
JoinInst::Compute(MirLikeInst::BinOp {
|
||||
op: BinOpKind::Sub,
|
||||
..
|
||||
})
|
||||
)));
|
||||
assert!(body.iter().any(|i| matches!(
|
||||
i,
|
||||
JoinInst::Compute(MirLikeInst::BinOp {
|
||||
op: BinOpKind::Mul,
|
||||
..
|
||||
})
|
||||
)));
|
||||
assert!(body.iter().any(|i| matches!(
|
||||
i,
|
||||
JoinInst::Compute(MirLikeInst::BinOp {
|
||||
op: BinOpKind::Div,
|
||||
..
|
||||
})
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_compare_eq_lt_ints() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("x".to_string(), ValueId(1));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let eq = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&eq, &env, &mut body, &mut next).unwrap();
|
||||
assert!(got.is_some());
|
||||
|
||||
let lt = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Less,
|
||||
left: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: span(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: span(),
|
||||
}),
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(<, &env, &mut body, &mut next).unwrap();
|
||||
assert!(got.is_some());
|
||||
|
||||
assert!(body.iter().any(|i| matches!(
|
||||
i,
|
||||
JoinInst::Compute(MirLikeInst::Compare {
|
||||
op: CompareOp::Eq,
|
||||
..
|
||||
})
|
||||
)));
|
||||
assert!(body.iter().any(|i| matches!(
|
||||
i,
|
||||
JoinInst::Compute(MirLikeInst::Compare {
|
||||
op: CompareOp::Lt,
|
||||
..
|
||||
})
|
||||
)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn out_of_scope_call() {
|
||||
let env = BTreeMap::new();
|
||||
let mut body = vec![];
|
||||
let mut next = 1;
|
||||
|
||||
let ast = ASTNode::FunctionCall {
|
||||
name: "f".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
let got = NormalizedExprLowererBox::lower_expr(&ast, &env, &mut body, &mut next).unwrap();
|
||||
assert_eq!(got, None);
|
||||
assert!(body.is_empty());
|
||||
assert_eq!(next, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn call_is_out_of_scope_in_pure_only() {
|
||||
let env = BTreeMap::new();
|
||||
let ast = ASTNode::Call {
|
||||
callee: Box::new(ASTNode::Variable {
|
||||
name: "f".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
assert_eq!(
|
||||
NormalizedExprLowererBox::out_of_scope_reason(ExprLoweringScope::PureOnly, &ast, &env),
|
||||
Some(OutOfScopeReason::Call)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn methodcall_is_out_of_scope_in_pure_only() {
|
||||
let env = BTreeMap::new();
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "x".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
method: "m".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
assert_eq!(
|
||||
NormalizedExprLowererBox::out_of_scope_reason(ExprLoweringScope::PureOnly, &ast, &env),
|
||||
Some(OutOfScopeReason::MethodCall)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn methodcall_length0_is_in_scope_with_known_intrinsic_only() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("s".to_string(), ValueId(1));
|
||||
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
NormalizedExprLowererBox::out_of_scope_reason(
|
||||
ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly),
|
||||
&ast,
|
||||
&env
|
||||
),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lower_methodcall_length0_emits_method_call_inst() {
|
||||
let mut env = BTreeMap::new();
|
||||
env.insert("s".to_string(), ValueId(7));
|
||||
let mut body = vec![];
|
||||
let mut next = 100;
|
||||
|
||||
let ast = ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: span(),
|
||||
}),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: span(),
|
||||
};
|
||||
|
||||
let got = NormalizedExprLowererBox::lower_expr_with_scope(
|
||||
ExprLoweringScope::WithImpure(ImpurePolicy::KnownIntrinsicOnly),
|
||||
&ast,
|
||||
&env,
|
||||
&mut body,
|
||||
&mut next,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(got, Some(ValueId(100)));
|
||||
assert_eq!(next, 101);
|
||||
assert!(matches!(
|
||||
body.as_slice(),
|
||||
[JoinInst::MethodCall {
|
||||
dst: ValueId(100),
|
||||
receiver: ValueId(7),
|
||||
method,
|
||||
args,
|
||||
type_hint: Some(MirType::Integer),
|
||||
}] if method == "length" && args.is_empty()
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user