Files
hakorune/src/runner/json_v0_bridge.rs

821 lines
39 KiB
Rust
Raw Normal View History

use serde::{Deserialize, Serialize};
use crate::mir::{
MirModule, MirFunction, FunctionSignature, BasicBlockId, MirInstruction,
ConstValue, BinaryOp, MirType, EffectMask, MirPrinter, ValueId,
};
#[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 },
Extern { iface: String, method: String, args: Vec<ExprV0> },
// Optional: expression statement (side effects only)
Expr { expr: ExprV0 },
// Optional: local binding (Stage-2)
Local { name: String, expr: ExprV0 },
// Optional: if/else (Stage-2)
If { cond: ExprV0, then: Vec<StmtV0>, #[serde(rename="else", default)] r#else: Option<Vec<StmtV0>> },
// Optional: loop (Stage-2)
Loop { cond: ExprV0, body: Vec<StmtV0> },
Break,
Continue,
Try { #[serde(rename="try")] try_body: Vec<StmtV0>, #[serde(default)] catches: Vec<CatchV0>, #[serde(default)] finally: Vec<StmtV0> },
}
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
struct CatchV0 {
#[serde(rename="param", default)]
param: Option<String>,
#[serde(rename="typeHint", default)]
type_hint: Option<String>,
#[serde(default)]
body: Vec<StmtV0>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(tag = "type")]
enum ExprV0 {
Int { value: serde_json::Value },
Str { value: String },
Bool { value: bool },
Binary { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
Extern { iface: String, method: String, args: Vec<ExprV0> },
Compare { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> },
Logical { op: String, lhs: Box<ExprV0>, rhs: Box<ExprV0> }, // short-circuit: &&, || (or: "and"/"or")
// Stage-2 additions (optional):
Call { name: String, args: Vec<ExprV0> },
Method { recv: Box<ExprV0>, method: String, args: Vec<ExprV0> },
New { class: String, args: Vec<ExprV0> },
Var { name: String },
Throw { expr: 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());
}
// 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);
if prog.body.is_empty() { return Err("empty body".into()); }
// Variable map for simple locals (Stage-2; currently minimal)
let mut var_map: std::collections::HashMap<String, crate::mir::ValueId> = std::collections::HashMap::new();
let start_bb = f.entry_block;
let end_bb = lower_stmt_list_with_vars(&mut f, start_bb, &prog.body, &mut var_map)?;
// Ensure function terminates: add `ret 0` to last un-terminated block (prefer end_bb else entry)
let need_default_ret = f.blocks.iter().any(|(_k,b)| !b.is_terminated());
if need_default_ret {
let target_bb = end_bb;
let dst_id = f.next_value_id();
if let Some(bb) = f.get_block_mut(target_bb) {
if !bb.is_terminated() {
bb.add_instruction(MirInstruction::Const { dst: dst_id, value: ConstValue::Integer(0) });
bb.set_terminator(MirInstruction::Return { value: Some(dst_id) });
}
}
}
// Keep return type unknown to allow dynamic display (VM/Interpreter)
f.signature.return_type = MirType::Unknown;
module.add_function(f);
Ok(module)
}
fn next_block_id(f: &MirFunction) -> BasicBlockId {
let mut mx = 0u32;
for k in f.blocks.keys() { if k.0 >= mx { mx = k.0 + 1; } }
BasicBlockId::new(mx)
}
fn lower_expr(f: &mut MirFunction, cur_bb: BasicBlockId, e: &ExprV0) -> Result<(crate::mir::ValueId, BasicBlockId), 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(cur_bb) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(ival) });
}
Ok((dst, cur_bb))
}
ExprV0::Str { value } => {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::String(value.clone()) });
}
Ok((dst, cur_bb))
}
ExprV0::Bool { value } => {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Bool(*value) });
}
Ok((dst, cur_bb))
}
ExprV0::Binary { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
let (r, cur_after_r) = lower_expr(f, cur_after_l, 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(cur_after_r) {
bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r });
}
Ok((dst, cur_after_r))
}
ExprV0::Extern { iface, method, args } => {
let (arg_ids, cur2) = lower_args(f, cur_bb, args)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur2) {
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO });
}
Ok((dst, cur2))
}
ExprV0::Compare { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
let (r, cur_after_r) = lower_expr(f, cur_after_l, rhs)?;
let cop = match op.as_str() {
"==" => crate::mir::CompareOp::Eq,
"!=" => crate::mir::CompareOp::Ne,
"<" => crate::mir::CompareOp::Lt,
"<=" => crate::mir::CompareOp::Le,
">" => crate::mir::CompareOp::Gt,
">=" => crate::mir::CompareOp::Ge,
_ => return Err("unsupported compare op".into()),
};
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_after_r) {
bb.add_instruction(MirInstruction::Compare { dst, op: cop, lhs: l, rhs: r });
}
Ok((dst, cur_after_r))
}
ExprV0::Logical { op, lhs, rhs } => {
// Short-circuit boolean logic with branches + phi
let (l, cur_after_l) = lower_expr(f, cur_bb, lhs)?;
let rhs_bb = next_block_id(f);
let fall_bb = BasicBlockId::new(rhs_bb.0 + 1);
let merge_bb = BasicBlockId::new(rhs_bb.0 + 2);
f.add_block(crate::mir::BasicBlock::new(rhs_bb));
f.add_block(crate::mir::BasicBlock::new(fall_bb));
f.add_block(crate::mir::BasicBlock::new(merge_bb));
// Branch depending on op
let is_and = matches!(op.as_str(), "&&" | "and");
if let Some(bb) = f.get_block_mut(cur_after_l) {
if is_and {
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: rhs_bb, else_bb: fall_bb });
} else {
// OR: if lhs true, go to fall_bb (true path), else evaluate rhs
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb });
}
}
// Telemetry: note short-circuit lowering
crate::jit::events::emit_lower(
serde_json::json!({
"id": "shortcircuit",
"op": if is_and { "and" } else { "or" },
"rhs_bb": rhs_bb.0,
"fall_bb": fall_bb.0,
"merge_bb": merge_bb.0
}),
"shortcircuit",
"<json_v0>"
);
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] op={} rhs_bb={} fall_bb={} merge_bb={}", if is_and {"and"} else {"or"}, rhs_bb.0, fall_bb.0, merge_bb.0); }
// false/true constant in fall_bb depending on op
let cdst = f.next_value_id();
if let Some(bb) = f.get_block_mut(fall_bb) {
let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) };
bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval });
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
}
// evaluate rhs starting at rhs_bb and ensure the terminal block jumps to merge
let (rval, rhs_end) = lower_expr(f, rhs_bb, rhs)?;
if let Some(bb) = f.get_block_mut(rhs_end) {
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
}
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") { eprintln!("[bridge/logical] rhs_end={} jump->merge_bb={}", rhs_end.0, merge_bb.0); }
// merge with phi (use actual predecessors rhs_end and fall_bb)
let out = f.next_value_id();
if let Some(bb) = f.get_block_mut(merge_bb) {
let mut inputs: Vec<(BasicBlockId, ValueId)> = vec![(fall_bb, cdst)];
if rhs_end != fall_bb {
inputs.push((rhs_end, rval));
} else {
// Degenerate case: RHS ended in fall_bb (e.g., constant expression).
// Reuse the constant to keep PHI well-formed.
inputs.push((fall_bb, rval));
}
inputs.sort_by_key(|(bbid, _)| bbid.0);
bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs });
}
Ok((out, merge_bb))
}
ExprV0::Call { name, args } => {
🔍 Research: GPT-5-Codex capabilities and GitHub PR integration ## Summary Investigated OpenAI's new GPT-5-Codex model and Codex GitHub PR review integration capabilities. ## GPT-5-Codex Analysis ### Benchmark Performance (Good) - SWE-bench Verified: 74.5% (vs GPT-5's 72.8%) - Refactoring tasks: 51.3% (vs GPT-5's 33.9%) - Code review: Higher developer ratings ### Real-World Issues (Concerning) - Users report degraded coding performance - Scripts that previously worked now fail - Less consistent than GPT-4.5 - Longer response times (minutes vs instant) - "Creatively and emotionally flat" - Basic errors (e.g., counting letters incorrectly) ### Key Finding Classic case of "optimizing for benchmarks vs real usability" - scores well on tests but performs poorly in practice. ## Codex GitHub PR Integration ### Setup Process 1. Enable MFA and connect GitHub account 2. Authorize Codex GitHub app for repos 3. Enable "Code review" in repository settings ### Usage Methods - **Manual**: Comment '@codex review' in PR - **Automatic**: Triggers when PR moves from draft to ready ### Current Limitations - One-way communication (doesn't respond to review comments) - Prefers creating new PRs over updating existing ones - Better for single-pass reviews than iterative feedback ## 'codex resume' Feature New session management capability: - Resume previous codex exec sessions - Useful for continuing long tasks across days - Maintains context from interrupted work 🐱 The investigation reveals that while GPT-5-Codex shows benchmark improvements, practical developer experience has declined - a reminder that metrics don't always reflect real-world utility\!
2025-09-16 16:28:25 +09:00
// Special: array literal lowering — Call{name:"array.of", args:[...]} → new ArrayBox(); push(...); result=array
if name == "array.of" {
// Create array first
let arr = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] });
}
// For each element: eval then push
let mut cur = cur_bb;
for e in args {
let (v, c) = lower_expr(f, cur, e)?; cur = c;
let tmp = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ });
}
}
return Ok((arr, cur));
}
// Special: map literal lowering — Call{name:"map.of", args:[k1, v1, k2, v2, ...]} → new MapBox(); set(k,v)...; result=map
if name == "map.of" {
let mapv = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] });
}
let mut cur = cur_bb;
let mut it = args.iter();
while let Some(k) = it.next() {
if let Some(v) = it.next() {
let (kv, cur2) = lower_expr(f, cur, k)?; cur = cur2;
let (vv, cur3) = lower_expr(f, cur, v)?; cur = cur3;
let tmp = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ });
}
} else { break; }
}
return Ok((mapv, cur));
}
// Fallback: treat as normal dynamic call
let (arg_ids, cur) = lower_args(f, cur_bb, args)?;
let fun_val = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::Const { dst: fun_val, value: ConstValue::String(name.clone()) });
}
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::Call { dst: Some(dst), func: fun_val, args: arg_ids, effects: EffectMask::READ });
}
Ok((dst, cur))
}
ExprV0::Method { recv, method, args } => {
// Heuristic: new ConsoleBox().println(x) → externcall env.console.log(x)
let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox");
if recv_is_console_new && (method == "println" || method == "print" || method == "log") {
let (arg_ids, cur2) = lower_args(f, cur_bb, args)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur2) {
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ });
}
return Ok((dst, cur2));
}
let (recv_v, cur) = lower_expr(f, cur_bb, recv)?;
let (arg_ids, cur2) = lower_args(f, cur, args)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur2) {
bb.add_instruction(MirInstruction::BoxCall { dst: Some(dst), box_val: recv_v, method: method.clone(), method_id: None, args: arg_ids, effects: EffectMask::READ });
}
Ok((dst, cur2))
}
ExprV0::New { class, args } => {
let (arg_ids, cur) = lower_args(f, cur_bb, args)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_ids });
}
Ok((dst, cur))
}
ExprV0::Var { name } => Err(format!("undefined variable in this context: {}", name)),
ExprV0::Throw { expr } => {
let (_ignored, cur) = lower_expr(f, cur_bb, expr)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(0) });
}
Ok((dst, cur))
}
}
}
fn lower_expr_with_vars(
f: &mut MirFunction,
cur_bb: BasicBlockId,
e: &ExprV0,
vars: &mut std::collections::HashMap<String, crate::mir::ValueId>,
) -> Result<(crate::mir::ValueId, BasicBlockId), String> {
match e {
ExprV0::Var { name } => {
if let Some(&vid) = vars.get(name) {
return Ok((vid, cur_bb));
}
if name == "me" {
// Optional gate: allow a dummy 'me' instance for Stage-2 JSON smoke
if std::env::var("NYASH_BRIDGE_ME_DUMMY").ok().as_deref() == Some("1") {
let class = std::env::var("NYASH_BRIDGE_ME_CLASS").unwrap_or_else(|_| "Main".to_string());
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::NewBox { dst, box_type: class, args: vec![] });
}
vars.insert("me".to_string(), dst);
return Ok((dst, cur_bb));
} else {
return Err("undefined 'me' outside box context (set NYASH_BRIDGE_ME_DUMMY=1 to inject placeholder)".into());
}
}
Err(format!("undefined variable: {}", name))
}
ExprV0::Throw { expr } => {
let (_ignored, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::Const { dst, value: ConstValue::Integer(0) });
}
Ok((dst, cur))
}
ExprV0::Call { name, args } => {
🔍 Research: GPT-5-Codex capabilities and GitHub PR integration ## Summary Investigated OpenAI's new GPT-5-Codex model and Codex GitHub PR review integration capabilities. ## GPT-5-Codex Analysis ### Benchmark Performance (Good) - SWE-bench Verified: 74.5% (vs GPT-5's 72.8%) - Refactoring tasks: 51.3% (vs GPT-5's 33.9%) - Code review: Higher developer ratings ### Real-World Issues (Concerning) - Users report degraded coding performance - Scripts that previously worked now fail - Less consistent than GPT-4.5 - Longer response times (minutes vs instant) - "Creatively and emotionally flat" - Basic errors (e.g., counting letters incorrectly) ### Key Finding Classic case of "optimizing for benchmarks vs real usability" - scores well on tests but performs poorly in practice. ## Codex GitHub PR Integration ### Setup Process 1. Enable MFA and connect GitHub account 2. Authorize Codex GitHub app for repos 3. Enable "Code review" in repository settings ### Usage Methods - **Manual**: Comment '@codex review' in PR - **Automatic**: Triggers when PR moves from draft to ready ### Current Limitations - One-way communication (doesn't respond to review comments) - Prefers creating new PRs over updating existing ones - Better for single-pass reviews than iterative feedback ## 'codex resume' Feature New session management capability: - Resume previous codex exec sessions - Useful for continuing long tasks across days - Maintains context from interrupted work 🐱 The investigation reveals that while GPT-5-Codex shows benchmark improvements, practical developer experience has declined - a reminder that metrics don't always reflect real-world utility\!
2025-09-16 16:28:25 +09:00
// Special: array literal lowering in vars context
if name == "array.of" {
let arr = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] });
}
let mut cur = cur_bb;
for e in args {
let (v, c) = lower_expr_with_vars(f, cur, e, vars)?; cur = c;
let tmp = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: arr, method: "push".into(), method_id: None, args: vec![v], effects: EffectMask::READ });
}
}
return Ok((arr, cur));
}
// Special: map literal lowering in vars context
if name == "map.of" {
let mapv = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_bb) {
bb.add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] });
}
let mut cur = cur_bb;
let mut it = args.iter();
while let Some(k) = it.next() {
if let Some(v) = it.next() {
let (kv, cur2) = lower_expr_with_vars(f, cur, k, vars)?; cur = cur2;
let (vv, cur3) = lower_expr_with_vars(f, cur, v, vars)?; cur = cur3;
let tmp = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::BoxCall { dst: Some(tmp), box_val: mapv, method: "set".into(), method_id: None, args: vec![kv, vv], effects: EffectMask::READ });
}
} else { break; }
}
return Ok((mapv, cur));
}
// Lower args
let (arg_ids, cur) = lower_args_with_vars(f, cur_bb, args, vars)?;
// Encode as: const fun_name; call
let fun_val = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::Const { dst: fun_val, value: ConstValue::String(name.clone()) });
}
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::Call { dst: Some(dst), func: fun_val, args: arg_ids, effects: EffectMask::READ });
}
Ok((dst, cur))
}
ExprV0::Method { recv, method, args } => {
let recv_is_console_new = matches!(&**recv, ExprV0::New { class, .. } if class == "ConsoleBox");
if recv_is_console_new && (method == "println" || method == "print" || method == "log") {
let (arg_ids, cur2) = lower_args_with_vars(f, cur_bb, args, vars)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur2) {
bb.add_instruction(MirInstruction::ExternCall { dst: Some(dst), iface_name: "env.console".into(), method_name: "log".into(), args: arg_ids, effects: EffectMask::READ });
}
return Ok((dst, cur2));
}
let (recv_v, cur) = lower_expr_with_vars(f, cur_bb, recv, vars)?;
let (arg_ids, cur2) = lower_args_with_vars(f, cur, args, vars)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur2) {
bb.add_instruction(MirInstruction::BoxCall { dst: Some(dst), box_val: recv_v, method: method.clone(), method_id: None, args: arg_ids, effects: EffectMask::READ });
}
Ok((dst, cur2))
}
ExprV0::New { class, args } => {
let (arg_ids, cur) = lower_args_with_vars(f, cur_bb, args, vars)?;
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur) {
bb.add_instruction(MirInstruction::NewBox { dst, box_type: class.clone(), args: arg_ids });
}
Ok((dst, cur))
}
ExprV0::Binary { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr_with_vars(f, cur_bb, lhs, vars)?;
let (r, cur_after_r) = lower_expr_with_vars(f, cur_after_l, rhs, vars)?;
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(cur_after_r) {
bb.add_instruction(MirInstruction::BinOp { dst, op: bop, lhs: l, rhs: r });
}
Ok((dst, cur_after_r))
}
ExprV0::Compare { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr_with_vars(f, cur_bb, lhs, vars)?;
let (r, cur_after_r) = lower_expr_with_vars(f, cur_after_l, rhs, vars)?;
let cop = match op.as_str() {
"==" => crate::mir::CompareOp::Eq,
"!=" => crate::mir::CompareOp::Ne,
"<" => crate::mir::CompareOp::Lt,
"<=" => crate::mir::CompareOp::Le,
">" => crate::mir::CompareOp::Gt,
">=" => crate::mir::CompareOp::Ge,
_ => return Err("unsupported compare op".into()),
};
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cur_after_r) {
bb.add_instruction(MirInstruction::Compare { dst, op: cop, lhs: l, rhs: r });
}
Ok((dst, cur_after_r))
}
ExprV0::Logical { op, lhs, rhs } => {
let (l, cur_after_l) = lower_expr_with_vars(f, cur_bb, lhs, vars)?;
let rhs_bb = next_block_id(f);
let fall_bb = BasicBlockId::new(rhs_bb.0 + 1);
let merge_bb = BasicBlockId::new(rhs_bb.0 + 2);
f.add_block(crate::mir::BasicBlock::new(rhs_bb));
f.add_block(crate::mir::BasicBlock::new(fall_bb));
f.add_block(crate::mir::BasicBlock::new(merge_bb));
let is_and = matches!(op.as_str(), "&&" | "and");
if let Some(bb) = f.get_block_mut(cur_after_l) {
if is_and {
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: rhs_bb, else_bb: fall_bb });
} else {
bb.set_terminator(MirInstruction::Branch { condition: l, then_bb: fall_bb, else_bb: rhs_bb });
}
}
let cdst = f.next_value_id();
if let Some(bb) = f.get_block_mut(fall_bb) {
let cval = if is_and { ConstValue::Bool(false) } else { ConstValue::Bool(true) };
bb.add_instruction(MirInstruction::Const { dst: cdst, value: cval });
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
}
let (rval, rhs_end) = lower_expr_with_vars(f, rhs_bb, rhs, vars)?;
if let Some(bb) = f.get_block_mut(rhs_end) { if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); } }
let out = f.next_value_id();
if let Some(bb) = f.get_block_mut(merge_bb) { bb.insert_instruction_after_phis(MirInstruction::Phi { dst: out, inputs: vec![(rhs_end, rval), (fall_bb, cdst)] }); }
Ok((out, merge_bb))
}
_ => lower_expr(f, cur_bb, e),
}
}
fn lower_stmt_with_vars(
f: &mut MirFunction,
cur_bb: BasicBlockId,
s: &StmtV0,
vars: &mut std::collections::HashMap<String, crate::mir::ValueId>,
) -> Result<BasicBlockId, String> {
match s {
StmtV0::Return { expr } => {
let (v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?;
if let Some(bb) = f.get_block_mut(cur) { bb.set_terminator(MirInstruction::Return { value: Some(v) }); }
Ok(cur)
}
StmtV0::Extern { iface, method, args } => {
let (arg_ids, cur) = lower_args_with_vars(f, cur_bb, args, vars)?;
if let Some(bb) = f.get_block_mut(cur) { bb.add_instruction(MirInstruction::ExternCall { dst: None, iface_name: iface.clone(), method_name: method.clone(), args: arg_ids, effects: EffectMask::IO }); }
Ok(cur)
}
StmtV0::Expr { expr } => {
let (_v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; Ok(cur)
}
StmtV0::Local { name, expr } => {
let (v, cur) = lower_expr_with_vars(f, cur_bb, expr, vars)?; vars.insert(name.clone(), v); Ok(cur)
}
StmtV0::Break => {
// Stage-3 placeholder: no-op until loop lowering supports break
Ok(cur_bb)
}
StmtV0::Continue => {
// Stage-3 placeholder: no-op until loop lowering supports continue
Ok(cur_bb)
}
StmtV0::Try { try_body, .. } => {
// Stage-3 placeholder: lower try body sequentially, ignore catches/finally for now
let mut tmp_vars = vars.clone();
let next_bb = lower_stmt_list_with_vars(f, cur_bb, try_body, &mut tmp_vars)?;
*vars = tmp_vars;
Ok(next_bb)
}
StmtV0::If { cond, then, r#else } => {
// Lower condition first
let (cval, cur) = lower_expr_with_vars(f, cur_bb, cond, vars)?;
// Create then/else/merge blocks
let then_bb = next_block_id(f);
let else_bb = BasicBlockId::new(then_bb.0 + 1);
let merge_bb = BasicBlockId::new(then_bb.0 + 2);
f.add_block(crate::mir::BasicBlock::new(then_bb));
f.add_block(crate::mir::BasicBlock::new(else_bb));
f.add_block(crate::mir::BasicBlock::new(merge_bb));
// Branch to then/else
if let Some(bb) = f.get_block_mut(cur) {
bb.set_terminator(MirInstruction::Branch { condition: cval, then_bb, else_bb });
}
// Clone current vars as branch-local maps
let base_vars = vars.clone();
let mut then_vars = base_vars.clone();
let tend = lower_stmt_list_with_vars(f, then_bb, then, &mut then_vars)?;
if let Some(bb) = f.get_block_mut(tend) {
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
}
let (else_end_pred, else_vars) = if let Some(elses) = r#else {
let mut ev = base_vars.clone();
let eend = lower_stmt_list_with_vars(f, else_bb, elses, &mut ev)?;
if let Some(bb) = f.get_block_mut(eend) {
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: merge_bb }); }
}
(eend, ev)
} else {
// No else: empty path falls through with base vars
if let Some(bb) = f.get_block_mut(else_bb) {
bb.set_terminator(MirInstruction::Jump { target: merge_bb });
}
(else_bb, base_vars.clone())
};
// PHI merge at merge_bb
use std::collections::HashSet;
let mut names: HashSet<String> = base_vars.keys().cloned().collect();
// Also merge variables newly defined on both sides
for k in then_vars.keys() { names.insert(k.clone()); }
for k in else_vars.keys() { names.insert(k.clone()); }
for name in names {
let tv = then_vars.get(&name).copied();
let ev = else_vars.get(&name).copied();
// Only propagate if variable exists on both paths or existed before
let exists_base = base_vars.contains_key(&name);
match (tv, ev, exists_base) {
(Some(tval), Some( eval), _) => {
let merged = if tval == eval {
tval
} else {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(merge_bb) {
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, tval), (else_end_pred, eval)] });
}
dst
};
vars.insert(name, merged);
}
(Some(tval), None, true) => {
// Else path inherits base; merge then vs base
if let Some(&bval) = base_vars.get(&name) {
let merged = if tval == bval { tval } else {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(merge_bb) {
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, tval), (else_end_pred, bval)] });
}
dst
};
vars.insert(name, merged);
}
}
(None, Some(eval), true) => {
// Then path inherits base; merge else vs base
if let Some(&bval) = base_vars.get(&name) {
let merged = if eval == bval { eval } else {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(merge_bb) {
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(tend, bval), (else_end_pred, eval)] });
}
dst
};
vars.insert(name, merged);
}
}
// If neither side has it, or only one side has it without base, skip (out-of-scope new var)
_ => {}
}
}
Ok(merge_bb)
}
StmtV0::Loop { cond, body } => {
// Create loop blocks
let cond_bb = next_block_id(f);
let body_bb = BasicBlockId::new(cond_bb.0 + 1);
let exit_bb = BasicBlockId::new(cond_bb.0 + 2);
f.add_block(crate::mir::BasicBlock::new(cond_bb));
f.add_block(crate::mir::BasicBlock::new(body_bb));
f.add_block(crate::mir::BasicBlock::new(exit_bb));
// Preheader jump into cond
if let Some(bb) = f.get_block_mut(cur_bb) {
if !bb.is_terminated() { bb.add_instruction(MirInstruction::Jump { target: cond_bb }); }
}
// Snapshot base vars and set up PHI placeholders at cond for loop-carried vars
let base_vars = vars.clone();
let orig_names: Vec<String> = base_vars.keys().cloned().collect();
let mut phi_map: std::collections::HashMap<String, crate::mir::ValueId> = std::collections::HashMap::new();
for name in &orig_names {
if let Some(&bval) = base_vars.get(name) {
let dst = f.next_value_id();
if let Some(bb) = f.get_block_mut(cond_bb) {
// Initial incoming from preheader
bb.insert_instruction_after_phis(MirInstruction::Phi { dst, inputs: vec![(cur_bb, bval)] });
}
phi_map.insert(name.clone(), dst);
}
}
// Redirect current vars to PHIs for use in cond/body
for (name, &phi) in &phi_map { vars.insert(name.clone(), phi); }
// Lower condition using phi-backed vars
let (cval, _cend) = lower_expr_with_vars(f, cond_bb, cond, vars)?;
if let Some(bb) = f.get_block_mut(cond_bb) {
bb.set_terminator(MirInstruction::Branch { condition: cval, then_bb: body_bb, else_bb: exit_bb });
}
// Lower body; record end block and body-out vars
let mut body_vars = vars.clone();
let bend = lower_stmt_list_with_vars(f, body_bb, body, &mut body_vars)?;
if let Some(bb) = f.get_block_mut(bend) {
if !bb.is_terminated() { bb.set_terminator(MirInstruction::Jump { target: cond_bb }); }
}
// Wire PHI second incoming from latch (body end)
if let Some(bb) = f.get_block_mut(cond_bb) {
for (name, &phi_dst) in &phi_map {
if let Some(&latch_val) = body_vars.get(name) {
for inst in &mut bb.instructions {
if let MirInstruction::Phi { dst, inputs } = inst {
if *dst == phi_dst {
inputs.push((bend, latch_val));
break;
}
}
}
}
}
}
// After the loop, keep vars mapped to the PHI values (current loop state)
for (name, &phi) in &phi_map { vars.insert(name.clone(), phi); }
Ok(exit_bb)
}
}
}
fn lower_stmt_list_with_vars(
f: &mut MirFunction,
start_bb: BasicBlockId,
stmts: &[StmtV0],
vars: &mut std::collections::HashMap<String, crate::mir::ValueId>,
) -> Result<BasicBlockId, String> {
let mut cur = start_bb;
for s in stmts {
cur = lower_stmt_with_vars(f, cur, s, vars)?;
if let Some(bb) = f.blocks.get(&cur) { if bb.is_terminated() { break; } }
}
Ok(cur)
}
fn lower_args_with_vars(
f: &mut MirFunction,
cur_bb: BasicBlockId,
args: &[ExprV0],
vars: &mut std::collections::HashMap<String, crate::mir::ValueId>,
) -> Result<(Vec<crate::mir::ValueId>, BasicBlockId), String> {
let mut out = Vec::with_capacity(args.len());
let mut cur = cur_bb;
for a in args {
let (v, c) = lower_expr_with_vars(f, cur, a, vars)?; out.push(v); cur = c;
}
Ok((out, cur))
}
fn lower_args(f: &mut MirFunction, cur_bb: BasicBlockId, args: &[ExprV0]) -> Result<(Vec<crate::mir::ValueId>, BasicBlockId), String> {
let mut out = Vec::with_capacity(args.len());
let mut cur = cur_bb;
for a in args {
let (v, c) = lower_expr(f, cur, a)?; out.push(v); cur = c;
}
Ok((out, cur))
}
pub fn maybe_dump_mir(module: &MirModule) {
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let 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;
// Treat semicolon as whitespace (Stage-1 minimal ASI: optional ';')
if c.is_whitespace() || c == ';' { 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)
}