Files
hakorune/src/runner/modes/vm.rs
nyash-codex 86489ffe43 Phase 21.3 WIP: Hako Source Checker improvements
## 🎯 Checker/Analyzer拡張

###  実装追加
- テストフレームワーク追加(tools/hako_check/tests/)
- ルール改善(HC003グローバルassign、HC040静的箱トップレベルassign)
- テストランナー(run_tests.sh)

### 🔧 Rust側修正
- AST utilities拡張(src/ast/utils.rs)
- MIR lowerers新設(src/mir/lowerers/)
- Parser制御フロー改善(src/parser/statements/control_flow.rs)
- Tokenizer識別子処理改善(src/tokenizer/lex_ident.rs)

### 📁 主要変更
- tools/hako_check/cli.hako - CLI改善
- tools/hako_check/hako_source_checker.hako - Checker core更新
- tools/hako_check/tests/ - NEW (テストケース追加)
- tools/hako_check/run_tests.sh - NEW (テストランナー)
- src/mir/lowerers/ - NEW (MIR lowering utilities)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-07 21:04:01 +09:00

463 lines
20 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use super::super::NyashRunner;
use nyash_rust::{
ast::ASTNode,
parser::NyashParser,
mir::MirCompiler,
};
use std::{fs, process};
impl NyashRunner {
/// Execute VM mode with full plugin initialization and AST prelude merge
pub(crate) fn execute_vm_mode(&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.
// Quiet mode for child pipelines (e.g., selfhost compiler JSON emit)
let quiet_pipe = crate::config::env::env_bool("NYASH_JSON_ONLY");
// Enforce plugin-first policy for VM on this branch (deterministic):
// - Initialize plugin host if not yet loaded
// - Prefer plugin implementations for core boxes
// - Optionally fail fast when plugins are missing (NYASH_VM_PLUGIN_STRICT=1)
{
// Initialize unified registry globals (idempotent)
nyash_rust::runtime::init_global_unified_registry();
// Init plugin host from nyash.toml if not yet loaded
let need_init = {
let host = nyash_rust::runtime::get_global_plugin_host();
host.read()
.map(|h| h.config_ref().is_none())
.unwrap_or(true)
};
if need_init {
// Let init_bid_plugins resolve hakorune.toml/nyash.toml and configure
crate::runner_plugin_init::init_bid_plugins();
}
// Prefer plugin-builtins for core types unless explicitly disabled
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() {
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
}
// Build stable override list
let mut override_types: Vec<String> =
if let Ok(list) = std::env::var("NYASH_PLUGIN_OVERRIDE_TYPES") {
list.split(',')
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
} else {
vec![]
};
for t in [
"FileBox",
"TOMLBox", // IO/config
"ConsoleBox",
"StringBox",
"IntegerBox", // core value-ish
"ArrayBox",
"MapBox", // collections
"MathBox",
"TimeBox", // math/time helpers
] {
if !override_types.iter().any(|x| x == t) {
override_types.push(t.to_string());
}
}
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
// Strict mode: verify providers exist for override types
if crate::config::env::env_bool("NYASH_VM_PLUGIN_STRICT") {
let v2 = nyash_rust::runtime::get_global_registry();
let mut missing: Vec<String> = Vec::new();
for t in [
"FileBox",
"ConsoleBox",
"ArrayBox",
"MapBox",
"StringBox",
"IntegerBox",
] {
if v2.get_provider(t).is_none() {
missing.push(t.to_string());
}
}
if !missing.is_empty() {
eprintln!(
"❌ VM plugin-first strict: missing providers for: {:?}",
missing
);
std::process::exit(1);
}
}
}
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,
Err(e) => {
eprintln!("❌ Error reading file {}: {}", filename, e);
process::exit(1);
}
};
// Unified using/prelude handling (SSOT):
// - resolve_prelude_paths_profiled: discover preludes (DFS, operator boxes, etc.)
// - merge_prelude_text: text-based merge for all VM paths
// * .hako プレリュードは AST に突っ込まないParse error防止
// * .hako は text-merge + Stage-3 parser で一貫処理
let trace = crate::config::env::cli_verbose()
|| crate::config::env::env_bool("NYASH_RESOLVE_TRACE");
// When using is enabled, resolve preludes/profile; otherwise, keep original code.
let mut code_final = 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() {
// SSOT: always text-merge for VM (includes .hako-safe handling inside)
match crate::runner::modes::common_util::resolve::merge_prelude_text(
self,
&code,
filename,
) {
Ok(merged) => {
if trace {
eprintln!(
"[using/text-merge] preludes={} (vm)",
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
code_final =
crate::runner::modes::common_util::resolve::preexpand_at_local(&code_final);
// Hako-friendly normalize: strip leading `local ` at line head for Nyash parser compatibility.
if crate::runner::modes::common_util::hako::looks_like_hako_code(&code_final)
|| filename.ends_with(".hako")
{
code_final =
crate::runner::modes::common_util::hako::strip_local_decl(&code_final);
}
if trace && (std::env::var("NYASH_PARSER_STAGE3").ok() == Some("1".into())
|| std::env::var("HAKO_PARSER_STAGE3").ok() == Some("1".into()))
{
eprintln!("[vm] Stage-3: enabled (env) for {}", filename);
}
// FailFast (optin): Hako 構文を Nyash VM 経路で実行しない
// 目的: .hako は Hakorune VM、MIR は Core/LLVM に役割分離するためのガード
{
let on = crate::runner::modes::common_util::hako::fail_fast_on_hako();
if on {
let hako_like = code_final.contains("static box ")
|| code_final.contains("using selfhost.")
|| code_final.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 (after text-merge and Hako normalization)
let ast_combined = match NyashParser::parse_from_string(&code_final) {
Ok(ast) => ast,
Err(e) => {
eprintln!("❌ Parse error in {}: {}", filename, e);
process::exit(1);
}
};
// Optional: dump AST statement kinds for quick diagnostics
if std::env::var("NYASH_AST_DUMP").ok().as_deref() == Some("1") {
eprintln!("[ast] dump start (vm)");
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");
}
// Macro expand (if enabled)
let ast = crate::r#macro::maybe_expand_and_dump(&ast_combined, false);
// Minimal user-defined Box support (inline factory)
let static_box_decls = {
use crate::{
box_factory::{BoxFactory, RuntimeError},
core::model::BoxDeclaration as CoreBoxDecl,
instance_v2::InstanceBox,
};
use std::sync::{Arc, RwLock};
// 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).
// Also collect static box declarations for VM singleton persistence.
let mut nonstatic_decls: std::collections::HashMap<String, CoreBoxDecl> =
std::collections::HashMap::new();
let mut static_names: Vec<String> = Vec::new();
let mut static_box_decls: std::collections::HashMap<String, CoreBoxDecl> =
std::collections::HashMap::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());
// Store static box declaration for VM singleton persistence
let static_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(),
};
static_box_decls.insert(name.clone(), static_decl);
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(std::sync::Arc::new(factory));
}
// Return static_box_decls for VM registration
static_box_decls
};
// Compile to MIR
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 fallback 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] escape_elide_barriers: removed {} barriers",
removed
);
}
}
// Optional: dump MIR for diagnostics
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
use crate::backend::MirInterpreter;
let mut vm = MirInterpreter::new();
// Register static box declarations for singleton persistence
for (name, decl) in static_box_decls {
vm.register_static_box_decl(name, decl);
}
// 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
};
// Quiet mode: suppress "RC:" output for JSON-only pipelines
if !quiet_pipe {
println!("RC: {}", exit_code);
}
// Exit with the return value as exit code
process::exit(exit_code);
}
Err(e) => {
eprintln!("❌ VM error: {}", e);
process::exit(1);
}
}
}
}