phase15: update CLAUDE.md and sync with current progress

- Update phase indicator to Phase 15 (Self-Hosting)
- Update documentation links to Phase 15 resources
- Reflect completion of R1-R5 tasks and ongoing work
- Fix CURRENT_TASK.md location to root directory

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Tomoaki
2025-09-05 13:29:17 +09:00
parent e05a385524
commit a2b89fae7e
29 changed files with 1163 additions and 2562 deletions

View File

@ -0,0 +1,174 @@
use serde::{Deserialize, Serialize};
use crate::mir::{
MirModule, MirFunction, FunctionSignature, BasicBlockId, MirInstruction,
ConstValue, BinaryOp, MirType, EffectMask, MirPrinter,
};
#[derive(Debug, Deserialize, Serialize)]
struct ProgramV0 {
version: i32,
kind: String,
body: Vec<StmtV0>,
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
enum StmtV0 {
Return { expr: ExprV0 },
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(tag = "type")]
enum ExprV0 {
Int { value: serde_json::Value },
Binary { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
}
pub fn parse_json_v0_to_module(json: &str) -> Result<MirModule, String> {
let prog: ProgramV0 = serde_json::from_str(json).map_err(|e| format!("invalid JSON v0: {}", e))?;
if prog.version != 0 || prog.kind != "Program" {
return Err("unsupported IR: expected {version:0, kind:\"Program\"}".into());
}
let stmt = prog.body.get(0).ok_or("empty body")?;
// Create module and main function
let mut module = MirModule::new("ny_json_v0".into());
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE };
let entry = BasicBlockId::new(0);
let mut f = MirFunction::new(sig, entry);
// Build expression
let ret_val = match stmt {
StmtV0::Return { expr } => lower_expr(&mut f, expr)?,
};
// Return
if let Some(bb) = f.get_block_mut(entry) {
bb.set_terminator(MirInstruction::Return { value: Some(ret_val) });
}
// Infer return type (integer only for v0)
f.signature.return_type = MirType::Integer;
module.add_function(f);
Ok(module)
}
fn lower_expr(f: &mut MirFunction, e: &ExprV0) -> Result<crate::mir::ValueId, String> {
match e {
ExprV0::Int { value } => {
// Accept number or stringified digits
let ival: i64 = if let Some(n) = value.as_i64() {
n
} else if let Some(s) = value.as_str() { s.parse().map_err(|_| "invalid int literal")? } else {
return Err("invalid int literal".into());
};
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(f.entry_block) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(ival) });
}
Ok(dst)
}
ExprV0::Binary { op, lhs, rhs } => {
let l = lower_expr(f, lhs)?;
let r = lower_expr(f, rhs)?;
let bop = match op.as_str() { "+" => BinaryOp::Add, "-" => BinaryOp::Sub, "*" => BinaryOp::Mul, "/" => BinaryOp::Div, _ => return Err("unsupported op".into()) };
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(f.entry_block) {
bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r });
}
Ok(dst)
}
}
}
pub fn maybe_dump_mir(module: &MirModule) {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let mut p = MirPrinter::new();
println!("{}", p.print_module(module));
}
}
// ========== Direct bridge (source → JSON v0 → MIR) ==========
#[derive(Clone, Debug)]
enum Tok {
Return,
Int(i64),
Plus,
Minus,
Star,
Slash,
LParen,
RParen,
Eof,
}
fn lex(input: &str) -> Result<Vec<Tok>, String> {
let bytes = input.as_bytes();
let mut i = 0usize;
let n = bytes.len();
let mut toks = Vec::new();
while i < n {
let c = bytes[i] as char;
if c.is_whitespace() { i += 1; continue; }
match c {
'+' => { toks.push(Tok::Plus); i+=1; }
'-' => { toks.push(Tok::Minus); i+=1; }
'*' => { toks.push(Tok::Star); i+=1; }
'/' => { toks.push(Tok::Slash); i+=1; }
'(' => { toks.push(Tok::LParen); i+=1; }
')' => { toks.push(Tok::RParen); i+=1; }
'0'..='9' => {
let start = i; while i<n { let cc = bytes[i] as char; if cc.is_ascii_digit() { i+=1; } else { break; } }
let s = std::str::from_utf8(&bytes[start..i]).unwrap();
let v: i64 = s.parse().map_err(|_| "invalid int")?;
toks.push(Tok::Int(v));
}
'r' => {
// return
if i+6<=n && &input[i..i+6]=="return" { toks.push(Tok::Return); i+=6; } else { return Err("unexpected 'r'".into()); }
}
_ => return Err(format!("unexpected char '{}'", c)),
}
}
toks.push(Tok::Eof);
Ok(toks)
}
struct P { toks: Vec<Tok>, pos: usize }
impl P {
fn new(toks: Vec<Tok>) -> Self { Self{ toks, pos:0 } }
fn peek(&self) -> &Tok { self.toks.get(self.pos).unwrap() }
fn next(&mut self) -> Tok { let t = self.toks.get(self.pos).unwrap().clone(); self.pos+=1; t }
fn expect_return(&mut self) -> Result<(), String> { match self.next() { Tok::Return => Ok(()), _ => Err("expected 'return'".into()) } }
fn parse_program(&mut self) -> Result<ExprV0, String> { self.expect_return()?; self.parse_expr() }
fn parse_expr(&mut self) -> Result<ExprV0,String> {
let mut left = self.parse_term()?;
loop { match self.peek() { Tok::Plus => { self.next(); let r=self.parse_term()?; left = ExprV0::Binary{op:"+".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, Tok::Minus => { self.next(); let r=self.parse_term()?; left = ExprV0::Binary{op:"-".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, _ => break }
}
Ok(left)
}
fn parse_term(&mut self) -> Result<ExprV0,String> {
let mut left = self.parse_factor()?;
loop { match self.peek() { Tok::Star => { self.next(); let r=self.parse_factor()?; left = ExprV0::Binary{op:"*".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, Tok::Slash => { self.next(); let r=self.parse_factor()?; left = ExprV0::Binary{op:"/".into(), lhs:Box::new(left), rhs:Box::new(r)}; }, _ => break }
}
Ok(left)
}
fn parse_factor(&mut self) -> Result<ExprV0,String> {
match self.next() {
Tok::Int(v) => Ok(ExprV0::Int{ value: serde_json::Value::from(v) }),
Tok::LParen => { let e = self.parse_expr()?; match self.next() { Tok::RParen => Ok(e), _ => Err(") expected".into()) } }
_ => Err("factor expected".into()),
}
}
}
pub fn parse_source_v0_to_json(input: &str) -> Result<String, String> {
let toks = lex(input)?; let mut p = P::new(toks);
let expr = p.parse_program()?;
let prog = ProgramV0 { version:0, kind: "Program".into(), body: vec![StmtV0::Return{ expr }] };
serde_json::to_string(&prog).map_err(|e| e.to_string())
}
pub fn parse_source_v0_to_module(input: &str) -> Result<MirModule, String> {
let json = parse_source_v0_to_json(input)?;
if std::env::var("NYASH_DUMP_JSON_IR").ok().as_deref() == Some("1") { println!("{}", json); }
parse_json_v0_to_module(&json)
}

View File

@ -29,6 +29,7 @@ use nyash_rust::backend::{llvm_compile_and_execute};
use std::{fs, process};
mod modes;
mod demos;
mod json_v0_bridge;
// v2 plugin system imports
use nyash_rust::runtime;
@ -74,6 +75,35 @@ impl NyashRunner {
/// Run Nyash based on the configuration
pub fn run(&self) {
// Phase-15: JSON IR v0 bridge (stdin/file)
if self.config.ny_parser_pipe || self.config.json_file.is_some() {
let json = if let Some(path) = &self.config.json_file {
match std::fs::read_to_string(path) {
Ok(s) => s,
Err(e) => { eprintln!("❌ json-file read error: {}", e); std::process::exit(1); }
}
} else {
use std::io::Read;
let mut buf = String::new();
if let Err(e) = std::io::stdin().read_to_string(&mut buf) {
eprintln!("❌ stdin read error: {}", e); std::process::exit(1);
}
buf
};
match json_v0_bridge::parse_json_v0_to_module(&json) {
Ok(module) => {
// Optional dump via env verbose
json_v0_bridge::maybe_dump_mir(&module);
// Execute via MIR interpreter
self.execute_mir_module(&module);
return;
}
Err(e) => {
eprintln!("❌ JSON v0 bridge error: {}", e);
std::process::exit(1);
}
}
}
// Run named task from nyash.toml (MVP)
if let Some(task) = self.config.run_task.clone() {
if let Err(e) = run_named_task(&task) {
@ -144,6 +174,70 @@ impl NyashRunner {
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
runner_plugin_init::init_bid_plugins();
}
// Allow interpreter to create plugin-backed boxes via unified registry
// Opt-in by default for FileBox/TOMLBox which are required by ny-config and similar tools.
if std::env::var("NYASH_USE_PLUGIN_BUILTINS").ok().is_none() {
std::env::set_var("NYASH_USE_PLUGIN_BUILTINS", "1");
}
// Merge FileBox,TOMLBox with defaults if present
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!["ArrayBox".into(), "MapBox".into()]
};
for t in ["FileBox", "TOMLBox"] { if !override_types.iter().any(|x| x==t) { override_types.push(t.into()); } }
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", override_types.join(","));
// Opt-in: load Ny script plugins listed in nyash.toml [ny_plugins]
if self.config.load_ny_plugins || std::env::var("NYASH_LOAD_NY_PLUGINS").ok().as_deref() == Some("1") {
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
if let Some(np) = doc.get("ny_plugins") {
let mut list: Vec<String> = Vec::new();
if let Some(arr) = np.as_array() {
for v in arr { if let Some(s) = v.as_str() { list.push(s.to_string()); } }
} else if let Some(tbl) = np.as_table() {
for (_k, v) in tbl { if let Some(s) = v.as_str() { list.push(s.to_string()); }
else if let Some(arr) = v.as_array() { for e in arr { if let Some(s) = e.as_str() { list.push(s.to_string()); } } }
}
}
if !list.is_empty() {
let list_only = std::env::var("NYASH_NY_PLUGINS_LIST_ONLY").ok().as_deref() == Some("1");
println!("🧩 Ny script plugins ({}):", list.len());
for p in list {
if list_only {
println!("{}", p);
continue;
}
// Execute each script best-effort via interpreter
match std::fs::read_to_string(&p) {
Ok(code) => {
match nyash_rust::parser::NyashParser::parse_from_string(&code) {
Ok(ast) => {
let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new();
match interpreter.execute(ast) {
Ok(_) => println!("[ny_plugins] {}: OK", p),
Err(e) => {
println!("[ny_plugins] {}: FAIL ({})", p, e);
// continue to next
}
}
}
Err(e) => {
println!("[ny_plugins] {}: FAIL (parse: {})", p, e);
}
}
}
Err(e) => {
println!("[ny_plugins] {}: FAIL (read: {})", p, e);
}
}
}
}
}
}
}
}
// Optional: enable VM stats via CLI flags
if self.config.vm_stats {
@ -662,6 +756,58 @@ impl NyashRunner {
if speedup > 1.0 { speedup } else { 1.0 / speedup },
if speedup > 1.0 { "faster" } else { "slower" });
}
/// Execute a prepared MIR module via the interpreter (Phase-15 path)
fn execute_mir_module(&self, module: &crate::mir::MirModule) {
use crate::backend::MirInterpreter;
use crate::mir::MirType;
use crate::box_trait::{NyashBox, IntegerBox, BoolBox, StringBox};
use crate::boxes::FloatBox;
let mut interp = MirInterpreter::new();
match interp.execute_module(module) {
Ok(result) => {
println!("✅ MIR interpreter execution completed!");
if let Some(func) = module.functions.get("main") {
let (ety, sval) = match &func.signature.return_type {
MirType::Float => {
if let Some(fb) = result.as_any().downcast_ref::<FloatBox>() {
("Float", format!("{}", fb.value))
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Float", format!("{}", ib.value as f64))
} else { ("Float", result.to_string_box().value) }
}
MirType::Integer => {
if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Integer", ib.value.to_string())
} else { ("Integer", result.to_string_box().value) }
}
MirType::Bool => {
if let Some(bb) = result.as_any().downcast_ref::<BoolBox>() {
("Bool", bb.value.to_string())
} else if let Some(ib) = result.as_any().downcast_ref::<IntegerBox>() {
("Bool", (ib.value != 0).to_string())
} else { ("Bool", result.to_string_box().value) }
}
MirType::String => {
if let Some(sb) = result.as_any().downcast_ref::<StringBox>() {
("String", sb.value.clone())
} else { ("String", result.to_string_box().value) }
}
_ => { (result.type_name(), result.to_string_box().value) }
};
println!("ResultType(MIR): {}", ety);
println!("Result: {}", sval);
} else {
println!("Result: {:?}", result);
}
}
Err(e) => {
eprintln!("❌ MIR interpreter error: {}", e);
std::process::exit(1);
}
}
}
}
impl NyashRunner {

View File

@ -1,10 +1,31 @@
use super::super::NyashRunner;
use crate::runner::json_v0_bridge;
use nyash_rust::{parser::NyashParser, interpreter::NyashInterpreter};
// Use the library crate's plugin init module rather than the bin crate root
use nyash_rust::runner_plugin_init;
use std::{fs, process};
impl NyashRunner {
/// File-mode dispatcher (thin wrapper around backend/mode selection)
pub(crate) fn run_file(&self, filename: &str) {
// Direct v0 bridge when requested via CLI/env
let use_ny_parser = self.config.parser_ny || std::env::var("NYASH_USE_NY_PARSER").ok().as_deref() == Some("1");
if use_ny_parser {
let code = match fs::read_to_string(filename) {
Ok(content) => content,
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
};
match json_v0_bridge::parse_source_v0_to_module(&code) {
Ok(module) => {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
println!("🚀 Nyash MIR Interpreter - (parser=ny) Executing file: {} 🚀", filename);
}
self.execute_mir_module(&module);
return;
}
Err(e) => { eprintln!("❌ Direct bridge parse error: {}", e); process::exit(1); }
}
}
// AST dump mode
if self.config.dump_ast {
println!("🧠 Nyash AST Dump - Processing file: {}", filename);
@ -94,6 +115,11 @@ impl NyashRunner {
/// Execute Nyash file with interpreter (common helper)
pub(crate) fn execute_nyash_file(&self, filename: &str) {
// Ensure plugin host and provider mappings are initialized (idempotent)
if std::env::var("NYASH_DISABLE_PLUGINS").ok().as_deref() != Some("1") {
// Call via lib crate to avoid referring to the bin crate root
runner_plugin_init::init_bid_plugins();
}
// Read the file
let code = match fs::read_to_string(filename) {
Ok(content) => content,