Add CLI builtins: args, env, http_post, http_get, str ops, json_get, cwd; fix block tail expr and wildcard match codegen; add run-file command
This commit is contained in:
@@ -65,6 +65,27 @@ impl Codegen {
|
||||
|
||||
// ── Statement code generation ─────────────────────────────────────────────
|
||||
|
||||
/// Generate a statement in tail position (the last stmt of a block).
|
||||
/// Expression statements leave their value on the stack instead of popping it.
|
||||
fn gen_stmt_tail(&mut self, stmt: &Stmt) -> CompileResult<()> {
|
||||
match stmt {
|
||||
Stmt::Expr(expr, _) => {
|
||||
// In tail position, leave the value on the stack.
|
||||
self.gen_expr(expr)?;
|
||||
}
|
||||
Stmt::Return(expr, _) => {
|
||||
self.gen_expr(expr)?;
|
||||
self.emit(Bytecode::Return);
|
||||
}
|
||||
// All other statement kinds behave the same as non-tail; push Nil as block value.
|
||||
other => {
|
||||
self.gen_stmt(other)?;
|
||||
self.emit(Bytecode::Push(Value::Nil));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_stmt(&mut self, stmt: &Stmt) -> CompileResult<()> {
|
||||
// Record the source span for this statement in the source map
|
||||
if self.emit_source_map {
|
||||
@@ -187,20 +208,18 @@ impl Codegen {
|
||||
self.emit(Bytecode::Call { name: fn_name, arity: args.len() as u32 });
|
||||
}
|
||||
Expr::Block(stmts) => {
|
||||
for (i, s) in stmts.iter().enumerate() {
|
||||
self.gen_stmt(s)?;
|
||||
// The last expression statement is the block's value
|
||||
if i == stmts.len() - 1 {
|
||||
if let Stmt::Expr(_, _) = s {
|
||||
// Already on stack from gen_stmt (before the Pop)
|
||||
// We need to not pop it — handled by gen_stmt not
|
||||
// popping Block results; but we already did Pop.
|
||||
// Push nil as fallback for empty/void blocks.
|
||||
}
|
||||
}
|
||||
}
|
||||
if stmts.is_empty() {
|
||||
self.emit(Bytecode::Push(Value::Nil));
|
||||
} else {
|
||||
for (i, s) in stmts.iter().enumerate() {
|
||||
let is_last = i == stmts.len() - 1;
|
||||
if is_last {
|
||||
// Last statement: emit as a tail expression (leave value on stack).
|
||||
self.gen_stmt_tail(s)?;
|
||||
} else {
|
||||
self.gen_stmt(s)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Expr::If { cond, then, else_ } => {
|
||||
@@ -225,48 +244,68 @@ impl Codegen {
|
||||
// compare, branch. A full implementation would use a jump table.
|
||||
let mut end_jumps = Vec::new();
|
||||
for arm in arms {
|
||||
self.emit(Bytecode::Dup);
|
||||
// Push pattern value
|
||||
match &arm.pattern {
|
||||
el_parser::Pattern::Literal(lit) => {
|
||||
let v = match lit {
|
||||
Literal::Int(n) => Value::Int(*n),
|
||||
Literal::Str(s) => Value::Str(s.clone()),
|
||||
Literal::Bool(b) => Value::Bool(*b),
|
||||
Literal::Float(f) => Value::Float(*f),
|
||||
};
|
||||
self.emit(Bytecode::Push(v));
|
||||
}
|
||||
el_parser::Pattern::EnumVariant { variant, payload, .. } => {
|
||||
// Push the variant name as a string for comparison
|
||||
self.emit(Bytecode::Push(Value::Str(variant.clone())));
|
||||
if let Some(bind) = payload {
|
||||
// Store the payload in a local (simplified: store subject as payload)
|
||||
self.emit(Bytecode::StoreLocal(bind.clone()));
|
||||
}
|
||||
el_parser::Pattern::Wildcard => {
|
||||
// Wildcard always matches — pop subject and run body directly.
|
||||
self.emit(Bytecode::Pop);
|
||||
self.gen_expr(&arm.body)?;
|
||||
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||
// No jump_no_match needed — wildcard always matches.
|
||||
// But we still need to patch the end jumps at the end.
|
||||
// Nothing else to do; break out of the loop since wildcard
|
||||
// is a catch-all and subsequent arms are unreachable.
|
||||
break;
|
||||
}
|
||||
el_parser::Pattern::Binding(name) => {
|
||||
// Bind and always match — push duplicate and store
|
||||
// Bind and always match.
|
||||
self.emit(Bytecode::Dup);
|
||||
self.emit(Bytecode::StoreLocal(name.clone()));
|
||||
// Fall through — will compare to itself (always true)
|
||||
// Dup'd subject is still on stack; compare to itself.
|
||||
self.emit(Bytecode::Dup);
|
||||
self.emit(Bytecode::Eq);
|
||||
let jump_no_match = self.emit(Bytecode::JumpIfNot(0));
|
||||
self.emit(Bytecode::Pop);
|
||||
self.gen_expr(&arm.body)?;
|
||||
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||
let next_arm = self.current_idx();
|
||||
self.patch_jump(jump_no_match, next_arm);
|
||||
}
|
||||
el_parser::Pattern::Wildcard => {
|
||||
// Wildcard — push nil (always "matches")
|
||||
self.emit(Bytecode::Push(Value::Nil));
|
||||
_ => {
|
||||
self.emit(Bytecode::Dup);
|
||||
// Push pattern value
|
||||
match &arm.pattern {
|
||||
el_parser::Pattern::Literal(lit) => {
|
||||
let v = match lit {
|
||||
Literal::Int(n) => Value::Int(*n),
|
||||
Literal::Str(s) => Value::Str(s.clone()),
|
||||
Literal::Bool(b) => Value::Bool(*b),
|
||||
Literal::Float(f) => Value::Float(*f),
|
||||
};
|
||||
self.emit(Bytecode::Push(v));
|
||||
}
|
||||
el_parser::Pattern::EnumVariant { variant, payload, .. } => {
|
||||
// Push the variant name as a string for comparison
|
||||
self.emit(Bytecode::Push(Value::Str(variant.clone())));
|
||||
if let Some(bind) = payload {
|
||||
// Store the subject (simplified: payload = subject)
|
||||
self.emit(Bytecode::StoreLocal(bind.clone()));
|
||||
}
|
||||
}
|
||||
_ => unreachable!("wildcard and binding handled above"),
|
||||
}
|
||||
self.emit(Bytecode::Eq);
|
||||
let jump_no_match = self.emit(Bytecode::JumpIfNot(0));
|
||||
// Pop subject from stack
|
||||
self.emit(Bytecode::Pop);
|
||||
// Generate arm body
|
||||
self.gen_expr(&arm.body)?;
|
||||
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||
let next_arm = self.current_idx();
|
||||
self.patch_jump(jump_no_match, next_arm);
|
||||
}
|
||||
}
|
||||
self.emit(Bytecode::Eq);
|
||||
let jump_no_match = self.emit(Bytecode::JumpIfNot(0));
|
||||
// Pop subject from stack
|
||||
self.emit(Bytecode::Pop);
|
||||
// Generate arm body
|
||||
self.gen_expr(&arm.body)?;
|
||||
end_jumps.push(self.emit(Bytecode::Jump(0)));
|
||||
let next_arm = self.current_idx();
|
||||
self.patch_jump(jump_no_match, next_arm);
|
||||
}
|
||||
// Default: pop subject, push nil
|
||||
// Default fallthrough: pop subject, push nil
|
||||
self.emit(Bytecode::Pop);
|
||||
self.emit(Bytecode::Push(Value::Nil));
|
||||
let end = self.current_idx();
|
||||
|
||||
Reference in New Issue
Block a user