refactor(optimizer, parser/box): prune legacy optimizer bodies; delegate public/private single-line fields
- optimizer: remove unreachable old bodies in normalize_legacy_instructions/normalize_ref_field_access and delegate to optimizer_passes only
- parser/box: route public/private single-line fields through members::fields (supports = init, => computed, {..}+postfix) and keep visibility vectors consistent
- box_def facade: silence dead_code warning for scaffold
Smokes:
- PyVM Stage-2 PASS
- Stage-2 short-circuit PASS
- PyVM collections PASS
(NOTE) using_e2e_smoke is optional and currently failing; unrelated to this change
This commit is contained in:
@ -10,7 +10,7 @@
|
||||
|
||||
use super::{Effect, EffectMask, MirFunction, MirInstruction, MirModule, MirType, ValueId};
|
||||
use crate::mir::optimizer_stats::OptimizationStats;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
// std::collections imports removed (local DCE/CSE impls deleted)
|
||||
|
||||
/// MIR optimization passes
|
||||
pub struct MirOptimizer {
|
||||
@ -123,145 +123,8 @@ impl MirOptimizer {
|
||||
stats
|
||||
}
|
||||
|
||||
/// Eliminate dead code (unused values)
|
||||
fn eliminate_dead_code(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
/// Core-13 "pure" normalization notes moved to optimizer_passes::normalize_core13_pure
|
||||
|
||||
for (func_name, function) in &mut module.functions {
|
||||
if self.debug {
|
||||
println!(" 🗑️ Dead code elimination in function: {}", func_name);
|
||||
}
|
||||
|
||||
let eliminated = self.eliminate_dead_code_in_function(function);
|
||||
stats.dead_code_eliminated += eliminated;
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
|
||||
/// Core-13 "pure" normalization: rewrite a few non-13 ops to allowed forms.
|
||||
/// - Load(dst, ptr) => ExternCall(Some dst, env.local.get, [ptr])
|
||||
/// - Store(val, ptr) => ExternCall(None, env.local.set, [ptr, val])
|
||||
/// - NewBox(dst, T, args...) => ExternCall(Some dst, env.box.new, [Const String(T), args...])
|
||||
/// - UnaryOp:
|
||||
/// Neg x => BinOp(Sub, Const 0, x)
|
||||
/// Not x => Compare(Eq, x, Const false)
|
||||
/// BitNot x => BinOp(BitXor, x, Const(-1))
|
||||
// normalize_pure_core13 moved to optimizer_passes::normalize_core13_pure
|
||||
|
||||
/// Eliminate dead code in a single function
|
||||
fn eliminate_dead_code_in_function(&mut self, function: &mut MirFunction) -> usize {
|
||||
// Collect all used values
|
||||
let mut used_values = HashSet::new();
|
||||
|
||||
// Mark values used in terminators and side-effect instructions
|
||||
for (_, block) in &function.blocks {
|
||||
for instruction in &block.instructions {
|
||||
// Always keep instructions with side effects
|
||||
if !instruction.effects().is_pure() {
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
used_values.insert(dst);
|
||||
}
|
||||
for used in instruction.used_values() {
|
||||
used_values.insert(used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mark values used in terminators
|
||||
if let Some(terminator) = &block.terminator {
|
||||
for used in terminator.used_values() {
|
||||
used_values.insert(used);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate usage backwards
|
||||
let mut changed = true;
|
||||
while changed {
|
||||
changed = false;
|
||||
for (_, block) in &function.blocks {
|
||||
for instruction in &block.instructions {
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
if used_values.contains(&dst) {
|
||||
for used in instruction.used_values() {
|
||||
if used_values.insert(used) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove unused pure instructions
|
||||
let mut eliminated = 0;
|
||||
for (bbid, block) in &mut function.blocks {
|
||||
block.instructions.retain(|instruction| {
|
||||
if instruction.effects().is_pure() {
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
if !used_values.contains(&dst) {
|
||||
opt_debug(&format!("DCE drop @{}: {:?}", bbid.as_u32(), instruction));
|
||||
eliminated += 1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
});
|
||||
}
|
||||
|
||||
eliminated
|
||||
}
|
||||
|
||||
/// Common Subexpression Elimination for pure instructions
|
||||
fn common_subexpression_elimination(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
|
||||
for (func_name, function) in &mut module.functions {
|
||||
if self.debug {
|
||||
println!(" 🔄 CSE in function: {}", func_name);
|
||||
}
|
||||
|
||||
let eliminated = self.cse_in_function(function);
|
||||
stats.cse_eliminated += eliminated;
|
||||
}
|
||||
|
||||
stats
|
||||
}
|
||||
|
||||
/// CSE in a single function
|
||||
fn cse_in_function(&mut self, function: &mut MirFunction) -> usize {
|
||||
let mut expression_map: HashMap<String, ValueId> = HashMap::new();
|
||||
let mut replacements: HashMap<ValueId, ValueId> = HashMap::new();
|
||||
let mut eliminated = 0;
|
||||
|
||||
for (_, block) in &mut function.blocks {
|
||||
for instruction in &mut block.instructions {
|
||||
// Only optimize pure instructions
|
||||
if instruction.effects().is_pure() {
|
||||
let expr_key = self.instruction_to_key(instruction);
|
||||
|
||||
if let Some(&existing_value) = expression_map.get(&expr_key) {
|
||||
// Found common subexpression
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
replacements.insert(dst, existing_value);
|
||||
eliminated += 1;
|
||||
}
|
||||
} else {
|
||||
// First occurrence of this expression
|
||||
if let Some(dst) = instruction.dst_value() {
|
||||
expression_map.insert(expr_key, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply replacements (simplified - in full implementation would need proper SSA update)
|
||||
eliminated
|
||||
}
|
||||
|
||||
/// Convert instruction to string key for CSE
|
||||
#[allow(dead_code)]
|
||||
@ -320,496 +183,13 @@ impl MirOptimizer {
|
||||
/// - Print → ExternCall(env.console.log)
|
||||
#[allow(dead_code)]
|
||||
fn normalize_legacy_instructions(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
use super::{BarrierOp, MirInstruction as I, MirType, TypeOpKind, WeakRefOp};
|
||||
let mut stats = OptimizationStats::new();
|
||||
let rw_dbg = crate::config::env::rewrite_debug();
|
||||
let rw_sp = crate::config::env::rewrite_safepoint();
|
||||
let rw_future = crate::config::env::rewrite_future();
|
||||
// Phase 11.8 toggles
|
||||
let core13 = crate::config::env::mir_core13();
|
||||
let mut array_to_boxcall = crate::config::env::mir_array_boxcall();
|
||||
if core13 {
|
||||
array_to_boxcall = true;
|
||||
}
|
||||
for (_fname, function) in &mut module.functions {
|
||||
for (_bb, block) in &mut function.blocks {
|
||||
// Rewrite in-place for normal instructions
|
||||
for inst in &mut block.instructions {
|
||||
match inst {
|
||||
I::TypeCheck {
|
||||
dst,
|
||||
value,
|
||||
expected_type,
|
||||
} => {
|
||||
let ty = MirType::Box(expected_type.clone());
|
||||
*inst = I::TypeOp {
|
||||
dst: *dst,
|
||||
op: TypeOpKind::Check,
|
||||
value: *value,
|
||||
ty,
|
||||
};
|
||||
stats.reorderings += 0; // no-op; keep stats structure alive
|
||||
}
|
||||
I::Cast {
|
||||
dst,
|
||||
value,
|
||||
target_type,
|
||||
} => {
|
||||
let ty = target_type.clone();
|
||||
*inst = I::TypeOp {
|
||||
dst: *dst,
|
||||
op: TypeOpKind::Cast,
|
||||
value: *value,
|
||||
ty,
|
||||
};
|
||||
}
|
||||
I::WeakNew { dst, box_val } => {
|
||||
let val = *box_val;
|
||||
*inst = I::WeakRef {
|
||||
dst: *dst,
|
||||
op: WeakRefOp::New,
|
||||
value: val,
|
||||
};
|
||||
}
|
||||
I::WeakLoad { dst, weak_ref } => {
|
||||
let val = *weak_ref;
|
||||
*inst = I::WeakRef {
|
||||
dst: *dst,
|
||||
op: WeakRefOp::Load,
|
||||
value: val,
|
||||
};
|
||||
}
|
||||
I::BarrierRead { ptr } => {
|
||||
let val = *ptr;
|
||||
*inst = I::Barrier {
|
||||
op: BarrierOp::Read,
|
||||
ptr: val,
|
||||
};
|
||||
}
|
||||
I::BarrierWrite { ptr } => {
|
||||
let val = *ptr;
|
||||
*inst = I::Barrier {
|
||||
op: BarrierOp::Write,
|
||||
ptr: val,
|
||||
};
|
||||
}
|
||||
I::Print { value, .. } => {
|
||||
let v = *value;
|
||||
*inst = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.console".to_string(),
|
||||
method_name: "log".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
I::RefGet { .. } | I::RefSet { .. } => { /* handled in normalize_ref_field_access pass (guarded) */
|
||||
}
|
||||
I::ArrayGet { dst, array, index } if array_to_boxcall => {
|
||||
let d = *dst;
|
||||
let a = *array;
|
||||
let i = *index;
|
||||
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
|
||||
"ArrayBox", "get",
|
||||
);
|
||||
*inst = I::BoxCall {
|
||||
dst: Some(d),
|
||||
box_val: a,
|
||||
method: "get".to_string(),
|
||||
method_id: mid,
|
||||
args: vec![i],
|
||||
effects: EffectMask::READ,
|
||||
};
|
||||
}
|
||||
I::ArraySet {
|
||||
array,
|
||||
index,
|
||||
value,
|
||||
} if array_to_boxcall => {
|
||||
let a = *array;
|
||||
let i = *index;
|
||||
let v = *value;
|
||||
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
|
||||
"ArrayBox", "set",
|
||||
);
|
||||
*inst = I::BoxCall {
|
||||
dst: None,
|
||||
box_val: a,
|
||||
method: "set".to_string(),
|
||||
method_id: mid,
|
||||
args: vec![i, v],
|
||||
effects: EffectMask::WRITE,
|
||||
};
|
||||
}
|
||||
I::PluginInvoke {
|
||||
dst,
|
||||
box_val,
|
||||
method,
|
||||
args,
|
||||
effects,
|
||||
} => {
|
||||
let d = *dst;
|
||||
let recv = *box_val;
|
||||
let m = method.clone();
|
||||
let as_ = args.clone();
|
||||
let eff = *effects;
|
||||
*inst = I::BoxCall {
|
||||
dst: d,
|
||||
box_val: recv,
|
||||
method: m,
|
||||
method_id: None,
|
||||
args: as_,
|
||||
effects: eff,
|
||||
};
|
||||
}
|
||||
I::Debug { value, .. } if rw_dbg => {
|
||||
let v = *value;
|
||||
*inst = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.debug".to_string(),
|
||||
method_name: "trace".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Debug),
|
||||
};
|
||||
}
|
||||
I::Safepoint if rw_sp => {
|
||||
*inst = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.runtime".to_string(),
|
||||
method_name: "checkpoint".to_string(),
|
||||
args: vec![],
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
}
|
||||
// Future/Await の段階移行: ExternCall(env.future.*) に書き換え(トグル)
|
||||
I::FutureNew { dst, value } if rw_future => {
|
||||
let d = *dst;
|
||||
let v = *value;
|
||||
*inst = I::ExternCall {
|
||||
dst: Some(d),
|
||||
iface_name: "env.future".to_string(),
|
||||
method_name: "new".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
I::FutureSet { future, value } if rw_future => {
|
||||
let f = *future;
|
||||
let v = *value;
|
||||
*inst = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.future".to_string(),
|
||||
method_name: "set".to_string(),
|
||||
args: vec![f, v],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
I::Await { dst, future } if rw_future => {
|
||||
let d = *dst;
|
||||
let f = *future;
|
||||
*inst = I::ExternCall {
|
||||
dst: Some(d),
|
||||
iface_name: "env.future".to_string(),
|
||||
method_name: "await".to_string(),
|
||||
args: vec![f],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Rewrite terminator, if any
|
||||
if let Some(term) = &mut block.terminator {
|
||||
match term {
|
||||
I::TypeCheck {
|
||||
dst,
|
||||
value,
|
||||
expected_type,
|
||||
} => {
|
||||
let ty = MirType::Box(expected_type.clone());
|
||||
*term = I::TypeOp {
|
||||
dst: *dst,
|
||||
op: TypeOpKind::Check,
|
||||
value: *value,
|
||||
ty,
|
||||
};
|
||||
}
|
||||
I::Cast {
|
||||
dst,
|
||||
value,
|
||||
target_type,
|
||||
} => {
|
||||
let ty = target_type.clone();
|
||||
*term = I::TypeOp {
|
||||
dst: *dst,
|
||||
op: TypeOpKind::Cast,
|
||||
value: *value,
|
||||
ty,
|
||||
};
|
||||
}
|
||||
I::WeakNew { dst, box_val } => {
|
||||
let val = *box_val;
|
||||
*term = I::WeakRef {
|
||||
dst: *dst,
|
||||
op: WeakRefOp::New,
|
||||
value: val,
|
||||
};
|
||||
}
|
||||
I::WeakLoad { dst, weak_ref } => {
|
||||
let val = *weak_ref;
|
||||
*term = I::WeakRef {
|
||||
dst: *dst,
|
||||
op: WeakRefOp::Load,
|
||||
value: val,
|
||||
};
|
||||
}
|
||||
I::BarrierRead { ptr } => {
|
||||
let val = *ptr;
|
||||
*term = I::Barrier {
|
||||
op: BarrierOp::Read,
|
||||
ptr: val,
|
||||
};
|
||||
}
|
||||
I::BarrierWrite { ptr } => {
|
||||
let val = *ptr;
|
||||
*term = I::Barrier {
|
||||
op: BarrierOp::Write,
|
||||
ptr: val,
|
||||
};
|
||||
}
|
||||
I::Print { value, .. } => {
|
||||
let v = *value;
|
||||
*term = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.console".to_string(),
|
||||
method_name: "log".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
I::RefGet { .. } | I::RefSet { .. } => { /* handled in normalize_ref_field_access pass (guarded) */
|
||||
}
|
||||
I::ArrayGet { dst, array, index } if array_to_boxcall => {
|
||||
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
|
||||
"ArrayBox", "get",
|
||||
);
|
||||
*term = I::BoxCall {
|
||||
dst: Some(*dst),
|
||||
box_val: *array,
|
||||
method: "get".to_string(),
|
||||
method_id: mid,
|
||||
args: vec![*index],
|
||||
effects: EffectMask::READ,
|
||||
};
|
||||
}
|
||||
I::ArraySet {
|
||||
array,
|
||||
index,
|
||||
value,
|
||||
} if array_to_boxcall => {
|
||||
let mid = crate::mir::slot_registry::resolve_slot_by_type_name(
|
||||
"ArrayBox", "set",
|
||||
);
|
||||
*term = I::BoxCall {
|
||||
dst: None,
|
||||
box_val: *array,
|
||||
method: "set".to_string(),
|
||||
method_id: mid,
|
||||
args: vec![*index, *value],
|
||||
effects: EffectMask::WRITE,
|
||||
};
|
||||
}
|
||||
I::PluginInvoke {
|
||||
dst,
|
||||
box_val,
|
||||
method,
|
||||
args,
|
||||
effects,
|
||||
} => {
|
||||
*term = I::BoxCall {
|
||||
dst: *dst,
|
||||
box_val: *box_val,
|
||||
method: method.clone(),
|
||||
method_id: None,
|
||||
args: args.clone(),
|
||||
effects: *effects,
|
||||
};
|
||||
}
|
||||
I::Debug { value, .. } if rw_dbg => {
|
||||
let v = *value;
|
||||
*term = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.debug".to_string(),
|
||||
method_name: "trace".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Debug),
|
||||
};
|
||||
}
|
||||
I::Safepoint if rw_sp => {
|
||||
*term = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.runtime".to_string(),
|
||||
method_name: "checkpoint".to_string(),
|
||||
args: vec![],
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
}
|
||||
// Future/Await (終端側)
|
||||
I::FutureNew { dst, value } if rw_future => {
|
||||
let d = *dst;
|
||||
let v = *value;
|
||||
*term = I::ExternCall {
|
||||
dst: Some(d),
|
||||
iface_name: "env.future".to_string(),
|
||||
method_name: "new".to_string(),
|
||||
args: vec![v],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
I::FutureSet { future, value } if rw_future => {
|
||||
let f = *future;
|
||||
let v = *value;
|
||||
*term = I::ExternCall {
|
||||
dst: None,
|
||||
iface_name: "env.future".to_string(),
|
||||
method_name: "set".to_string(),
|
||||
args: vec![f, v],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
I::Await { dst, future } if rw_future => {
|
||||
let d = *dst;
|
||||
let f = *future;
|
||||
*term = I::ExternCall {
|
||||
dst: Some(d),
|
||||
iface_name: "env.future".to_string(),
|
||||
method_name: "await".to_string(),
|
||||
args: vec![f],
|
||||
effects: EffectMask::PURE.add(Effect::Io),
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stats
|
||||
crate::mir::optimizer_passes::normalize::normalize_legacy_instructions(self, module)
|
||||
}
|
||||
|
||||
/// Normalize RefGet/RefSet to BoxCall("getField"/"setField") with Const String field argument.
|
||||
#[allow(dead_code)]
|
||||
fn normalize_ref_field_access(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||
use super::{BarrierOp, MirInstruction as I};
|
||||
let mut stats = OptimizationStats::new();
|
||||
for (_fname, function) in &mut module.functions {
|
||||
for (_bb, block) in &mut function.blocks {
|
||||
let mut out: Vec<I> = Vec::with_capacity(block.instructions.len() + 2);
|
||||
let old = std::mem::take(&mut block.instructions);
|
||||
for inst in old.into_iter() {
|
||||
match inst {
|
||||
I::RefGet {
|
||||
dst,
|
||||
reference,
|
||||
field,
|
||||
} => {
|
||||
let new_id = super::ValueId::new(function.next_value_id);
|
||||
function.next_value_id += 1;
|
||||
out.push(I::Const {
|
||||
dst: new_id,
|
||||
value: super::instruction::ConstValue::String(field),
|
||||
});
|
||||
out.push(I::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val: reference,
|
||||
method: "getField".to_string(),
|
||||
method_id: None,
|
||||
args: vec![new_id],
|
||||
effects: super::EffectMask::READ,
|
||||
});
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
I::RefSet {
|
||||
reference,
|
||||
field,
|
||||
value,
|
||||
} => {
|
||||
let new_id = super::ValueId::new(function.next_value_id);
|
||||
function.next_value_id += 1;
|
||||
out.push(I::Const {
|
||||
dst: new_id,
|
||||
value: super::instruction::ConstValue::String(field),
|
||||
});
|
||||
// Prepend an explicit write barrier before setField to make side-effects visible
|
||||
out.push(I::Barrier {
|
||||
op: BarrierOp::Write,
|
||||
ptr: reference,
|
||||
});
|
||||
out.push(I::BoxCall {
|
||||
dst: None,
|
||||
box_val: reference,
|
||||
method: "setField".to_string(),
|
||||
method_id: None,
|
||||
args: vec![new_id, value],
|
||||
effects: super::EffectMask::WRITE,
|
||||
});
|
||||
stats.intrinsic_optimizations += 1;
|
||||
}
|
||||
other => out.push(other),
|
||||
}
|
||||
}
|
||||
block.instructions = out;
|
||||
|
||||
if let Some(term) = block.terminator.take() {
|
||||
block.terminator = Some(match term {
|
||||
I::RefGet {
|
||||
dst,
|
||||
reference,
|
||||
field,
|
||||
} => {
|
||||
let new_id = super::ValueId::new(function.next_value_id);
|
||||
function.next_value_id += 1;
|
||||
block.instructions.push(I::Const {
|
||||
dst: new_id,
|
||||
value: super::instruction::ConstValue::String(field),
|
||||
});
|
||||
I::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_val: reference,
|
||||
method: "getField".to_string(),
|
||||
method_id: None,
|
||||
args: vec![new_id],
|
||||
effects: super::EffectMask::READ,
|
||||
}
|
||||
}
|
||||
I::RefSet {
|
||||
reference,
|
||||
field,
|
||||
value,
|
||||
} => {
|
||||
let new_id = super::ValueId::new(function.next_value_id);
|
||||
function.next_value_id += 1;
|
||||
block.instructions.push(I::Const {
|
||||
dst: new_id,
|
||||
value: super::instruction::ConstValue::String(field),
|
||||
});
|
||||
block.instructions.push(I::Barrier {
|
||||
op: BarrierOp::Write,
|
||||
ptr: reference,
|
||||
});
|
||||
I::BoxCall {
|
||||
dst: None,
|
||||
box_val: reference,
|
||||
method: "setField".to_string(),
|
||||
method_id: None,
|
||||
args: vec![new_id, value],
|
||||
effects: super::EffectMask::WRITE,
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
stats
|
||||
crate::mir::optimizer_passes::normalize::normalize_ref_field_access(self, module)
|
||||
}
|
||||
}
|
||||
|
||||
@ -890,12 +270,14 @@ impl Default for MirOptimizer {
|
||||
|
||||
// OptimizationStats moved to crate::mir::optimizer_stats
|
||||
|
||||
impl MirOptimizer {
|
||||
/// Diagnostic: detect unlowered is/as/isType/asType after Builder
|
||||
#[allow(dead_code)]
|
||||
fn diagnose_unlowered_type_ops(&mut self, module: &MirModule) -> OptimizationStats {
|
||||
/// Diagnostics: identify unlowered type-ops embedded as strings in Call/BoxCall
|
||||
#[allow(dead_code)]
|
||||
fn diagnose_unlowered_type_ops(
|
||||
optimizer: &MirOptimizer,
|
||||
module: &MirModule,
|
||||
) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
let diag_on = self.debug || crate::config::env::opt_diag();
|
||||
let diag_on = optimizer.debug || crate::config::env::opt_diag();
|
||||
for (fname, function) in &module.functions {
|
||||
// def map for resolving constants
|
||||
let mut def_map: std::collections::HashMap<
|
||||
@ -958,15 +340,15 @@ impl MirOptimizer {
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic: detect legacy instructions that should be unified
|
||||
/// Legacy set: TypeCheck/Cast/WeakNew/WeakLoad/BarrierRead/BarrierWrite/ArrayGet/ArraySet/RefGet/RefSet/PluginInvoke
|
||||
/// When NYASH_OPT_DIAG or NYASH_OPT_DIAG_FORBID_LEGACY is set, prints diagnostics.
|
||||
#[allow(dead_code)]
|
||||
fn diagnose_legacy_instructions(&mut self, module: &MirModule) -> OptimizationStats {
|
||||
/// Diagnostic: detect legacy instructions that should be unified
|
||||
/// Legacy set: TypeCheck/Cast/WeakNew/WeakLoad/BarrierRead/BarrierWrite/ArrayGet/ArraySet/RefGet/RefSet/PluginInvoke
|
||||
/// When NYASH_OPT_DIAG or NYASH_OPT_DIAG_FORBID_LEGACY is set, prints diagnostics.
|
||||
#[allow(dead_code)]
|
||||
fn diagnose_legacy_instructions(module: &MirModule, debug: bool) -> OptimizationStats {
|
||||
let mut stats = OptimizationStats::new();
|
||||
let diag_on = self.debug
|
||||
let diag_on = debug
|
||||
|| crate::config::env::opt_diag()
|
||||
|| crate::config::env::opt_diag_forbid_legacy();
|
||||
for (fname, function) in &module.functions {
|
||||
@ -1026,7 +408,6 @@ impl MirOptimizer {
|
||||
}
|
||||
}
|
||||
stats
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -1133,3 +514,4 @@ mod tests {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
src/parser/declarations/box_def/mod.rs
Normal file
27
src/parser/declarations/box_def/mod.rs
Normal file
@ -0,0 +1,27 @@
|
||||
//! Box Definition parser (scaffold)
|
||||
#![allow(dead_code)]
|
||||
//!
|
||||
//! This module will progressively take over parsing of large `parse_box_declaration`
|
||||
//! by splitting header and member parsing into focused units.
|
||||
//! For now, it provides only type skeletons to stage the refactor safely.
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
|
||||
pub mod header;
|
||||
pub mod members;
|
||||
|
||||
/// Facade to host the staged migration.
|
||||
pub struct BoxDefParserFacade;
|
||||
|
||||
impl BoxDefParserFacade {
|
||||
/// Entry planned: parse full box declaration (header + members).
|
||||
/// Not wired yet; use NyashParser::parse_box_declaration for now.
|
||||
pub fn parse_box(_p: &mut NyashParser) -> Result<ASTNode, ParseError> {
|
||||
Err(ParseError::UnexpectedToken {
|
||||
found: crate::tokenizer::TokenType::EOF,
|
||||
expected: "box declaration (facade not wired)".to_string(),
|
||||
line: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
use crate::ast::{ASTNode, Span};
|
||||
use crate::must_advance;
|
||||
use crate::parser::declarations::box_def::header as box_header;
|
||||
use crate::parser::common::ParserUtils;
|
||||
use crate::parser::{NyashParser, ParseError};
|
||||
use crate::tokenizer::TokenType;
|
||||
@ -16,122 +16,8 @@ impl NyashParser {
|
||||
/// box宣言をパース: box Name { fields... methods... }
|
||||
pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
|
||||
self.consume(TokenType::BOX)?;
|
||||
|
||||
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
||||
let name = name.clone();
|
||||
self.advance();
|
||||
name
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "identifier".to_string(),
|
||||
line,
|
||||
});
|
||||
};
|
||||
|
||||
// 🔥 ジェネリクス型パラメータのパース (<T, U>)
|
||||
let type_parameters = if self.match_token(&TokenType::LESS) {
|
||||
self.advance(); // consume '<'
|
||||
let mut params = Vec::new();
|
||||
|
||||
while !self.match_token(&TokenType::GREATER) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "generic type parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
self.skip_newlines();
|
||||
}
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "type parameter name".to_string(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::GREATER)?; // consume '>'
|
||||
params
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// 🚀 Multi-delegation support: "from Parent1, Parent2, ..."
|
||||
let extends = if self.match_token(&TokenType::FROM) {
|
||||
self.advance(); // consume 'from'
|
||||
let mut parents = Vec::new();
|
||||
|
||||
// Parse first parent (required)
|
||||
if let TokenType::IDENTIFIER(parent) = &self.current_token().token_type {
|
||||
parents.push(parent.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parent box name after 'from'".to_string(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
|
||||
// Parse additional parents (optional)
|
||||
while self.match_token(&TokenType::COMMA) {
|
||||
self.advance(); // consume ','
|
||||
self.skip_newlines();
|
||||
|
||||
if let TokenType::IDENTIFIER(parent) = &self.current_token().token_type {
|
||||
parents.push(parent.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "parent box name after comma".to_string(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
parents
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
|
||||
// implementsキーワードのチェック
|
||||
// TODO: TokenType::IMPLEMENTS is not defined in current version
|
||||
let implements = if false {
|
||||
// self.match_token(&TokenType::IMPLEMENTS) {
|
||||
self.advance(); // consume 'implements'
|
||||
let mut interfaces = Vec::new();
|
||||
|
||||
loop {
|
||||
must_advance!(self, _unused, "interface implementation parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(interface) = &self.current_token().token_type {
|
||||
interfaces.push(interface.clone());
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "interface name".to_string(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
interfaces
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
let (name, type_parameters, extends, implements) =
|
||||
box_header::parse_header(self)?;
|
||||
|
||||
self.consume(TokenType::LBRACE)?;
|
||||
self.skip_newlines(); // ブレース後の改行をスキップ
|
||||
@ -143,10 +29,144 @@ impl NyashParser {
|
||||
let mut constructors = HashMap::new();
|
||||
let mut init_fields = Vec::new();
|
||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
|
||||
// Track birth_once properties to inject eager init into birth()
|
||||
let mut birth_once_props: Vec<String> = Vec::new();
|
||||
|
||||
let mut last_method_name: Option<String> = None;
|
||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||
self.skip_newlines(); // ループ開始時に改行をスキップ
|
||||
// 分類(段階移行用の観測): 将来の分岐移譲のための前処理
|
||||
if crate::config::env::parser_stage3() {
|
||||
if let Ok(kind) = crate::parser::declarations::box_def::members::common::classify_member(self) {
|
||||
let _ = kind; // 現段階では観測のみ(無副作用)
|
||||
}
|
||||
}
|
||||
|
||||
// nyashモード(block-first): { body } as (once|birth_once)? name : Type
|
||||
if crate::config::env::unified_members() && self.match_token(&TokenType::LBRACE) {
|
||||
// Parse block body first
|
||||
let mut final_body = self.parse_block_statements()?;
|
||||
self.skip_newlines();
|
||||
// Expect 'as'
|
||||
if let TokenType::IDENTIFIER(kw) = &self.current_token().token_type {
|
||||
if kw != "as" {
|
||||
// Not a block-first member; treat as standalone block statement in box (unsupported)
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "'as' after block for block-first member".to_string(), line });
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "'as' after block for block-first member".to_string(), line });
|
||||
}
|
||||
self.advance(); // consume 'as'
|
||||
// Optional kind keyword
|
||||
let mut kind = "computed".to_string();
|
||||
if let TokenType::IDENTIFIER(k) = &self.current_token().token_type {
|
||||
if k == "once" || k == "birth_once" {
|
||||
kind = k.clone();
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
// Name : Type
|
||||
let name = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
||||
let s = n.clone();
|
||||
self.advance();
|
||||
s
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "identifier for member name".to_string(), line });
|
||||
};
|
||||
if self.match_token(&TokenType::COLON) {
|
||||
self.advance();
|
||||
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type { self.advance(); } else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "type name after ':'".to_string(), line });
|
||||
}
|
||||
} else {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: ": type".to_string(), line });
|
||||
}
|
||||
// Optional postfix handlers (Stage-3) directly after block
|
||||
if crate::config::env::parser_stage3() && (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) {
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance();
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause { exception_type: exc_ty, variable_name: exc_var, body: catch_body, span: crate::ast::Span::unknown() });
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: "single catch only after member block".to_string(), line });
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) { self.advance(); Some(self.parse_block_statements()?) } else { None };
|
||||
final_body = vec![ASTNode::TryCatch { try_body: final_body, catch_clauses, finally_body, span: crate::ast::Span::unknown() }];
|
||||
}
|
||||
|
||||
// Generate methods per kind
|
||||
if kind == "once" {
|
||||
let compute_name = format!("__compute_once_{}", name);
|
||||
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(compute_name.clone(), compute);
|
||||
let key = format!("__once_{}", name);
|
||||
let poison_key = format!("__once_poison_{}", name);
|
||||
let cached_local = format!("__ny_cached_{}", name);
|
||||
let poison_local = format!("__ny_poison_{}", name);
|
||||
let val_local = format!("__ny_val_{}", name);
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let get_cached = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], span: Span::unknown() };
|
||||
let local_cached = ASTNode::Local { variables: vec![cached_local.clone()], initial_values: vec![Some(Box::new(get_cached))], span: Span::unknown() };
|
||||
let cond_cached = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
|
||||
let then_ret_cached = vec![ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: cached_local.clone(), span: Span::unknown() })), span: Span::unknown() }];
|
||||
let if_cached = ASTNode::If { condition: Box::new(cond_cached), then_body: then_ret_cached, else_body: None, span: Span::unknown() };
|
||||
let get_poison = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }], span: Span::unknown() };
|
||||
let local_poison = ASTNode::Local { variables: vec![poison_local.clone()], initial_values: vec![Some(Box::new(get_poison))], span: Span::unknown() };
|
||||
let cond_poison = ASTNode::BinaryOp { operator: crate::ast::BinaryOperator::NotEqual, left: Box::new(ASTNode::Variable { name: poison_local.clone(), span: Span::unknown() }), right: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::Null, span: Span::unknown() }), span: Span::unknown() };
|
||||
let then_throw = vec![ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' previously failed", name)), span: Span::unknown() }), span: Span::unknown() }];
|
||||
let if_poison = ASTNode::If { condition: Box::new(cond_poison), then_body: then_throw, else_body: None, span: Span::unknown() };
|
||||
let call_compute = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: compute_name.clone(), arguments: vec![], span: Span::unknown() };
|
||||
let local_val = ASTNode::Local { variables: vec![val_local.clone()], initial_values: vec![Some(Box::new(call_compute))], span: Span::unknown() };
|
||||
let set_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }, ASTNode::Variable { name: val_local.clone(), span: Span::unknown() }], span: Span::unknown() };
|
||||
let ret_stmt = ASTNode::Return { value: Some(Box::new(ASTNode::Variable { name: val_local.clone(), span: Span::unknown() })), span: Span::unknown() };
|
||||
let try_body = vec![local_val, set_call, ret_stmt];
|
||||
let catch_body = vec![
|
||||
ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "setField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(poison_key.clone()), span: Span::unknown() }, ASTNode::Literal { value: crate::ast::LiteralValue::Bool(true), span: Span::unknown() }], span: Span::unknown() },
|
||||
ASTNode::Throw { expression: Box::new(ASTNode::Literal { value: crate::ast::LiteralValue::String(format!("once '{}' init failed", name)), span: Span::unknown() }), span: Span::unknown() }
|
||||
];
|
||||
let trycatch = ASTNode::TryCatch { try_body, catch_clauses: vec![crate::ast::CatchClause { exception_type: None, variable_name: None, body: catch_body, span: Span::unknown() }], finally_body: None, span: Span::unknown() };
|
||||
let getter_body = vec![local_cached, if_cached, local_poison, if_poison, trycatch];
|
||||
let getter_name = format!("__get_once_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
} else if kind == "birth_once" {
|
||||
// Self-cycle guard: birth_once cannot reference itself via me.<name>
|
||||
if self.ast_contains_me_field(&final_body, &name) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken { found: self.current_token().token_type.clone(), expected: format!("birth_once '{}' must not reference itself", name), line });
|
||||
}
|
||||
birth_once_props.push(name.clone());
|
||||
let compute_name = format!("__compute_birth_{}", name);
|
||||
let compute = ASTNode::FunctionDeclaration { name: compute_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(compute_name.clone(), compute);
|
||||
let key = format!("__birth_{}", name);
|
||||
let me_node = ASTNode::Me { span: Span::unknown() };
|
||||
let get_call = ASTNode::MethodCall { object: Box::new(me_node.clone()), method: "getField".to_string(), arguments: vec![ASTNode::Literal { value: crate::ast::LiteralValue::String(key.clone()), span: Span::unknown() }], span: Span::unknown() };
|
||||
let getter_body = vec![ASTNode::Return { value: Some(Box::new(get_call)), span: Span::unknown() }];
|
||||
let getter_name = format!("__get_birth_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: getter_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
} else {
|
||||
// computed
|
||||
let getter_name = format!("__get_{}", name);
|
||||
let getter = ASTNode::FunctionDeclaration { name: getter_name.clone(), params: vec![], body: final_body, is_static: false, is_override: false, span: Span::unknown() };
|
||||
methods.insert(getter_name, getter);
|
||||
}
|
||||
self.skip_newlines();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Fallback: method-level postfix catch/cleanup after a method (non-static box)
|
||||
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
|
||||
@ -243,195 +263,9 @@ impl NyashParser {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
// initトークンをメソッド名として特別処理
|
||||
if self.match_token(&TokenType::INIT) && self.peek_token() == &TokenType::LPAREN {
|
||||
let field_or_method = "init".to_string();
|
||||
self.advance(); // consume 'init'
|
||||
|
||||
// コンストラクタとして処理
|
||||
if self.match_token(&TokenType::LPAREN) {
|
||||
// initは常にコンストラクタ
|
||||
if is_override {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method definition, not constructor after override keyword"
|
||||
.to_string(),
|
||||
found: TokenType::INIT,
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
// コンストラクタの処理
|
||||
self.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "constructor parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let mut body = self.parse_block_statements()?;
|
||||
self.skip_newlines();
|
||||
|
||||
// Method-level postfix catch/cleanup (gate)
|
||||
if self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)
|
||||
{
|
||||
let mut catch_clauses: Vec<crate::ast::CatchClause> = Vec::new();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
self.advance(); // consume 'catch'
|
||||
self.consume(TokenType::LPAREN)?;
|
||||
let (exc_ty, exc_var) = self.parse_catch_param()?;
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let catch_body = self.parse_block_statements()?;
|
||||
catch_clauses.push(crate::ast::CatchClause {
|
||||
exception_type: exc_ty,
|
||||
variable_name: exc_var,
|
||||
body: catch_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
});
|
||||
self.skip_newlines();
|
||||
if self.match_token(&TokenType::CATCH) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "single catch only after method body".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
let finally_body = if self.match_token(&TokenType::CLEANUP) {
|
||||
self.advance();
|
||||
Some(self.parse_block_statements()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Wrap original body with TryCatch
|
||||
body = vec![ASTNode::TryCatch {
|
||||
try_body: body,
|
||||
catch_clauses,
|
||||
finally_body,
|
||||
span: crate::ast::Span::unknown(),
|
||||
}];
|
||||
}
|
||||
|
||||
let constructor = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params: params.clone(),
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false, // コンストラクタは常に非オーバーライド
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
// 🔥 init/引数数 形式でキーを作成(インタープリターと一致させる)
|
||||
let constructor_key = format!("{}/{}", field_or_method, params.len());
|
||||
constructors.insert(constructor_key, constructor);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// packキーワードの処理(ビルトインBox継承用)
|
||||
if self.match_token(&TokenType::PACK) && self.peek_token() == &TokenType::LPAREN {
|
||||
let field_or_method = "pack".to_string();
|
||||
self.advance(); // consume 'pack'
|
||||
|
||||
// packは常にコンストラクタ
|
||||
if is_override {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method definition, not constructor after override keyword"
|
||||
.to_string(),
|
||||
found: TokenType::PACK,
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
// packコンストラクタの処理
|
||||
self.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "pack parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
let constructor = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params: params.clone(),
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false, // packは常に非オーバーライド
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
// 🔥 pack/引数数 形式でキーを作成(インタープリターと一致させる)
|
||||
let constructor_key = format!("{}/{}", field_or_method, params.len());
|
||||
constructors.insert(constructor_key, constructor);
|
||||
continue;
|
||||
}
|
||||
|
||||
// birthキーワードの処理(生命を与えるコンストラクタ)
|
||||
if self.match_token(&TokenType::BIRTH) && self.peek_token() == &TokenType::LPAREN {
|
||||
let field_or_method = "birth".to_string();
|
||||
self.advance(); // consume 'birth'
|
||||
|
||||
// birthは常にコンストラクタ
|
||||
if is_override {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method definition, not constructor after override keyword"
|
||||
.to_string(),
|
||||
found: TokenType::BIRTH,
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
// birthコンストラクタの処理
|
||||
self.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "birth parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
let constructor = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params: params.clone(),
|
||||
body,
|
||||
is_static: false,
|
||||
is_override: false, // birthは常に非オーバーライド
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
// 🔥 birth/引数数 形式でキーを作成(インタープリターと一致させる)
|
||||
let constructor_key = format!("{}/{}", field_or_method, params.len());
|
||||
constructors.insert(constructor_key, constructor);
|
||||
// constructor parsing moved to members::constructors
|
||||
if let Some((ctor_key, ctor_node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
|
||||
constructors.insert(ctor_key, ctor_node);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -447,7 +281,7 @@ impl NyashParser {
|
||||
}
|
||||
}
|
||||
|
||||
// 通常のフィールド名またはメソッド名を読み取り
|
||||
// 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り
|
||||
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
||||
let field_or_method = field_or_method.clone();
|
||||
self.advance();
|
||||
@ -490,35 +324,35 @@ impl NyashParser {
|
||||
self.skip_newlines();
|
||||
continue;
|
||||
} else if matches!(self.current_token().token_type, TokenType::IDENTIFIER(_)) {
|
||||
// 単行形式: public name[: Type]
|
||||
let fname =
|
||||
if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
||||
// 単行形式: public/private name[: Type] (= init | => expr | { ... }[postfix]) を委譲
|
||||
let fname = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
||||
n.clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
self.advance();
|
||||
if self.match_token(&TokenType::COLON) {
|
||||
self.advance(); // consume ':'
|
||||
// 型名(識別子)を受理して破棄(P0)
|
||||
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
|
||||
self.advance();
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "type name".to_string(),
|
||||
line: self.current_token().line,
|
||||
});
|
||||
}
|
||||
}
|
||||
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
|
||||
self,
|
||||
fname.clone(),
|
||||
&mut methods,
|
||||
&mut fields,
|
||||
)? {
|
||||
if field_or_method == "public" {
|
||||
public_fields.push(fname.clone());
|
||||
} else {
|
||||
private_fields.push(fname.clone());
|
||||
}
|
||||
// プロパティ経路ではメソッド後置(catch/cleanup)を無効化する
|
||||
last_method_name = None;
|
||||
self.skip_newlines();
|
||||
continue;
|
||||
} else {
|
||||
// 解析不能な場合は従来どおりフィールド扱い(後方互換の保険)
|
||||
if field_or_method == "public" { public_fields.push(fname.clone()); } else { private_fields.push(fname.clone()); }
|
||||
fields.push(fname);
|
||||
self.skip_newlines();
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// public/private の後に '{' でも識別子でもない
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
@ -529,51 +363,41 @@ impl NyashParser {
|
||||
}
|
||||
}
|
||||
|
||||
// メソッドかフィールドかを判定
|
||||
if self.match_token(&TokenType::LPAREN) {
|
||||
// メソッド定義
|
||||
self.advance(); // consume '('
|
||||
|
||||
let mut params = Vec::new();
|
||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
||||
must_advance!(self, _unused, "method parameter parsing");
|
||||
|
||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
||||
params.push(param.clone());
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.match_token(&TokenType::COMMA) {
|
||||
self.advance();
|
||||
// Unified Members (header-first) gate: support once/birth_once via members::properties
|
||||
if crate::config::env::unified_members() && (field_or_method == "once" || field_or_method == "birth_once") {
|
||||
if crate::parser::declarations::box_def::members::properties::try_parse_unified_property(
|
||||
self,
|
||||
&field_or_method,
|
||||
&mut methods,
|
||||
&mut birth_once_props,
|
||||
)? {
|
||||
last_method_name = None; // do not attach method-level postfix here
|
||||
self.skip_newlines();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
self.consume(TokenType::RPAREN)?;
|
||||
let body = self.parse_block_statements()?;
|
||||
|
||||
let method = ASTNode::FunctionDeclaration {
|
||||
name: field_or_method.clone(),
|
||||
params,
|
||||
body,
|
||||
is_static: false,
|
||||
// メソッド or フィールド(委譲)
|
||||
if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
|
||||
self,
|
||||
field_or_method.clone(),
|
||||
is_override,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
&birth_once_props,
|
||||
)? {
|
||||
last_method_name = Some(field_or_method.clone());
|
||||
methods.insert(field_or_method, method);
|
||||
} else {
|
||||
// フィールド定義(P0: 型注釈 name: Type を受理して破棄)
|
||||
// フィールド or 統一メンバ(computed/once/birth_once header-first)
|
||||
let fname = field_or_method;
|
||||
if self.match_token(&TokenType::COLON) {
|
||||
self.advance(); // consume ':'
|
||||
// 型名(識別子)を許可(P0は保持せず破棄)
|
||||
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
|
||||
self.advance();
|
||||
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
|
||||
self,
|
||||
fname,
|
||||
&mut methods,
|
||||
&mut fields,
|
||||
)? {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
fields.push(fname);
|
||||
}
|
||||
} else {
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
expected: "method or field name".to_string(),
|
||||
@ -590,6 +414,69 @@ impl NyashParser {
|
||||
self.validate_override_methods(&name, parent, &methods)?;
|
||||
}
|
||||
|
||||
// birth_once 相互依存の簡易検出(宣言間の循環)
|
||||
if crate::config::env::unified_members() {
|
||||
// Collect birth_once compute bodies
|
||||
use std::collections::{HashMap, HashSet};
|
||||
let mut birth_bodies: HashMap<String, Vec<ASTNode>> = HashMap::new();
|
||||
for (mname, mast) in &methods {
|
||||
if let Some(prop) = mname.strip_prefix("__compute_birth_") {
|
||||
if let ASTNode::FunctionDeclaration { body, .. } = mast {
|
||||
birth_bodies.insert(prop.to_string(), body.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
// Build dependency graph: A -> {B | me.B used inside A}
|
||||
let mut deps: HashMap<String, HashSet<String>> = HashMap::new();
|
||||
let props: HashSet<String> = birth_bodies.keys().cloned().collect();
|
||||
for (p, body) in &birth_bodies {
|
||||
let used = self.ast_collect_me_fields(body);
|
||||
let mut set = HashSet::new();
|
||||
for u in used {
|
||||
if props.contains(&u) && u != *p {
|
||||
set.insert(u);
|
||||
}
|
||||
}
|
||||
deps.insert(p.clone(), set);
|
||||
}
|
||||
// Detect cycle via DFS
|
||||
fn has_cycle(
|
||||
node: &str,
|
||||
deps: &HashMap<String, HashSet<String>>,
|
||||
temp: &mut HashSet<String>,
|
||||
perm: &mut HashSet<String>,
|
||||
) -> bool {
|
||||
if perm.contains(node) {
|
||||
return false;
|
||||
}
|
||||
if !temp.insert(node.to_string()) {
|
||||
return true; // back-edge
|
||||
}
|
||||
if let Some(ns) = deps.get(node) {
|
||||
for n in ns {
|
||||
if has_cycle(n, deps, temp, perm) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
temp.remove(node);
|
||||
perm.insert(node.to_string());
|
||||
false
|
||||
}
|
||||
let mut perm = HashSet::new();
|
||||
let mut temp = HashSet::new();
|
||||
for p in deps.keys() {
|
||||
if has_cycle(p, &deps, &mut temp, &mut perm) {
|
||||
let line = self.current_token().line;
|
||||
return Err(ParseError::UnexpectedToken {
|
||||
found: self.current_token().token_type.clone(),
|
||||
expected: "birth_once declarations must not have cyclic dependencies".to_string(),
|
||||
line,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ASTNode::BoxDeclaration {
|
||||
name,
|
||||
fields,
|
||||
@ -709,3 +596,71 @@ impl NyashParser {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl NyashParser {
|
||||
/// Minimal scan: does body contain `me.<field>` access (direct self-cycle guard)
|
||||
fn ast_contains_me_field(&self, nodes: &Vec<ASTNode>, field: &str) -> bool {
|
||||
fn scan(nodes: &Vec<ASTNode>, field: &str) -> bool {
|
||||
for n in nodes {
|
||||
match n {
|
||||
ASTNode::FieldAccess { object, field: f, .. } => {
|
||||
if f == field {
|
||||
if let ASTNode::Me { .. } = object.as_ref() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => {
|
||||
if scan(statements, field) { return true; }
|
||||
}
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
if scan(then_body, field) { return true; }
|
||||
if let Some(eb) = else_body { if scan(eb, field) { return true; } }
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
if scan(try_body, field) { return true; }
|
||||
for c in catch_clauses { if scan(&c.body, field) { return true; } }
|
||||
if let Some(fb) = finally_body { if scan(fb, field) { return true; } }
|
||||
}
|
||||
ASTNode::FunctionDeclaration { body, .. } => {
|
||||
if scan(body, field) { return true; }
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
scan(nodes, field)
|
||||
}
|
||||
|
||||
/// Collect all `me.<field>` accessed in nodes (flat set)
|
||||
fn ast_collect_me_fields(&self, nodes: &Vec<ASTNode>) -> std::collections::HashSet<String> {
|
||||
use std::collections::HashSet;
|
||||
fn scan(nodes: &Vec<ASTNode>, out: &mut HashSet<String>) {
|
||||
for n in nodes {
|
||||
match n {
|
||||
ASTNode::FieldAccess { object, field, .. } => {
|
||||
if let ASTNode::Me { .. } = object.as_ref() {
|
||||
out.insert(field.clone());
|
||||
}
|
||||
}
|
||||
ASTNode::Program { statements, .. } => scan(statements, out),
|
||||
ASTNode::If { then_body, else_body, .. } => {
|
||||
scan(then_body, out);
|
||||
if let Some(eb) = else_body { scan(eb, out); }
|
||||
}
|
||||
ASTNode::TryCatch { try_body, catch_clauses, finally_body, .. } => {
|
||||
scan(try_body, out);
|
||||
for c in catch_clauses { scan(&c.body, out); }
|
||||
if let Some(fb) = finally_body { scan(fb, out); }
|
||||
}
|
||||
ASTNode::FunctionDeclaration { body, .. } => scan(body, out),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut hs = HashSet::new();
|
||||
scan(nodes, &mut hs);
|
||||
hs
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user