🔧 Fix peek expression terminator issues and add ternary operator support
- Fix LLVM IR terminator missing in peek expression entry blocks - Add proper jump instructions between peek blocks - Implement ternary operator (? :) as syntactic sugar for peek - Update Python LLVM externcall handling for improved compatibility - Add comprehensive test cases for peek and ternary expressions - Update language guide with ternary operator documentation ChatGPTが頑張って修正してくれたにゃ!🐱 Co-Authored-By: ChatGPT <noreply@openai.com>
This commit is contained in:
@ -82,6 +82,7 @@ def lower_externcall(
|
||||
# Prepare/coerce arguments
|
||||
call_args: List[ir.Value] = []
|
||||
for i, arg_id in enumerate(args):
|
||||
orig_arg_id = arg_id
|
||||
# Prefer resolver
|
||||
if resolver is not None and preds is not None and block_end_values is not None and bb_map is not None:
|
||||
if len(func.args) > i and isinstance(func.args[i].type, ir.PointerType):
|
||||
@ -99,19 +100,47 @@ def lower_externcall(
|
||||
expected_ty = func.args[i].type
|
||||
if isinstance(expected_ty, ir.PointerType):
|
||||
# Need pointer
|
||||
if hasattr(aval, 'type'):
|
||||
if isinstance(aval.type, ir.IntType):
|
||||
aval = builder.inttoptr(aval, expected_ty, name=f"ext_i2p_arg{i}")
|
||||
elif not aval.type.is_pointer:
|
||||
aval = ir.Constant(expected_ty, None)
|
||||
else:
|
||||
# Pointer but wrong element type: if pointer-to-array -> GEP to i8*
|
||||
try:
|
||||
if isinstance(aval.type.pointee, ir.ArrayType) and isinstance(expected_ty.pointee, ir.IntType) and expected_ty.pointee.width == 8:
|
||||
c0 = ir.Constant(ir.IntType(32), 0)
|
||||
aval = builder.gep(aval, [c0, c0], name=f"ext_gep_arg{i}")
|
||||
except Exception:
|
||||
pass
|
||||
# Prefer string literal pointer or handle->i8* bridge when argument is string-ish
|
||||
used_string_h2p = False
|
||||
try:
|
||||
if resolver is not None and hasattr(resolver, 'string_ptrs'):
|
||||
sp = resolver.string_ptrs.get(orig_arg_id)
|
||||
if sp is not None:
|
||||
aval = sp
|
||||
used_string_h2p = True
|
||||
if not used_string_h2p and resolver is not None and hasattr(resolver, 'is_stringish') and resolver.is_stringish(orig_arg_id):
|
||||
# Declare nyash.string.to_i8p_h(i64) and call with handle
|
||||
i64 = ir.IntType(64)
|
||||
i8p = ir.IntType(8).as_pointer()
|
||||
to_i8p = None
|
||||
for f in module.functions:
|
||||
if f.name == 'nyash.string.to_i8p_h':
|
||||
to_i8p = f; break
|
||||
if to_i8p is None:
|
||||
to_i8p = ir.Function(module, ir.FunctionType(i8p, [i64]), name='nyash.string.to_i8p_h')
|
||||
# Ensure we have an i64 handle to pass
|
||||
if hasattr(aval, 'type') and isinstance(aval.type, ir.PointerType):
|
||||
aval = builder.ptrtoint(aval, i64, name=f"ext_p2h_{i}")
|
||||
elif hasattr(aval, 'type') and isinstance(aval.type, ir.IntType) and aval.type.width != 64:
|
||||
aval = builder.zext(aval, i64, name=f"ext_zext_h_{i}")
|
||||
aval = builder.call(to_i8p, [aval], name=f"ext_h2p_arg{i}")
|
||||
used_string_h2p = True
|
||||
except Exception:
|
||||
used_string_h2p = used_string_h2p or False
|
||||
if not used_string_h2p:
|
||||
if hasattr(aval, 'type'):
|
||||
if isinstance(aval.type, ir.IntType):
|
||||
aval = builder.inttoptr(aval, expected_ty, name=f"ext_i2p_arg{i}")
|
||||
elif not aval.type.is_pointer:
|
||||
aval = ir.Constant(expected_ty, None)
|
||||
else:
|
||||
# Pointer but wrong element type: if pointer-to-array -> GEP to i8*
|
||||
try:
|
||||
if isinstance(aval.type.pointee, ir.ArrayType) and isinstance(expected_ty.pointee, ir.IntType) and expected_ty.pointee.width == 8:
|
||||
c0 = ir.Constant(ir.IntType(32), 0)
|
||||
aval = builder.gep(aval, [c0, c0], name=f"ext_gep_arg{i}")
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
aval = ir.Constant(expected_ty, None)
|
||||
elif isinstance(expected_ty, ir.IntType) and expected_ty.width == 64:
|
||||
|
||||
@ -9,42 +9,81 @@ impl super::MirBuilder {
|
||||
arms: Vec<(LiteralValue, ASTNode)>,
|
||||
else_expr: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
// Evaluate scrutinee in the current block
|
||||
let scr_val = self.build_expression_impl(scrutinee)?;
|
||||
|
||||
// Prepare merge and result
|
||||
let merge_block: BasicBlockId = self.block_gen.next();
|
||||
let result_val = self.value_gen.next();
|
||||
let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
|
||||
let mut next_block = self.block_gen.next();
|
||||
self.start_new_block(next_block)?;
|
||||
|
||||
// Create dispatch block where we start comparing arms
|
||||
let dispatch_block = self.block_gen.next();
|
||||
// Jump from current block to dispatch (ensure terminator exists)
|
||||
let need_jump = {
|
||||
let cur = self.current_block;
|
||||
if let (Some(cb), Some(ref func)) = (cur, &self.current_function) {
|
||||
if let Some(bb) = func.blocks.get(&cb) { !bb.is_terminated() } else { true }
|
||||
} else { true }
|
||||
};
|
||||
if need_jump {
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: dispatch_block })?;
|
||||
}
|
||||
self.start_new_block(dispatch_block)?;
|
||||
|
||||
// If there are no arms, fall through to else directly
|
||||
if arms.is_empty() {
|
||||
let else_block = self.block_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: else_block })?;
|
||||
self.start_new_block(else_block)?;
|
||||
let else_val = self.build_expression_impl(else_expr)?;
|
||||
phi_inputs.push((else_block, else_val));
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
self.start_new_block(merge_block)?;
|
||||
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
|
||||
return Ok(result_val);
|
||||
}
|
||||
|
||||
// Else block to handle default case
|
||||
let else_block = self.block_gen.next();
|
||||
|
||||
// Chain dispatch blocks for each arm
|
||||
let mut cur_dispatch = dispatch_block;
|
||||
for (i, (label, arm_expr)) in arms.iter().cloned().enumerate() {
|
||||
let then_block = self.block_gen.next();
|
||||
// Only string labels handled here (behavior unchanged)
|
||||
// Next dispatch (only for non-last arm)
|
||||
let next_dispatch = if i + 1 < arms.len() { Some(self.block_gen.next()) } else { None };
|
||||
let else_target = next_dispatch.unwrap_or(else_block);
|
||||
|
||||
// In current dispatch block, compare and branch
|
||||
self.start_new_block(cur_dispatch)?;
|
||||
if let LiteralValue::String(s) = label {
|
||||
let lit_id = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Const { dst: lit_id, value: super::ConstValue::String(s) })?;
|
||||
let cond_id = self.value_gen.next();
|
||||
self.emit_instruction(super::MirInstruction::Compare { dst: cond_id, op: super::CompareOp::Eq, lhs: scr_val, rhs: lit_id })?;
|
||||
self.emit_instruction(super::MirInstruction::Branch { condition: cond_id, then_bb: then_block, else_bb: next_block })?;
|
||||
self.start_new_block(then_block)?;
|
||||
let then_val = self.build_expression_impl(arm_expr)?;
|
||||
phi_inputs.push((then_block, then_val));
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
if i < arms.len() - 1 {
|
||||
let b = self.block_gen.next();
|
||||
self.start_new_block(b)?;
|
||||
next_block = b;
|
||||
}
|
||||
self.emit_instruction(super::MirInstruction::Branch { condition: cond_id, then_bb: then_block, else_bb: else_target })?;
|
||||
}
|
||||
|
||||
// then arm
|
||||
self.start_new_block(then_block)?;
|
||||
let then_val = self.build_expression_impl(arm_expr)?;
|
||||
phi_inputs.push((then_block, then_val));
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
|
||||
// Move to next dispatch or else block
|
||||
cur_dispatch = else_target;
|
||||
}
|
||||
|
||||
let else_block_id = next_block;
|
||||
self.start_new_block(else_block_id)?;
|
||||
// Lower else expression in else_block
|
||||
self.start_new_block(else_block)?;
|
||||
let else_val = self.build_expression_impl(else_expr)?;
|
||||
phi_inputs.push((else_block_id, else_val));
|
||||
phi_inputs.push((else_block, else_val));
|
||||
self.emit_instruction(super::MirInstruction::Jump { target: merge_block })?;
|
||||
|
||||
// Merge and yield result
|
||||
self.start_new_block(merge_block)?;
|
||||
self.emit_instruction(super::MirInstruction::Phi { dst: result_val, inputs: phi_inputs })?;
|
||||
Ok(result_val)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ impl NyashParser {
|
||||
condition: Box::new(cond),
|
||||
then_body: vec![then_expr],
|
||||
else_body: Some(vec![else_expr]),
|
||||
span: Span::Unknown,
|
||||
span: Span::unknown(),
|
||||
});
|
||||
}
|
||||
Ok(cond)
|
||||
|
||||
@ -151,15 +151,14 @@ impl NyashRunner {
|
||||
.status()
|
||||
.map_err(|e| format!("spawn pyvm: {}", e))
|
||||
.unwrap();
|
||||
// Always propagate PyVM exit code to match llvmlite semantics
|
||||
let code = status.code().unwrap_or(1);
|
||||
if !status.success() {
|
||||
eprintln!("❌ PyVM failed (status={})", status.code().unwrap_or(-1));
|
||||
process::exit(1);
|
||||
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
|
||||
eprintln!("❌ PyVM failed (status={})", code);
|
||||
}
|
||||
}
|
||||
// Propagate exit code if set
|
||||
if let Some(code) = status.code() {
|
||||
process::exit(code);
|
||||
}
|
||||
process::exit(0);
|
||||
process::exit(code);
|
||||
} else {
|
||||
eprintln!("❌ PyVM runner not found: {}", runner.display());
|
||||
process::exit(1);
|
||||
|
||||
Reference in New Issue
Block a user