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 super::{Effect, EffectMask, MirFunction, MirInstruction, MirModule, MirType, ValueId};
|
||||||
use crate::mir::optimizer_stats::OptimizationStats;
|
use crate::mir::optimizer_stats::OptimizationStats;
|
||||||
use std::collections::{HashMap, HashSet};
|
// std::collections imports removed (local DCE/CSE impls deleted)
|
||||||
|
|
||||||
/// MIR optimization passes
|
/// MIR optimization passes
|
||||||
pub struct MirOptimizer {
|
pub struct MirOptimizer {
|
||||||
@ -123,145 +123,8 @@ impl MirOptimizer {
|
|||||||
stats
|
stats
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Eliminate dead code (unused values)
|
/// Core-13 "pure" normalization notes moved to optimizer_passes::normalize_core13_pure
|
||||||
fn eliminate_dead_code(&mut self, module: &mut MirModule) -> OptimizationStats {
|
|
||||||
let mut stats = OptimizationStats::new();
|
|
||||||
|
|
||||||
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
|
/// Convert instruction to string key for CSE
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -320,496 +183,13 @@ impl MirOptimizer {
|
|||||||
/// - Print → ExternCall(env.console.log)
|
/// - Print → ExternCall(env.console.log)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn normalize_legacy_instructions(&mut self, module: &mut MirModule) -> OptimizationStats {
|
fn normalize_legacy_instructions(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||||
use super::{BarrierOp, MirInstruction as I, MirType, TypeOpKind, WeakRefOp};
|
crate::mir::optimizer_passes::normalize::normalize_legacy_instructions(self, module)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize RefGet/RefSet to BoxCall("getField"/"setField") with Const String field argument.
|
/// 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 {
|
fn normalize_ref_field_access(&mut self, module: &mut MirModule) -> OptimizationStats {
|
||||||
use super::{BarrierOp, MirInstruction as I};
|
crate::mir::optimizer_passes::normalize::normalize_ref_field_access(self, module)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -890,143 +270,144 @@ impl Default for MirOptimizer {
|
|||||||
|
|
||||||
// OptimizationStats moved to crate::mir::optimizer_stats
|
// OptimizationStats moved to crate::mir::optimizer_stats
|
||||||
|
|
||||||
impl MirOptimizer {
|
/// Diagnostics: identify unlowered type-ops embedded as strings in Call/BoxCall
|
||||||
/// Diagnostic: detect unlowered is/as/isType/asType after Builder
|
#[allow(dead_code)]
|
||||||
#[allow(dead_code)]
|
fn diagnose_unlowered_type_ops(
|
||||||
fn diagnose_unlowered_type_ops(&mut self, module: &MirModule) -> OptimizationStats {
|
optimizer: &MirOptimizer,
|
||||||
let mut stats = OptimizationStats::new();
|
module: &MirModule,
|
||||||
let diag_on = self.debug || crate::config::env::opt_diag();
|
) -> OptimizationStats {
|
||||||
for (fname, function) in &module.functions {
|
let mut stats = OptimizationStats::new();
|
||||||
// def map for resolving constants
|
let diag_on = optimizer.debug || crate::config::env::opt_diag();
|
||||||
let mut def_map: std::collections::HashMap<
|
for (fname, function) in &module.functions {
|
||||||
ValueId,
|
// def map for resolving constants
|
||||||
(super::basic_block::BasicBlockId, usize),
|
let mut def_map: std::collections::HashMap<
|
||||||
> = std::collections::HashMap::new();
|
ValueId,
|
||||||
for (bb_id, block) in &function.blocks {
|
(super::basic_block::BasicBlockId, usize),
|
||||||
for (i, inst) in block.instructions.iter().enumerate() {
|
> = std::collections::HashMap::new();
|
||||||
if let Some(dst) = inst.dst_value() {
|
for (bb_id, block) in &function.blocks {
|
||||||
def_map.insert(dst, (*bb_id, i));
|
for (i, inst) in block.instructions.iter().enumerate() {
|
||||||
}
|
if let Some(dst) = inst.dst_value() {
|
||||||
}
|
def_map.insert(dst, (*bb_id, i));
|
||||||
if let Some(term) = &block.terminator {
|
|
||||||
if let Some(dst) = term.dst_value() {
|
|
||||||
def_map.insert(dst, (*bb_id, usize::MAX));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut count = 0usize;
|
if let Some(term) = &block.terminator {
|
||||||
for (_bb, block) in &function.blocks {
|
if let Some(dst) = term.dst_value() {
|
||||||
for inst in &block.instructions {
|
def_map.insert(dst, (*bb_id, usize::MAX));
|
||||||
match inst {
|
}
|
||||||
MirInstruction::BoxCall { method, .. }
|
}
|
||||||
if method == "is"
|
}
|
||||||
|| method == "as"
|
let mut count = 0usize;
|
||||||
|| method == "isType"
|
for (_bb, block) in &function.blocks {
|
||||||
|| method == "asType" =>
|
for inst in &block.instructions {
|
||||||
{
|
match inst {
|
||||||
count += 1;
|
MirInstruction::BoxCall { method, .. }
|
||||||
}
|
if method == "is"
|
||||||
MirInstruction::Call { func, .. } => {
|
|| method == "as"
|
||||||
if let Some((bb, idx)) = def_map.get(func).copied() {
|
|| method == "isType"
|
||||||
if let Some(b) = function.blocks.get(&bb) {
|
|| method == "asType" =>
|
||||||
if idx < b.instructions.len() {
|
{
|
||||||
if let MirInstruction::Const {
|
count += 1;
|
||||||
value: super::instruction::ConstValue::String(s),
|
}
|
||||||
..
|
MirInstruction::Call { func, .. } => {
|
||||||
} = &b.instructions[idx]
|
if let Some((bb, idx)) = def_map.get(func).copied() {
|
||||||
{
|
if let Some(b) = function.blocks.get(&bb) {
|
||||||
if s == "isType" || s == "asType" {
|
if idx < b.instructions.len() {
|
||||||
count += 1;
|
if let MirInstruction::Const {
|
||||||
}
|
value: super::instruction::ConstValue::String(s),
|
||||||
|
..
|
||||||
|
} = &b.instructions[idx]
|
||||||
|
{
|
||||||
|
if s == "isType" || s == "asType" {
|
||||||
|
count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
_ => {}
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
stats.diagnostics_reported += count;
|
|
||||||
if diag_on {
|
|
||||||
eprintln!(
|
|
||||||
"[OPT][DIAG] Function '{}' has {} unlowered type-op calls",
|
|
||||||
fname, count
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats
|
if count > 0 {
|
||||||
|
stats.diagnostics_reported += count;
|
||||||
|
if diag_on {
|
||||||
|
eprintln!(
|
||||||
|
"[OPT][DIAG] Function '{}' has {} unlowered type-op calls",
|
||||||
|
fname, count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
|
||||||
/// Diagnostic: detect legacy instructions that should be unified
|
/// Diagnostic: detect legacy instructions that should be unified
|
||||||
/// Legacy set: TypeCheck/Cast/WeakNew/WeakLoad/BarrierRead/BarrierWrite/ArrayGet/ArraySet/RefGet/RefSet/PluginInvoke
|
/// 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.
|
/// When NYASH_OPT_DIAG or NYASH_OPT_DIAG_FORBID_LEGACY is set, prints diagnostics.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn diagnose_legacy_instructions(&mut self, module: &MirModule) -> OptimizationStats {
|
fn diagnose_legacy_instructions(module: &MirModule, debug: bool) -> OptimizationStats {
|
||||||
let mut stats = OptimizationStats::new();
|
let mut stats = OptimizationStats::new();
|
||||||
let diag_on = self.debug
|
let diag_on = debug
|
||||||
|| crate::config::env::opt_diag()
|
|| crate::config::env::opt_diag()
|
||||||
|| crate::config::env::opt_diag_forbid_legacy();
|
|| crate::config::env::opt_diag_forbid_legacy();
|
||||||
for (fname, function) in &module.functions {
|
for (fname, function) in &module.functions {
|
||||||
let mut count = 0usize;
|
let mut count = 0usize;
|
||||||
for (_bb, block) in &function.blocks {
|
for (_bb, block) in &function.blocks {
|
||||||
for inst in &block.instructions {
|
for inst in &block.instructions {
|
||||||
match inst {
|
match inst {
|
||||||
MirInstruction::TypeCheck { .. }
|
MirInstruction::TypeCheck { .. }
|
||||||
| MirInstruction::Cast { .. }
|
| MirInstruction::Cast { .. }
|
||||||
| MirInstruction::WeakNew { .. }
|
| MirInstruction::WeakNew { .. }
|
||||||
| MirInstruction::WeakLoad { .. }
|
| MirInstruction::WeakLoad { .. }
|
||||||
| MirInstruction::BarrierRead { .. }
|
| MirInstruction::BarrierRead { .. }
|
||||||
| MirInstruction::BarrierWrite { .. }
|
| MirInstruction::BarrierWrite { .. }
|
||||||
| MirInstruction::ArrayGet { .. }
|
| MirInstruction::ArrayGet { .. }
|
||||||
| MirInstruction::ArraySet { .. }
|
| MirInstruction::ArraySet { .. }
|
||||||
| MirInstruction::RefGet { .. }
|
| MirInstruction::RefGet { .. }
|
||||||
| MirInstruction::RefSet { .. }
|
| MirInstruction::RefSet { .. }
|
||||||
| MirInstruction::PluginInvoke { .. } => {
|
| MirInstruction::PluginInvoke { .. } => {
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(term) = &block.terminator {
|
|
||||||
match term {
|
|
||||||
MirInstruction::TypeCheck { .. }
|
|
||||||
| MirInstruction::Cast { .. }
|
|
||||||
| MirInstruction::WeakNew { .. }
|
|
||||||
| MirInstruction::WeakLoad { .. }
|
|
||||||
| MirInstruction::BarrierRead { .. }
|
|
||||||
| MirInstruction::BarrierWrite { .. }
|
|
||||||
| MirInstruction::ArrayGet { .. }
|
|
||||||
| MirInstruction::ArraySet { .. }
|
|
||||||
| MirInstruction::RefGet { .. }
|
|
||||||
| MirInstruction::RefSet { .. }
|
|
||||||
| MirInstruction::PluginInvoke { .. } => {
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if let Some(term) = &block.terminator {
|
||||||
stats.diagnostics_reported += count;
|
match term {
|
||||||
if diag_on {
|
MirInstruction::TypeCheck { .. }
|
||||||
eprintln!(
|
| MirInstruction::Cast { .. }
|
||||||
"[OPT][DIAG] Function '{}' has {} legacy MIR ops: unify to Core‑13 (TypeOp/WeakRef/Barrier/BoxCall)",
|
| MirInstruction::WeakNew { .. }
|
||||||
fname, count
|
| MirInstruction::WeakLoad { .. }
|
||||||
);
|
| MirInstruction::BarrierRead { .. }
|
||||||
if crate::config::env::opt_diag_forbid_legacy() {
|
| MirInstruction::BarrierWrite { .. }
|
||||||
panic!(
|
| MirInstruction::ArrayGet { .. }
|
||||||
"NYASH_OPT_DIAG_FORBID_LEGACY=1: legacy MIR ops detected in '{}': {}",
|
| MirInstruction::ArraySet { .. }
|
||||||
fname, count
|
| MirInstruction::RefGet { .. }
|
||||||
);
|
| MirInstruction::RefSet { .. }
|
||||||
|
| MirInstruction::PluginInvoke { .. } => {
|
||||||
|
count += 1;
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
stats.diagnostics_reported += count;
|
||||||
|
if diag_on {
|
||||||
|
eprintln!(
|
||||||
|
"[OPT][DIAG] Function '{}' has {} legacy MIR ops: unify to Core‑13 (TypeOp/WeakRef/Barrier/BoxCall)",
|
||||||
|
fname, count
|
||||||
|
);
|
||||||
|
if crate::config::env::opt_diag_forbid_legacy() {
|
||||||
|
panic!(
|
||||||
|
"NYASH_OPT_DIAG_FORBID_LEGACY=1: legacy MIR ops detected in '{}': {}",
|
||||||
|
fname, count
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stats
|
|
||||||
}
|
}
|
||||||
|
stats
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[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::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::common::ParserUtils;
|
||||||
use crate::parser::{NyashParser, ParseError};
|
use crate::parser::{NyashParser, ParseError};
|
||||||
use crate::tokenizer::TokenType;
|
use crate::tokenizer::TokenType;
|
||||||
@ -16,122 +16,8 @@ impl NyashParser {
|
|||||||
/// box宣言をパース: box Name { fields... methods... }
|
/// box宣言をパース: box Name { fields... methods... }
|
||||||
pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
|
pub fn parse_box_declaration(&mut self) -> Result<ASTNode, ParseError> {
|
||||||
self.consume(TokenType::BOX)?;
|
self.consume(TokenType::BOX)?;
|
||||||
|
let (name, type_parameters, extends, implements) =
|
||||||
let name = if let TokenType::IDENTIFIER(name) = &self.current_token().token_type {
|
box_header::parse_header(self)?;
|
||||||
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()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.consume(TokenType::LBRACE)?;
|
self.consume(TokenType::LBRACE)?;
|
||||||
self.skip_newlines(); // ブレース後の改行をスキップ
|
self.skip_newlines(); // ブレース後の改行をスキップ
|
||||||
@ -143,10 +29,144 @@ impl NyashParser {
|
|||||||
let mut constructors = HashMap::new();
|
let mut constructors = HashMap::new();
|
||||||
let mut init_fields = Vec::new();
|
let mut init_fields = Vec::new();
|
||||||
let mut weak_fields = Vec::new(); // 🔗 Track weak fields
|
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;
|
let mut last_method_name: Option<String> = None;
|
||||||
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
while !self.match_token(&TokenType::RBRACE) && !self.is_at_end() {
|
||||||
self.skip_newlines(); // ループ開始時に改行をスキップ
|
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)
|
// 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() {
|
if (self.match_token(&TokenType::CATCH) || self.match_token(&TokenType::CLEANUP)) && last_method_name.is_some() {
|
||||||
@ -243,195 +263,9 @@ impl NyashParser {
|
|||||||
self.advance();
|
self.advance();
|
||||||
}
|
}
|
||||||
|
|
||||||
// initトークンをメソッド名として特別処理
|
// constructor parsing moved to members::constructors
|
||||||
if self.match_token(&TokenType::INIT) && self.peek_token() == &TokenType::LPAREN {
|
if let Some((ctor_key, ctor_node)) = crate::parser::declarations::box_def::members::constructors::try_parse_constructor(self, is_override)? {
|
||||||
let field_or_method = "init".to_string();
|
constructors.insert(ctor_key, ctor_node);
|
||||||
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);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -447,7 +281,7 @@ impl NyashParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通常のフィールド名またはメソッド名を読み取り
|
// 通常のフィールド名またはメソッド名、または unified members の先頭キーワードを読み取り
|
||||||
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
if let TokenType::IDENTIFIER(field_or_method) = &self.current_token().token_type {
|
||||||
let field_or_method = field_or_method.clone();
|
let field_or_method = field_or_method.clone();
|
||||||
self.advance();
|
self.advance();
|
||||||
@ -490,35 +324,35 @@ impl NyashParser {
|
|||||||
self.skip_newlines();
|
self.skip_newlines();
|
||||||
continue;
|
continue;
|
||||||
} else if matches!(self.current_token().token_type, TokenType::IDENTIFIER(_)) {
|
} else if matches!(self.current_token().token_type, TokenType::IDENTIFIER(_)) {
|
||||||
// 単行形式: public name[: Type]
|
// 単行形式: public/private name[: Type] (= init | => expr | { ... }[postfix]) を委譲
|
||||||
let fname =
|
let fname = if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
||||||
if let TokenType::IDENTIFIER(n) = &self.current_token().token_type {
|
n.clone()
|
||||||
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 field_or_method == "public" {
|
|
||||||
public_fields.push(fname.clone());
|
|
||||||
} else {
|
} else {
|
||||||
private_fields.push(fname.clone());
|
unreachable!()
|
||||||
|
};
|
||||||
|
self.advance();
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
fields.push(fname);
|
|
||||||
self.skip_newlines();
|
|
||||||
continue;
|
|
||||||
} else {
|
} else {
|
||||||
// public/private の後に '{' でも識別子でもない
|
// public/private の後に '{' でも識別子でもない
|
||||||
return Err(ParseError::UnexpectedToken {
|
return Err(ParseError::UnexpectedToken {
|
||||||
@ -529,50 +363,40 @@ impl NyashParser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// メソッドかフィールドかを判定
|
// Unified Members (header-first) gate: support once/birth_once via members::properties
|
||||||
if self.match_token(&TokenType::LPAREN) {
|
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.advance(); // consume '('
|
self,
|
||||||
|
&field_or_method,
|
||||||
let mut params = Vec::new();
|
&mut methods,
|
||||||
while !self.match_token(&TokenType::RPAREN) && !self.is_at_end() {
|
&mut birth_once_props,
|
||||||
must_advance!(self, _unused, "method parameter parsing");
|
)? {
|
||||||
|
last_method_name = None; // do not attach method-level postfix here
|
||||||
if let TokenType::IDENTIFIER(param) = &self.current_token().token_type {
|
self.skip_newlines();
|
||||||
params.push(param.clone());
|
continue;
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.match_token(&TokenType::COMMA) {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.consume(TokenType::RPAREN)?;
|
// メソッド or フィールド(委譲)
|
||||||
let body = self.parse_block_statements()?;
|
if let Some(method) = crate::parser::declarations::box_def::members::methods::try_parse_method(
|
||||||
|
self,
|
||||||
let method = ASTNode::FunctionDeclaration {
|
field_or_method.clone(),
|
||||||
name: field_or_method.clone(),
|
is_override,
|
||||||
params,
|
&birth_once_props,
|
||||||
body,
|
)? {
|
||||||
is_static: false,
|
|
||||||
is_override,
|
|
||||||
span: Span::unknown(),
|
|
||||||
};
|
|
||||||
|
|
||||||
last_method_name = Some(field_or_method.clone());
|
last_method_name = Some(field_or_method.clone());
|
||||||
methods.insert(field_or_method, method);
|
methods.insert(field_or_method, method);
|
||||||
} else {
|
} else {
|
||||||
// フィールド定義(P0: 型注釈 name: Type を受理して破棄)
|
// フィールド or 統一メンバ(computed/once/birth_once header-first)
|
||||||
let fname = field_or_method;
|
let fname = field_or_method;
|
||||||
if self.match_token(&TokenType::COLON) {
|
if crate::parser::declarations::box_def::members::fields::try_parse_header_first_field_or_property(
|
||||||
self.advance(); // consume ':'
|
self,
|
||||||
// 型名(識別子)を許可(P0は保持せず破棄)
|
fname,
|
||||||
if let TokenType::IDENTIFIER(_ty) = &self.current_token().token_type {
|
&mut methods,
|
||||||
self.advance();
|
&mut fields,
|
||||||
}
|
)? {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
fields.push(fname);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(ParseError::UnexpectedToken {
|
return Err(ParseError::UnexpectedToken {
|
||||||
@ -590,6 +414,69 @@ impl NyashParser {
|
|||||||
self.validate_override_methods(&name, parent, &methods)?;
|
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 {
|
Ok(ASTNode::BoxDeclaration {
|
||||||
name,
|
name,
|
||||||
fields,
|
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