主な変更: - ✅ HC011 (dead methods) 実装・テスト緑 - ✅ HC016 (unused alias) 実装・テスト緑 - ✅ HC017 (non-ascii quotes) 実装完了 - 🔧 tokenizer/parser_core 強化(AST優先ルート) - 🛡️ plugin_guard.rs 追加(stderr専用出力) - 📋 テストインフラ整備(run_tests.sh改善) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
455 lines
20 KiB
Rust
455 lines
20 KiB
Rust
use super::super::NyashRunner;
|
||
use crate::{
|
||
backend::MirInterpreter,
|
||
box_factory::{BoxFactory, RuntimeError},
|
||
core::model::BoxDeclaration as CoreBoxDecl,
|
||
instance_v2::InstanceBox,
|
||
mir::MirCompiler,
|
||
parser::NyashParser,
|
||
};
|
||
use std::sync::{Arc, RwLock};
|
||
use std::{fs, process};
|
||
|
||
impl NyashRunner {
|
||
/// Lightweight VM fallback using the in-crate MIR interpreter.
|
||
/// - Respects using preprocessing done earlier in the pipeline
|
||
/// - Relies on global plugin host initialized by runner
|
||
pub(crate) fn execute_vm_fallback_interpreter(&self, filename: &str) {
|
||
// Note: hv1 direct route is now handled at main.rs entry point (before plugin initialization).
|
||
// This function is only called after plugin initialization has already occurred.
|
||
|
||
// Read source
|
||
let code = match fs::read_to_string(filename) {
|
||
Ok(s) => s,
|
||
Err(e) => {
|
||
eprintln!("❌ Error reading file {}: {}", filename, e);
|
||
process::exit(1);
|
||
}
|
||
};
|
||
|
||
let trace = crate::config::env::cli_verbose()
|
||
|| crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
|
||
|
||
// Unified using/prelude handling (SSOT, parity with vm.rs):
|
||
// - resolve_prelude_paths_profiled で preludes を発見
|
||
// - merge_prelude_text で text-merge(.hako は AST parse しない)
|
||
let mut code2 = if crate::config::env::enable_using() {
|
||
match crate::runner::modes::common_util::resolve::resolve_prelude_paths_profiled(
|
||
self,
|
||
&code,
|
||
filename,
|
||
) {
|
||
Ok((_, prelude_paths)) => {
|
||
if !prelude_paths.is_empty() {
|
||
match crate::runner::modes::common_util::resolve::merge_prelude_text(
|
||
self,
|
||
&code,
|
||
filename,
|
||
) {
|
||
Ok(merged) => {
|
||
if trace {
|
||
eprintln!(
|
||
"[using/text-merge] preludes={} (vm-fallback)",
|
||
prelude_paths.len()
|
||
);
|
||
}
|
||
merged
|
||
}
|
||
Err(e) => {
|
||
eprintln!("❌ {}", e);
|
||
process::exit(1);
|
||
}
|
||
}
|
||
} else {
|
||
code.clone()
|
||
}
|
||
}
|
||
Err(e) => {
|
||
eprintln!("❌ {}", e);
|
||
process::exit(1);
|
||
}
|
||
}
|
||
} else {
|
||
// using disabled: detect and fail fast if present
|
||
if code.contains("\nusing ") || code.trim_start().starts_with("using ") {
|
||
eprintln!(
|
||
"❌ using: prelude merge is disabled in this profile. Enable NYASH_USING_AST=1 or remove 'using' lines."
|
||
);
|
||
process::exit(1);
|
||
}
|
||
code
|
||
};
|
||
|
||
// Dev sugar pre-expand: @name = expr → local name = expr
|
||
code2 = crate::runner::modes::common_util::resolve::preexpand_at_local(&code2);
|
||
|
||
// Hako-friendly normalize
|
||
if crate::runner::modes::common_util::hako::looks_like_hako_code(&code2)
|
||
|| filename.ends_with(".hako")
|
||
{
|
||
code2 = crate::runner::modes::common_util::hako::strip_local_decl(&code2);
|
||
}
|
||
|
||
if trace && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into())
|
||
|| std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into()))
|
||
{
|
||
eprintln!("[vm-fallback] Stage-3: enabled (env) for {}", filename);
|
||
}
|
||
|
||
// Fail‑Fast (opt‑in): Hako 構文を Nyash VM 経路で実行しない
|
||
// 目的: .hako は Hakorune VM、MIR は Core/LLVM に役割分離するためのガード
|
||
{
|
||
let on = crate::runner::modes::common_util::hako::fail_fast_on_hako();
|
||
if on {
|
||
let s = code2.as_str();
|
||
let hako_like = s.contains("static box ") || s.contains("using selfhost.") || s.contains("using hakorune.");
|
||
if hako_like {
|
||
eprintln!(
|
||
"❌ Hako-like source detected in Nyash VM path. Use Hakorune VM (v1 dispatcher) or Core/LLVM for MIR.\n hint: set HAKO_VERIFY_PRIMARY=hakovm in verify path"
|
||
);
|
||
process::exit(1);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Parse main code
|
||
let main_ast = match NyashParser::parse_from_string(&code2) {
|
||
Ok(ast) => ast,
|
||
Err(e) => {
|
||
eprintln!("❌ Parse error in {}: {}", filename, e);
|
||
process::exit(1);
|
||
}
|
||
};
|
||
// No AST preludes (text path or no using) → use the parsed main AST as-is
|
||
let ast_combined = main_ast;
|
||
// Optional: dump AST statement kinds for quick diagnostics
|
||
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
|
||
use nyash_rust::ast::ASTNode;
|
||
eprintln!("[ast] dump start (vm-fallback)");
|
||
if let ASTNode::Program { statements, .. } = &ast_combined {
|
||
for (i, st) in statements.iter().enumerate().take(50) {
|
||
let kind = match st {
|
||
ASTNode::BoxDeclaration {
|
||
is_static, name, ..
|
||
} => {
|
||
if *is_static {
|
||
format!("StaticBox({})", name)
|
||
} else {
|
||
format!("Box({})", name)
|
||
}
|
||
}
|
||
ASTNode::FunctionDeclaration { name, .. } => format!("FuncDecl({})", name),
|
||
ASTNode::FunctionCall { name, .. } => format!("FuncCall({})", name),
|
||
ASTNode::MethodCall { method, .. } => format!("MethodCall({})", method),
|
||
ASTNode::ScopeBox { .. } => "ScopeBox".to_string(),
|
||
ASTNode::ImportStatement { path, .. } => format!("Import({})", path),
|
||
ASTNode::UsingStatement { namespace_name, .. } => {
|
||
format!("Using({})", namespace_name)
|
||
}
|
||
_ => format!("{:?}", st),
|
||
};
|
||
eprintln!("[ast] {}: {}", i, kind);
|
||
}
|
||
}
|
||
eprintln!("[ast] dump end");
|
||
}
|
||
let ast = crate::r#macro::maybe_expand_and_dump(&ast_combined, false);
|
||
|
||
// Minimal user-defined Box support (Option A):
|
||
// Collect BoxDeclaration entries from AST and register a lightweight
|
||
// factory into the unified registry so `new UserBox()` works on the
|
||
// VM fallback path as well.
|
||
{
|
||
use nyash_rust::ast::ASTNode;
|
||
|
||
// Collect user-defined (non-static) box declarations at program level.
|
||
// Additionally, record static box names so we can alias
|
||
// `StaticBoxName` -> `StaticBoxNameInstance` when such a
|
||
// concrete instance box exists (common pattern in libs).
|
||
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> =
|
||
std::collections::HashMap::new();
|
||
let mut static_names: Vec<String> = Vec::new();
|
||
if let ASTNode::Program { statements, .. } = &ast {
|
||
for st in statements {
|
||
if let ASTNode::BoxDeclaration {
|
||
name,
|
||
fields,
|
||
public_fields,
|
||
private_fields,
|
||
methods,
|
||
constructors,
|
||
init_fields,
|
||
weak_fields,
|
||
is_interface,
|
||
extends,
|
||
implements,
|
||
type_parameters,
|
||
is_static,
|
||
..
|
||
} = st {
|
||
if *is_static {
|
||
static_names.push(name.clone());
|
||
continue; // modules/static boxes are not user-instantiable directly
|
||
}
|
||
let decl = CoreBoxDecl {
|
||
name: name.clone(),
|
||
fields: fields.clone(),
|
||
public_fields: public_fields.clone(),
|
||
private_fields: private_fields.clone(),
|
||
methods: methods.clone(),
|
||
constructors: constructors.clone(),
|
||
init_fields: init_fields.clone(),
|
||
weak_fields: weak_fields.clone(),
|
||
is_interface: *is_interface,
|
||
extends: extends.clone(),
|
||
implements: implements.clone(),
|
||
type_parameters: type_parameters.clone(),
|
||
};
|
||
nonstatic_decls.insert(name.clone(), decl);
|
||
}
|
||
}
|
||
}
|
||
// Build final map with optional aliases for StaticName -> StaticNameInstance
|
||
let mut decls = nonstatic_decls.clone();
|
||
for s in static_names.into_iter() {
|
||
let inst = format!("{}Instance", s);
|
||
if let Some(d) = nonstatic_decls.get(&inst) {
|
||
decls.insert(s, d.clone());
|
||
}
|
||
}
|
||
|
||
if !decls.is_empty() {
|
||
// Inline factory: minimal User factory backed by collected declarations
|
||
struct InlineUserBoxFactory {
|
||
decls: Arc<RwLock<std::collections::HashMap<String, CoreBoxDecl>>>,
|
||
}
|
||
impl BoxFactory for InlineUserBoxFactory {
|
||
fn create_box(
|
||
&self,
|
||
name: &str,
|
||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError>
|
||
{
|
||
let opt = { self.decls.read().unwrap().get(name).cloned() };
|
||
let decl = match opt {
|
||
Some(d) => d,
|
||
None => {
|
||
return Err(RuntimeError::InvalidOperation {
|
||
message: format!("Unknown Box type: {}", name),
|
||
})
|
||
}
|
||
};
|
||
let mut inst = InstanceBox::from_declaration(
|
||
decl.name.clone(),
|
||
decl.fields.clone(),
|
||
decl.methods.clone(),
|
||
);
|
||
let _ = inst.init(args);
|
||
Ok(Box::new(inst))
|
||
}
|
||
|
||
fn box_types(&self) -> Vec<&str> {
|
||
vec![]
|
||
}
|
||
|
||
fn is_available(&self) -> bool {
|
||
true
|
||
}
|
||
|
||
fn factory_type(&self) -> crate::box_factory::FactoryType {
|
||
crate::box_factory::FactoryType::User
|
||
}
|
||
}
|
||
let factory = InlineUserBoxFactory {
|
||
decls: Arc::new(RwLock::new(decls)),
|
||
};
|
||
crate::runtime::unified_registry::register_user_defined_factory(Arc::new(factory));
|
||
}
|
||
}
|
||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||
let compile = match compiler.compile(ast) {
|
||
Ok(c) => c,
|
||
Err(e) => {
|
||
eprintln!("❌ MIR compilation error: {}", e);
|
||
process::exit(1);
|
||
}
|
||
};
|
||
|
||
// Optional barrier-elision for parity with VM path
|
||
let mut module_vm = compile.module.clone();
|
||
if crate::config::env::env_bool("NYASH_VM_ESCAPE_ANALYSIS") {
|
||
let removed = crate::mir::passes::escape::escape_elide_barriers_vm(&mut module_vm);
|
||
if removed > 0 {
|
||
crate::cli_v!(
|
||
"[VM-fallback] escape_elide_barriers: removed {} barriers",
|
||
removed
|
||
);
|
||
}
|
||
}
|
||
|
||
// Optional: dump MIR for diagnostics (parity with vm path)
|
||
if crate::config::env::env_bool("NYASH_VM_DUMP_MIR") {
|
||
let p = crate::mir::MirPrinter::new();
|
||
eprintln!("{}", p.print_module(&module_vm));
|
||
}
|
||
|
||
// Execute via MIR interpreter
|
||
let mut vm = MirInterpreter::new();
|
||
// Centralized plugin guard (non-strict by default on fallback route)
|
||
crate::runner::modes::common_util::plugin_guard::check_and_report(
|
||
false,
|
||
crate::config::env::env_bool("NYASH_JSON_ONLY"),
|
||
"vm-fallback",
|
||
);
|
||
// Optional: verify MIR before execution (dev-only)
|
||
if crate::config::env::env_bool("NYASH_VM_VERIFY_MIR") {
|
||
let mut verifier = crate::mir::verification::MirVerifier::new();
|
||
for (name, func) in module_vm.functions.iter() {
|
||
if let Err(errors) = verifier.verify_function(func) {
|
||
if !errors.is_empty() {
|
||
eprintln!("[vm-verify] function: {}", name);
|
||
for er in errors { eprintln!(" • {}", er); }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if std::env::var("NYASH_DUMP_FUNCS").ok().as_deref() == Some("1") {
|
||
eprintln!("[vm] functions available:");
|
||
for k in module_vm.functions.keys() {
|
||
eprintln!(" - {}", k);
|
||
}
|
||
}
|
||
match vm.execute_module(&module_vm) {
|
||
Ok(ret) => {
|
||
use crate::box_trait::{NyashBox, IntegerBox, BoolBox};
|
||
|
||
// Extract exit code from return value
|
||
let exit_code = if let Some(ib) = ret.as_any().downcast_ref::<IntegerBox>() {
|
||
ib.value as i32
|
||
} else if let Some(bb) = ret.as_any().downcast_ref::<BoolBox>() {
|
||
if bb.value { 1 } else { 0 }
|
||
} else {
|
||
// For non-integer/bool returns, default to 0 (success)
|
||
0
|
||
};
|
||
|
||
// Exit with the return value as exit code
|
||
process::exit(exit_code);
|
||
}
|
||
Err(e) => {
|
||
eprintln!("❌ VM fallback error: {}", e);
|
||
process::exit(1);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl NyashRunner {
|
||
/// Small helper to continue fallback execution once AST is prepared
|
||
fn execute_vm_fallback_from_ast(&self, filename: &str, ast: nyash_rust::ast::ASTNode) {
|
||
use crate::{
|
||
backend::MirInterpreter,
|
||
box_factory::{BoxFactory, RuntimeError},
|
||
core::model::BoxDeclaration as CoreBoxDecl,
|
||
instance_v2::InstanceBox,
|
||
mir::MirCompiler,
|
||
};
|
||
use std::sync::{Arc, RwLock};
|
||
use std::process;
|
||
|
||
// Macro expand (if enabled)
|
||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||
// Minimal user-defined Box support (inline factory)
|
||
{
|
||
use nyash_rust::ast::ASTNode;
|
||
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> = std::collections::HashMap::new();
|
||
let mut static_names: Vec<String> = Vec::new();
|
||
if let ASTNode::Program { statements, .. } = &ast {
|
||
for st in statements {
|
||
if let ASTNode::BoxDeclaration { name, fields, public_fields, private_fields, methods, constructors, init_fields, weak_fields, is_interface, extends, implements, type_parameters, is_static, .. } = st {
|
||
if *is_static { static_names.push(name.clone()); continue; }
|
||
let decl = CoreBoxDecl { name: name.clone(), fields: fields.clone(), public_fields: public_fields.clone(), private_fields: private_fields.clone(), methods: methods.clone(), constructors: constructors.clone(), init_fields: init_fields.clone(), weak_fields: weak_fields.clone(), is_interface: *is_interface, extends: extends.clone(), implements: implements.clone(), type_parameters: type_parameters.clone() };
|
||
nonstatic_decls.insert(name.clone(), decl);
|
||
}
|
||
}
|
||
}
|
||
let mut decls = nonstatic_decls.clone();
|
||
for s in static_names.into_iter() {
|
||
let inst = format!("{}Instance", s);
|
||
if let Some(d) = nonstatic_decls.get(&inst) {
|
||
decls.insert(s, d.clone());
|
||
}
|
||
}
|
||
if !decls.is_empty() {
|
||
struct InlineUserBoxFactory {
|
||
decls: Arc<RwLock<std::collections::HashMap<String, CoreBoxDecl>>>,
|
||
}
|
||
impl BoxFactory for InlineUserBoxFactory {
|
||
fn create_box(
|
||
&self,
|
||
name: &str,
|
||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||
) -> Result<Box<dyn crate::box_trait::NyashBox>, RuntimeError> {
|
||
let opt = { self.decls.read().unwrap().get(name).cloned() };
|
||
let decl = match opt {
|
||
Some(d) => d,
|
||
None => {
|
||
return Err(RuntimeError::InvalidOperation {
|
||
message: format!("Unknown Box type: {}", name),
|
||
})
|
||
}
|
||
};
|
||
let mut inst = InstanceBox::from_declaration(
|
||
decl.name.clone(),
|
||
decl.fields.clone(),
|
||
decl.methods.clone(),
|
||
);
|
||
let _ = inst.init(args);
|
||
Ok(Box::new(inst))
|
||
}
|
||
|
||
fn box_types(&self) -> Vec<&str> { vec![] }
|
||
fn is_available(&self) -> bool { true }
|
||
fn factory_type(&self) -> crate::box_factory::FactoryType {
|
||
crate::box_factory::FactoryType::User
|
||
}
|
||
}
|
||
let factory = InlineUserBoxFactory {
|
||
decls: Arc::new(RwLock::new(decls)),
|
||
};
|
||
crate::runtime::unified_registry::register_user_defined_factory(Arc::new(factory));
|
||
}
|
||
}
|
||
// Compile to MIR and execute via interpreter
|
||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||
let module = match compiler.compile(ast) {
|
||
Ok(r) => r.module,
|
||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||
};
|
||
let mut interp = MirInterpreter::new();
|
||
match interp.execute_module(&module) {
|
||
Ok(result) => {
|
||
use nyash_rust::box_trait::{BoolBox, IntegerBox};
|
||
let rc = if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
|
||
ib.value as i32
|
||
} else if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
|
||
if bb.value { 1 } else { 0 }
|
||
} else {
|
||
0
|
||
};
|
||
// For C‑API pure pipeline, suppress "RC:" text to keep last line = exe path
|
||
let capi = std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() == Some("1");
|
||
let pure = std::env::var("HAKO_CAPI_PURE").ok().as_deref() == Some("1");
|
||
if !(capi && pure) {
|
||
println!("RC: {}", rc);
|
||
}
|
||
process::exit(rc);
|
||
}
|
||
Err(e) => {
|
||
eprintln!("❌ VM fallback runtime error: {}", e);
|
||
process::exit(1);
|
||
}
|
||
}
|
||
}
|
||
}
|