🚀 feat: Multiple improvements for Nyash parser and LLVM backend

Parser improvements:
- Added expression statement fallback in parse_statement() for flexible syntax
- Fixed ternary operator to use PeekExpr instead of If AST (better lowering)
- Added peek_token() check to avoid ?/?: operator conflicts

LLVM Python improvements:
- Added optional ESC_JSON_FIX environment flag for string concatenation
- Improved PHI generation with better default handling
- Enhanced substring tracking for esc_json pattern

Documentation updates:
- Updated language guide with peek expression examples
- Added box theory diagrams to Phase 15 planning
- Clarified peek vs when syntax differences

These changes enable cleaner parser implementation for self-hosting,
especially for handling digit conversion with peek expressions instead
of 19-line if-else chains.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Selfhosting Dev
2025-09-14 19:16:32 +09:00
parent ab1afbc57b
commit 3ba96d9a03
30 changed files with 685 additions and 375 deletions

View File

@ -104,29 +104,36 @@ impl super::MirBuilder {
let merge_block = self.block_gen.next();
self.emit_instruction(MirInstruction::Branch { condition: condition_val, then_bb: then_block, else_bb: else_block })?;
// Snapshot variable map before entering branches to avoid cross-branch pollution
let pre_if_var_map = self.variable_map.clone();
// Pre-analysis: detect then-branch assigned var and capture its pre-if value
let assigned_then_pre = extract_assigned_var(&then_branch);
let pre_then_var_value: Option<ValueId> = assigned_then_pre
.as_ref()
.and_then(|name| self.variable_map.get(name).copied());
.and_then(|name| pre_if_var_map.get(name).copied());
// then
self.current_block = Some(then_block);
self.ensure_block_exists(then_block)?;
let then_ast_for_analysis = then_branch.clone();
let then_value = self.build_expression(then_branch)?;
// Build then with a clean snapshot of pre-if variables
self.variable_map = pre_if_var_map.clone();
let then_value_raw = self.build_expression(then_branch)?;
let then_var_map_end = self.variable_map.clone();
if !self.is_current_block_terminated() { self.emit_instruction(MirInstruction::Jump { target: merge_block })?; }
// else
self.current_block = Some(else_block);
self.ensure_block_exists(else_block)?;
let (mut else_value, else_ast_for_analysis) = if let Some(else_ast) = else_branch {
// Build else with a clean snapshot of pre-if variables
let (mut else_value_raw, else_ast_for_analysis, else_var_map_end_opt) = if let Some(else_ast) = else_branch {
self.variable_map = pre_if_var_map.clone();
let val = self.build_expression(else_ast.clone())?;
(val, Some(else_ast))
(val, Some(else_ast), Some(self.variable_map.clone()))
} else {
let void_val = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_val, value: ConstValue::Void })?;
(void_val, None)
(void_val, None, None)
};
if !self.is_current_block_terminated() { self.emit_instruction(MirInstruction::Jump { target: merge_block })?; }
@ -140,22 +147,23 @@ impl super::MirBuilder {
let result_val = self.value_gen.next();
if let Some(var_name) = assigned_var_then.clone() {
let else_assigns_same = assigned_var_else.as_ref().map(|s| s == &var_name).unwrap_or(false);
if !else_assigns_same {
if let Some(pre) = pre_then_var_value {
// Use pre-if value for else input so SSA is well-formed
else_value = pre;
}
// After merge, the variable should refer to the Phi result
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
self.variable_map.insert(var_name, result_val);
// Resolve branch-end values for the assigned variable
let then_value_for_var = then_var_map_end.get(&var_name).copied().unwrap_or(then_value_raw);
let else_value_for_var = if else_assigns_same {
else_var_map_end_opt.as_ref().and_then(|m| m.get(&var_name).copied()).unwrap_or(else_value_raw)
} else {
// Both sides assign same variable emit Phi normally and bind
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
self.variable_map.insert(var_name, result_val);
}
// Else doesn't assign: use pre-if value if available
pre_then_var_value.unwrap_or(else_value_raw)
};
// Emit Phi for the assigned variable and bind it
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_for_var), (else_block, else_value_for_var)] })?;
self.variable_map = pre_if_var_map.clone();
self.variable_map.insert(var_name, result_val);
} else {
// No variable assignment pattern detected just emit Phi for expression result
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value), (else_block, else_value)] })?;
self.emit_instruction(MirInstruction::Phi { dst: result_val, inputs: vec![(then_block, then_value_raw), (else_block, else_value_raw)] })?;
// Merge variable map conservatively to pre-if snapshot (no new bindings)
self.variable_map = pre_if_var_map.clone();
}
Ok(result_val)
@ -327,6 +335,19 @@ fn extract_assigned_var(ast: &ASTNode) -> Option<String> {
if let ASTNode::Variable { name, .. } = target.as_ref() { Some(name.clone()) } else { None }
}
ASTNode::Program { statements, .. } => statements.last().and_then(|st| extract_assigned_var(st)),
ASTNode::If { then_body, else_body, .. } => {
// Look into nested if: if both sides assign the same variable, propagate that name upward.
let then_prog = ASTNode::Program { statements: then_body.clone(), span: crate::ast::Span::unknown() };
let tvar = extract_assigned_var(&then_prog);
let evar = else_body.as_ref().and_then(|eb| {
let ep = ASTNode::Program { statements: eb.clone(), span: crate::ast::Span::unknown() };
extract_assigned_var(&ep)
});
match (tvar, evar) {
(Some(tv), Some(ev)) if tv == ev => Some(tv),
_ => None,
}
}
_ => None,
}
}