diff --git a/FIXME.md b/FIXME.md new file mode 100644 index 0000000..00eb975 --- /dev/null +++ b/FIXME.md @@ -0,0 +1,39 @@ +# FIXME + +Commits on this branch (main..HEAD), in order: + +- 57fc54c Add break and continue statements +- a22c056 Fix nested match codegen panic +- 978f14b Use break in sort example +- d9e0aa3 Add for loop support with range syntax +- a6fe98f Add string comparison operators == and != +- 9b746d3 Add index syntax [] and change generics to <> +- aa09a93 check tests properly +- 7555204 Change default target name from capable +- f64de33 Remove outdated plan file +- 4bd4f97 remove outdated progress file +- 529b9ed move docs +- 4c6e29e Simple ok+err handlers +- f0b3345 Add Vec indexing syntax and fix for-loop range parsing +- c167d11 Move Result enum from compiler builtin to stdlib +- 2f28e94 [WIP] Move Result methods from compiler special-case to stdlib +- 993e805 Fix some stuff +- 7416ad8 Fix loop linearity, indexing constraints, and call conv +- 3f2aca3 Add docs and TODO list +- fc93b87 Add never type and Vec specializations +- d925d5b Add top-level defer statement +- 4763187 Make defer scope-based +- a29d59b Improve net listener and string helpers +- c53e84d Add if let statement sugar +- 15276b0 Add for {} infinite loop sugar +- 4ee669e Add char literals and string helpers +- 48b63f0 stdlib: flesh out string/vec helpers +- 1df8699 codegen: lower struct returns via sret/result-out +- 29effac docs: describe ABI lowering for struct returns + +Fixes applied from review: + +- Allow user-defined `string` type names now that `string` is userland; update reserved-name test to `i32`. +- Support sret lowering in runtime wrapper emission to avoid ABI mismatches. +- Use return lowering in `try` error path to avoid returning wrong signature. +- Initialize zero values for non-opaque structs to avoid null deref when building Result payloads. diff --git a/PLAN.md b/PLAN.md deleted file mode 100644 index 5801b38..0000000 --- a/PLAN.md +++ /dev/null @@ -1,85 +0,0 @@ -# Capable Roadmap (Austral-Aligned) - -This plan keeps Capable aligned with Austral’s goals while staying pragmatic about implementation order. Each phase ends with real examples and tests. - -## Completed (Recently) - -- Borrow‑lite locals with non‑escaping rules and error coverage. -- Capability hardening: root normalization, symlink escape guard, and tests. -- Linear control‑flow checks with attenuation/untrusted tests. -- Match exhaustiveness for `bool`, `Result`, and enums. -- Safe arithmetic with trap‑on‑overflow and modular helpers. -- Result helpers (`unwrap_or`, `unwrap_err_or`) with tests. -- Explicitness policy: shadowing rejected, unsigned ordering enforced. -- Docs and workflow: architecture/policy/samples, justfile tasks, tutorial updates. - -## Priority 1: Backend Cleanup (Highest) - -Goal: keep the compiler/backend simple and trustworthy so future features don’t compound complexity. - -- Typed lowering bridge: codegen should consume typed HIR directly, no re‑inferring shapes. -- Shared type utilities: centralize numeric/orderable/unsigned checks to avoid drift. -- Clean error plumbing: consistent spans/messages across parse → typeck → lower → codegen. -- HIR simplification: fully resolved, no spans, no unresolved paths. -- Runtime handle conventions: unify tables + lifecycle patterns to prep for drop/close. - -Deliverables: -- Typed lowering bridge complete; codegen does not re‑infer type shapes. -- Error messages consistently point at user code spans. -- Small cleanup PRs that reduce “special cases” in codegen. - -## Phase 2: Core Discipline - -Goal: make capability/linearity guarantees reliable without full borrow checking. - -- Linear/affine rules are complete and consistent across control flow. -- Capability attenuation is enforced (consume `self` on narrowing APIs). -- Borrow‑lite: allow `&T` for locals with strict non‑escaping rules. -- Stdlib/runtime tightenings: ensure cap states are checked and canonicalization is consistent. - -Deliverables: -- Tests for linear “must consume” on all paths (if/match/loop). -- Tests for borrow‑lite locals (read‑only, non‑escaping). -- At least two sample programs that stress capabilities and linear resources. - -## Phase 3: Reliability and Predictability - -Goal: make the language predictable under failure and in large programs. - -- Exhaustive `match` checking for enums/Result. -- Safe arithmetic: explicit trap‑on‑overflow vs modular operations. -- Enforce explicitness policies (no implicit conversions, no implicit calls). -- Decide and enforce shadowing policy (prefer “no shadowing” for clarity). - -Deliverables: -- Exhaustiveness tests with helpful diagnostics. -- Arithmetic tests covering overflow behavior. -- Lint/error surfaces for disallowed implicitness. - -## Phase 4: Expressiveness Without Magic - -Goal: add power without hidden control flow or inference. - -- Typeclasses (bounded ad‑hoc polymorphism). -- Better collections/strings APIs for real programs. -- Error ergonomics (`Result` helpers like `map`, `map_err`, `and_then`). - -Deliverables: -- Typeclass MVP with a small stdlib surface (e.g., `Eq`, `Show`). -- Revised sample programs that feel “natural” without macros or inference. - -## Next Horizon - -- Borrow‑lite polish: allow `&T` forwarding through helpers; tighten diagnostics. -- Capability ergonomics: `&self` methods where attenuation should not consume parents. -- Resource lifecycle: decide whether `drop()` stays a sink or add explicit close/free APIs. -- Runtime error clarity: structured error codes/messages for FS/alloc paths. -- Sample apps: config loader with validation, a log tailer, and a dir walker. - -## Ongoing Non‑Goals - -Keep these as invariants: -- No GC, no exceptions, no implicit conversions/calls, no macros, no reflection. -- No global state; no subtyping. -- No uninitialized variables. -- No first‑class async. diff --git a/PROGRESS.md b/PROGRESS.md deleted file mode 100644 index fb99c71..0000000 --- a/PROGRESS.md +++ /dev/null @@ -1,3 +0,0 @@ -# Progress Log - -This file is now intentionally minimal. Recent progress is tracked in `PLAN.md`. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..347d1ca --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +Done: +- Result + is_ok/is_err/ok/err/unwrap_* implemented in stdlib (panic uses never type). +- Stdlib APIs updated to use Vec (compiler maps Vec/Vec/Vec to VecU8/VecI32/VecString). +- Codex reviewed Claude-generated commits. diff --git a/capc/src/abi.rs b/capc/src/abi.rs index 2cc796e..7495cc9 100644 --- a/capc/src/abi.rs +++ b/capc/src/abi.rs @@ -10,10 +10,7 @@ pub enum AbiType { Bool, Handle, Ptr, - String, Result(Box, Box), - /// ABI-only return lowering for `Result`. - ResultString, /// ABI-only return lowering for `Result` where out params are used. ResultOut(Box, Box), } diff --git a/capc/src/ast.rs b/capc/src/ast.rs index 05bd6f1..26099d8 100644 --- a/capc/src/ast.rs +++ b/capc/src/ast.rs @@ -141,9 +141,13 @@ pub struct Block { pub enum Stmt { Let(LetStmt), Assign(AssignStmt), + Defer(DeferStmt), Return(ReturnStmt), + Break(BreakStmt), + Continue(ContinueStmt), If(IfStmt), While(WhileStmt), + For(ForStmt), Expr(ExprStmt), } @@ -161,6 +165,16 @@ pub struct ReturnStmt { pub span: Span, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BreakStmt { + pub span: Span, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContinueStmt { + pub span: Span, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct IfStmt { pub cond: Expr, @@ -176,6 +190,15 @@ pub struct WhileStmt { pub span: Span, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ForStmt { + pub var: Ident, + pub start: Expr, + pub end: Expr, + pub body: Block, + pub span: Span, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct ExprStmt { pub expr: Expr, @@ -189,14 +212,24 @@ pub struct AssignStmt { pub span: Span, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DeferStmt { + pub expr: Expr, + pub span: Span, +} + impl Stmt { pub fn span(&self) -> Span { match self { Stmt::Let(s) => s.span, Stmt::Assign(s) => s.span, + Stmt::Defer(s) => s.span, Stmt::Return(s) => s.span, + Stmt::Break(s) => s.span, + Stmt::Continue(s) => s.span, Stmt::If(s) => s.span, Stmt::While(s) => s.span, + Stmt::For(s) => s.span, Stmt::Expr(s) => s.span, } } @@ -209,6 +242,7 @@ pub enum Expr { Call(CallExpr), MethodCall(MethodCallExpr), FieldAccess(FieldAccessExpr), + Index(IndexExpr), StructLiteral(StructLiteralExpr), Unary(UnaryExpr), Binary(BinaryExpr), @@ -288,6 +322,13 @@ pub struct FieldAccessExpr { pub span: Span, } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IndexExpr { + pub object: Box, + pub index: Box, + pub span: Span, +} + #[derive(Debug, Clone, PartialEq, Eq)] pub struct MethodCallExpr { pub receiver: Box, diff --git a/capc/src/codegen/abi_quirks.rs b/capc/src/codegen/abi_quirks.rs index ecddc0b..9b8b18c 100644 --- a/capc/src/codegen/abi_quirks.rs +++ b/capc/src/codegen/abi_quirks.rs @@ -1,22 +1,12 @@ //! ABI quirks and lowering helpers. //! //! These helpers are the single source of truth for the special-case ABI -//! shapes we use to lower `Result` values (ResultString and ResultOut). +//! shapes we use to lower `Result` values (ResultOut). use crate::abi::AbiType; use super::FnSig; -/// ResultString ABI uses a u64 length slot across targets. -pub fn result_string_len_bytes() -> u32 { - 8 -} - -/// Return true if the ABI type is lowered as ResultString. -pub fn is_result_string(ty: &AbiType) -> bool { - matches!(ty, AbiType::ResultString) -} - /// Return true if the ABI type is lowered using ResultOut parameters. pub fn is_result_out(ty: &AbiType) -> bool { matches!(ty, AbiType::ResultOut(_, _)) @@ -24,12 +14,25 @@ pub fn is_result_out(ty: &AbiType) -> bool { /// Return true if the ABI type uses any Result lowering. pub fn is_result_lowering(ty: &AbiType) -> bool { - is_result_string(ty) || is_result_out(ty) + is_result_out(ty) } -/// Return true if a signature mismatch is explained by Result lowering. +fn is_sret_lowering(abi_sig: &FnSig, sig: &FnSig) -> bool { + if sig.ret != AbiType::Ptr || abi_sig.ret != AbiType::Unit { + return false; + } + if abi_sig.params.len() != sig.params.len() + 1 { + return false; + } + if abi_sig.params.first() != Some(&AbiType::Ptr) { + return false; + } + abi_sig.params[1..] == sig.params +} + +/// Return true if a signature mismatch is explained by Result or sret lowering. pub fn abi_sig_requires_lowering(abi_sig: &FnSig, sig: &FnSig) -> bool { - abi_sig != sig && is_result_lowering(&abi_sig.ret) + abi_sig != sig && (is_result_lowering(&abi_sig.ret) || is_sret_lowering(abi_sig, sig)) } /// Error message used when a layout is requested for a lowered Result ABI. @@ -46,8 +49,3 @@ pub fn result_abi_mismatch_error() -> &'static str { pub fn result_out_params_error() -> &'static str { "result out params" } - -/// Error message used when ResultString lowering is requested but unsupported. -pub fn result_string_params_error() -> &'static str { - "result abi" -} diff --git a/capc/src/codegen/emit.rs b/capc/src/codegen/emit.rs index 8b41f9d..57c9d49 100644 --- a/capc/src/codegen/emit.rs +++ b/capc/src/codegen/emit.rs @@ -15,23 +15,195 @@ use crate::abi::AbiType; use crate::ast::{BinaryOp, Literal, UnaryOp}; use super::{ - CodegenError, EnumIndex, Flow, FnInfo, LocalValue, ResultKind, ResultShape, StructLayoutIndex, - TypeLayout, ValueRepr, + CodegenError, EnumIndex, Flow, FnInfo, LocalValue, ResultKind, ResultShape, + StructLayoutIndex, TypeLayout, ValueRepr, }; use super::abi_quirks; use super::layout::{align_to, resolve_struct_layout, type_layout_for_abi}; use super::sig_to_clif; +/// Target blocks for break/continue inside a loop. #[derive(Copy, Clone, Debug)] -struct ResultStringSlots { - slot_ptr: ir::StackSlot, - slot_len: ir::StackSlot, - slot_err: ir::StackSlot, - ptr_align: u32, - len_align: u32, - err_align: u32, +pub(super) struct LoopTarget { + pub continue_block: ir::Block, // for while: header_block, for: increment_block + pub exit_block: ir::Block, } +#[derive(Clone, Debug)] +pub(super) enum ReturnLowering { + Direct, + SRet { + out_ptr: ir::Value, + ret_ty: crate::hir::HirType, + }, + ResultOut { + out_ok: Option, + out_err: Option, + ok_ty: crate::hir::HirType, + err_ty: crate::hir::HirType, + }, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +enum DeferScopeKind { + Regular, + LoopBody, +} + +#[derive(Clone, Debug)] +struct DeferScope { + kind: DeferScopeKind, + defers: Vec, +} + +#[derive(Clone, Debug)] +pub(super) struct DeferStack { + scopes: Vec, +} + +impl DeferStack { + pub(super) fn new() -> Self { + Self { scopes: Vec::new() } + } + + pub(super) fn push_block_scope(&mut self) { + self.push_scope(DeferScopeKind::Regular); + } + + pub(super) fn push_loop_scope(&mut self) { + self.push_scope(DeferScopeKind::LoopBody); + } + + fn push_scope(&mut self, kind: DeferScopeKind) { + self.scopes.push(DeferScope { + kind, + defers: Vec::new(), + }); + } + + pub(super) fn pop_scope(&mut self) { + let _ = self.scopes.pop(); + } + + pub(super) fn push_defer(&mut self, expr: crate::hir::HirExpr) { + if let Some(scope) = self.scopes.last_mut() { + scope.defers.push(expr); + } + } + + fn emit_scope_defers( + &self, + scope: &DeferScope, + builder: &mut FunctionBuilder, + locals: &HashMap, + fn_map: &HashMap, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, + module: &mut ObjectModule, + data_counter: &mut u32, + ) -> Result<(), CodegenError> { + for defer_expr in scope.defers.iter().rev() { + let _ = emit_hir_expr( + builder, + defer_expr, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + } + Ok(()) + } + + pub(super) fn emit_current_and_pop( + &mut self, + builder: &mut FunctionBuilder, + locals: &HashMap, + fn_map: &HashMap, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, + module: &mut ObjectModule, + data_counter: &mut u32, + ) -> Result<(), CodegenError> { + if let Some(scope) = self.scopes.last() { + self.emit_scope_defers( + scope, + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + } + self.pop_scope(); + Ok(()) + } + + pub(super) fn emit_all_and_clear( + &mut self, + builder: &mut FunctionBuilder, + locals: &HashMap, + fn_map: &HashMap, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, + module: &mut ObjectModule, + data_counter: &mut u32, + ) -> Result<(), CodegenError> { + while let Some(scope) = self.scopes.pop() { + self.emit_scope_defers( + &scope, + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + } + Ok(()) + } + + pub(super) fn emit_until_loop_and_pop( + &mut self, + builder: &mut FunctionBuilder, + locals: &HashMap, + fn_map: &HashMap, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, + module: &mut ObjectModule, + data_counter: &mut u32, + ) -> Result<(), CodegenError> { + while let Some(scope) = self.scopes.pop() { + self.emit_scope_defers( + &scope, + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + if scope.kind == DeferScopeKind::LoopBody { + break; + } + } + Ok(()) + } +} /// Emit a single HIR statement. pub(super) fn emit_hir_stmt( @@ -43,6 +215,9 @@ pub(super) fn emit_hir_stmt( struct_layouts: &StructLayoutIndex, module: &mut ObjectModule, data_counter: &mut u32, + loop_target: Option, + return_lowering: &ReturnLowering, + defer_stack: &mut DeferStack, ) -> Result { emit_hir_stmt_inner( builder, @@ -53,6 +228,9 @@ pub(super) fn emit_hir_stmt( struct_layouts, module, data_counter, + loop_target, + return_lowering, + defer_stack, ) .map_err(|err| err.with_span(stmt.span())) } @@ -66,6 +244,9 @@ fn emit_hir_stmt_inner( struct_layouts: &StructLayoutIndex, module: &mut ObjectModule, data_counter: &mut u32, + loop_target: Option, + return_lowering: &ReturnLowering, + defer_stack: &mut DeferStack, ) -> Result { use crate::hir::HirStmt; @@ -90,6 +271,7 @@ fn emit_hir_stmt_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -134,6 +316,7 @@ fn emit_hir_stmt_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -172,6 +355,9 @@ fn emit_hir_stmt_inner( } } } + HirStmt::Defer(defer_stmt) => { + defer_stack.push_defer(defer_stmt.expr.clone()); + } HirStmt::Return(ret_stmt) => { if let Some(expr) = &ret_stmt.expr { let value = emit_hir_expr( @@ -181,23 +367,152 @@ fn emit_hir_stmt_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; - match value { - ValueRepr::Unit => builder.ins().return_(&[]), - ValueRepr::Single(val) => builder.ins().return_(&[val]), - ValueRepr::Pair(_, _) | ValueRepr::Result { .. } => { - let values = flatten_value(&value); - builder.ins().return_(&values) + match return_lowering { + ReturnLowering::Direct => { + defer_stack.emit_all_and_clear( + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + match value { + ValueRepr::Unit => builder.ins().return_(&[]), + ValueRepr::Single(val) => builder.ins().return_(&[val]), + ValueRepr::Result { .. } => { + let values = flatten_value(&value); + builder.ins().return_(&values) + } + }; } - }; + ReturnLowering::SRet { out_ptr, ret_ty } => { + store_out_value( + builder, + *out_ptr, + ret_ty, + value, + struct_layouts, + module, + )?; + defer_stack.emit_all_and_clear( + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + builder.ins().return_(&[]); + } + ReturnLowering::ResultOut { + out_ok, + out_err, + ok_ty, + err_ty, + } => { + let ValueRepr::Result { tag, ok, err } = value else { + return Err(CodegenError::Unsupported( + "return expects Result value".to_string(), + )); + }; + if let Some(out_ok) = out_ok { + store_out_value( + builder, + *out_ok, + ok_ty, + *ok, + struct_layouts, + module, + )?; + } + if let Some(out_err) = out_err { + store_out_value( + builder, + *out_err, + err_ty, + *err, + struct_layouts, + module, + )?; + } + defer_stack.emit_all_and_clear( + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + builder.ins().return_(&[tag]); + } + } } else { + defer_stack.emit_all_and_clear( + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; builder.ins().return_(&[]); } return Ok(Flow::Terminated); } HirStmt::Expr(expr_stmt) => { + if matches!(expr_stmt.expr, crate::hir::HirExpr::Trap(_)) { + let _ = emit_hir_expr( + builder, + &expr_stmt.expr, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + return Ok(Flow::Terminated); + } + // Special handling for match expressions that might diverge + if let crate::hir::HirExpr::Match(match_expr) = &expr_stmt.expr { + if matches!( + match_expr.result_ty.ty, + crate::typeck::Ty::Builtin(crate::typeck::BuiltinType::Unit) + ) { + let diverged = emit_hir_match_stmt( + builder, + match_expr, + locals, + fn_map, + enum_index, + struct_layouts, + module, + data_counter, + loop_target, + return_lowering, + defer_stack, + )?; + if diverged { + return Ok(Flow::Terminated); + } + // Don't fall through to emit_hir_expr - we already emitted the match + return Ok(Flow::Continues); + } + } let _ = emit_hir_expr( builder, &expr_stmt.expr, @@ -205,6 +520,7 @@ fn emit_hir_stmt_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -212,6 +528,7 @@ fn emit_hir_stmt_inner( HirStmt::If(if_stmt) => { // Snapshot locals so branch-scoped lets don't leak let saved_locals = locals.clone(); + let saved_defers = defer_stack.clone(); let then_block = builder.create_block(); let merge_block = builder.create_block(); @@ -228,6 +545,7 @@ fn emit_hir_stmt_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -239,6 +557,8 @@ fn emit_hir_stmt_inner( // THEN branch with its own locals builder.switch_to_block(then_block); let mut then_locals = saved_locals.clone(); + let mut then_defers = saved_defers.clone(); + then_defers.push_block_scope(); let mut then_terminated = false; for stmt in &if_stmt.then_block.stmts { let flow = emit_hir_stmt( @@ -250,6 +570,9 @@ fn emit_hir_stmt_inner( struct_layouts, module, data_counter, + loop_target, + return_lowering, + &mut then_defers, )?; if flow == Flow::Terminated { then_terminated = true; @@ -257,6 +580,16 @@ fn emit_hir_stmt_inner( } } if !then_terminated { + then_defers.emit_current_and_pop( + builder, + &then_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; builder.ins().jump(merge_block, &[]); } builder.seal_block(then_block); @@ -265,6 +598,8 @@ fn emit_hir_stmt_inner( if let Some(else_block_hir) = &if_stmt.else_block { builder.switch_to_block(else_block); let mut else_locals = saved_locals.clone(); + let mut else_defers = saved_defers.clone(); + else_defers.push_block_scope(); let mut else_terminated = false; for stmt in &else_block_hir.stmts { let flow = emit_hir_stmt( @@ -276,6 +611,9 @@ fn emit_hir_stmt_inner( struct_layouts, module, data_counter, + loop_target, + return_lowering, + &mut else_defers, )?; if flow == Flow::Terminated { else_terminated = true; @@ -283,6 +621,16 @@ fn emit_hir_stmt_inner( } } if !else_terminated { + else_defers.emit_current_and_pop( + builder, + &else_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; builder.ins().jump(merge_block, &[]); } builder.seal_block(else_block); @@ -297,6 +645,7 @@ fn emit_hir_stmt_inner( HirStmt::While(while_stmt) => { // Snapshot locals so loop-body lets don't leak out of the loop let saved_locals = locals.clone(); + let saved_defers = defer_stack.clone(); let header_block = builder.create_block(); let body_block = builder.create_block(); @@ -312,6 +661,7 @@ fn emit_hir_stmt_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -322,8 +672,17 @@ fn emit_hir_stmt_inner( builder.switch_to_block(body_block); + // Create loop target for break/continue + // For while loops, continue goes to header (re-evaluate condition) + let body_loop_target = Some(LoopTarget { + continue_block: header_block, + exit_block, + }); + // Loop body gets its own locals let mut body_locals = saved_locals.clone(); + let mut body_defers = saved_defers.clone(); + body_defers.push_loop_scope(); let mut body_terminated = false; for stmt in &while_stmt.body.stmts { let flow = emit_hir_stmt( @@ -335,6 +694,9 @@ fn emit_hir_stmt_inner( struct_layouts, module, data_counter, + body_loop_target, + return_lowering, + &mut body_defers, )?; if flow == Flow::Terminated { body_terminated = true; @@ -343,6 +705,16 @@ fn emit_hir_stmt_inner( } if !body_terminated { + body_defers.emit_current_and_pop( + builder, + &body_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; builder.ins().jump(header_block, &[]); } @@ -355,6 +727,167 @@ fn emit_hir_stmt_inner( // After the loop, restore the pre-loop locals snapshot *locals = saved_locals; } + HirStmt::For(for_stmt) => { + // Snapshot locals so loop-body lets don't leak out of the loop + let saved_locals = locals.clone(); + let saved_defers = defer_stack.clone(); + + // Create stack slot for loop variable + let loop_var_slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + 4, // i32 is 4 bytes + )); + + // Evaluate start value and store in slot + let start_val = emit_hir_expr( + builder, + &for_stmt.start, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + let start_i32 = match start_val { + ValueRepr::Single(v) => v, + _ => return Err(CodegenError::Unsupported("for loop start".to_string())), + }; + builder.ins().stack_store(start_i32, loop_var_slot, 0); + + // Evaluate end value (once, before the loop) + let end_val = emit_hir_expr( + builder, + &for_stmt.end, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + let end_i32 = match end_val { + ValueRepr::Single(v) => v, + _ => return Err(CodegenError::Unsupported("for loop end".to_string())), + }; + + let header_block = builder.create_block(); + let body_block = builder.create_block(); + let increment_block = builder.create_block(); + let exit_block = builder.create_block(); + + builder.ins().jump(header_block, &[]); + builder.switch_to_block(header_block); + + // Load loop variable and compare with end + let current_val = builder.ins().stack_load(ir::types::I32, loop_var_slot, 0); + let cond = builder.ins().icmp(ir::condcodes::IntCC::SignedLessThan, current_val, end_i32); + builder.ins().brif(cond, body_block, &[], exit_block, &[]); + + builder.switch_to_block(body_block); + + // Create loop target for break/continue + // For for loops, continue goes to increment block (not header) + let body_loop_target = Some(LoopTarget { + continue_block: increment_block, + exit_block, + }); + + // Loop body gets its own locals with the loop variable bound + let mut body_locals = saved_locals.clone(); + body_locals.insert( + for_stmt.var_id, + LocalValue::Slot(loop_var_slot, ir::types::I32), + ); + let mut body_defers = saved_defers.clone(); + body_defers.push_loop_scope(); + + let mut body_terminated = false; + for stmt in &for_stmt.body.stmts { + let flow = emit_hir_stmt( + builder, + stmt, + &mut body_locals, + fn_map, + enum_index, + struct_layouts, + module, + data_counter, + body_loop_target, + return_lowering, + &mut body_defers, + )?; + if flow == Flow::Terminated { + body_terminated = true; + break; + } + } + + // Fall through to increment block (if not terminated by break/continue/return) + if !body_terminated { + body_defers.emit_current_and_pop( + builder, + &body_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + builder.ins().jump(increment_block, &[]); + } + + // Increment block: increment loop variable and jump back to header + builder.switch_to_block(increment_block); + let current = builder.ins().stack_load(ir::types::I32, loop_var_slot, 0); + let one = builder.ins().iconst(ir::types::I32, 1); + let next = builder.ins().iadd(current, one); + builder.ins().stack_store(next, loop_var_slot, 0); + builder.ins().jump(header_block, &[]); + + builder.seal_block(body_block); + builder.seal_block(increment_block); + builder.seal_block(header_block); + + builder.switch_to_block(exit_block); + builder.seal_block(exit_block); + + // After the loop, restore the pre-loop locals snapshot + *locals = saved_locals; + } + HirStmt::Break(_) => { + let target = loop_target.expect("break outside of loop (should be caught by typeck)"); + defer_stack.emit_until_loop_and_pop( + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + builder.ins().jump(target.exit_block, &[]); + return Ok(Flow::Terminated); + } + HirStmt::Continue(_) => { + let target = loop_target.expect("continue outside of loop (should be caught by typeck)"); + defer_stack.emit_until_loop_and_pop( + builder, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + builder.ins().jump(target.continue_block, &[]); + return Ok(Flow::Terminated); + } } Ok(Flow::Continues) } @@ -367,6 +900,7 @@ fn emit_hir_expr( fn_map: &HashMap, enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { @@ -377,6 +911,7 @@ fn emit_hir_expr( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, ) @@ -390,6 +925,7 @@ fn emit_hir_expr_inner( fn_map: &HashMap, enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { @@ -406,7 +942,9 @@ fn emit_hir_expr_inner( Literal::Bool(value) => Ok(ValueRepr::Single( builder.ins().iconst(ir::types::I8, *value as i64), )), - Literal::String(value) => emit_string(builder, value, module, data_counter), + Literal::String(value) => { + emit_string(builder, value, module, data_counter, struct_layouts) + } Literal::Unit => Ok(ValueRepr::Unit), }, HirExpr::Local(local) => { @@ -421,7 +959,7 @@ fn emit_hir_expr_inner( HirExpr::EnumVariant(variant) => { // Check if this is a Result type with payload (Ok/Err) if let crate::typeck::Ty::Path(ty_name, args) = &variant.enum_ty.ty { - if ty_name == "Result" && args.len() == 2 { + if ty_name == "sys.result.Result" && args.len() == 2 { let AbiType::Result(ok_abi, err_abi) = &variant.enum_ty.abi else { return Err(CodegenError::Unsupported( abi_quirks::result_abi_mismatch_error().to_string(), @@ -449,19 +987,36 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )? } else { - zero_value_for_ty(builder, &ok_ty, ptr_ty, Some(struct_layouts))? + zero_value_for_ty( + builder, + &ok_ty, + ptr_ty, + Some(struct_layouts), + module, + )? }; - let err_zero = - zero_value_for_ty(builder, &err_ty, ptr_ty, Some(struct_layouts))?; + let err_zero = zero_value_for_ty( + builder, + &err_ty, + ptr_ty, + Some(struct_layouts), + module, + )?; (payload, err_zero) } "Err" => { - let ok_zero = - zero_value_for_ty(builder, &ok_ty, ptr_ty, Some(struct_layouts))?; + let ok_zero = zero_value_for_ty( + builder, + &ok_ty, + ptr_ty, + Some(struct_layouts), + module, + )?; let payload = if let Some(payload_expr) = &variant.payload { emit_hir_expr( builder, @@ -470,11 +1025,18 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )? } else { - zero_value_for_ty(builder, &err_ty, ptr_ty, Some(struct_layouts))? + zero_value_for_ty( + builder, + &err_ty, + ptr_ty, + Some(struct_layouts), + module, + )? }; (ok_zero, payload) } @@ -522,6 +1084,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -539,7 +1102,7 @@ fn emit_hir_expr_inner( builder.switch_to_block(err_block); let ret_value = match &try_expr.ret_ty.ty { - crate::typeck::Ty::Path(name, args) if name == "Result" && args.len() == 2 => { + crate::typeck::Ty::Path(name, args) if name == "sys.result.Result" && args.len() == 2 => { let AbiType::Result(ok_abi, _err_abi) = &try_expr.ret_ty.abi else { return Err(CodegenError::Unsupported( abi_quirks::result_abi_mismatch_error().to_string(), @@ -550,8 +1113,13 @@ fn emit_hir_expr_inner( abi: (**ok_abi).clone(), }; let ptr_ty = module.isa().pointer_type(); - let ok_zero = - zero_value_for_ty(builder, &ok_ty, ptr_ty, Some(struct_layouts))?; + let ok_zero = zero_value_for_ty( + builder, + &ok_ty, + ptr_ty, + Some(struct_layouts), + module, + )?; let tag_val = builder.ins().iconst(ir::types::I8, 1); ValueRepr::Result { tag: tag_val, @@ -565,8 +1133,56 @@ fn emit_hir_expr_inner( )) } }; - let ret_values = flatten_value(&ret_value); - builder.ins().return_(&ret_values); + match return_lowering { + ReturnLowering::Direct => { + let ret_values = flatten_value(&ret_value); + builder.ins().return_(&ret_values); + } + ReturnLowering::SRet { out_ptr, ret_ty } => { + store_out_value( + builder, + *out_ptr, + ret_ty, + ret_value, + struct_layouts, + module, + )?; + builder.ins().return_(&[]); + } + ReturnLowering::ResultOut { + out_ok, + out_err, + ok_ty, + err_ty, + } => { + let ValueRepr::Result { tag, ok, err } = ret_value else { + return Err(CodegenError::Unsupported( + "try expects a Result value".to_string(), + )); + }; + if let Some(out_ok) = out_ok { + store_out_value( + builder, + *out_ok, + ok_ty, + *ok, + struct_layouts, + module, + )?; + } + if let Some(out_err) = out_err { + store_out_value( + builder, + *out_err, + err_ty, + *err, + struct_layouts, + module, + )?; + } + builder.ins().return_(&[tag]); + } + } builder.seal_block(err_block); builder.switch_to_block(ok_block); @@ -574,6 +1190,13 @@ fn emit_hir_expr_inner( Ok(*ok) } + HirExpr::Trap(_trap) => { + builder.ins().trap(ir::TrapCode::UnreachableCodeReached); + // Return a dummy value - the block is now filled so no more + // instructions can be added. The match handler will detect this + // and skip value storage. + Ok(ValueRepr::Unit) + } HirExpr::Call(call) => { // HIR calls are already fully resolved - no path resolution needed! let (module_path, func_name, _symbol) = match &call.callee { @@ -589,6 +1212,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -604,9 +1228,30 @@ fn emit_hir_expr_inner( .ok_or_else(|| CodegenError::UnknownFunction(key.clone()))? .clone(); ensure_abi_sig_handled(&info)?; + let abi_sig = info.abi_sig.as_ref().unwrap_or(&info.sig); // Emit arguments let mut args = Vec::new(); + let mut sret_ptr = None; + if info.sig.ret == AbiType::Ptr + && abi_sig.ret == AbiType::Unit + && is_non_opaque_struct_type(&call.ret_ty, struct_layouts) + { + let layout = + resolve_struct_layout(&call.ret_ty.ty, "", &struct_layouts.layouts).ok_or_else( + || CodegenError::Unsupported("struct layout missing".to_string()), + )?; + let ptr_ty = module.isa().pointer_type(); + let align = layout.align.max(1); + let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let base_ptr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(base_ptr); + sret_ptr = Some(base_ptr); + } for arg in &call.args { let value = emit_hir_expr( builder, @@ -615,6 +1260,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -622,15 +1268,31 @@ fn emit_hir_expr_inner( } // Handle result out-parameters (same logic as AST version) - let mut out_slots: Option = None; let mut result_out = None; - let abi_sig = info.abi_sig.as_ref().unwrap_or(&info.sig); + let result_payloads = match &call.ret_ty.ty { + crate::typeck::Ty::Path(name, args) + if name == "sys.result.Result" && args.len() == 2 => + { + match &call.ret_ty.abi { + AbiType::Result(ok_abi, err_abi) => Some(( + crate::hir::HirType { + ty: args[0].clone(), + abi: (**ok_abi).clone(), + }, + crate::hir::HirType { + ty: args[1].clone(), + abi: (**err_abi).clone(), + }, + )), + _ => None, + } + } + _ => None, + }; - if abi_quirks::is_result_string(&abi_sig.ret) { - let ptr_ty = module.isa().pointer_type(); - let slots = result_string_slots(builder, ptr_ty); - push_result_string_out_params(builder, ptr_ty, &slots, &mut args); - out_slots = Some(slots); + enum ResultOutSlot { + Scalar(ir::StackSlot, ir::Type, u32), + Struct(ir::Value), } if let AbiType::ResultOut(ok_ty, err_ty) = &abi_sig.ret { @@ -638,36 +1300,106 @@ fn emit_hir_expr_inner( let ok_slot = if **ok_ty == AbiType::Unit { None } else { - let ty = value_type_for_result_out(ok_ty, ptr_ty)?; - let align = ty.bytes().max(1) as u32; - debug_assert!(align.is_power_of_two()); - let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( - ir::StackSlotKind::ExplicitSlot, - aligned_slot_size(ty.bytes().max(1) as u32, align), - )); - let addr = aligned_stack_addr(builder, slot, align, ptr_ty); - args.push(addr); - Some((slot, ty, align)) + if let Some((ok_hir, _)) = &result_payloads { + if is_non_opaque_struct_type(ok_hir, struct_layouts) { + let layout = resolve_struct_layout( + &ok_hir.ty, + "", + &struct_layouts.layouts, + ) + .ok_or_else(|| { + CodegenError::Unsupported("struct layout missing".to_string()) + })?; + let align = layout.align.max(1); + let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(addr); + Some(ResultOutSlot::Struct(addr)) + } else { + let ty = value_type_for_result_out(ok_ty, ptr_ty)?; + let align = ty.bytes().max(1) as u32; + debug_assert!(align.is_power_of_two()); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(ty.bytes().max(1) as u32, align), + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(addr); + Some(ResultOutSlot::Scalar(slot, ty, align)) + } + } else { + let ty = value_type_for_result_out(ok_ty, ptr_ty)?; + let align = ty.bytes().max(1) as u32; + debug_assert!(align.is_power_of_two()); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(ty.bytes().max(1) as u32, align), + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(addr); + Some(ResultOutSlot::Scalar(slot, ty, align)) + } }; let err_slot = if **err_ty == AbiType::Unit { None } else { - let ty = value_type_for_result_out(err_ty, ptr_ty)?; - let align = ty.bytes().max(1) as u32; - debug_assert!(align.is_power_of_two()); - let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( - ir::StackSlotKind::ExplicitSlot, - aligned_slot_size(ty.bytes().max(1) as u32, align), - )); - let addr = aligned_stack_addr(builder, slot, align, ptr_ty); - args.push(addr); - Some((slot, ty, align)) + if let Some((_, err_hir)) = &result_payloads { + if is_non_opaque_struct_type(err_hir, struct_layouts) { + let layout = resolve_struct_layout( + &err_hir.ty, + "", + &struct_layouts.layouts, + ) + .ok_or_else(|| { + CodegenError::Unsupported("struct layout missing".to_string()) + })?; + let align = layout.align.max(1); + let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(addr); + Some(ResultOutSlot::Struct(addr)) + } else { + let ty = value_type_for_result_out(err_ty, ptr_ty)?; + let align = ty.bytes().max(1) as u32; + debug_assert!(align.is_power_of_two()); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(ty.bytes().max(1) as u32, align), + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(addr); + Some(ResultOutSlot::Scalar(slot, ty, align)) + } + } else { + let ty = value_type_for_result_out(err_ty, ptr_ty)?; + let align = ty.bytes().max(1) as u32; + debug_assert!(align.is_power_of_two()); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(ty.bytes().max(1) as u32, align), + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + args.push(addr); + Some(ResultOutSlot::Scalar(slot, ty, align)) + } }; result_out = Some((ok_slot, err_slot, ok_ty.clone(), err_ty.clone())); } // Emit the call - let sig = sig_to_clif(abi_sig, module.isa().pointer_type()); + let sig = sig_to_clif( + abi_sig, + module.isa().pointer_type(), + module.isa().default_call_conv(), + ); let call_symbol = info.runtime_symbol.as_deref().unwrap_or(&info.symbol); let func_id = module .declare_function( @@ -685,58 +1417,43 @@ fn emit_hir_expr_inner( let results = builder.inst_results(call_inst).to_vec(); // Handle result unpacking (same logic as AST version) - if abi_quirks::is_result_string(&abi_sig.ret) { - let tag = results - .get(0) - .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; - let slots = - out_slots.ok_or_else(|| CodegenError::Codegen("missing slots".to_string()))?; - let ptr_ty = module.isa().pointer_type(); - let (ptr, len, err) = read_result_string_slots(builder, ptr_ty, &slots); - match &info.sig.ret { - AbiType::Result(ok_ty, err_ty) => { - if **ok_ty != AbiType::String || **err_ty != AbiType::I32 { - return Err(CodegenError::Unsupported( - abi_quirks::result_out_params_error().to_string(), - )); - } - Ok(ValueRepr::Result { - tag: *tag, - ok: Box::new(ValueRepr::Pair(ptr, len)), - err: Box::new(ValueRepr::Single(err)), - }) - } - _ => Err(CodegenError::Unsupported( - abi_quirks::result_out_params_error().to_string(), - )), - } - } else if abi_quirks::is_result_out(&abi_sig.ret) { + if abi_quirks::is_result_out(&abi_sig.ret) { let tag = results .get(0) .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; let (ok_slot, err_slot, ok_ty, err_ty) = result_out .ok_or_else(|| CodegenError::Codegen("missing result slots".to_string()))?; - let ok_val = if let Some((slot, ty, align)) = ok_slot { - let addr = aligned_stack_addr( - builder, - slot, - align, - module.isa().pointer_type(), - ); - let val = builder.ins().load(ty, MemFlags::new(), addr, 0); - ValueRepr::Single(val) + let ok_val = if let Some(slot) = ok_slot { + match slot { + ResultOutSlot::Scalar(slot, ty, align) => { + let addr = aligned_stack_addr( + builder, + slot, + align, + module.isa().pointer_type(), + ); + let val = builder.ins().load(ty, MemFlags::new(), addr, 0); + ValueRepr::Single(val) + } + ResultOutSlot::Struct(ptr) => ValueRepr::Single(ptr), + } } else { ValueRepr::Unit }; - let err_val = if let Some((slot, ty, align)) = err_slot { - let addr = aligned_stack_addr( - builder, - slot, - align, - module.isa().pointer_type(), - ); - let val = builder.ins().load(ty, MemFlags::new(), addr, 0); - ValueRepr::Single(val) + let err_val = if let Some(slot) = err_slot { + match slot { + ResultOutSlot::Scalar(slot, ty, align) => { + let addr = aligned_stack_addr( + builder, + slot, + align, + module.isa().pointer_type(), + ); + let val = builder.ins().load(ty, MemFlags::new(), addr, 0); + ValueRepr::Single(val) + } + ResultOutSlot::Struct(ptr) => ValueRepr::Single(ptr), + } } else { ValueRepr::Unit }; @@ -750,6 +1467,8 @@ fn emit_hir_expr_inner( "result out params for {ok_ty:?}/{err_ty:?}" ))), } + } else if let Some(ptr) = sret_ptr { + Ok(ValueRepr::Single(ptr)) } else { let mut index = 0; value_from_results(builder, &info.sig.ret, &results, &mut index) @@ -763,6 +1482,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -773,9 +1493,9 @@ fn emit_hir_expr_inner( ValueRepr::Unit => { return Err(CodegenError::Unsupported("boolean op on unit".to_string())) } - ValueRepr::Pair(_, _) | ValueRepr::Result { .. } => { + ValueRepr::Result { .. } => { return Err(CodegenError::Unsupported( - "boolean op on string".to_string(), + "boolean op on result".to_string(), )) } }; @@ -788,6 +1508,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, ); @@ -800,6 +1521,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -817,6 +1539,21 @@ fn emit_hir_expr_inner( (BinaryOp::Div, ValueRepr::Single(a), ValueRepr::Single(b)) => { Ok(ValueRepr::Single(emit_checked_div(builder, a, b, &binary.ty)?)) } + (BinaryOp::Eq, ValueRepr::Single(a), ValueRepr::Single(b)) + if is_string_type(&binary.left.ty().ty) => + { + let result = emit_string_eq(builder, module, a, b)?; + Ok(ValueRepr::Single(result)) + } + (BinaryOp::Neq, ValueRepr::Single(a), ValueRepr::Single(b)) + if is_string_type(&binary.left.ty().ty) => + { + let eq_result = emit_string_eq(builder, module, a, b)?; + // Invert the result: 1 becomes 0, 0 becomes 1 + let one = builder.ins().iconst(ir::types::I8, 1); + let neq_result = builder.ins().bxor(eq_result, one); + Ok(ValueRepr::Single(neq_result)) + } (BinaryOp::Eq, ValueRepr::Single(a), ValueRepr::Single(b)) => { let cmp = builder.ins().icmp(IntCC::Equal, a, b); Ok(ValueRepr::Single(bool_to_i8(builder, cmp))) @@ -868,6 +1605,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -888,7 +1626,10 @@ fn emit_hir_expr_inner( match_expr.result_ty.ty, crate::typeck::Ty::Builtin(crate::typeck::BuiltinType::Unit) ) { - emit_hir_match_stmt( + // Note: divergence handling is done at HirStmt::Expr level. + // Here we just emit the match and return Unit. + let mut temp_defers = DeferStack::new(); + let _diverged = emit_hir_match_stmt( builder, match_expr, locals, @@ -897,7 +1638,11 @@ fn emit_hir_expr_inner( struct_layouts, module, data_counter, - ) + None, // break/continue not supported in expression-context matches + return_lowering, + &mut temp_defers, + )?; + Ok(ValueRepr::Unit) } else { emit_hir_match_expr( builder, @@ -906,6 +1651,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, ) @@ -918,6 +1664,18 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, + module, + data_counter, + ), + HirExpr::Index(index_expr) => emit_hir_index( + builder, + index_expr, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, module, data_counter, ), @@ -929,6 +1687,7 @@ fn emit_hir_expr_inner( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, ) @@ -966,55 +1725,286 @@ fn emit_checked_sub( Ok(diff) } -fn emit_checked_mul( +fn emit_checked_mul( + builder: &mut FunctionBuilder, + a: Value, + b: Value, + ty: &crate::hir::HirType, +) -> Result { + let (prod, overflow) = if crate::typeck::is_unsigned_type(&ty.ty) { + builder.ins().umul_overflow(a, b) + } else { + builder.ins().smul_overflow(a, b) + }; + trap_on_overflow(builder, overflow); + Ok(prod) +} + +fn emit_checked_div( + builder: &mut FunctionBuilder, + a: Value, + b: Value, + ty: &crate::hir::HirType, +) -> Result { + let b_ty = builder.func.dfg.value_type(b); + let zero = builder.ins().iconst(b_ty, 0); + let is_zero = builder.ins().icmp(IntCC::Equal, b, zero); + let ok_block = builder.create_block(); + let trap_block = builder.create_block(); + builder.ins().brif(is_zero, trap_block, &[], ok_block, &[]); + builder.switch_to_block(trap_block); + builder.ins().trap(ir::TrapCode::IntegerDivisionByZero); + builder.switch_to_block(ok_block); + builder.seal_block(trap_block); + builder.seal_block(ok_block); + let value = if crate::typeck::is_unsigned_type(&ty.ty) { + builder.ins().udiv(a, b) + } else { + builder.ins().sdiv(a, b) + }; + Ok(value) +} + +fn trap_on_overflow(builder: &mut FunctionBuilder, overflow: Value) { + let ok_block = builder.create_block(); + let trap_block = builder.create_block(); + builder.ins().brif(overflow, trap_block, &[], ok_block, &[]); + builder.switch_to_block(trap_block); + builder.ins().trap(ir::TrapCode::IntegerOverflow); + builder.switch_to_block(ok_block); + builder.seal_block(trap_block); + builder.seal_block(ok_block); +} + +/// Emit a call to the runtime string equality function. +/// Returns an i8 value: 1 if strings are equal, 0 otherwise. +fn emit_string_eq( + builder: &mut FunctionBuilder, + module: &mut ObjectModule, + lhs: Value, + rhs: Value, +) -> Result { + use cranelift_codegen::ir::{AbiParam, Signature}; + + let ptr_ty = module.isa().pointer_type(); + + // Build signature: (ptr, ptr) -> i8 + let mut sig = Signature::new(module.isa().default_call_conv()); + sig.params.push(AbiParam::new(ptr_ty)); + sig.params.push(AbiParam::new(ptr_ty)); + sig.returns.push(AbiParam::new(ir::types::I8)); + + // Declare and import the runtime function + let func_id = module + .declare_function("capable_rt_string_eq", Linkage::Import, &sig) + .map_err(|err| CodegenError::Codegen(err.to_string()))?; + let local_func = module.declare_func_in_func(func_id, builder.func); + + // Call the function + let call_inst = builder.ins().call(local_func, &[lhs, rhs]); + let results = builder.inst_results(call_inst); + Ok(results[0]) +} + +fn is_string_type(ty: &crate::typeck::Ty) -> bool { + matches!(ty, crate::typeck::Ty::Path(name, _) if name == "sys.string.string" || name == "string") +} + +/// Emit an index expression, calling the appropriate runtime function. +fn emit_hir_index( + builder: &mut FunctionBuilder, + index_expr: &crate::hir::HirIndex, + locals: &HashMap, + fn_map: &HashMap, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, + module: &mut ObjectModule, + data_counter: &mut u32, +) -> Result { + // Emit the object expression + let object = emit_hir_expr( + builder, + &index_expr.object, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + + // Emit the index expression + let index = emit_hir_expr( + builder, + &index_expr.index, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + + let index_val = match index { + ValueRepr::Single(v) => v, + _ => { + return Err(CodegenError::Codegen( + "index must be a single value".to_string(), + )) + } + }; + + // Get the object type to determine what runtime function to call + let object_ty = &index_expr.object.ty().ty; + + match object_ty { + ty if is_string_type(ty) => { + // For strings, index into the backing Slice. + let base_ptr = match object { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Codegen( + "expected string to be a struct pointer".to_string(), + )) + } + }; + let layout = resolve_struct_layout(object_ty, "", &struct_layouts.layouts).ok_or_else( + || CodegenError::Unsupported("string layout missing".to_string()), + )?; + let field = layout.fields.get("bytes").ok_or_else(|| { + CodegenError::Unsupported("string.bytes field missing".to_string()) + })?; + let addr = ptr_add(builder, base_ptr, field.offset); + let handle = builder + .ins() + .load(ir::types::I64, MemFlags::new(), addr, 0); + let result = emit_slice_at(builder, module, handle, index_val)?; + Ok(ValueRepr::Single(result)) + } + crate::typeck::Ty::Path(name, _) if name == "Slice" || name == "sys.buffer.Slice" => { + // For Slice[u8], call capable_rt_slice_at(handle, index) -> u8 + let handle = match object { + ValueRepr::Single(h) => h, + _ => { + return Err(CodegenError::Codegen( + "expected Slice to be a handle".to_string(), + )) + } + }; + + let result = emit_slice_at(builder, module, handle, index_val)?; + Ok(ValueRepr::Single(result)) + } + crate::typeck::Ty::Path(name, _) if name == "MutSlice" || name == "sys.buffer.MutSlice" => { + // For MutSlice[u8], call capable_rt_mut_slice_at(handle, index) -> u8 + let handle = match object { + ValueRepr::Single(h) => h, + _ => { + return Err(CodegenError::Codegen( + "expected MutSlice to be a handle".to_string(), + )) + } + }; + + let result = emit_mut_slice_at(builder, module, handle, index_val)?; + Ok(ValueRepr::Single(result)) + } + _ => Err(CodegenError::Codegen(format!( + "cannot index into type {:?}", + object_ty + ))), + } +} + +/// Emit a call to the runtime slice at function. +/// Returns a u8 value at the given index. +fn emit_slice_at( builder: &mut FunctionBuilder, - a: Value, - b: Value, - ty: &crate::hir::HirType, + module: &mut ObjectModule, + handle: Value, + index: Value, ) -> Result { - let (prod, overflow) = if crate::typeck::is_unsigned_type(&ty.ty) { - builder.ins().umul_overflow(a, b) - } else { - builder.ins().smul_overflow(a, b) - }; - trap_on_overflow(builder, overflow); - Ok(prod) + use cranelift_codegen::ir::{AbiParam, Signature}; + + let ptr_ty = module.isa().pointer_type(); + + // Build signature: (handle, i32) -> u8 + let mut sig = Signature::new(module.isa().default_call_conv()); + sig.params.push(AbiParam::new(ptr_ty)); // Handle is a usize + sig.params.push(AbiParam::new(ir::types::I32)); + sig.returns.push(AbiParam::new(ir::types::I8)); + + // Declare and import the runtime function + let func_id = module + .declare_function("capable_rt_slice_at", Linkage::Import, &sig) + .map_err(|err| CodegenError::Codegen(err.to_string()))?; + let local_func = module.declare_func_in_func(func_id, builder.func); + + // Call the function + let call_inst = builder.ins().call(local_func, &[handle, index]); + let results = builder.inst_results(call_inst); + Ok(results[0]) } -fn emit_checked_div( +/// Emit a call to the runtime slice_from_ptr helper. +fn emit_slice_from_ptr( builder: &mut FunctionBuilder, - a: Value, - b: Value, - ty: &crate::hir::HirType, + module: &mut ObjectModule, + ptr: Value, + len: Value, ) -> Result { - let b_ty = builder.func.dfg.value_type(b); - let zero = builder.ins().iconst(b_ty, 0); - let is_zero = builder.ins().icmp(IntCC::Equal, b, zero); - let ok_block = builder.create_block(); - let trap_block = builder.create_block(); - builder.ins().brif(is_zero, trap_block, &[], ok_block, &[]); - builder.switch_to_block(trap_block); - builder.ins().trap(ir::TrapCode::IntegerDivisionByZero); - builder.switch_to_block(ok_block); - builder.seal_block(trap_block); - builder.seal_block(ok_block); - let value = if crate::typeck::is_unsigned_type(&ty.ty) { - builder.ins().udiv(a, b) - } else { - builder.ins().sdiv(a, b) - }; - Ok(value) + use cranelift_codegen::ir::{AbiParam, Signature}; + + let ptr_ty = module.isa().pointer_type(); + + // Build signature: (handle, ptr, i32) -> handle + let mut sig = Signature::new(module.isa().default_call_conv()); + sig.params.push(AbiParam::new(ir::types::I64)); + sig.params.push(AbiParam::new(ptr_ty)); + sig.params.push(AbiParam::new(ir::types::I32)); + sig.returns.push(AbiParam::new(ir::types::I64)); + + let func_id = module + .declare_function("capable_rt_slice_from_ptr", Linkage::Import, &sig) + .map_err(|err| CodegenError::Codegen(err.to_string()))?; + let local_func = module.declare_func_in_func(func_id, builder.func); + let default_alloc = builder.ins().iconst(ir::types::I64, 0); + let call_inst = builder.ins().call(local_func, &[default_alloc, ptr, len]); + let results = builder.inst_results(call_inst); + Ok(results[0]) } -fn trap_on_overflow(builder: &mut FunctionBuilder, overflow: Value) { - let ok_block = builder.create_block(); - let trap_block = builder.create_block(); - builder.ins().brif(overflow, trap_block, &[], ok_block, &[]); - builder.switch_to_block(trap_block); - builder.ins().trap(ir::TrapCode::IntegerOverflow); - builder.switch_to_block(ok_block); - builder.seal_block(trap_block); - builder.seal_block(ok_block); +/// Emit a call to the runtime mutable slice at function. +/// Returns a u8 value at the given index. +fn emit_mut_slice_at( + builder: &mut FunctionBuilder, + module: &mut ObjectModule, + handle: Value, + index: Value, +) -> Result { + use cranelift_codegen::ir::{AbiParam, Signature}; + + let ptr_ty = module.isa().pointer_type(); + + // Build signature: (handle, i32) -> u8 + let mut sig = Signature::new(module.isa().default_call_conv()); + sig.params.push(AbiParam::new(ptr_ty)); // Handle is a usize + sig.params.push(AbiParam::new(ir::types::I32)); + sig.returns.push(AbiParam::new(ir::types::I8)); + + // Declare and import the runtime function + let func_id = module + .declare_function("capable_rt_mut_slice_at", Linkage::Import, &sig) + .map_err(|err| CodegenError::Codegen(err.to_string()))?; + let local_func = module.declare_func_in_func(func_id, builder.func); + + // Call the function + let call_inst = builder.ins().call(local_func, &[handle, index]); + let results = builder.inst_results(call_inst); + Ok(results[0]) } fn cmp_cc(expr: &crate::hir::HirExpr, signed: IntCC, unsigned: IntCC) -> IntCC { @@ -1024,11 +2014,13 @@ fn cmp_cc(expr: &crate::hir::HirExpr, signed: IntCC, unsigned: IntCC) -> IntCC { crate::hir::HirExpr::EnumVariant(variant) => &variant.enum_ty, crate::hir::HirExpr::Call(call) => &call.ret_ty, crate::hir::HirExpr::FieldAccess(field) => &field.field_ty, + crate::hir::HirExpr::Index(idx) => &idx.elem_ty, crate::hir::HirExpr::StructLiteral(lit) => &lit.struct_ty, crate::hir::HirExpr::Unary(unary) => &unary.ty, crate::hir::HirExpr::Binary(binary) => &binary.ty, crate::hir::HirExpr::Match(m) => &m.result_ty, crate::hir::HirExpr::Try(t) => &t.ok_ty, + crate::hir::HirExpr::Trap(t) => &t.ty, }; if crate::typeck::is_unsigned_type(&ty.ty) { unsigned @@ -1045,6 +2037,7 @@ fn emit_hir_struct_literal( fn_map: &HashMap, enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { @@ -1078,6 +2071,7 @@ fn emit_hir_struct_literal( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -1103,6 +2097,7 @@ fn emit_hir_field_access( fn_map: &HashMap, enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { @@ -1128,6 +2123,7 @@ fn emit_hir_field_access( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -1150,6 +2146,51 @@ fn emit_hir_field_access( ) } +fn is_non_opaque_struct_type( + ty: &crate::hir::HirType, + struct_layouts: &StructLayoutIndex, +) -> bool { + resolve_struct_layout(&ty.ty, "", &struct_layouts.layouts).is_some() +} + +fn store_out_value( + builder: &mut FunctionBuilder, + out_ptr: ir::Value, + ty: &crate::hir::HirType, + value: ValueRepr, + struct_layouts: &StructLayoutIndex, + module: &mut ObjectModule, +) -> Result<(), CodegenError> { + if is_non_opaque_struct_type(ty, struct_layouts) { + return store_value_by_ty( + builder, + out_ptr, + 0, + ty, + value, + struct_layouts, + module, + ); + } + match ty.abi { + AbiType::I32 + | AbiType::U32 + | AbiType::U8 + | AbiType::Bool + | AbiType::Handle + | AbiType::Ptr => store_value_by_tykind( + builder, + out_ptr, + &ty.abi, + value, + module.isa().pointer_type(), + ), + _ => Err(CodegenError::Unsupported( + "return out param type".to_string(), + )), + } +} + /// Store a lowered value into memory using a typeck::Ty layout. fn store_value_by_ty( builder: &mut FunctionBuilder, @@ -1166,7 +2207,7 @@ fn store_value_by_ty( let ptr_ty = module.isa().pointer_type(); match &ty.ty { Ty::Builtin(b) => match b { - BuiltinType::Unit => Ok(()), + BuiltinType::Unit | BuiltinType::Never => Ok(()), BuiltinType::I32 | BuiltinType::U32 => { let ValueRepr::Single(val) = value else { return Err(CodegenError::Unsupported("store i32".to_string())); @@ -1181,17 +2222,6 @@ fn store_value_by_ty( builder.ins().store(MemFlags::new(), val, addr, 0); Ok(()) } - BuiltinType::String => { - let ValueRepr::Pair(ptr, len) = value else { - return Err(CodegenError::Unsupported("store string".to_string())); - }; - let (ptr_off, len_off) = string_offsets(ptr_ty); - let ptr_addr = ptr_add(builder, base_ptr, offset + ptr_off); - let len_addr = ptr_add(builder, base_ptr, offset + len_off); - builder.ins().store(MemFlags::new(), ptr, ptr_addr, 0); - builder.ins().store(MemFlags::new(), len, len_addr, 0); - Ok(()) - } BuiltinType::I64 => Err(CodegenError::Unsupported("i64 not yet supported".to_string())), }, Ty::Ptr(_) => { @@ -1220,7 +2250,7 @@ fn store_value_by_ty( "generic type parameters must be monomorphized before codegen".to_string(), )), Ty::Path(name, args) => { - if name == "Result" && args.len() == 2 { + if name == "sys.result.Result" && args.len() == 2 { let ValueRepr::Result { tag, ok, err } = value else { return Err(CodegenError::Unsupported("store result".to_string())); }; @@ -1333,25 +2363,13 @@ fn load_value_by_ty( let ptr_ty = module.isa().pointer_type(); match &ty.ty { Ty::Builtin(b) => match b { - BuiltinType::Unit => Ok(ValueRepr::Unit), + BuiltinType::Unit | BuiltinType::Never => Ok(ValueRepr::Unit), BuiltinType::I32 | BuiltinType::U32 => Ok(ValueRepr::Single( builder.ins().load(ir::types::I32, MemFlags::new(), addr, 0), )), BuiltinType::U8 | BuiltinType::Bool => Ok(ValueRepr::Single( builder.ins().load(ir::types::I8, MemFlags::new(), addr, 0), )), - BuiltinType::String => { - let (ptr_off, len_off) = string_offsets(ptr_ty); - let ptr_addr = ptr_add(builder, base_ptr, offset + ptr_off); - let len_addr = ptr_add(builder, base_ptr, offset + len_off); - let ptr = builder - .ins() - .load(ptr_ty, MemFlags::new(), ptr_addr, 0); - let len = builder - .ins() - .load(ir::types::I64, MemFlags::new(), len_addr, 0); - Ok(ValueRepr::Pair(ptr, len)) - } BuiltinType::I64 => Err(CodegenError::Unsupported("i64 not yet supported".to_string())), }, Ty::Ptr(_) => Ok(ValueRepr::Single( @@ -1375,7 +2393,7 @@ fn load_value_by_ty( "generic type parameters must be monomorphized before codegen".to_string(), )), Ty::Path(name, args) => { - if name == "Result" && args.len() == 2 { + if name == "sys.result.Result" && args.len() == 2 { let AbiType::Result(ok_abi, err_abi) = &ty.abi else { return Err(CodegenError::Unsupported( abi_quirks::result_abi_mismatch_error().to_string(), @@ -1480,72 +2498,6 @@ fn aligned_slot_size(size: u32, align: u32) -> u32 { size.max(1).saturating_add(align.saturating_sub(1)) } -fn result_string_slots(builder: &mut FunctionBuilder, ptr_ty: Type) -> ResultStringSlots { - let ptr_align = ptr_ty.bytes() as u32; - let len_bytes = abi_quirks::result_string_len_bytes(); - let len_align = len_bytes; - let err_align = 4u32; - let slot_ptr = builder.create_sized_stack_slot(ir::StackSlotData::new( - ir::StackSlotKind::ExplicitSlot, - aligned_slot_size(ptr_ty.bytes() as u32, ptr_align), - )); - let slot_len = builder.create_sized_stack_slot(ir::StackSlotData::new( - ir::StackSlotKind::ExplicitSlot, - aligned_slot_size(len_bytes, len_align), - )); - let slot_err = builder.create_sized_stack_slot(ir::StackSlotData::new( - ir::StackSlotKind::ExplicitSlot, - aligned_slot_size(4, err_align), - )); - ResultStringSlots { - slot_ptr, - slot_len, - slot_err, - ptr_align, - len_align, - err_align, - } -} - -fn push_result_string_out_params( - builder: &mut FunctionBuilder, - ptr_ty: Type, - slots: &ResultStringSlots, - args: &mut Vec, -) { - let ptr_ptr = aligned_stack_addr(builder, slots.slot_ptr, slots.ptr_align, ptr_ty); - let len_ptr = aligned_stack_addr(builder, slots.slot_len, slots.len_align, ptr_ty); - let err_ptr = aligned_stack_addr(builder, slots.slot_err, slots.err_align, ptr_ty); - args.push(ptr_ptr); - args.push(len_ptr); - args.push(err_ptr); -} - -fn read_result_string_slots( - builder: &mut FunctionBuilder, - ptr_ty: Type, - slots: &ResultStringSlots, -) -> (Value, Value, Value) { - let ptr_addr = aligned_stack_addr(builder, slots.slot_ptr, slots.ptr_align, ptr_ty); - let len_addr = aligned_stack_addr(builder, slots.slot_len, slots.len_align, ptr_ty); - let err_addr = aligned_stack_addr(builder, slots.slot_err, slots.err_align, ptr_ty); - let ptr = builder.ins().load(ptr_ty, MemFlags::new(), ptr_addr, 0); - let len = builder - .ins() - .load(ir::types::I64, MemFlags::new(), len_addr, 0); - let err = builder - .ins() - .load(ir::types::I32, MemFlags::new(), err_addr, 0); - (ptr, len, err) -} - -/// Compute pointer/len offsets for the string layout. -fn string_offsets(ptr_ty: Type) -> (u32, u32) { - let ptr_size = ptr_ty.bytes() as u32; - let len_offset = align_to(ptr_size, 8); - (0, len_offset) -} - /// Compute offsets for Result layout (tag, ok, err). fn result_offsets(ok: TypeLayout, err: TypeLayout) -> (u32, u32, u32) { let tag_offset = 0u32; @@ -1579,6 +2531,7 @@ fn emit_hir_short_circuit_expr( fn_map: &HashMap, enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { @@ -1606,6 +2559,7 @@ fn emit_hir_short_circuit_expr( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -1621,8 +2575,8 @@ fn emit_hir_short_circuit_expr( Ok(ValueRepr::Single(param)) } -/// Emit HIR match as statement (arms can contain returns, don't produce values) /// Emit HIR match as statement (arms can contain returns, don't produce values). +/// Returns true if all paths diverged (returned/broke/continued). fn emit_hir_match_stmt( builder: &mut FunctionBuilder, match_expr: &crate::hir::HirMatch, @@ -1632,7 +2586,10 @@ fn emit_hir_match_stmt( struct_layouts: &StructLayoutIndex, module: &mut ObjectModule, data_counter: &mut u32, -) -> Result { + loop_target: Option, + return_lowering: &ReturnLowering, + defer_stack: &mut DeferStack, +) -> Result { // Emit the scrutinee expression let value = emit_hir_expr( builder, @@ -1641,6 +2598,7 @@ fn emit_hir_match_stmt( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -1649,9 +2607,6 @@ fn emit_hir_match_stmt( ValueRepr::Single(v) => (v, None), ValueRepr::Result { tag, ok, err } => (tag, Some((*ok, *err))), ValueRepr::Unit => (builder.ins().iconst(ir::types::I32, 0), None), - ValueRepr::Pair(_, _) => { - return Err(CodegenError::Unsupported("match on string".to_string())) - } }; let merge_block = builder.create_block(); @@ -1694,6 +2649,8 @@ fn emit_hir_match_stmt( builder.switch_to_block(arm_block); let mut arm_locals = locals.clone(); + let mut arm_defers = defer_stack.clone(); + arm_defers.push_block_scope(); hir_bind_match_pattern_value( builder, &arm.pattern, @@ -1714,6 +2671,9 @@ fn emit_hir_match_stmt( struct_layouts, module, data_counter, + loop_target, + return_lowering, + &mut arm_defers, )?; if flow == Flow::Terminated { arm_terminated = true; @@ -1723,6 +2683,16 @@ fn emit_hir_match_stmt( // If the arm didn't terminate (e.g., with return), jump to merge block if !arm_terminated { + arm_defers.emit_current_and_pop( + builder, + &arm_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; builder.ins().jump(merge_block, &[]); any_arm_continues = true; } @@ -1739,7 +2709,8 @@ fn emit_hir_match_stmt( builder.ins().trap(ir::TrapCode::UnreachableCodeReached); } - Ok(ValueRepr::Unit) + // Return true if all paths diverged (no arm continues to merge_block) + Ok(!any_arm_continues) } /// Emit HIR match expression @@ -1751,6 +2722,7 @@ fn emit_hir_match_expr( fn_map: &HashMap, enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { @@ -1764,6 +2736,7 @@ fn emit_hir_match_expr( fn_map, enum_index, struct_layouts, + return_lowering, module, data_counter, )?; @@ -1772,9 +2745,6 @@ fn emit_hir_match_expr( ValueRepr::Single(v) => (v, None), ValueRepr::Result { tag, ok, err } => (tag, Some((*ok, *err))), ValueRepr::Unit => (builder.ins().iconst(ir::types::I32, 0), None), - ValueRepr::Pair(_, _) => { - return Err(CodegenError::Unsupported("match on string".to_string())) - } }; let merge_block = builder.create_block(); @@ -1807,6 +2777,8 @@ fn emit_hir_match_expr( builder.switch_to_block(arm_block); let mut arm_locals = locals.clone(); + let mut arm_defers = DeferStack::new(); + arm_defers.push_block_scope(); hir_bind_match_pattern_value( builder, &arm.pattern, @@ -1832,6 +2804,9 @@ fn emit_hir_match_expr( struct_layouts, module, data_counter, + None, // break/continue not allowed in value-producing match + return_lowering, + &mut arm_defers, )?; if flow == Flow::Terminated { prefix_terminated = true; @@ -1847,17 +2822,23 @@ fn emit_hir_match_expr( } // Last statement should be an expression - let arm_value = match last { - HirStmt::Expr(expr_stmt) => emit_hir_expr( - builder, - &expr_stmt.expr, - &arm_locals, - fn_map, - enum_index, - struct_layouts, - module, - data_counter, - )?, + let (arm_value, arm_diverges) = match last { + HirStmt::Expr(expr_stmt) => { + // Check if this arm ends with a Trap - if so, it diverges + let diverges = matches!(&expr_stmt.expr, crate::hir::HirExpr::Trap(_)); + let value = emit_hir_expr( + builder, + &expr_stmt.expr, + &arm_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + (value, diverges) + } _ => { return Err(CodegenError::Unsupported( "match arm must end with expression".to_string(), @@ -1865,55 +2846,66 @@ fn emit_hir_match_expr( } }; - let values = match &arm_value { - ValueRepr::Single(val) => vec![*val], - ValueRepr::Pair(a, b) => vec![*a, *b], - ValueRepr::Unit => vec![], - ValueRepr::Result { .. } => { - return Err(CodegenError::Unsupported("match result value".to_string())) - } - }; + // If the arm diverges (e.g., with a trap), skip value storage + if arm_diverges { + builder.seal_block(arm_block); + } else { + let values = match &arm_value { + ValueRepr::Single(val) => vec![*val], + ValueRepr::Unit => vec![], + ValueRepr::Result { .. } => { + return Err(CodegenError::Unsupported("match result value".to_string())) + } + }; - // Set up result shape and stack slots on first arm - if result_shape.is_none() { - let mut types = Vec::new(); - let mut slots = Vec::new(); - for val in &values { - let ty = builder.func.dfg.value_type(*val); - let size = ty.bytes() as u32; - let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( - ir::StackSlotKind::ExplicitSlot, - size.max(1), - )); - types.push(ty); - slots.push(slot); + // Set up result shape and stack slots on first non-terminated arm + if result_shape.is_none() { + let mut types = Vec::new(); + let mut slots = Vec::new(); + for val in &values { + let ty = builder.func.dfg.value_type(*val); + let size = ty.bytes() as u32; + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + size.max(1), + )); + types.push(ty); + slots.push(slot); + } + result_shape = Some(ResultShape { + kind: match &arm_value { + ValueRepr::Unit => ResultKind::Unit, + ValueRepr::Single(_) => ResultKind::Single, + _ => ResultKind::Single, + }, + slots, + types, + }); } - result_shape = Some(ResultShape { - kind: match &arm_value { - ValueRepr::Unit => ResultKind::Unit, - ValueRepr::Single(_) => ResultKind::Single, - ValueRepr::Pair(_, _) => ResultKind::Pair, - _ => ResultKind::Single, - }, - slots, - types, - }); - } - // Store values to stack slots - let shape = result_shape - .as_ref() - .ok_or_else(|| CodegenError::Codegen("missing match result shape".to_string()))?; - if values.len() != shape.types.len() { - eprintln!("DEBUG: arm value mismatch - values: {:?}, expected: {:?}", values.len(), shape.types.len()); - eprintln!("DEBUG: arm_value = {:?}", arm_value); - return Err(CodegenError::Unsupported("mismatched match arm".to_string())); - } - for (idx, val) in values.iter().enumerate() { - builder.ins().stack_store(*val, shape.slots[idx], 0); + // Store values to stack slots + let shape = result_shape + .as_ref() + .ok_or_else(|| CodegenError::Codegen("missing match result shape".to_string()))?; + if values.len() != shape.types.len() { + return Err(CodegenError::Unsupported("mismatched match arm".to_string())); + } + for (idx, val) in values.iter().enumerate() { + builder.ins().stack_store(*val, shape.slots[idx], 0); + } + arm_defers.emit_current_and_pop( + builder, + &arm_locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + builder.ins().jump(merge_block, &[]); + builder.seal_block(arm_block); } - builder.ins().jump(merge_block, &[]); - builder.seal_block(arm_block); if is_last { break; @@ -1937,7 +2929,6 @@ fn emit_hir_match_expr( let result = match shape.kind { ResultKind::Unit => ValueRepr::Unit, ResultKind::Single => ValueRepr::Single(loaded[0]), - ResultKind::Pair => ValueRepr::Pair(loaded[0], loaded[1]), }; Ok(result) @@ -1996,7 +2987,7 @@ fn hir_match_pattern_cond( let val_ty = builder.func.dfg.value_type(match_val); // Special handling for Result type (built-in, not in enum_index) - if qualified == "Result" { + if qualified == "sys.result.Result" { let discr = match variant_name.as_str() { "Ok" => 0i64, "Err" => 1i64, @@ -2065,9 +3056,7 @@ fn to_b1(builder: &mut FunctionBuilder, value: ValueRepr) -> Result Ok(builder.ins().icmp_imm(IntCC::NotEqual, val, 0)), ValueRepr::Unit => Err(CodegenError::Unsupported("unit condition".to_string())), - ValueRepr::Pair(_, _) | ValueRepr::Result { .. } => { - Err(CodegenError::Unsupported("string condition".to_string())) - } + ValueRepr::Result { .. } => Err(CodegenError::Unsupported("result condition".to_string())), } } @@ -2087,6 +3076,7 @@ fn emit_string( value: &str, module: &mut ObjectModule, data_counter: &mut u32, + struct_layouts: &StructLayoutIndex, ) -> Result { let name = format!("__str_{}", data_counter); *data_counter += 1; @@ -2102,8 +3092,29 @@ fn emit_string( let ptr = builder .ins() .global_value(module.isa().pointer_type(), global); - let len = builder.ins().iconst(ir::types::I64, value.len() as i64); - Ok(ValueRepr::Pair(ptr, len)) + let len = builder.ins().iconst(ir::types::I32, value.len() as i64); + let slice_handle = emit_slice_from_ptr(builder, module, ptr, len)?; + + let string_ty = crate::typeck::Ty::Path("sys.string.string".to_string(), Vec::new()); + let layout = resolve_struct_layout(&string_ty, "", &struct_layouts.layouts).ok_or_else(|| { + CodegenError::Unsupported("string layout missing".to_string()) + })?; + let field = layout.fields.get("bytes").ok_or_else(|| { + CodegenError::Unsupported("string.bytes field missing".to_string()) + })?; + let ptr_ty = module.isa().pointer_type(); + let align = layout.align.max(1); + let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let base_ptr = aligned_stack_addr(builder, slot, align, ptr_ty); + let addr = ptr_add(builder, base_ptr, field.offset); + builder + .ins() + .store(MemFlags::new(), slice_handle, addr, 0); + Ok(ValueRepr::Single(base_ptr)) } /// Flatten a ValueRepr into ABI-ready Cranelift values. @@ -2111,7 +3122,6 @@ pub(super) fn flatten_value(value: &ValueRepr) -> Vec { match value { ValueRepr::Unit => vec![], ValueRepr::Single(val) => vec![*val], - ValueRepr::Pair(a, b) => vec![*a, *b], ValueRepr::Result { tag, ok, err } => { let mut out = vec![*tag]; out.extend(flatten_value(ok)); @@ -2176,11 +3186,6 @@ fn zero_value_for_tykind( AbiType::U8 | AbiType::Bool => Ok(ValueRepr::Single(builder.ins().iconst(ir::types::I8, 0))), AbiType::Handle => Ok(ValueRepr::Single(builder.ins().iconst(ir::types::I64, 0))), AbiType::Ptr => Ok(ValueRepr::Single(builder.ins().iconst(ptr_ty, 0))), - AbiType::String => { - let ptr = builder.ins().iconst(ptr_ty, 0); - let len = builder.ins().iconst(ir::types::I64, 0); - Ok(ValueRepr::Pair(ptr, len)) - } AbiType::Result(ok, err) => { let tag = builder.ins().iconst(ir::types::I8, 0); let ok_val = zero_value_for_tykind(builder, ok, ptr_ty)?; @@ -2194,9 +3199,6 @@ fn zero_value_for_tykind( AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported( abi_quirks::result_out_params_error().to_string(), )), - AbiType::ResultString => Err(CodegenError::Unsupported( - abi_quirks::result_string_params_error().to_string(), - )), } } @@ -2205,7 +3207,8 @@ fn zero_value_for_ty( builder: &mut FunctionBuilder, ty: &crate::hir::HirType, ptr_ty: Type, - _struct_layouts: Option<&StructLayoutIndex>, + struct_layouts: Option<&StructLayoutIndex>, + module: &mut ObjectModule, ) -> Result { use crate::typeck::Ty; @@ -2216,13 +3219,13 @@ fn zero_value_for_ty( ty: *inner.clone(), abi: ty.abi.clone(), }; - zero_value_for_ty(builder, &inner_ty, ptr_ty, _struct_layouts) + zero_value_for_ty(builder, &inner_ty, ptr_ty, struct_layouts, module) } Ty::Param(_) => Err(CodegenError::Unsupported( "generic type parameters must be monomorphized before codegen".to_string(), )), Ty::Path(name, args) => { - if name == "Result" && args.len() == 2 { + if name == "sys.result.Result" && args.len() == 2 { let AbiType::Result(ok_abi, err_abi) = &ty.abi else { return Err(CodegenError::Unsupported( abi_quirks::result_abi_mismatch_error().to_string(), @@ -2237,14 +3240,42 @@ fn zero_value_for_ty( abi: (**err_abi).clone(), }; let tag = builder.ins().iconst(ir::types::I8, 0); - let ok_val = zero_value_for_ty(builder, &ok_ty, ptr_ty, _struct_layouts)?; - let err_val = zero_value_for_ty(builder, &err_ty, ptr_ty, _struct_layouts)?; + let ok_val = zero_value_for_ty(builder, &ok_ty, ptr_ty, struct_layouts, module)?; + let err_val = zero_value_for_ty(builder, &err_ty, ptr_ty, struct_layouts, module)?; return Ok(ValueRepr::Result { tag, ok: Box::new(ok_val), err: Box::new(err_val), }); } + if let Some(struct_layouts) = struct_layouts { + if let Some(layout) = resolve_struct_layout(&ty.ty, "", &struct_layouts.layouts) { + let align = layout.align.max(1); + let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let base_ptr = aligned_stack_addr(builder, slot, align, ptr_ty); + for name in &layout.field_order { + let Some(field) = layout.fields.get(name) else { + continue; + }; + let field_zero = + zero_value_for_ty(builder, &field.ty, ptr_ty, Some(struct_layouts), module)?; + store_value_by_ty( + builder, + base_ptr, + field.offset, + &field.ty, + field_zero, + struct_layouts, + module, + )?; + } + return Ok(ValueRepr::Single(base_ptr)); + } + } zero_value_for_tykind(builder, &ty.abi, ptr_ty) } } @@ -2264,12 +3295,6 @@ pub(super) fn value_from_params( *idx += 1; Ok(ValueRepr::Single(val)) } - AbiType::String => { - let ptr = params[*idx]; - let len = params[*idx + 1]; - *idx += 2; - Ok(ValueRepr::Pair(ptr, len)) - } AbiType::Result(ok, err) => { let tag = params[*idx]; *idx += 1; @@ -2281,18 +3306,13 @@ pub(super) fn value_from_params( err: Box::new(err_val), }) } - // ResultOut and ResultString are ABI-level return types, not input types. + // ResultOut is an ABI-level return type, not an input type. // They should never appear as function parameters. AbiType::ResultOut(ok, err) => { Err(CodegenError::Codegen(format!( "ResultOut<{ok:?}, {err:?}> cannot be a parameter type (ABI return type only)" ))) } - AbiType::ResultString => { - Err(CodegenError::Codegen( - "ResultString cannot be a parameter type (ABI return type only)".to_string() - )) - } } } @@ -2312,15 +3332,6 @@ fn value_from_results( *idx += 1; Ok(ValueRepr::Single(*val)) } - AbiType::String => { - if results.len() < *idx + 2 { - return Err(CodegenError::Codegen("string return count".to_string())); - } - let ptr = results[*idx]; - let len = results[*idx + 1]; - *idx += 2; - Ok(ValueRepr::Pair(ptr, len)) - } AbiType::Result(ok, err) => { let tag = results .get(*idx) @@ -2337,9 +3348,6 @@ fn value_from_results( AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported( abi_quirks::result_out_params_error().to_string(), )), - AbiType::ResultString => Err(CodegenError::Unsupported( - abi_quirks::result_string_params_error().to_string(), - )), } } @@ -2349,24 +3357,53 @@ pub(super) fn emit_runtime_wrapper_call( builder: &mut FunctionBuilder, module: &mut ObjectModule, info: &FnInfo, - mut args: Vec, + args: Vec, + ret_ty: &crate::hir::HirType, + struct_layouts: &StructLayoutIndex, ) -> Result { ensure_abi_sig_handled(info)?; let abi_sig = info.abi_sig.as_ref().unwrap_or(&info.sig); - let mut out_slots: Option = None; let mut result_out = None; + let mut sret_ptr = None; + let mut call_args = args; + + enum ResultOutSlot { + Scalar(ir::StackSlot, ir::Type, u32), + Struct(ir::Value), + } - if abi_quirks::is_result_string(&abi_sig.ret) { + if info.sig.ret == AbiType::Ptr + && abi_sig.ret == AbiType::Unit + && is_non_opaque_struct_type(ret_ty, struct_layouts) + { + let layout = resolve_struct_layout(&ret_ty.ty, "", &struct_layouts.layouts).ok_or_else( + || CodegenError::Unsupported("struct layout missing".to_string()), + )?; let ptr_ty = module.isa().pointer_type(); - let slots = result_string_slots(builder, ptr_ty); - push_result_string_out_params(builder, ptr_ty, &slots, &mut args); - out_slots = Some(slots); + let align = layout.align.max(1); + let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let base_ptr = aligned_stack_addr(builder, slot, align, ptr_ty); + call_args.insert(0, base_ptr); + sret_ptr = Some(base_ptr); } if let AbiType::ResultOut(ok_ty, err_ty) = &abi_sig.ret { let ptr_ty = module.isa().pointer_type(); let ok_slot = if **ok_ty == AbiType::Unit { None + } else if **ok_ty == AbiType::Ptr { + let align = ptr_ty.bytes().max(1) as u32; + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(ptr_ty.bytes() as u32, align), + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + call_args.push(addr); + Some(ResultOutSlot::Struct(addr)) } else { let ty = value_type_for_result_out(ok_ty, ptr_ty)?; let align = ty.bytes().max(1) as u32; @@ -2376,11 +3413,20 @@ pub(super) fn emit_runtime_wrapper_call( aligned_slot_size(ty.bytes().max(1) as u32, align), )); let addr = aligned_stack_addr(builder, slot, align, ptr_ty); - args.push(addr); - Some((slot, ty, align)) + call_args.push(addr); + Some(ResultOutSlot::Scalar(slot, ty, align)) }; let err_slot = if **err_ty == AbiType::Unit { None + } else if **err_ty == AbiType::Ptr { + let align = ptr_ty.bytes().max(1) as u32; + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(ptr_ty.bytes() as u32, align), + )); + let addr = aligned_stack_addr(builder, slot, align, ptr_ty); + call_args.push(addr); + Some(ResultOutSlot::Struct(addr)) } else { let ty = value_type_for_result_out(err_ty, ptr_ty)?; let align = ty.bytes().max(1) as u32; @@ -2390,13 +3436,17 @@ pub(super) fn emit_runtime_wrapper_call( aligned_slot_size(ty.bytes().max(1) as u32, align), )); let addr = aligned_stack_addr(builder, slot, align, ptr_ty); - args.push(addr); - Some((slot, ty, align)) + call_args.push(addr); + Some(ResultOutSlot::Scalar(slot, ty, align)) }; result_out = Some((ok_slot, err_slot, ok_ty.clone(), err_ty.clone())); } - let sig = sig_to_clif(abi_sig, module.isa().pointer_type()); + let sig = sig_to_clif( + abi_sig, + module.isa().pointer_type(), + module.isa().default_call_conv(), + ); let call_symbol = info .runtime_symbol .as_deref() @@ -2405,52 +3455,38 @@ pub(super) fn emit_runtime_wrapper_call( .declare_function(call_symbol, Linkage::Import, &sig) .map_err(|err| CodegenError::Codegen(err.to_string()))?; let local = module.declare_func_in_func(func_id, builder.func); - let call_inst = builder.ins().call(local, &args); + let call_inst = builder.ins().call(local, &call_args); let results = builder.inst_results(call_inst).to_vec(); - if abi_quirks::is_result_string(&abi_sig.ret) { - let tag = results - .get(0) - .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; - let slots = out_slots.ok_or_else(|| CodegenError::Codegen("missing slots".to_string()))?; - let ptr_ty = module.isa().pointer_type(); - let (ptr, len, err) = read_result_string_slots(builder, ptr_ty, &slots); - match &info.sig.ret { - AbiType::Result(ok_ty, err_ty) => { - if **ok_ty != AbiType::String || **err_ty != AbiType::I32 { - return Err(CodegenError::Unsupported( - abi_quirks::result_out_params_error().to_string(), - )); - } - return Ok(ValueRepr::Result { - tag: *tag, - ok: Box::new(ValueRepr::Pair(ptr, len)), - err: Box::new(ValueRepr::Single(err)), - }); - } - _ => return Err(CodegenError::Unsupported( - abi_quirks::result_out_params_error().to_string(), - )), - } - } - if abi_quirks::is_result_out(&abi_sig.ret) { let tag = results .get(0) .ok_or_else(|| CodegenError::Codegen("missing result tag".to_string()))?; let (ok_slot, err_slot, ok_ty, err_ty) = result_out .ok_or_else(|| CodegenError::Codegen("missing result slots".to_string()))?; - let ok_val = if let Some((slot, ty, align)) = ok_slot { - let addr = aligned_stack_addr(builder, slot, align, module.isa().pointer_type()); - let val = builder.ins().load(ty, MemFlags::new(), addr, 0); - ValueRepr::Single(val) + let ok_val = if let Some(slot) = ok_slot { + match slot { + ResultOutSlot::Scalar(slot, ty, align) => { + let addr = + aligned_stack_addr(builder, slot, align, module.isa().pointer_type()); + let val = builder.ins().load(ty, MemFlags::new(), addr, 0); + ValueRepr::Single(val) + } + ResultOutSlot::Struct(addr) => ValueRepr::Single(addr), + } } else { ValueRepr::Unit }; - let err_val = if let Some((slot, ty, align)) = err_slot { - let addr = aligned_stack_addr(builder, slot, align, module.isa().pointer_type()); - let val = builder.ins().load(ty, MemFlags::new(), addr, 0); - ValueRepr::Single(val) + let err_val = if let Some(slot) = err_slot { + match slot { + ResultOutSlot::Scalar(slot, ty, align) => { + let addr = + aligned_stack_addr(builder, slot, align, module.isa().pointer_type()); + let val = builder.ins().load(ty, MemFlags::new(), addr, 0); + ValueRepr::Single(val) + } + ResultOutSlot::Struct(addr) => ValueRepr::Single(addr), + } } else { ValueRepr::Unit }; @@ -2470,6 +3506,10 @@ pub(super) fn emit_runtime_wrapper_call( } } + if let Some(ptr) = sret_ptr { + return Ok(ValueRepr::Single(ptr)); + } + let mut idx = 0; value_from_results(builder, &info.sig.ret, &results, &mut idx) } @@ -2485,7 +3525,7 @@ fn ensure_abi_sig_handled(info: &FnInfo) -> Result<(), CodegenError> { Ok(()) } else { Err(CodegenError::Codegen(format!( - "abi signature mismatch for {} without ResultString/ResultOut lowering", + "abi signature mismatch for {} without ResultOut lowering", info.symbol ))) } @@ -2502,31 +3542,6 @@ mod tests { assert_eq!(aligned_slot_size(8, 8), 15); } - #[test] - fn result_string_len_is_u64() { - assert_eq!(abi_quirks::result_string_len_bytes(), 8); - } - - #[test] - fn ensure_abi_sig_allows_result_string_lowering() { - let sig = FnSig { - params: Vec::new(), - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), - }; - let abi_sig = FnSig { - params: Vec::new(), - ret: AbiType::ResultString, - }; - let info = FnInfo { - sig, - abi_sig: Some(abi_sig), - symbol: "test".to_string(), - runtime_symbol: None, - is_runtime: false, - }; - assert!(ensure_abi_sig_handled(&info).is_ok()); - } - #[test] fn ensure_abi_sig_rejects_unhandled_mismatch() { let sig = FnSig { diff --git a/capc/src/codegen/intrinsics.rs b/capc/src/codegen/intrinsics.rs index 3958155..12c1c5b 100644 --- a/capc/src/codegen/intrinsics.rs +++ b/capc/src/codegen/intrinsics.rs @@ -21,11 +21,11 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Handle, }; let system_fs_read = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Handle, }; let system_filesystem = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Handle, }; // Filesystem. @@ -34,47 +34,47 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Handle, }; let fs_subdir = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Handle, }; let fs_open_read = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Handle, }; let fs_read_to_string = FnSig { - params: vec![AbiType::Handle, AbiType::String], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + params: vec![AbiType::Handle, AbiType::Ptr], + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let fs_read_to_string_abi = FnSig { - params: vec![AbiType::Handle, AbiType::String, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let fs_read_bytes = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_read_bytes_abi = FnSig { params: vec![ AbiType::Handle, - AbiType::String, + AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), ], ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_list_dir = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_list_dir_abi = FnSig { params: vec![ AbiType::Handle, - AbiType::String, + AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), ], ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_exists = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Bool, }; let fs_readfs_close = FnSig { @@ -91,11 +91,11 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }; let fs_file_read_to_string = FnSig { params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let fs_file_read_to_string_abi = FnSig { - params: vec![AbiType::Handle, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let fs_file_read_close = FnSig { params: vec![AbiType::Handle], @@ -113,16 +113,20 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_join = FnSig { - params: vec![AbiType::String, AbiType::String], - ret: AbiType::String, + params: vec![AbiType::Ptr, AbiType::Ptr], + ret: AbiType::Ptr, + }; + let fs_join_abi = FnSig { + params: vec![AbiType::Ptr, AbiType::Ptr, AbiType::Ptr], + ret: AbiType::Unit, }; // Console. let console_println = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Unit, }; let console_print = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Unit, }; let console_print_i32 = FnSig { @@ -173,26 +177,26 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }; // Net. let net_listen = FnSig { - params: vec![AbiType::Handle, AbiType::String, AbiType::I32], + params: vec![AbiType::Handle, AbiType::Ptr, AbiType::I32], ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let net_listen_abi = FnSig { params: vec![ AbiType::Handle, - AbiType::String, + AbiType::Ptr, AbiType::I32, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), ], ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let net_connect = FnSig { - params: vec![AbiType::Handle, AbiType::String, AbiType::I32], + params: vec![AbiType::Handle, AbiType::Ptr, AbiType::I32], ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let net_connect_abi = FnSig { params: vec![ AbiType::Handle, - AbiType::String, + AbiType::Ptr, AbiType::I32, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), ], @@ -200,28 +204,28 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }; let net_read_to_string = FnSig { params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let net_read_to_string_abi = FnSig { - params: vec![AbiType::Handle, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let net_read = FnSig { params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let net_read_abi = FnSig { - params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let net_write = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; let net_write_abi = FnSig { params: vec![ AbiType::Handle, - AbiType::String, + AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), @@ -247,11 +251,11 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }; let args_at = FnSig { params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let args_at_abi = FnSig { - params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; // Buffer + slices. let mem_slice_from_ptr = FnSig { @@ -279,6 +283,17 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ], ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; + let mem_buffer_new_default = FnSig { + params: vec![AbiType::I32], + ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; + let mem_buffer_new_default_abi = FnSig { + params: vec![ + AbiType::I32, + AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + ], + ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), + }; let mem_buffer_len = FnSig { params: vec![AbiType::Handle], ret: AbiType::I32, @@ -327,6 +342,10 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::Handle], ret: AbiType::Handle, }; + let vec_new_default = FnSig { + params: vec![], + ret: AbiType::Handle, + }; let vec_u8_get = FnSig { params: vec![AbiType::Handle, AbiType::I32], ret: AbiType::Result(Box::new(AbiType::U8), Box::new(AbiType::I32)), @@ -494,20 +513,20 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }; let vec_string_get = FnSig { params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let vec_string_get_abi = FnSig { - params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let vec_string_push = FnSig { - params: vec![AbiType::Handle, AbiType::String], + params: vec![AbiType::Handle, AbiType::Ptr], ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), }; let vec_string_push_abi = FnSig { params: vec![ AbiType::Handle, - AbiType::String, + AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), ], ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), @@ -526,53 +545,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { }; let vec_string_pop = FnSig { params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let vec_string_pop_abi = FnSig { - params: vec![AbiType::Handle, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let vec_string_free = FnSig { params: vec![AbiType::Handle, AbiType::Handle], ret: AbiType::Unit, }; - // Strings. - let string_len = FnSig { - params: vec![AbiType::String], - ret: AbiType::I32, - }; - let string_byte_at = FnSig { - params: vec![AbiType::String, AbiType::I32], - ret: AbiType::U8, - }; - let string_as_slice = FnSig { - params: vec![AbiType::String], - ret: AbiType::Handle, - }; - let string_split = FnSig { - params: vec![AbiType::String], - ret: AbiType::Handle, - }; - let string_lines = FnSig { - params: vec![AbiType::String], - ret: AbiType::Handle, - }; - let string_split_delim = FnSig { - params: vec![AbiType::String, AbiType::U8], - ret: AbiType::Handle, - }; - let string_trim = FnSig { - params: vec![AbiType::String], - ret: AbiType::String, - }; - let string_trim_start = FnSig { - params: vec![AbiType::String], - ret: AbiType::String, - }; - let string_trim_end = FnSig { - params: vec![AbiType::String], - ret: AbiType::String, - }; // Vec lengths. let vec_u8_len = FnSig { params: vec![AbiType::Handle], @@ -673,11 +655,11 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { FnInfo { sig: FnSig { params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }, abi_sig: Some(FnSig { - params: vec![AbiType::Handle, AbiType::ResultString], - ret: AbiType::ResultString, + params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + ret: AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }), symbol: "capable_rt_read_stdin_to_string".to_string(), runtime_symbol: None, @@ -821,7 +803,7 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { "sys.console.Console__assert".to_string(), FnInfo { sig: FnSig { - params: vec![AbiType::Handle, AbiType::Bool, AbiType::String], + params: vec![AbiType::Handle, AbiType::Bool, AbiType::Ptr], ret: AbiType::Unit, }, abi_sig: None, @@ -1076,13 +1058,23 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { "sys.fs.join".to_string(), FnInfo { sig: fs_join, - abi_sig: None, + abi_sig: Some(fs_join_abi), symbol: "capable_rt_fs_join".to_string(), runtime_symbol: None, is_runtime: true, }, ); // === Buffer + slices === + map.insert( + "sys.buffer.new".to_string(), + FnInfo { + sig: mem_buffer_new_default, + abi_sig: Some(mem_buffer_new_default_abi), + symbol: "capable_rt_buffer_new_default".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.buffer.Alloc__buffer_new".to_string(), FnInfo { @@ -1304,6 +1296,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.buffer.vec_string_new".to_string(), + FnInfo { + sig: vec_new_default, + abi_sig: None, + symbol: "capable_rt_vec_string_new_default".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.buffer.Alloc__vec_string_free".to_string(), FnInfo { @@ -1544,108 +1546,6 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); - // === String === - map.insert( - "sys.string.string__len".to_string(), - FnInfo { - sig: string_len, - abi_sig: None, - symbol: "capable_rt_string_len".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__byte_at".to_string(), - FnInfo { - sig: string_byte_at, - abi_sig: None, - symbol: "capable_rt_string_byte_at".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__as_slice".to_string(), - FnInfo { - sig: string_as_slice.clone(), - abi_sig: None, - symbol: "capable_rt_string_as_slice".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__bytes".to_string(), - FnInfo { - // bytes() is an alias for as_slice(); both map to the same runtime symbol. - sig: string_as_slice, - abi_sig: None, - symbol: "capable_rt_string_as_slice".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__split_whitespace".to_string(), - FnInfo { - sig: string_split, - abi_sig: None, - symbol: "capable_rt_string_split_whitespace".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__lines".to_string(), - FnInfo { - sig: string_lines, - abi_sig: None, - symbol: "capable_rt_string_split_lines".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__split".to_string(), - FnInfo { - sig: string_split_delim, - abi_sig: None, - symbol: "capable_rt_string_split".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__trim".to_string(), - FnInfo { - sig: string_trim, - abi_sig: None, - symbol: "capable_rt_string_trim".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__trim_start".to_string(), - FnInfo { - sig: string_trim_start, - abi_sig: None, - symbol: "capable_rt_string_trim_start".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.string.string__trim_end".to_string(), - FnInfo { - sig: string_trim_end, - abi_sig: None, - symbol: "capable_rt_string_trim_end".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); // === Bytes === map.insert( "sys.bytes.u8__is_whitespace".to_string(), diff --git a/capc/src/codegen/layout.rs b/capc/src/codegen/layout.rs index 3fcc8ef..5cafc23 100644 --- a/capc/src/codegen/layout.rs +++ b/capc/src/codegen/layout.rs @@ -242,14 +242,6 @@ pub(super) fn type_layout_for_abi( size: ptr_ty.bytes() as u32, align: ptr_ty.bytes() as u32, }), - AbiType::String => { - let ptr_size = ptr_ty.bytes() as u32; - let len_offset = align_to(ptr_size, 8); - Ok(TypeLayout { - size: len_offset + 8, - align: ptr_ty.bytes().max(8) as u32, - }) - } AbiType::Result(ok, err) => { let tag = TypeLayout { size: 1, align: 1 }; let ok = type_layout_for_abi(ok, ptr_ty)?; @@ -266,7 +258,7 @@ pub(super) fn type_layout_for_abi( let size = align_to(offset, align); Ok(TypeLayout { size, align }) } - AbiType::ResultOut(_, _) | AbiType::ResultString => Err(CodegenError::Unsupported( + AbiType::ResultOut(_, _) => Err(CodegenError::Unsupported( abi_quirks::result_lowering_layout_error().to_string(), )), } diff --git a/capc/src/codegen/mod.rs b/capc/src/codegen/mod.rs index 31c16ad..b63f3e9 100644 --- a/capc/src/codegen/mod.rs +++ b/capc/src/codegen/mod.rs @@ -28,8 +28,11 @@ mod abi_quirks; mod intrinsics; mod layout; -use emit::{emit_hir_stmt, emit_runtime_wrapper_call, flatten_value, store_local, value_from_params}; -use layout::{build_enum_index, build_struct_layout_index}; +use emit::{ + emit_hir_stmt, emit_runtime_wrapper_call, flatten_value, store_local, value_from_params, + DeferStack, ReturnLowering, +}; +use layout::{build_enum_index, build_struct_layout_index, resolve_struct_layout}; #[derive(Debug, Error, Diagnostic)] #[allow(unused_assignments)] @@ -156,7 +159,6 @@ struct TypeLayout { enum ValueRepr { Unit, Single(ir::Value), - Pair(ir::Value, ir::Value), Result { tag: ir::Value, ok: Box, @@ -185,7 +187,6 @@ struct ResultShape { enum ResultKind { Unit, Single, - Pair, } /// Build and write the object file for a fully-checked HIR program. @@ -224,6 +225,7 @@ pub fn build_object( &enum_index, &mut fn_map, &runtime_intrinsics, + &struct_layouts, module.isa().pointer_type(), true, )?; @@ -236,6 +238,7 @@ pub fn build_object( &enum_index, &mut fn_map, &runtime_intrinsics, + &struct_layouts, module.isa().pointer_type(), false, )?; @@ -246,6 +249,7 @@ pub fn build_object( &enum_index, &mut fn_map, &runtime_intrinsics, + &struct_layouts, module.isa().pointer_type(), false, )?; @@ -257,7 +261,7 @@ pub fn build_object( .collect::>(); for module_ref in &all_modules { - register_extern_functions_from_hir(module_ref, &mut fn_map)?; + register_extern_functions_from_hir(module_ref, &mut fn_map, &struct_layouts)?; } let mut data_counter = 0u32; @@ -272,17 +276,31 @@ pub fn build_object( if info.is_runtime { continue; } + let abi_sig = info.abi_sig.as_ref().unwrap_or(&info.sig); + let sig_for_codegen = if info.runtime_symbol.is_some() { + &info.sig + } else { + abi_sig + }; let func_id = module .declare_function( &info.symbol, Linkage::Export, - &sig_to_clif(&info.sig, module.isa().pointer_type()), + &sig_to_clif( + sig_for_codegen, + module.isa().pointer_type(), + module.isa().default_call_conv(), + ), ) .map_err(|err| CodegenError::Codegen(err.to_string()))?; let mut ctx = module.make_context(); ctx.func = Function::with_name_signature( ir::UserFuncName::user(0, func_id.as_u32()), - sig_to_clif(&info.sig, module.isa().pointer_type()), + sig_to_clif( + sig_for_codegen, + module.isa().pointer_type(), + module.isa().default_call_conv(), + ), ); let mut builder_ctx = FunctionBuilderContext::new(); @@ -294,11 +312,18 @@ pub fn build_object( if info.runtime_symbol.is_some() { let args = builder.block_params(block).to_vec(); - let value = emit_runtime_wrapper_call(&mut builder, &mut module, &info, args)?; + let value = emit_runtime_wrapper_call( + &mut builder, + &mut module, + &info, + args, + &func.ret_ty, + &struct_layouts, + )?; match value { ValueRepr::Unit => builder.ins().return_(&[]), ValueRepr::Single(val) => builder.ins().return_(&[val]), - ValueRepr::Pair(_, _) | ValueRepr::Result { .. } => { + ValueRepr::Result { .. } => { let values = flatten_value(&value); builder.ins().return_(&values) } @@ -318,12 +343,83 @@ pub fn build_object( let mut locals: HashMap = HashMap::new(); let params = builder.block_params(block).to_vec(); let mut param_index = 0; + let mut return_lowering = ReturnLowering::Direct; + + if info.sig.ret == AbiType::Ptr + && abi_sig.ret == AbiType::Unit + && resolve_struct_layout(&func.ret_ty.ty, "", &struct_layouts.layouts).is_some() + { + let out_ptr = params + .get(0) + .copied() + .ok_or_else(|| CodegenError::Codegen("missing sret param".to_string()))?; + return_lowering = ReturnLowering::SRet { + out_ptr, + ret_ty: func.ret_ty.clone(), + }; + param_index = 1; + } else if let AbiType::ResultOut(ok_abi, err_abi) = &abi_sig.ret { + let (ok_ty, err_ty) = match &func.ret_ty.ty { + crate::typeck::Ty::Path(name, args) + if name == "sys.result.Result" && args.len() == 2 => + { + ( + crate::hir::HirType { + ty: args[0].clone(), + abi: (**ok_abi).clone(), + }, + crate::hir::HirType { + ty: args[1].clone(), + abi: (**err_abi).clone(), + }, + ) + } + _ => { + return Err(CodegenError::Codegen( + "result out params missing result type".to_string(), + )) + } + }; + return_lowering = ReturnLowering::ResultOut { + out_ok: None, + out_err: None, + ok_ty, + err_ty, + }; + } for param in &func.params { let value = value_from_params(&mut builder, ¶m.ty.abi, ¶ms, &mut param_index)?; let local = store_local(&mut builder, value); locals.insert(param.local_id, local); } + if let ReturnLowering::ResultOut { + out_ok, + out_err, + ok_ty, + err_ty, + } = &mut return_lowering + { + if ok_ty.abi != AbiType::Unit { + *out_ok = Some( + params + .get(param_index) + .copied() + .ok_or_else(|| CodegenError::Codegen("missing ok out param".to_string()))?, + ); + param_index += 1; + } + if err_ty.abi != AbiType::Unit { + *out_err = Some( + params + .get(param_index) + .copied() + .ok_or_else(|| CodegenError::Codegen("missing err out param".to_string()))?, + ); + } + } + let mut defer_stack = DeferStack::new(); + defer_stack.push_block_scope(); let mut terminated = false; for stmt in &func.body.stmts { @@ -336,6 +432,9 @@ pub fn build_object( &struct_layouts, &mut module, &mut data_counter, + None, // no loop context at function top level + &return_lowering, + &mut defer_stack, )?; if flow == Flow::Terminated { terminated = true; @@ -344,6 +443,16 @@ pub fn build_object( } if info.sig.ret == AbiType::Unit && !terminated { + defer_stack.emit_all_and_clear( + &mut builder, + &locals, + &fn_map, + &enum_index, + &struct_layouts, + &return_lowering, + &mut module, + &mut data_counter, + )?; builder.ins().return_(&[]); } @@ -371,8 +480,8 @@ pub fn build_object( } /// Lower a codegen signature into a Cranelift signature. -fn sig_to_clif(sig: &FnSig, ptr_ty: Type) -> Signature { - let mut signature = Signature::new(CallConv::SystemV); +fn sig_to_clif(sig: &FnSig, ptr_ty: Type, call_conv: CallConv) -> Signature { + let mut signature = Signature::new(call_conv); for param in &sig.params { append_ty_params(&mut signature, param, ptr_ty); } @@ -384,10 +493,6 @@ fn sig_to_clif(sig: &FnSig, ptr_ty: Type) -> Signature { fn append_ty_params(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { match ty { AbiType::Unit => {} - AbiType::String => { - signature.params.push(AbiParam::new(ptr_ty)); - signature.params.push(AbiParam::new(ir::types::I64)); - } AbiType::Handle => signature.params.push(AbiParam::new(ir::types::I64)), AbiType::Ptr => signature.params.push(AbiParam::new(ptr_ty)), AbiType::I32 => signature.params.push(AbiParam::new(ir::types::I32)), @@ -407,11 +512,6 @@ fn append_ty_params(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { signature.params.push(AbiParam::new(ptr_ty)); } } - AbiType::ResultString => { - signature.params.push(AbiParam::new(ptr_ty)); - signature.params.push(AbiParam::new(ptr_ty)); - signature.params.push(AbiParam::new(ptr_ty)); - } } } @@ -425,10 +525,6 @@ fn append_ty_returns(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { AbiType::Bool => signature.returns.push(AbiParam::new(ir::types::I8)), AbiType::Handle => signature.returns.push(AbiParam::new(ir::types::I64)), AbiType::Ptr => signature.returns.push(AbiParam::new(ptr_ty)), - AbiType::String => { - signature.returns.push(AbiParam::new(ptr_ty)); - signature.returns.push(AbiParam::new(ir::types::I64)); - } AbiType::Result(ok, err) => { signature.returns.push(AbiParam::new(ir::types::I8)); append_ty_returns(signature, ok, ptr_ty); @@ -437,9 +533,6 @@ fn append_ty_returns(signature: &mut Signature, ty: &AbiType, ptr_ty: Type) { AbiType::ResultOut(_, _) => { signature.returns.push(AbiParam::new(ir::types::I8)); } - AbiType::ResultString => { - signature.returns.push(AbiParam::new(ir::types::I8)); - } } } @@ -448,6 +541,49 @@ fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { intrinsics::register_runtime_intrinsics(ptr_ty) } +fn is_non_opaque_struct_type( + ty: &crate::typeck::Ty, + struct_layouts: &StructLayoutIndex, +) -> bool { + resolve_struct_layout(ty, "", &struct_layouts.layouts).is_some() +} + +fn lowered_abi_sig_for_return( + sig: &FnSig, + ret_ty: &crate::hir::HirType, + struct_layouts: &StructLayoutIndex, +) -> Option { + if let crate::typeck::Ty::Path(name, args) = &ret_ty.ty { + if name == "sys.result.Result" && args.len() == 2 { + let ok_is_struct = is_non_opaque_struct_type(&args[0], struct_layouts); + let err_is_struct = is_non_opaque_struct_type(&args[1], struct_layouts); + if ok_is_struct || err_is_struct { + let AbiType::Result(ok_abi, err_abi) = &ret_ty.abi else { + return None; + }; + let mut params = sig.params.clone(); + params.push(AbiType::ResultOut(ok_abi.clone(), err_abi.clone())); + return Some(FnSig { + params, + ret: AbiType::ResultOut(ok_abi.clone(), err_abi.clone()), + }); + } + } + } + + if is_non_opaque_struct_type(&ret_ty.ty, struct_layouts) { + let mut params = Vec::with_capacity(sig.params.len() + 1); + params.push(AbiType::Ptr); + params.extend(sig.params.iter().cloned()); + return Some(FnSig { + params, + ret: AbiType::Unit, + }); + } + + None +} + /// Register Capable-defined functions (stdlib or user) into the codegen map. fn register_user_functions( module: &crate::hir::HirModule, @@ -455,6 +591,7 @@ fn register_user_functions( _enum_index: &EnumIndex, map: &mut HashMap, runtime_intrinsics: &HashMap, + struct_layouts: &StructLayoutIndex, _ptr_ty: Type, is_stdlib: bool, ) -> Result<(), CodegenError> { @@ -487,6 +624,7 @@ fn register_user_functions( } else { (None, None) }; + let abi_sig = abi_sig.or_else(|| lowered_abi_sig_for_return(&sig, &func.ret_ty, struct_layouts)); map.insert( key, FnInfo { @@ -505,6 +643,7 @@ fn register_user_functions( fn register_extern_functions_from_hir( module: &crate::hir::HirModule, map: &mut HashMap, + struct_layouts: &StructLayoutIndex, ) -> Result<(), CodegenError> { let module_name = &module.name; for func in &module.extern_functions { @@ -517,11 +656,12 @@ fn register_extern_functions_from_hir( ret: func.ret_ty.abi.clone(), }; let key = format!("{}.{}", module_name, func.name); + let abi_sig = lowered_abi_sig_for_return(&sig, &func.ret_ty, struct_layouts); map.insert( key, FnInfo { sig, - abi_sig: None, + abi_sig, symbol: func.name.clone(), runtime_symbol: None, is_runtime: true, diff --git a/capc/src/hir.rs b/capc/src/hir.rs index 98759e4..15eb329 100644 --- a/capc/src/hir.rs +++ b/capc/src/hir.rs @@ -106,9 +106,13 @@ pub struct HirBlock { pub enum HirStmt { Let(HirLetStmt), Assign(HirAssignStmt), + Defer(HirDeferStmt), Return(HirReturnStmt), + Break(HirBreakStmt), + Continue(HirContinueStmt), If(HirIfStmt), While(HirWhileStmt), + For(HirForStmt), Expr(HirExprStmt), } @@ -117,9 +121,13 @@ impl HirStmt { match self { HirStmt::Let(s) => s.span, HirStmt::Assign(s) => s.span, + HirStmt::Defer(s) => s.span, HirStmt::Return(s) => s.span, + HirStmt::Break(s) => s.span, + HirStmt::Continue(s) => s.span, HirStmt::If(s) => s.span, HirStmt::While(s) => s.span, + HirStmt::For(s) => s.span, HirStmt::Expr(s) => s.span, } } @@ -140,12 +148,28 @@ pub struct HirAssignStmt { pub span: Span, } +#[derive(Debug, Clone)] +pub struct HirDeferStmt { + pub expr: HirExpr, + pub span: Span, +} + #[derive(Debug, Clone)] pub struct HirReturnStmt { pub expr: Option, pub span: Span, } +#[derive(Debug, Clone)] +pub struct HirBreakStmt { + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct HirContinueStmt { + pub span: Span, +} + #[derive(Debug, Clone)] pub struct HirIfStmt { pub cond: HirExpr, @@ -161,6 +185,15 @@ pub struct HirWhileStmt { pub span: Span, } +#[derive(Debug, Clone)] +pub struct HirForStmt { + pub var_id: LocalId, + pub start: HirExpr, + pub end: HirExpr, + pub body: HirBlock, + pub span: Span, +} + #[derive(Debug, Clone)] pub struct HirExprStmt { pub expr: HirExpr, @@ -175,11 +208,13 @@ pub enum HirExpr { EnumVariant(HirEnumVariantExpr), Call(HirCall), FieldAccess(HirFieldAccess), + Index(HirIndex), StructLiteral(HirStructLiteral), Unary(HirUnary), Binary(HirBinary), Match(HirMatch), Try(HirTry), + Trap(HirTrap), } impl HirExpr { @@ -190,11 +225,13 @@ impl HirExpr { HirExpr::EnumVariant(e) => e.span, HirExpr::Call(e) => e.span, HirExpr::FieldAccess(e) => e.span, + HirExpr::Index(e) => e.span, HirExpr::StructLiteral(e) => e.span, HirExpr::Unary(e) => e.span, HirExpr::Binary(e) => e.span, HirExpr::Match(e) => e.span, HirExpr::Try(e) => e.span, + HirExpr::Trap(e) => e.span, } } @@ -205,11 +242,13 @@ impl HirExpr { HirExpr::EnumVariant(e) => &e.enum_ty, HirExpr::Call(e) => &e.ret_ty, HirExpr::FieldAccess(e) => &e.field_ty, + HirExpr::Index(e) => &e.elem_ty, HirExpr::StructLiteral(e) => &e.struct_ty, HirExpr::Unary(e) => &e.ty, HirExpr::Binary(e) => &e.ty, HirExpr::Match(e) => &e.result_ty, HirExpr::Try(e) => &e.ok_ty, + HirExpr::Trap(e) => &e.ty, } } } @@ -278,6 +317,14 @@ pub struct HirFieldAccess { pub span: Span, } +#[derive(Debug, Clone)] +pub struct HirIndex { + pub object: Box, + pub index: Box, + pub elem_ty: HirType, + pub span: Span, +} + #[derive(Debug, Clone)] pub struct HirStructLiteral { pub struct_ty: HirType, @@ -324,6 +371,15 @@ pub struct HirTry { pub span: Span, } +/// Unconditional trap/panic. Used for unreachable code paths like +/// calling .ok() on an Err variant. +#[derive(Debug, Clone)] +pub struct HirTrap { + /// The type this expression would have produced (for type checking). + pub ty: HirType, + pub span: Span, +} + #[derive(Debug, Clone)] pub struct HirMatchArm { pub pattern: HirPattern, diff --git a/capc/src/lexer.rs b/capc/src/lexer.rs index 3121d8c..db6b86a 100644 --- a/capc/src/lexer.rs +++ b/capc/src/lexer.rs @@ -39,6 +39,16 @@ pub enum TokenKind { Else, #[token("while")] While, + #[token("for")] + For, + #[token("in")] + In, + #[token("break")] + Break, + #[token("continue")] + Continue, + #[token("defer")] + Defer, #[token("return")] Return, #[token("struct")] @@ -118,6 +128,8 @@ pub enum TokenKind { Colon, #[token(",")] Comma, + #[token("..", priority = 3)] + DotDot, #[token(".")] Dot, #[token(";")] @@ -131,6 +143,8 @@ pub enum TokenKind { Int, #[regex(r#"\"([^\"\\]|\\.)*\""#)] Str, + #[regex(r#"'([^'\\]|\\x[0-9a-fA-F]{2}|\\.)'"#)] + Char, Error, } diff --git a/capc/src/main.rs b/capc/src/main.rs index dc1d567..a33ddbd 100644 --- a/capc/src/main.rs +++ b/capc/src/main.rs @@ -188,7 +188,10 @@ fn build_binary( ) .map_err(|err| miette!("failed to write stub: {err}"))?; - let out_path = out.unwrap_or_else(|| build_dir.join("capable")); + let out_path = out.unwrap_or_else(|| { + let name = path.file_stem().and_then(|s| s.to_str()).unwrap_or("a.out"); + build_dir.join(name) + }); let runtime_lib_dir = workspace_root.join("target").join("debug"); let mut rustc = std::process::Command::new("rustc"); rustc diff --git a/capc/src/parser.rs b/capc/src/parser.rs index 76c9514..d764ec9 100644 --- a/capc/src/parser.rs +++ b/capc/src/parser.rs @@ -222,8 +222,8 @@ impl Parser { } Ok(Item::Impl(self.parse_impl_block(doc)?)) } - Some(_other) => Err(self.error_current(format!( - "expected item, found {{other:?}}" + Some(other) => Err(self.error_current(format!( + "expected item, found {other:?}" ))), None => Err(self.error_current("unexpected end of input".to_string())), } @@ -466,8 +466,12 @@ impl Parser { match self.peek_kind() { Some(TokenKind::Let) => Ok(Stmt::Let(self.parse_let()?)), Some(TokenKind::Return) => Ok(Stmt::Return(self.parse_return()?)), - Some(TokenKind::If) => Ok(Stmt::If(self.parse_if()?)), + Some(TokenKind::Break) => Ok(Stmt::Break(self.parse_break()?)), + Some(TokenKind::Continue) => Ok(Stmt::Continue(self.parse_continue()?)), + Some(TokenKind::Defer) => Ok(Stmt::Defer(self.parse_defer()?)), + Some(TokenKind::If) => self.parse_if_stmt(), Some(TokenKind::While) => Ok(Stmt::While(self.parse_while()?)), + Some(TokenKind::For) => self.parse_for_stmt(), Some(TokenKind::Ident) => { if self.peek_token(1).is_some_and(|t| t.kind == TokenKind::Eq) { Ok(Stmt::Assign(self.parse_assign()?)) @@ -529,17 +533,103 @@ impl Parser { }) } - fn parse_if(&mut self) -> Result { - let start = self.expect(TokenKind::If)?.span.start; - let cond = self.parse_expr()?; + fn parse_break(&mut self) -> Result { + let token = self.expect(TokenKind::Break)?; + let end = self + .maybe_consume(TokenKind::Semi) + .map_or(token.span.end, |t| t.span.end); + Ok(BreakStmt { + span: Span::new(token.span.start, end), + }) + } + + fn parse_continue(&mut self) -> Result { + let token = self.expect(TokenKind::Continue)?; + let end = self + .maybe_consume(TokenKind::Semi) + .map_or(token.span.end, |t| t.span.end); + Ok(ContinueStmt { + span: Span::new(token.span.start, end), + }) + } + + fn parse_defer(&mut self) -> Result { + let start = self.expect(TokenKind::Defer)?.span.start; + let expr = self.parse_expr()?; + let end = self + .maybe_consume(TokenKind::Semi) + .map_or(expr.span().end, |t| t.span.end); + Ok(DeferStmt { + expr, + span: Span::new(start, end), + }) + } + + fn parse_if_stmt(&mut self) -> Result { + let if_token = self.expect(TokenKind::If)?; + let start = if_token.span.start; + if self.peek_kind() == Some(TokenKind::Let) { + self.bump(); + let pattern = self.parse_pattern()?; + self.expect(TokenKind::Eq)?; + let expr = self.parse_expr_no_struct()?; + let then_block = self.parse_block()?; + let else_block = if self.peek_kind() == Some(TokenKind::Else) { + self.bump(); + if self.peek_kind() == Some(TokenKind::If) { + let else_if = self.parse_if_stmt()?; + let span = else_if.span(); + Some(Block { + stmts: vec![else_if], + span, + }) + } else { + Some(self.parse_block()?) + } + } else { + None + }; + let end = else_block + .as_ref() + .map_or(then_block.span.end, |b| b.span.end); + let else_body = else_block.unwrap_or(Block { + stmts: Vec::new(), + span: Span::new(end, end), + }); + let match_expr = MatchExpr { + expr: Box::new(expr), + arms: vec![ + MatchArm { + pattern, + body: then_block, + span: Span::new(start, end), + }, + MatchArm { + pattern: Pattern::Wildcard(Span::new(end, end)), + body: else_body, + span: Span::new(start, end), + }, + ], + span: Span::new(start, end), + match_span: if_token.span, + }; + return Ok(Stmt::Expr(ExprStmt { + expr: Expr::Match(match_expr), + span: Span::new(start, end), + })); + } + + // Use parse_expr_no_struct because `{` after condition starts the then-block, not a struct literal + let cond = self.parse_expr_no_struct()?; let then_block = self.parse_block()?; let else_block = if self.peek_kind() == Some(TokenKind::Else) { self.bump(); if self.peek_kind() == Some(TokenKind::If) { - let else_if = self.parse_if()?; + let else_if = self.parse_if_stmt()?; + let span = else_if.span(); Some(Block { - stmts: vec![Stmt::If(else_if.clone())], - span: else_if.span, + stmts: vec![else_if], + span, }) } else { Some(self.parse_block()?) @@ -550,17 +640,18 @@ impl Parser { let end = else_block .as_ref() .map_or(then_block.span.end, |b| b.span.end); - Ok(IfStmt { + Ok(Stmt::If(IfStmt { cond, then_block, else_block, span: Span::new(start, end), - }) + })) } fn parse_while(&mut self) -> Result { let start = self.expect(TokenKind::While)?.span.start; - let cond = self.parse_expr()?; + // Use parse_expr_no_struct because `{` after condition starts the loop body, not a struct literal + let cond = self.parse_expr_no_struct()?; let body = self.parse_block()?; let end = body.span.end; Ok(WhileStmt { @@ -570,6 +661,94 @@ impl Parser { }) } + fn parse_for_after(&mut self, start: usize) -> Result { + let var = self.expect_ident()?; + self.expect(TokenKind::In)?; + let range_start = self.parse_range_bound()?; + self.expect(TokenKind::DotDot)?; + let range_end = self.parse_range_bound()?; + let body = self.parse_block()?; + let end = body.span.end; + Ok(ForStmt { + var, + start: range_start, + end: range_end, + body, + span: Span::new(start, end), + }) + } + + fn parse_for_stmt(&mut self) -> Result { + let for_token = self.expect(TokenKind::For)?; + let start = for_token.span.start; + if self.peek_kind() == Some(TokenKind::LBrace) { + let body = self.parse_block()?; + let end = body.span.end; + let cond = Expr::Literal(LiteralExpr { + value: Literal::Bool(true), + span: for_token.span, + }); + return Ok(Stmt::While(WhileStmt { + cond, + body, + span: Span::new(start, end), + })); + } + Ok(Stmt::For(self.parse_for_after(start)?)) + } + + /// Parse a simple expression for range bounds (no struct literals allowed) + fn parse_range_bound(&mut self) -> Result { + match self.peek_kind() { + Some(TokenKind::Int) => { + let token = self.bump().unwrap(); + let value = token.text.parse::().map_err(|_| { + self.error_at(token.span, "invalid integer literal".to_string()) + })?; + Ok(Expr::Literal(LiteralExpr { + value: Literal::Int(value), + span: token.span, + })) + } + Some(TokenKind::True) => { + let token = self.bump().unwrap(); + Ok(Expr::Literal(LiteralExpr { + value: Literal::Bool(true), + span: token.span, + })) + } + Some(TokenKind::False) => { + let token = self.bump().unwrap(); + Ok(Expr::Literal(LiteralExpr { + value: Literal::Bool(false), + span: token.span, + })) + } + Some(TokenKind::Ident) => { + // Parse just a simple path (no struct literal) + let first_ident = self.expect_ident()?; + let start = first_ident.span.start; + let mut segments = vec![first_ident]; + + while self.peek_kind() == Some(TokenKind::ColonColon) { + self.bump(); + let segment = self.expect_ident()?; + segments.push(segment); + } + + let end = segments.last().unwrap().span.end; + Ok(Expr::Path(Path { + segments, + span: Span::new(start, end), + })) + } + Some(other) => Err(self.error_current(format!( + "expected integer or identifier in range bound, found {other:?}" + ))), + None => Err(self.error_current("unexpected end of input".to_string())), + } + } + fn parse_expr_stmt(&mut self) -> Result { let expr = self.parse_expr()?; let expr_span = expr.span(); @@ -583,11 +762,51 @@ impl Parser { } fn parse_expr(&mut self) -> Result { - self.parse_expr_bp(0) + self.parse_expr_inner(true) } - fn parse_expr_bp(&mut self, min_bp: u8) -> Result { - let mut lhs = self.parse_prefix()?; + /// Parse an expression where struct literals are not allowed. + /// Used in if/while/for/match scrutinee positions where `{` starts a block, not a struct literal. + fn parse_expr_no_struct(&mut self) -> Result { + self.parse_expr_inner(false) + } + + fn parse_expr_inner(&mut self, allow_struct_literal: bool) -> Result { + self.parse_expr_bp(0, allow_struct_literal) + } + + /// Look ahead to see if a `<...>` type-arg list is closed and followed by + /// a call `(` or struct literal `{`. + fn type_args_followed_by_call_or_struct(&self) -> bool { + if self.peek_kind() != Some(TokenKind::Lt) { + return false; + } + let mut depth = 0usize; + let mut idx = self.index; + while idx < self.tokens.len() { + match self.tokens[idx].kind { + TokenKind::Lt => depth += 1, + TokenKind::Gt => { + if depth == 0 { + return false; + } + depth -= 1; + if depth == 0 { + return matches!( + self.tokens.get(idx + 1).map(|t| &t.kind), + Some(TokenKind::LParen) | Some(TokenKind::LBrace) + ); + } + } + _ => {} + } + idx += 1; + } + false + } + + fn parse_expr_bp(&mut self, min_bp: u8, allow_struct_literal: bool) -> Result { + let mut lhs = self.parse_prefix(allow_struct_literal)?; loop { // First, check for postfix operators @@ -602,14 +821,14 @@ impl Parser { let start = lhs.span().start; self.bump(); // consume '.' let field = self.expect_ident()?; - let type_args = if self.peek_kind() == Some(TokenKind::LBracket) { + let type_args = if self.peek_kind() == Some(TokenKind::Lt) { self.parse_type_args()? } else { Vec::new() }; // Check if this is a struct literal (followed by '{') - if self.peek_kind() == Some(TokenKind::LBrace) { + if allow_struct_literal && self.peek_kind() == Some(TokenKind::LBrace) { // Convert the lhs and field into a path for the struct literal let mut path = match lhs { Expr::Path(p) => p, @@ -668,26 +887,16 @@ impl Parser { continue; } TokenKind::LBracket => { - let type_args = self.parse_type_args()?; - if self.peek_kind() == Some(TokenKind::LBrace) { - let path = match lhs { - Expr::Path(p) => p, - Expr::FieldAccess(ref fa) => self.field_access_to_path(fa)?, - _ => { - return Err(self.error_current( - "expected path before struct literal".to_string(), - )) - } - }; - lhs = self.parse_struct_literal(path, type_args)?; - continue; - } - if self.peek_kind() != Some(TokenKind::LParen) { - return Err(self.error_current( - "type arguments require a call or struct literal".to_string(), - )); - } - lhs = self.finish_call(lhs, type_args)?; + // With <> for generics, [] is unambiguously for indexing + let start = lhs.span().start; + self.bump(); // consume '[' + let index = self.parse_expr()?; + let end = self.expect(TokenKind::RBracket)?.span.end; + lhs = Expr::Index(IndexExpr { + object: Box::new(lhs), + index: Box::new(index), + span: Span::new(start, end), + }); continue; } TokenKind::Question => { @@ -704,6 +913,38 @@ impl Parser { } } + // Special handling for '<' which can be type arguments or less-than + if self.peek_kind() == Some(TokenKind::Lt) { + // Check if this looks like type arguments: path(args) or path{ ... } + if matches!(&lhs, Expr::Path(_) | Expr::FieldAccess(_)) + && self.type_args_followed_by_call_or_struct() + { + let type_args = self.parse_type_args()?; + if allow_struct_literal && self.peek_kind() == Some(TokenKind::LBrace) { + let path = match lhs { + Expr::Path(p) => p, + Expr::FieldAccess(ref fa) => self.field_access_to_path(fa)?, + _ => unreachable!(), + }; + lhs = self.parse_struct_literal(path, type_args)?; + continue; + } + if self.peek_kind() == Some(TokenKind::LParen) { + lhs = self.finish_call(lhs, type_args)?; + continue; + } + if !allow_struct_literal && self.peek_kind() == Some(TokenKind::LBrace) { + return Err(self.error_current( + "generic expressions in this context require parentheses".to_string(), + )); + } + return Err(self.error_current( + "type arguments require a call or struct literal".to_string(), + )); + } + // Fall through to treat as less-than comparison + } + // Then, check for binary operators let op = match self.peek_kind() { Some(TokenKind::OrOr) => BinaryOp::Or, @@ -727,7 +968,8 @@ impl Parser { } self.bump(); - let rhs = self.parse_expr_bp(r_bp)?; + // Propagate struct-literal allowance to avoid block ambiguity in no-struct contexts. + let rhs = self.parse_expr_bp(r_bp, allow_struct_literal)?; let span = Span::new(lhs.span().start, rhs.span().end); lhs = Expr::Binary(BinaryExpr { op, @@ -740,11 +982,12 @@ impl Parser { Ok(lhs) } - fn parse_prefix(&mut self) -> Result { + fn parse_prefix(&mut self, allow_struct_literal: bool) -> Result { match self.peek_kind() { Some(TokenKind::Minus) => { let start = self.bump().unwrap().span.start; - let expr = self.parse_expr_bp(7)?; + // Propagate struct-literal allowance to avoid block ambiguity in no-struct contexts. + let expr = self.parse_expr_bp(7, allow_struct_literal)?; Ok(Expr::Unary(UnaryExpr { op: UnaryOp::Neg, span: Span::new(start, expr.span().end), @@ -753,7 +996,8 @@ impl Parser { } Some(TokenKind::Bang) => { let start = self.bump().unwrap().span.start; - let expr = self.parse_expr_bp(7)?; + // Propagate struct-literal allowance to avoid block ambiguity in no-struct contexts. + let expr = self.parse_expr_bp(7, allow_struct_literal)?; Ok(Expr::Unary(UnaryExpr { op: UnaryOp::Not, span: Span::new(start, expr.span().end), @@ -761,11 +1005,11 @@ impl Parser { })) } Some(TokenKind::Match) => self.parse_match(), - _ => self.parse_primary(), + _ => self.parse_primary(allow_struct_literal), } } - fn parse_primary(&mut self) -> Result { + fn parse_primary(&mut self, allow_struct_literal: bool) -> Result { match self.peek_kind() { Some(TokenKind::Int) => { let token = self.bump().unwrap(); @@ -805,6 +1049,16 @@ impl Parser { span: token.span, })) } + Some(TokenKind::Char) => { + let token = self.bump().unwrap(); + let value = unescape_char(&token.text).map_err(|message| { + self.error_at(token.span, format!("invalid char literal: {message}")) + })?; + Ok(Expr::Literal(LiteralExpr { + value: Literal::U8(value), + span: token.span, + })) + } Some(TokenKind::True) => { let token = self.bump().unwrap(); Ok(Expr::Literal(LiteralExpr { @@ -855,14 +1109,14 @@ impl Parser { span: Span::new(start, end), }; - if self.peek_kind() == Some(TokenKind::LBrace) { + if allow_struct_literal && self.peek_kind() == Some(TokenKind::LBrace) { self.parse_struct_literal(path, Vec::new()) } else { Ok(Expr::Path(path)) } } - Some(_other) => Err(self.error_current(format!( - "unexpected token in expression: {{other:?}}" + Some(other) => Err(self.error_current(format!( + "unexpected token in expression: {other:?}" ))), None => Err(self.error_current("unexpected end of input".to_string())), } @@ -871,7 +1125,8 @@ impl Parser { fn parse_match(&mut self) -> Result { let match_token = self.expect(TokenKind::Match)?; let start = match_token.span.start; - let expr = self.parse_expr()?; + // Use parse_expr_no_struct because `{` after the scrutinee starts the match arms, not a struct literal + let expr = self.parse_expr_no_struct()?; self.expect(TokenKind::LBrace)?; let mut arms = Vec::new(); while self.peek_kind() != Some(TokenKind::RBrace) { @@ -909,6 +1164,13 @@ impl Parser { })?; Ok(Pattern::Literal(Literal::Int(value))) } + Some(TokenKind::Char) => { + let token = self.bump().unwrap(); + let value = unescape_char(&token.text).map_err(|message| { + self.error_at(token.span, format!("invalid char literal: {message}")) + })?; + Ok(Pattern::Literal(Literal::U8(value))) + } Some(TokenKind::True) => { self.bump(); Ok(Pattern::Literal(Literal::Bool(true))) @@ -1028,9 +1290,9 @@ impl Parser { let path = self.parse_path()?; let mut args = Vec::new(); let mut end = path.span.end; - if self.peek_kind() == Some(TokenKind::LBracket) { + if self.peek_kind() == Some(TokenKind::Lt) { self.bump(); - if self.peek_kind() != Some(TokenKind::RBracket) { + if self.peek_kind() != Some(TokenKind::Gt) { loop { args.push(self.parse_type()?); if self.maybe_consume(TokenKind::Comma).is_none() { @@ -1038,7 +1300,7 @@ impl Parser { } } } - end = self.expect(TokenKind::RBracket)?.span.end; + end = self.expect(TokenKind::Gt)?.span.end; } let span = Span::new(path.span.start, end); Ok(Type::Path { path, args, span }) @@ -1099,12 +1361,12 @@ impl Parser { } fn parse_type_params(&mut self) -> Result, ParseError> { - if self.peek_kind() != Some(TokenKind::LBracket) { + if self.peek_kind() != Some(TokenKind::Lt) { return Ok(Vec::new()); } self.bump(); let mut params = Vec::new(); - if self.peek_kind() != Some(TokenKind::RBracket) { + if self.peek_kind() != Some(TokenKind::Gt) { loop { let ident = self.expect_ident()?; params.push(ident); @@ -1113,17 +1375,17 @@ impl Parser { } } } - self.expect(TokenKind::RBracket)?; + self.expect(TokenKind::Gt)?; Ok(params) } fn parse_type_args(&mut self) -> Result, ParseError> { - if self.peek_kind() != Some(TokenKind::LBracket) { + if self.peek_kind() != Some(TokenKind::Lt) { return Ok(Vec::new()); } self.bump(); let mut args = Vec::new(); - if self.peek_kind() != Some(TokenKind::RBracket) { + if self.peek_kind() != Some(TokenKind::Gt) { loop { args.push(self.parse_type()?); if self.maybe_consume(TokenKind::Comma).is_none() { @@ -1131,15 +1393,15 @@ impl Parser { } } } - self.expect(TokenKind::RBracket)?; + self.expect(TokenKind::Gt)?; Ok(args) } fn expect(&mut self, kind: TokenKind) -> Result { match self.peek_kind() { Some(k) if k == kind => Ok(self.bump().unwrap()), - Some(_other) => Err(self.error_current(format!( - "expected {{kind:?}}, found {{other:?}}" + Some(other) => Err(self.error_current(format!( + "expected {kind:?}, found {other:?}" ))), None => Err(self.error_current("unexpected end of input".to_string())), } @@ -1148,8 +1410,8 @@ impl Parser { fn expect_ident(&mut self) -> Result { match self.peek_kind() { Some(TokenKind::Ident) => Ok(to_ident(&self.bump().unwrap())), - Some(_other) => Err(self.error_current(format!( - "expected identifier, found {{other:?}}" + Some(other) => Err(self.error_current(format!( + "expected identifier, found {other:?}" ))), None => Err(self.error_current("unexpected end of input".to_string())), } @@ -1252,6 +1514,42 @@ fn unescape_string(text: &str) -> Result { Ok(out) } +fn unescape_char(text: &str) -> Result { + let mut chars = text.chars(); + if chars.next() != Some('\'') || text.len() < 2 { + return Err("missing quotes".to_string()); + } + let Some(ch) = chars.next() else { + return Err("empty char literal".to_string()); + }; + let value = if ch == '\\' { + let Some(esc) = chars.next() else { + return Err("invalid escape".to_string()); + }; + match esc { + 'n' => b'\n', + 'r' => b'\r', + 't' => b'\t', + '\\' => b'\\', + '\'' => b'\'', + 'x' => { + let hi = chars.next().ok_or_else(|| "invalid hex escape".to_string())?; + let lo = chars.next().ok_or_else(|| "invalid hex escape".to_string())?; + let hex = format!("{hi}{lo}"); + u8::from_str_radix(&hex, 16).map_err(|_| "invalid hex escape".to_string())? + } + other => return Err(format!("unsupported escape \\{other}")), + } + } else { + let code = ch as u32; + if code > 255 { + return Err("char literal out of range".to_string()); + } + code as u8 + }; + Ok(value) +} + trait SpanExt { fn span(&self) -> Span; } @@ -1264,6 +1562,7 @@ impl SpanExt for Expr { Expr::Call(call) => call.span, Expr::MethodCall(method_call) => method_call.span, Expr::FieldAccess(field) => field.span, + Expr::Index(index) => index.span, Expr::StructLiteral(lit) => lit.span, Expr::Unary(unary) => unary.span, Expr::Binary(binary) => binary.span, diff --git a/capc/src/typeck/check.rs b/capc/src/typeck/check.rs index ccd62e9..2ee65b0 100644 --- a/capc/src/typeck/check.rs +++ b/capc/src/typeck/check.rs @@ -4,10 +4,11 @@ use crate::ast::*; use crate::error::TypeError; use super::{ - build_type_params, is_affine_type, is_numeric_type, is_orderable_type, lower_type, - resolve_enum_variant, resolve_method_target, resolve_path, resolve_type_name, type_contains_ref, - type_kind, validate_type_args, BuiltinType, EnumInfo, FunctionSig, MoveState, Scopes, - SpanExt, StdlibIndex, StructInfo, Ty, TypeKind, TypeTable, UseMap, UseMode, + build_type_params, is_affine_type, is_numeric_type, is_orderable_type, is_string_ty, + lower_type, resolve_enum_variant, resolve_method_target, resolve_path, resolve_type_name, + stdlib_string_ty, type_contains_ref, type_kind, validate_type_args, BuiltinType, EnumInfo, + FunctionSig, MoveState, Scopes, SpanExt, StdlibIndex, StructInfo, Ty, TypeKind, TypeTable, + UseMap, UseMode, }; /// Optional recorder for expression types during checking. @@ -148,6 +149,9 @@ fn block_contains_ptr(block: &Block) -> Option { } } Stmt::Assign(_) => {} + Stmt::Defer(_) => {} + Stmt::Break(_) => {} + Stmt::Continue(_) => {} Stmt::If(if_stmt) => { if let Some(span) = block_contains_ptr(&if_stmt.then_block) { return Some(span); @@ -165,6 +169,11 @@ fn block_contains_ptr(block: &Block) -> Option { return Some(span); } } + Stmt::For(for_stmt) => { + if let Some(span) = block_contains_ptr(&for_stmt.body) { + return Some(span); + } + } Stmt::Expr(expr_stmt) => { if let Expr::Match(match_expr) = &expr_stmt.expr { for arm in &match_expr.arms { @@ -185,6 +194,7 @@ fn block_contains_ptr(block: &Block) -> Option { fn stmt_is_total(stmt: &Stmt) -> bool { match stmt { Stmt::Return(ret_stmt) => ret_stmt.expr.is_some(), + Stmt::Defer(_) => false, Stmt::Expr(expr_stmt) => { if let Expr::Match(match_expr) = &expr_stmt.expr { match_is_total(match_expr) @@ -280,6 +290,7 @@ pub(super) fn check_function( stdlib, module_name, &type_params, + false, // not inside a loop at function top level )?; } @@ -317,6 +328,7 @@ fn check_stmt( stdlib: &StdlibIndex, module_name: &str, type_params: &HashSet, + in_loop: bool, ) -> Result<(), TypeError> { match stmt { Stmt::Let(let_stmt) => { @@ -376,7 +388,10 @@ fn check_stmt( } else { false }; - if annot_ty != expr_ty && !matches_ref { + if annot_ty != expr_ty + && !matches_ref + && !matches!(expr_ty, Ty::Builtin(BuiltinType::Never)) + { return Err(TypeError::new( format!("type mismatch: expected {annot_ty:?}, found {expr_ty:?}"), let_stmt.span, @@ -444,7 +459,7 @@ fn check_stmt( module_name, type_params, )?; - if expr_ty != existing { + if expr_ty != existing && !matches!(expr_ty, Ty::Builtin(BuiltinType::Never)) { return Err(TypeError::new( format!( "assignment type mismatch: expected {existing:?}, found {expr_ty:?}" @@ -454,6 +469,31 @@ fn check_stmt( } scopes.assign(&assign.name.item, expr_ty); } + Stmt::Defer(defer_stmt) => { + match &defer_stmt.expr { + Expr::Call(_) | Expr::MethodCall(_) => {} + _ => { + return Err(TypeError::new( + "defer expects a function or method call".to_string(), + defer_stmt.span, + )) + } + } + let _ = check_expr( + &defer_stmt.expr, + functions, + scopes, + UseMode::Move, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + ret_ty, + module_name, + type_params, + )?; + } Stmt::Return(ret_stmt) => { let expr_ty = if let Some(expr) = &ret_stmt.expr { check_expr( @@ -474,6 +514,10 @@ fn check_stmt( Ty::Builtin(BuiltinType::Unit) }; if &expr_ty != ret_ty { + if matches!(expr_ty, Ty::Builtin(BuiltinType::Never)) { + ensure_linear_all_consumed(scopes, struct_map, enum_map, ret_stmt.span)?; + return Ok(()); + } return Err(TypeError::new( format!("return type mismatch: expected {ret_ty:?}, found {expr_ty:?}"), ret_stmt.span, @@ -481,6 +525,36 @@ fn check_stmt( } ensure_linear_all_consumed(scopes, struct_map, enum_map, ret_stmt.span)?; } + Stmt::Break(break_stmt) => { + if !in_loop { + return Err(TypeError::new( + "break statement outside of loop".to_string(), + break_stmt.span, + )); + } + let depth = scopes.current_loop_depth().ok_or_else(|| { + TypeError::new("break statement outside of loop".to_string(), break_stmt.span) + })?; + ensure_linear_scopes_consumed_from(scopes, depth, struct_map, enum_map, break_stmt.span)?; + } + Stmt::Continue(continue_stmt) => { + if !in_loop { + return Err(TypeError::new( + "continue statement outside of loop".to_string(), + continue_stmt.span, + )); + } + let depth = scopes.current_loop_depth().ok_or_else(|| { + TypeError::new("continue statement outside of loop".to_string(), continue_stmt.span) + })?; + ensure_linear_scopes_consumed_from( + scopes, + depth, + struct_map, + enum_map, + continue_stmt.span, + )?; + } Stmt::If(if_stmt) => { let cond_ty = check_expr( &if_stmt.cond, @@ -515,6 +589,7 @@ fn check_stmt( stdlib, module_name, type_params, + in_loop, )?; let mut else_scopes = scopes.clone(); if let Some(block) = &if_stmt.else_block { @@ -530,6 +605,7 @@ fn check_stmt( stdlib, module_name, type_params, + in_loop, )?; } merge_branch_states( @@ -563,6 +639,7 @@ fn check_stmt( )); } let mut body_scopes = scopes.clone(); + body_scopes.push_loop(); check_block( &while_stmt.body, ret_ty, @@ -575,7 +652,9 @@ fn check_stmt( stdlib, module_name, type_params, + true, // inside loop, break/continue allowed )?; + body_scopes.pop_loop(); ensure_affine_states_match( scopes, &body_scopes, @@ -584,6 +663,87 @@ fn check_stmt( while_stmt.span, )?; } + Stmt::For(for_stmt) => { + // Check start expression - must be i32 + let start_ty = check_expr( + &for_stmt.start, + functions, + scopes, + UseMode::Read, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + ret_ty, + module_name, + type_params, + )?; + if start_ty != Ty::Builtin(BuiltinType::I32) { + return Err(TypeError::new( + "for loop range start must be i32".to_string(), + for_stmt.start.span(), + )); + } + + // Check end expression - must be i32 + let end_ty = check_expr( + &for_stmt.end, + functions, + scopes, + UseMode::Read, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + ret_ty, + module_name, + type_params, + )?; + if end_ty != Ty::Builtin(BuiltinType::I32) { + return Err(TypeError::new( + "for loop range end must be i32".to_string(), + for_stmt.end.span(), + )); + } + + // Create body scope with loop variable bound + let mut body_scopes = scopes.clone(); + body_scopes.push_scope(); + body_scopes.insert_local( + for_stmt.var.item.clone(), + Ty::Builtin(BuiltinType::I32), + ); + + body_scopes.push_loop(); + check_block( + &for_stmt.body, + ret_ty, + functions, + &mut body_scopes, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + module_name, + type_params, + true, // inside loop, break/continue allowed + )?; + body_scopes.pop_loop(); + + // Pop the loop variable scope before checking affine states + body_scopes.pop_scope(); + + ensure_affine_states_match( + scopes, + &body_scopes, + struct_map, + enum_map, + for_stmt.span, + )?; + } Stmt::Expr(expr_stmt) => { if let Expr::Match(match_expr) = &expr_stmt.expr { let _ = check_match_stmt( @@ -599,6 +759,7 @@ fn check_stmt( ret_ty, module_name, type_params, + in_loop, )?; } else { check_expr( @@ -635,6 +796,7 @@ fn check_block( stdlib: &StdlibIndex, module_name: &str, type_params: &HashSet, + in_loop: bool, ) -> Result<(), TypeError> { scopes.push_scope(); for stmt in &block.stmts { @@ -650,6 +812,7 @@ fn check_block( stdlib, module_name, type_params, + in_loop, )?; } ensure_linear_scope_consumed(scopes, struct_map, enum_map, block.span)?; @@ -752,6 +915,29 @@ fn ensure_linear_scope_consumed( Ok(()) } +/// Enforce that linear locals in scopes starting at a depth are consumed. +fn ensure_linear_scopes_consumed_from( + scopes: &Scopes, + depth: usize, + struct_map: &HashMap, + enum_map: &HashMap, + span: Span, +) -> Result<(), TypeError> { + for scope in scopes.stack.iter().skip(depth) { + for (name, info) in scope { + if type_kind(&info.ty, struct_map, enum_map) == TypeKind::Linear + && info.state != MoveState::Moved + { + return Err(TypeError::new( + format!("linear value `{name}` not consumed"), + span, + )); + } + } + } + Ok(()) +} + /// Enforce that all linear locals across scopes are consumed. fn ensure_linear_all_consumed( scopes: &Scopes, @@ -855,7 +1041,7 @@ pub(super) fn check_expr( Expr::Literal(lit) => match &lit.value { Literal::Int(_) => Ok(Ty::Builtin(BuiltinType::I32)), Literal::U8(_) => Ok(Ty::Builtin(BuiltinType::U8)), - Literal::String(_) => Ok(Ty::Builtin(BuiltinType::String)), + Literal::String(_) => Ok(stdlib_string_ty(stdlib)), Literal::Bool(_) => Ok(Ty::Builtin(BuiltinType::Bool)), Literal::Unit => Ok(Ty::Builtin(BuiltinType::Unit)), }, @@ -917,6 +1103,16 @@ pub(super) fn check_expr( )?; return record_expr_type(recorder, expr, Ty::Builtin(BuiltinType::Unit)); } + if name == "panic" { + if !call.args.is_empty() { + return Err(TypeError::new( + "panic takes no arguments".to_string(), + call.span, + )); + } + // panic() is a diverging expression - it never returns. + return record_expr_type(recorder, expr, Ty::Builtin(BuiltinType::Never)); + } if name == "Ok" || name == "Err" { if call.args.len() != 1 { return Err(TypeError::new( @@ -939,7 +1135,7 @@ pub(super) fn check_expr( type_params, )?; if let Ty::Path(ty_name, args) = ret_ty { - if ty_name == "Result" && args.len() == 2 { + if ty_name == "sys.result.Result" && args.len() == 2 { let expected = if name == "Ok" { &args[0] } else { &args[1] }; if &arg_ty != expected { return Err(TypeError::new( @@ -954,7 +1150,7 @@ pub(super) fn check_expr( recorder, expr, Ty::Path( - "Result".to_string(), + "sys.result.Result".to_string(), if name == "Ok" { vec![arg_ty, Ty::Builtin(BuiltinType::Unit)] } else { @@ -1040,7 +1236,10 @@ pub(super) fn check_expr( } else { false }; - if &arg_ty != expected_inner && !matches_ref { + if &arg_ty != expected_inner + && !matches_ref + && !matches!(arg_ty, Ty::Builtin(BuiltinType::Never)) + { return Err(TypeError::new( format!("argument type mismatch: expected {expected:?}, found {arg_ty:?}"), arg.span(), @@ -1150,7 +1349,10 @@ pub(super) fn check_expr( } else { false }; - if &arg_ty != expected_inner && !matches_ref { + if &arg_ty != expected_inner + && !matches_ref + && !matches!(arg_ty, Ty::Builtin(BuiltinType::Never)) + { return Err(TypeError::new( format!( "argument type mismatch: expected {expected:?}, found {arg_ty:?}" @@ -1176,81 +1378,11 @@ pub(super) fn check_expr( module_name, type_params, )?; - if let Ty::Path(name, args) = &receiver_ty { - if name == "Result" && args.len() == 2 { - let ok_ty = &args[0]; - let err_ty = &args[1]; - match method_call.method.item.as_str() { - "unwrap_or" => { - if method_call.args.len() != 1 { - return Err(TypeError::new( - "unwrap_or expects one argument".to_string(), - method_call.span, - )); - } - let arg_ty = check_expr( - &method_call.args[0], - functions, - scopes, - UseMode::Move, - recorder, - use_map, - struct_map, - enum_map, - stdlib, - ret_ty, - module_name, - type_params, - )?; - if &arg_ty != ok_ty { - return Err(TypeError::new( - format!( - "unwrap_or type mismatch: expected {ok_ty:?}, found {arg_ty:?}" - ), - method_call.args[0].span(), - )); - } - return record_expr_type(recorder, expr, ok_ty.clone()); - } - "unwrap_err_or" => { - if method_call.args.len() != 1 { - return Err(TypeError::new( - "unwrap_err_or expects one argument".to_string(), - method_call.span, - )); - } - let arg_ty = check_expr( - &method_call.args[0], - functions, - scopes, - UseMode::Move, - recorder, - use_map, - struct_map, - enum_map, - stdlib, - ret_ty, - module_name, - type_params, - )?; - if &arg_ty != err_ty { - return Err(TypeError::new( - format!( - "unwrap_err_or type mismatch: expected {err_ty:?}, found {arg_ty:?}" - ), - method_call.args[0].span(), - )); - } - return record_expr_type(recorder, expr, err_ty.clone()); - } - _ => {} - } - } - } let (method_module, type_name, receiver_args) = resolve_method_target( &receiver_ty, module_name, struct_map, + enum_map, method_call.receiver.span(), )?; let method_fn = format!("{type_name}__{}", method_call.method.item); @@ -1429,7 +1561,10 @@ pub(super) fn check_expr( } else { false }; - if &arg_ty != expected_inner && !matches_ref { + if &arg_ty != expected_inner + && !matches_ref + && !matches!(arg_ty, Ty::Builtin(BuiltinType::Never)) + { return Err(TypeError::new( format!("argument type mismatch: expected {expected:?}, found {arg_ty:?}"), arg.span(), @@ -1529,6 +1664,10 @@ pub(super) fn check_expr( || left == Ty::Builtin(BuiltinType::I64)) { Ok(left) + } else if matches!(left, Ty::Builtin(BuiltinType::Never)) + || matches!(right, Ty::Builtin(BuiltinType::Never)) + { + Ok(Ty::Builtin(BuiltinType::Never)) } else if left != right && is_numeric_type(&left) && is_numeric_type(&right) { Err(TypeError::new( "implicit numeric conversions are not allowed".to_string(), @@ -1544,6 +1683,10 @@ pub(super) fn check_expr( BinaryOp::Eq | BinaryOp::Neq => { if left == right { Ok(Ty::Builtin(BuiltinType::Bool)) + } else if matches!(left, Ty::Builtin(BuiltinType::Never)) + || matches!(right, Ty::Builtin(BuiltinType::Never)) + { + Ok(Ty::Builtin(BuiltinType::Bool)) } else if left != right && is_numeric_type(&left) && is_numeric_type(&right) { Err(TypeError::new( "implicit numeric conversions are not allowed".to_string(), @@ -1559,6 +1702,10 @@ pub(super) fn check_expr( BinaryOp::Lt | BinaryOp::Lte | BinaryOp::Gt | BinaryOp::Gte => { if left == right && is_orderable_type(&left) { Ok(Ty::Builtin(BuiltinType::Bool)) + } else if matches!(left, Ty::Builtin(BuiltinType::Never)) + || matches!(right, Ty::Builtin(BuiltinType::Never)) + { + Ok(Ty::Builtin(BuiltinType::Bool)) } else if left != right && is_numeric_type(&left) && is_numeric_type(&right) { Err(TypeError::new( "implicit numeric conversions are not allowed".to_string(), @@ -1598,6 +1745,7 @@ pub(super) fn check_expr( ret_ty, module_name, type_params, + false, // break/continue not allowed in value-producing match ), Expr::Try(try_expr) => { let inner_ty = check_expr( @@ -1615,7 +1763,7 @@ pub(super) fn check_expr( type_params, )?; let (ok_ty, err_ty) = match inner_ty { - Ty::Path(name, args) if name == "Result" && args.len() == 2 => { + Ty::Path(name, args) if name == "sys.result.Result" && args.len() == 2 => { (args[0].clone(), args[1].clone()) } _ => { @@ -1627,7 +1775,7 @@ pub(super) fn check_expr( }; let ret_err = match ret_ty { - Ty::Path(name, args) if name == "Result" && args.len() == 2 => &args[1], + Ty::Path(name, args) if name == "sys.result.Result" && args.len() == 2 => &args[1], _ => { return Err(TypeError::new( "the `?` operator can only be used in functions returning Result".to_string(), @@ -1762,6 +1910,113 @@ pub(super) fn check_expr( } Ok(field_ty) } + Expr::Index(index_expr) => { + // Type check the object (must be string, Slice[T], or MutSlice[T]) + let object_ty = check_expr( + &index_expr.object, + functions, + scopes, + UseMode::Read, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + ret_ty, + module_name, + type_params, + )?; + + // Type check the index (must be i32) + let index_ty = check_expr( + &index_expr.index, + functions, + scopes, + UseMode::Read, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + ret_ty, + module_name, + type_params, + )?; + + if index_ty != Ty::Builtin(BuiltinType::I32) { + return Err(TypeError::new( + format!("index must be i32, found {:?}", index_ty), + index_expr.index.span(), + )); + } + + // Determine element type based on object type + match &object_ty { + ty if is_string_ty(ty) => Ok(Ty::Builtin(BuiltinType::U8)), + Ty::Path(name, args) if name == "Slice" || name == "sys.buffer.Slice" => { + if args.len() != 1 { + return Err(TypeError::new( + "Slice requires exactly one type argument".to_string(), + index_expr.span, + )); + } + if args[0] != Ty::Builtin(BuiltinType::U8) { + return Err(TypeError::new( + "Slice indexing is only supported for Slice".to_string(), + index_expr.span, + )); + } + Ok(Ty::Builtin(BuiltinType::U8)) + } + Ty::Path(name, args) if name == "MutSlice" || name == "sys.buffer.MutSlice" => { + if args.len() != 1 { + return Err(TypeError::new( + "MutSlice requires exactly one type argument".to_string(), + index_expr.span, + )); + } + if args[0] != Ty::Builtin(BuiltinType::U8) { + return Err(TypeError::new( + "MutSlice indexing is only supported for MutSlice".to_string(), + index_expr.span, + )); + } + Ok(Ty::Builtin(BuiltinType::U8)) + } + // Vec types return Result + Ty::Path(name, _) if name == "VecString" || name == "sys.vec.VecString" => { + Ok(Ty::Path( + "sys.result.Result".to_string(), + vec![ + stdlib_string_ty(stdlib), + Ty::Path("sys.vec.VecErr".to_string(), vec![]), + ], + )) + } + Ty::Path(name, _) if name == "VecI32" || name == "sys.vec.VecI32" => { + Ok(Ty::Path( + "sys.result.Result".to_string(), + vec![ + Ty::Builtin(BuiltinType::I32), + Ty::Path("sys.vec.VecErr".to_string(), vec![]), + ], + )) + } + Ty::Path(name, _) if name == "VecU8" || name == "sys.vec.VecU8" => { + Ok(Ty::Path( + "sys.result.Result".to_string(), + vec![ + Ty::Builtin(BuiltinType::U8), + Ty::Path("sys.vec.VecErr".to_string(), vec![]), + ], + )) + } + _ => Err(TypeError::new( + format!("cannot index into type {:?}; only string, Slice[T], MutSlice[T], and Vec types are indexable", object_ty), + index_expr.span, + )), + } + } }?; recorder.record(expr, &ty); Ok(ty) @@ -1781,6 +2036,7 @@ fn check_match_stmt( ret_ty: &Ty, module_name: &str, type_params: &HashSet, + in_loop: bool, ) -> Result { let match_ty = check_expr( &match_expr.expr, @@ -1813,6 +2069,7 @@ fn check_match_stmt( stdlib, module_name, type_params, + in_loop, )?; arm_scope.pop_scope(); arm_scopes.push(arm_scope); @@ -1843,6 +2100,7 @@ fn check_match_expr_value( ret_ty: &Ty, module_name: &str, type_params: &HashSet, + in_loop: bool, ) -> Result { let match_ty = check_expr( &match_expr.expr, @@ -1876,11 +2134,16 @@ fn check_match_expr_value( ret_ty, module_name, type_params, + in_loop, )?; arm_scope.pop_scope(); arm_scopes.push(arm_scope); if let Some(prev) = &result_ty { - if prev != &arm_ty { + if matches!(prev, Ty::Builtin(BuiltinType::Never)) { + result_ty = Some(arm_ty); + } else if matches!(arm_ty, Ty::Builtin(BuiltinType::Never)) { + // Keep the previous type; never can coerce to any type. + } else if prev != &arm_ty { return Err(TypeError::new( format!("match arm type mismatch: expected {prev:?}, found {arm_ty:?}"), arm.body.span, @@ -1915,6 +2178,7 @@ fn check_match_arm_value( ret_ty: &Ty, module_name: &str, type_params: &HashSet, + in_loop: bool, ) -> Result { let Some((last, prefix)) = block.stmts.split_last() else { return Err(TypeError::new( @@ -1941,6 +2205,7 @@ fn check_match_arm_value( stdlib, module_name, type_params, + in_loop, )?; } match last { @@ -2008,7 +2273,7 @@ fn check_match_exhaustive( span, )); } - Ty::Path(name, args) if name == "Result" && args.len() == 2 => { + Ty::Path(name, args) if name == "sys.result.Result" && args.len() == 2 => { let mut seen_ok = false; let mut seen_err = false; for arm in arms { @@ -2374,7 +2639,7 @@ fn bind_pattern( .collect::>() .join("."); if let Ty::Path(ty_name, args) = match_ty { - if ty_name == "Result" && args.len() == 2 { + if ty_name == "sys.result.Result" && args.len() == 2 { let ty = if name == "Ok" { args[0].clone() } else if name == "Err" { diff --git a/capc/src/typeck/lower.rs b/capc/src/typeck/lower.rs index 7b40847..77d0b4b 100644 --- a/capc/src/typeck/lower.rs +++ b/capc/src/typeck/lower.rs @@ -4,11 +4,12 @@ use crate::ast::*; use crate::error::TypeError; use crate::abi::AbiType; use crate::hir::{ - HirAssignStmt, HirBinary, HirBlock, HirCall, HirEnum, HirEnumVariant, HirEnumVariantExpr, - HirExpr, HirExprStmt, HirExternFunction, HirField, HirFieldAccess, HirFunction, HirIfStmt, - HirLetStmt, HirLiteral, HirLocal, HirMatch, HirMatchArm, HirParam, HirPattern, HirReturnStmt, - HirStmt, HirStruct, HirStructLiteral, HirStructLiteralField, HirType, HirUnary, HirWhileStmt, - IntrinsicId, LocalId, ResolvedCallee, + HirAssignStmt, HirBinary, HirBlock, HirBreakStmt, HirCall, HirContinueStmt, HirDeferStmt, + HirEnum, HirEnumVariant, HirEnumVariantExpr, HirExpr, HirExprStmt, HirExternFunction, HirField, + HirFieldAccess, HirForStmt, HirFunction, HirIfStmt, HirLetStmt, HirLiteral, HirLocal, HirMatch, + HirMatchArm, HirParam, HirPattern, HirReturnStmt, HirStmt, HirStruct, HirStructLiteral, + HirStructLiteralField, HirTrap, HirType, HirUnary, HirWhileStmt, IntrinsicId, LocalId, + ResolvedCallee, }; use super::{ @@ -274,30 +275,30 @@ fn lower_function(func: &Function, ctx: &mut LoweringCtx) -> Result Result { ctx.push_scope(); - let stmts: Result, TypeError> = block - .stmts - .iter() - .map(|stmt| lower_stmt(stmt, ctx, ret_ty)) - .collect(); + let mut stmts = Vec::new(); + for stmt in &block.stmts { + let lowered = lower_stmt(stmt, ctx, ret_ty)?; + stmts.extend(lowered); + } ctx.pop_scope(); - Ok(HirBlock { stmts: stmts? }) + Ok(HirBlock { stmts }) } /// Lower a statement into HIR. -fn lower_stmt(stmt: &Stmt, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result { +fn lower_stmt(stmt: &Stmt, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result, TypeError> { match stmt { Stmt::Let(let_stmt) => { let expr = lower_expr(&let_stmt.expr, ctx, ret_ty)?; let ty = expr.ty().clone(); let local_id = ctx.fresh_local(let_stmt.name.item.clone(), ty.ty.clone()); - Ok(HirStmt::Let(HirLetStmt { + Ok(vec![HirStmt::Let(HirLetStmt { local_id, ty, expr, span: let_stmt.span, - })) + })]) } Stmt::Assign(assign) => { let expr = lower_expr(&assign.expr, ctx, ret_ty)?; @@ -305,18 +306,29 @@ fn lower_stmt(stmt: &Stmt, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result lower_defer_stmt(defer_stmt, ctx, ret_ty), Stmt::Return(ret) => { let expr = ret.expr.as_ref().map(|e| lower_expr(e, ctx, ret_ty)).transpose()?; - Ok(HirStmt::Return(HirReturnStmt { + Ok(vec![HirStmt::Return(HirReturnStmt { expr, span: ret.span, - })) + })]) + } + Stmt::Break(break_stmt) => { + Ok(vec![HirStmt::Break(HirBreakStmt { + span: break_stmt.span, + })]) + } + Stmt::Continue(continue_stmt) => { + Ok(vec![HirStmt::Continue(HirContinueStmt { + span: continue_stmt.span, + })]) } Stmt::If(if_stmt) => { let cond = lower_expr(&if_stmt.cond, ctx, ret_ty)?; @@ -327,49 +339,246 @@ fn lower_stmt(stmt: &Stmt, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result { let cond = lower_expr(&while_stmt.cond, ctx, ret_ty)?; let body = lower_block(&while_stmt.body, ctx, ret_ty)?; - Ok(HirStmt::While(HirWhileStmt { + Ok(vec![HirStmt::While(HirWhileStmt { cond, body, span: while_stmt.span, - })) + })]) + } + Stmt::For(for_stmt) => { + let start = lower_expr(&for_stmt.start, ctx, ret_ty)?; + let end = lower_expr(&for_stmt.end, ctx, ret_ty)?; + + // Create a fresh local for the loop variable + let var_id = ctx.fresh_local( + for_stmt.var.item.clone(), + crate::typeck::Ty::Builtin(crate::typeck::BuiltinType::I32), + ); + + let body = lower_block(&for_stmt.body, ctx, ret_ty)?; + + Ok(vec![HirStmt::For(HirForStmt { + var_id, + start, + end, + body, + span: for_stmt.span, + })]) } Stmt::Expr(expr_stmt) => { if let Expr::Match(match_expr) = &expr_stmt.expr { match lower_expr(&expr_stmt.expr, ctx, ret_ty) { Ok(expr) => { - return Ok(HirStmt::Expr(HirExprStmt { + return Ok(vec![HirStmt::Expr(HirExprStmt { expr, span: expr_stmt.span, - })); + })]); } Err(_) => { let expr = lower_match_stmt(match_expr, ctx, ret_ty)?; - return Ok(HirStmt::Expr(HirExprStmt { + return Ok(vec![HirStmt::Expr(HirExprStmt { expr, span: expr_stmt.span, - })); + })]); } } } let expr = lower_expr(&expr_stmt.expr, ctx, ret_ty)?; - Ok(HirStmt::Expr(HirExprStmt { + Ok(vec![HirStmt::Expr(HirExprStmt { expr, span: expr_stmt.span, - })) + })]) + } + } +} + +fn lower_defer_stmt( + defer_stmt: &DeferStmt, + ctx: &mut LoweringCtx, + ret_ty: &Ty, +) -> Result, TypeError> { + let mut stmts = Vec::new(); + let expr_ty = type_of_ast_expr(&defer_stmt.expr, ctx, ret_ty)?; + let hir_ret_ty = hir_type_for(expr_ty, ctx, defer_stmt.expr.span())?; + + let deferred = match &defer_stmt.expr { + Expr::Call(call) => { + let mut args = Vec::with_capacity(call.args.len()); + for arg in &call.args { + args.push(capture_defer_expr(arg, ctx, ret_ty, &mut stmts)?); + } + let path = call.callee.to_path().ok_or_else(|| { + TypeError::new( + "call target must be a function path".to_string(), + call.callee.span(), + ) + })?; + lower_defer_call_from_path(&path, &call.type_args, args, hir_ret_ty, ctx)? + } + Expr::MethodCall(method_call) => { + fn get_leftmost_segment(expr: &Expr) -> Option<&str> { + match expr { + Expr::Path(path) if path.segments.len() == 1 => Some(&path.segments[0].item), + Expr::FieldAccess(fa) => get_leftmost_segment(&fa.object), + _ => None, + } + } + + let base_is_local = if let Some(base_name) = get_leftmost_segment(&method_call.receiver) { + ctx.local_types.contains_key(base_name) + } else { + true + }; + + let path_call = method_call.receiver.to_path().map(|mut path| { + path.segments.push(method_call.method.clone()); + path.span = Span::new(path.span.start, method_call.method.span.end); + path + }); + + let is_function = if let Some(path) = &path_call { + let resolved = super::resolve_path(path, ctx.use_map); + let key = resolved.join("."); + ctx.functions.contains_key(&key) + } else { + false + }; + + if !base_is_local && is_function { + let path = path_call.expect("path exists for function call"); + let mut args = Vec::with_capacity(method_call.args.len()); + for arg in &method_call.args { + args.push(capture_defer_expr(arg, ctx, ret_ty, &mut stmts)?); + } + let deferred = lower_defer_call_from_path( + &path, + &method_call.type_args, + args, + hir_ret_ty, + ctx, + )?; + stmts.push(HirStmt::Defer(HirDeferStmt { + expr: deferred, + span: defer_stmt.span, + })); + return Ok(stmts); + } + + let receiver = capture_defer_expr(&method_call.receiver, ctx, ret_ty, &mut stmts)?; + let receiver_ty = type_of_ast_expr(&method_call.receiver, ctx, ret_ty)?; + let (method_module, type_name, _) = resolve_method_target( + &receiver_ty, + ctx.module_name, + ctx.structs, + ctx.enums, + method_call.receiver.span(), + )?; + let method_fn = format!("{type_name}__{}", method_call.method.item); + let key = format!("{method_module}.{method_fn}"); + let symbol = format!("capable_{}", key.replace('.', "_")); + + let mut args = Vec::with_capacity(method_call.args.len() + 1); + args.push(receiver); + for arg in &method_call.args { + args.push(capture_defer_expr(arg, ctx, ret_ty, &mut stmts)?); + } + + HirExpr::Call(HirCall { + callee: ResolvedCallee::Function { + module: method_module, + name: method_fn, + symbol, + }, + type_args: lower_call_type_args(&method_call.type_args, ctx)?, + args, + ret_ty: hir_ret_ty, + span: method_call.span, + }) + } + _ => { + return Err(TypeError::new( + "defer expects a function or method call".to_string(), + defer_stmt.span, + )) + } + }; + + stmts.push(HirStmt::Defer(HirDeferStmt { + expr: deferred, + span: defer_stmt.span, + })); + Ok(stmts) +} + +fn lower_defer_call_from_path( + path: &Path, + type_args: &[Type], + args: Vec, + ret_ty: HirType, + ctx: &mut LoweringCtx, +) -> Result { + + if path.segments.len() == 1 { + let name = &path.segments[0].item; + if name == "drop" || name == "panic" || name == "Ok" || name == "Err" { + return Err(TypeError::new( + "defer expects a function or method call".to_string(), + path.span, + )); } } + + let mut resolved = super::resolve_path(&path, ctx.use_map); + if resolved.len() == 1 { + resolved.insert(0, ctx.module_name.to_string()); + } + let key = resolved.join("."); + let module = resolved[..resolved.len() - 1].join("."); + let name = resolved.last().unwrap().clone(); + let symbol = format!("capable_{}", key.replace('.', "_")); + + Ok(HirExpr::Call(HirCall { + callee: ResolvedCallee::Function { module, name, symbol }, + type_args: lower_call_type_args(type_args, ctx)?, + args, + ret_ty, + span: path.span, + })) +} + +fn capture_defer_expr( + expr: &Expr, + ctx: &mut LoweringCtx, + ret_ty: &Ty, + stmts: &mut Vec, +) -> Result { + let hir_expr = lower_expr(expr, ctx, ret_ty)?; + let ty = hir_expr.ty().clone(); + let local_name = format!("__defer_{}", ctx.local_counter); + let local_id = ctx.fresh_local(local_name, ty.ty.clone()); + stmts.push(HirStmt::Let(HirLetStmt { + local_id, + ty: ty.clone(), + expr: hir_expr, + span: expr.span(), + })); + Ok(HirExpr::Local(HirLocal { + local_id, + ty, + span: expr.span(), + })) } /// Helper to get the type of an AST expression using the existing typechecker. @@ -431,14 +640,14 @@ fn abi_type_for(ty: &Ty, ctx: &LoweringCtx, span: Span) -> Result Ok(AbiType::U32), BuiltinType::U8 => Ok(AbiType::U8), BuiltinType::Bool => Ok(AbiType::Bool), - BuiltinType::String => Ok(AbiType::String), BuiltinType::Unit => Ok(AbiType::Unit), + BuiltinType::Never => Ok(AbiType::Unit), }, Ty::Ptr(_) => Ok(AbiType::Ptr), Ty::Ref(inner) => abi_type_for(inner, ctx, span), Ty::Param(_) => Ok(AbiType::Ptr), Ty::Path(name, args) => { - if name == "Result" && args.len() == 2 { + if name == "sys.result.Result" && args.len() == 2 { let ok = abi_type_for(&args[0], ctx, span)?; let err = abi_type_for(&args[1], ctx, span)?; return Ok(AbiType::Result(Box::new(ok), Box::new(err))); @@ -541,6 +750,13 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result Result { - let default_expr = lower_expr(&method_call.args[0], ctx, ret_ty)?; - let ok_name = format!("__unwrap_ok_{}", ctx.local_counter); - let ok_local_id = ctx.fresh_local(ok_name.clone(), args[0].clone()); - let ok_pattern = HirPattern::Variant { - variant_name: "Ok".to_string(), - binding: Some(ok_local_id), - }; - let err_pattern = HirPattern::Variant { - variant_name: "Err".to_string(), - binding: None, - }; - let ok_expr = HirExpr::Local(HirLocal { - local_id: ok_local_id, - ty: hir_type_for(args[0].clone(), ctx, method_call.span)?, - span: method_call.span, - }); - let ok_block = HirBlock { - stmts: vec![HirStmt::Expr(HirExprStmt { - expr: ok_expr, - span: method_call.span, - })], - }; - let err_block = HirBlock { - stmts: vec![HirStmt::Expr(HirExprStmt { - expr: default_expr, - span: method_call.span, - })], - }; - return Ok(HirExpr::Match(HirMatch { - expr: Box::new(receiver), - arms: vec![ - HirMatchArm { - pattern: ok_pattern, - body: ok_block, - }, - HirMatchArm { - pattern: err_pattern, - body: err_block, - }, - ], - result_ty: hir_ty, - span: method_call.span, - })); - } - "unwrap_err_or" => { - let default_expr = lower_expr(&method_call.args[0], ctx, ret_ty)?; - let err_name = format!("__unwrap_err_{}", ctx.local_counter); - let err_local_id = ctx.fresh_local(err_name.clone(), args[1].clone()); - let ok_pattern = HirPattern::Variant { - variant_name: "Ok".to_string(), - binding: None, - }; - let err_pattern = HirPattern::Variant { - variant_name: "Err".to_string(), - binding: Some(err_local_id), - }; - let err_expr = HirExpr::Local(HirLocal { - local_id: err_local_id, - ty: hir_type_for(args[1].clone(), ctx, method_call.span)?, - span: method_call.span, - }); - let ok_block = HirBlock { - stmts: vec![HirStmt::Expr(HirExprStmt { - expr: default_expr, - span: method_call.span, - })], - }; - let err_block = HirBlock { - stmts: vec![HirStmt::Expr(HirExprStmt { - expr: err_expr, - span: method_call.span, - })], - }; - return Ok(HirExpr::Match(HirMatch { - expr: Box::new(receiver), - arms: vec![ - HirMatchArm { - pattern: ok_pattern, - body: ok_block, - }, - HirMatchArm { - pattern: err_pattern, - body: err_block, - }, - ], - result_ty: hir_ty, - span: method_call.span, - })); - } - _ => {} - } - } - } let (method_module, type_name, _) = resolve_method_target( &receiver_ty, ctx.module_name, ctx.structs, + ctx.enums, method_call.receiver.span(), )?; let method_fn = format!("{type_name}__{}", method_call.method.item); @@ -907,7 +1027,7 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result args[0].clone(), + Ty::Path(name, args) if name == "sys.result.Result" && args.len() == 2 => args[0].clone(), _ => { return Err(TypeError::new( "the `?` operator expects a Result value".to_string(), @@ -923,6 +1043,54 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result { + let object = lower_expr(&index_expr.object, ctx, ret_ty)?; + let index = lower_expr(&index_expr.index, ctx, ret_ty)?; + + // Check if this is a Vec type - if so, desugar to .get() call + let object_ty = &object.ty().ty; + let is_vec_type = matches!(object_ty, + Ty::Path(name, _) if name == "VecString" || name == "sys.vec.VecString" + || name == "VecI32" || name == "sys.vec.VecI32" + || name == "VecU8" || name == "sys.vec.VecU8" + ); + + if is_vec_type { + // Desugar vec[i] to vec.get(i) + // Get the short type name (e.g., "VecString" from "sys.vec.VecString") + let type_name = match object_ty { + Ty::Path(name, _) => { + if name.starts_with("sys.vec.") { + name.strip_prefix("sys.vec.").unwrap() + } else { + name.as_str() + } + } + _ => unreachable!(), + }; + let key = format!("sys.vec.{}__get", type_name); + let symbol = format!("capable_{}", key.replace('.', "_").replace("__", "_")); + + Ok(HirExpr::Call(crate::hir::HirCall { + callee: ResolvedCallee::Function { + module: "sys.vec".to_string(), + name: format!("{}__get", type_name), + symbol, + }, + type_args: vec![], + args: vec![object, index], + ret_ty: hir_ty, + span: index_expr.span, + })) + } else { + Ok(HirExpr::Index(crate::hir::HirIndex { + object: Box::new(object), + index: Box::new(index), + elem_ty: hir_ty, + span: index_expr.span, + })) + } + } } } @@ -997,30 +1165,29 @@ fn lower_pattern( } Pattern::Call { path, binding, span } => { - if path.segments.len() == 1 { - let name = &path.segments[0].item; - if name == "Ok" || name == "Err" { - if let Ty::Path(ty_name, args) = &match_ty.ty { - if ty_name == "Result" && args.len() == 2 { - let variant_name = name.clone(); - - let binding_info = if let Some(bind_ident) = binding { - let bind_ty = if variant_name == "Ok" { - args[0].clone() - } else { - args[1].clone() - }; - let local_id = ctx.fresh_local(bind_ident.item.clone(), bind_ty); - Some(local_id) + // Check for Result::Ok/Err variants (both qualified and unqualified) + let variant_name = path.segments.last().map(|s| s.item.as_str()); + if variant_name == Some("Ok") || variant_name == Some("Err") { + if let Ty::Path(ty_name, args) = &match_ty.ty { + if ty_name == "sys.result.Result" && args.len() == 2 { + let variant_name = variant_name.unwrap().to_string(); + + let binding_info = if let Some(bind_ident) = binding { + let bind_ty = if variant_name == "Ok" { + args[0].clone() } else { - None + args[1].clone() }; + let local_id = ctx.fresh_local(bind_ident.item.clone(), bind_ty); + Some(local_id) + } else { + None + }; - return Ok(HirPattern::Variant { - variant_name, - binding: binding_info, - }); - } + return Ok(HirPattern::Variant { + variant_name, + binding: binding_info, + }); } } } diff --git a/capc/src/typeck/mod.rs b/capc/src/typeck/mod.rs index 142d76d..44a65b6 100644 --- a/capc/src/typeck/mod.rs +++ b/capc/src/typeck/mod.rs @@ -19,8 +19,8 @@ use crate::ast::*; use crate::error::TypeError; use crate::hir::HirModule; -pub(super) const RESERVED_TYPE_PARAMS: [&str; 8] = [ - "i32", "i64", "u32", "u8", "bool", "string", "unit", "Result", +pub(super) const RESERVED_TYPE_PARAMS: [&str; 7] = [ + "i32", "i64", "u32", "u8", "bool", "unit", "never", ]; /// Resolved type used after lowering. No spans, fully qualified paths. @@ -88,8 +88,8 @@ pub enum BuiltinType { U32, U8, Bool, - String, Unit, + Never, } pub(super) fn function_key(module_name: &str, func_name: &str) -> String { @@ -212,6 +212,8 @@ struct Scopes { /// Stack of scopes, where each scope is a map from name to local info. /// The last element is the innermost (current) scope. stack: Vec>, + /// Stack of scope depths that mark loop bodies for break/continue checks. + loop_stack: Vec, } impl Scopes { @@ -280,7 +282,22 @@ impl Scopes { }, ); } - Scopes { stack: vec![scope] } + Scopes { + stack: vec![scope], + loop_stack: Vec::new(), + } + } + + fn push_loop(&mut self) { + self.loop_stack.push(self.stack.len()); + } + + fn pop_loop(&mut self) { + self.loop_stack.pop(); + } + + fn current_loop_depth(&self) -> Option { + self.loop_stack.last().copied() } fn contains(&self, name: &str) -> bool { @@ -355,6 +372,7 @@ fn resolve_method_target( receiver_ty: &Ty, module_name: &str, struct_map: &HashMap, + enum_map: &HashMap, span: Span, ) -> Result<(String, String, Vec), TypeError> { let base_ty = match receiver_ty { @@ -363,13 +381,6 @@ fn resolve_method_target( }; let (receiver_name, receiver_args) = match base_ty { Ty::Path(name, args) => (name.as_str(), args), - Ty::Builtin(BuiltinType::String) => { - return Ok(( - "sys.string".to_string(), - "string".to_string(), - Vec::new(), - )); - } Ty::Builtin(BuiltinType::U8) => { return Ok(( "sys.bytes".to_string(), @@ -379,7 +390,7 @@ fn resolve_method_target( } _ => { return Err(TypeError::new( - "method receiver must be a struct value".to_string(), + "method receiver must be a struct or enum value".to_string(), span, )); } @@ -394,6 +405,19 @@ fn resolve_method_target( return Ok((info.module.clone(), type_name, receiver_args.clone())); } + if enum_map.contains_key(receiver_name) { + let type_name = receiver_name + .rsplit_once('.') + .map(|(_, t)| t) + .unwrap_or(receiver_name) + .to_string(); + let mod_part = receiver_name + .rsplit_once('.') + .map(|(m, _)| m) + .unwrap_or(module_name); + return Ok((mod_part.to_string(), type_name, receiver_args.clone())); + } + if receiver_name.contains('.') { let (mod_part, type_part) = receiver_name.rsplit_once('.').ok_or_else(|| { TypeError::new("invalid type path".to_string(), span) @@ -408,9 +432,12 @@ fn resolve_method_target( if let Some(info) = struct_map.get(&format!("{module_name}.{receiver_name}")) { return Ok((info.module.clone(), receiver_name.to_string(), receiver_args.clone())); } + if enum_map.contains_key(&format!("{module_name}.{receiver_name}")) { + return Ok((module_name.to_string(), receiver_name.to_string(), receiver_args.clone())); + } Err(TypeError::new( - format!("unknown struct `{receiver_name}`"), + format!("unknown struct or enum `{receiver_name}`"), span, )) } @@ -420,6 +447,7 @@ fn resolve_impl_target( use_map: &UseMap, stdlib: &StdlibIndex, struct_map: &HashMap, + enum_map: &HashMap, type_params: &HashSet, module_name: &str, span: Span, @@ -427,6 +455,7 @@ fn resolve_impl_target( let target_ty = lower_type(target, use_map, stdlib, type_params)?; let (impl_module, type_name) = match &target_ty { Ty::Path(target_name, _target_args) => { + // Check struct_map first if let Some(info) = struct_map.get(target_name) { let type_name = target_name .rsplit_once('.') @@ -434,6 +463,18 @@ fn resolve_impl_target( .unwrap_or(target_name) .to_string(); (info.module.clone(), type_name) + // Check enum_map + } else if enum_map.contains_key(target_name) { + let type_name = target_name + .rsplit_once('.') + .map(|(_, t)| t) + .unwrap_or(target_name) + .to_string(); + let mod_part = target_name + .rsplit_once('.') + .map(|(m, _)| m) + .unwrap_or(module_name); + (mod_part.to_string(), type_name) } else if target_name.contains('.') { let (mod_part, type_part) = target_name.rsplit_once('.').ok_or_else(|| { TypeError::new("invalid type path".to_string(), span) @@ -441,18 +482,19 @@ fn resolve_impl_target( (mod_part.to_string(), type_part.to_string()) } else if let Some(info) = struct_map.get(&format!("{module_name}.{target_name}")) { (info.module.clone(), target_name.clone()) + } else if enum_map.contains_key(&format!("{module_name}.{target_name}")) { + (module_name.to_string(), target_name.clone()) } else { return Err(TypeError::new( - "impl target must be a struct type name".to_string(), + "impl target must be a struct or enum type name".to_string(), span, )); } } - Ty::Builtin(BuiltinType::String) => ("sys.string".to_string(), "string".to_string()), Ty::Builtin(BuiltinType::U8) => ("sys.bytes".to_string(), "u8".to_string()), _ => { return Err(TypeError::new( - "impl target must be a struct type name".to_string(), + "impl target must be a struct or enum type name".to_string(), span, )); } @@ -531,10 +573,13 @@ fn validate_impl_method( let ret_ty = lower_type(&method.ret, use_map, stdlib, type_params)?; if receiver_is_ref && type_contains_capability(&ret_ty, struct_map, enum_map) { - return Err(TypeError::new( - "methods returning capabilities must take `self` by value".to_string(), - method.ret.span(), - )); + let receiver_kind = type_kind(target_ty, struct_map, enum_map); + if receiver_kind != TypeKind::Unrestricted { + return Err(TypeError::new( + "methods returning capabilities must take `self` by value".to_string(), + method.ret.span(), + )); + } } Ok(params) @@ -576,6 +621,7 @@ fn desugar_impl_methods( use_map, stdlib, struct_map, + enum_map, &impl_type_params, module_name, impl_block.span, @@ -658,19 +704,65 @@ fn lower_type( "u32" => Some(BuiltinType::U32), "u8" => Some(BuiltinType::U8), "bool" => Some(BuiltinType::Bool), - "string" => Some(BuiltinType::String), "unit" => Some(BuiltinType::Unit), + "never" => Some(BuiltinType::Never), _ => None, }; if let Some(builtin) = builtin { return Ok(Ty::Builtin(builtin)); } + let resolved_joined = resolved.join("."); let alias = resolve_type_name(path, use_map, stdlib); - if alias != resolved.join(".") { - return Ok(Ty::Path(alias, args)); + let joined = if alias != resolved_joined { + alias + } else { + resolved_joined + }; + if joined == "Vec" || joined == "sys.vec.Vec" { + if args.len() != 1 { + return Err(TypeError::new( + format!("Vec expects 1 type argument, found {}", args.len()), + path.span, + )); + } + let elem = &args[0]; + let vec_name = match elem { + Ty::Builtin(BuiltinType::U8) => "sys.vec.VecU8", + Ty::Builtin(BuiltinType::I32) => "sys.vec.VecI32", + _ if is_string_ty(elem) => "sys.vec.VecString", + _ => { + return Err(TypeError::new( + "Vec only supports u8, i32, and string element types".to_string(), + path.span, + )) + } + }; + return Ok(Ty::Path(vec_name.to_string(), Vec::new())); } + return Ok(Ty::Path(joined, args)); } let joined = path_segments.join("."); + if joined == "Vec" || joined == "sys.vec.Vec" { + if args.len() != 1 { + return Err(TypeError::new( + format!("Vec expects 1 type argument, found {}", args.len()), + path.span, + )); + } + let elem = &args[0]; + let vec_name = match elem { + Ty::Builtin(BuiltinType::U8) => "sys.vec.VecU8", + Ty::Builtin(BuiltinType::I32) => "sys.vec.VecI32", + _ if is_string_ty(elem) => "sys.vec.VecString", + _ => { + return Err(TypeError::new( + "Vec only supports u8, i32, and string element types".to_string(), + path.span, + )) + } + }; + return Ok(Ty::Path(vec_name.to_string(), Vec::new())); + } Ok(Ty::Path(joined, args)) } } @@ -720,6 +812,19 @@ fn resolve_type_name(path: &Path, use_map: &UseMap, stdlib: &StdlibIndex) -> Str resolved.join(".") } +pub(super) fn is_string_ty(ty: &Ty) -> bool { + matches!(ty, Ty::Path(name, _) if name == "sys.string.string" || name == "string") +} + +fn stdlib_string_ty(stdlib: &StdlibIndex) -> Ty { + let name = stdlib + .types + .get("string") + .cloned() + .unwrap_or_else(|| "sys.string.string".to_string()); + Ty::Path(name, Vec::new()) +} + fn type_contains_ref(ty: &Type) -> Option { match ty { Type::Ref { span, .. } => Some(*span), @@ -754,7 +859,7 @@ fn type_contains_capability_inner( Ty::Builtin(_) | Ty::Ptr(_) | Ty::Ref(_) => false, Ty::Param(_) => true, Ty::Path(name, args) => { - if name == "Result" { + if name == "sys.result.Result" { return args .iter() .any(|arg| type_contains_capability_inner(arg, struct_map, enum_map, visiting)); @@ -836,7 +941,7 @@ fn type_kind_inner( Ty::Builtin(_) | Ty::Ptr(_) | Ty::Ref(_) => TypeKind::Unrestricted, Ty::Param(_) => TypeKind::Affine, Ty::Path(name, args) => { - if name == "Result" { + if name == "sys.result.Result" { return args.iter().fold(TypeKind::Unrestricted, |acc, arg| { combine_kind(acc, type_kind_inner(arg, struct_map, enum_map, visiting)) }); @@ -879,14 +984,7 @@ fn validate_type_args( Ty::Builtin(_) | Ty::Param(_) => Ok(()), Ty::Ptr(inner) | Ty::Ref(inner) => validate_type_args(inner, struct_map, enum_map, span), Ty::Path(name, args) => { - if name == "Result" { - if args.len() != 2 { - return Err(TypeError::new( - format!("Result expects 2 type arguments, found {}", args.len()), - span, - )); - } - } else if let Some(info) = struct_map.get(name) { + if let Some(info) = struct_map.get(name) { if args.len() != info.type_params.len() { return Err(TypeError::new( format!( @@ -1086,6 +1184,7 @@ impl SpanExt for Expr { Expr::Call(call) => call.span, Expr::MethodCall(method_call) => method_call.span, Expr::FieldAccess(field) => field.span, + Expr::Index(index) => index.span, Expr::StructLiteral(lit) => lit.span, Expr::Unary(unary) => unary.span, Expr::Binary(binary) => binary.span, diff --git a/capc/src/typeck/monomorphize.rs b/capc/src/typeck/monomorphize.rs index 763223d..f8d2a1a 100644 --- a/capc/src/typeck/monomorphize.rs +++ b/capc/src/typeck/monomorphize.rs @@ -409,6 +409,13 @@ impl MonoCtx { span: assign.span, })) } + HirStmt::Defer(defer_stmt) => { + let expr = self.mono_expr(module, &defer_stmt.expr, subs)?; + Ok(HirStmt::Defer(HirDeferStmt { + expr, + span: defer_stmt.span, + })) + } HirStmt::Return(ret) => { let expr = ret .expr @@ -420,6 +427,12 @@ impl MonoCtx { span: ret.span, })) } + HirStmt::Break(break_stmt) => Ok(HirStmt::Break(HirBreakStmt { + span: break_stmt.span, + })), + HirStmt::Continue(continue_stmt) => Ok(HirStmt::Continue(HirContinueStmt { + span: continue_stmt.span, + })), HirStmt::If(if_stmt) => { let cond = self.mono_expr(module, &if_stmt.cond, subs)?; let then_block = self.mono_block(module, &if_stmt.then_block, subs)?; @@ -444,6 +457,18 @@ impl MonoCtx { span: while_stmt.span, })) } + HirStmt::For(for_stmt) => { + let start = self.mono_expr(module, &for_stmt.start, subs)?; + let end = self.mono_expr(module, &for_stmt.end, subs)?; + let body = self.mono_block(module, &for_stmt.body, subs)?; + Ok(HirStmt::For(HirForStmt { + var_id: for_stmt.var_id, + start, + end, + body, + span: for_stmt.span, + })) + } HirStmt::Expr(expr_stmt) => { let expr = self.mono_expr(module, &expr_stmt.expr, subs)?; Ok(HirStmt::Expr(HirExprStmt { @@ -620,6 +645,20 @@ impl MonoCtx { span: t.span, })) } + HirExpr::Trap(t) => Ok(HirExpr::Trap(HirTrap { + ty: self.mono_hir_type(module, &t.ty, subs)?, + span: t.span, + })), + HirExpr::Index(idx) => { + let object = self.mono_expr(module, &idx.object, subs)?; + let index = self.mono_expr(module, &idx.index, subs)?; + Ok(HirExpr::Index(crate::hir::HirIndex { + object: Box::new(object), + index: Box::new(index), + elem_ty: self.mono_hir_type(module, &idx.elem_ty, subs)?, + span: idx.span, + })) + } } } @@ -706,7 +745,7 @@ impl MonoCtx { Ty::Ptr(inner) => Ok(Ty::Ptr(Box::new(self.mono_ty(module, inner, subs)?))), Ty::Ref(inner) => Ok(Ty::Ref(Box::new(self.mono_ty(module, inner, subs)?))), Ty::Path(name, args) => { - if name == "Result" { + if name == "sys.result.Result" { let args = args .iter() .map(|arg| self.mono_ty(module, arg, subs)) @@ -820,8 +859,8 @@ impl MonoCtx { BuiltinType::U32 => Ok(AbiType::U32), BuiltinType::U8 => Ok(AbiType::U8), BuiltinType::Bool => Ok(AbiType::Bool), - BuiltinType::String => Ok(AbiType::String), BuiltinType::Unit => Ok(AbiType::Unit), + BuiltinType::Never => Ok(AbiType::Unit), }, Ty::Ptr(_) => Ok(AbiType::Ptr), Ty::Ref(inner) => self.abi_type_for(module, inner), @@ -830,7 +869,7 @@ impl MonoCtx { DUMMY_SPAN, )), Ty::Path(name, args) => { - if name == "Result" && args.len() == 2 { + if name == "sys.result.Result" && args.len() == 2 { let ok = self.abi_type_for(module, &args[0])?; let err = self.abi_type_for(module, &args[1])?; return Ok(AbiType::Result(Box::new(ok), Box::new(err))); @@ -1028,13 +1067,16 @@ fn mangle_type(ty: &Ty) -> String { crate::typeck::BuiltinType::U32 => "u32".to_string(), crate::typeck::BuiltinType::U8 => "u8".to_string(), crate::typeck::BuiltinType::Bool => "bool".to_string(), - crate::typeck::BuiltinType::String => "string".to_string(), crate::typeck::BuiltinType::Unit => "unit".to_string(), + crate::typeck::BuiltinType::Never => "never".to_string(), }, Ty::Ptr(inner) => format!("ptr_{}", mangle_type(inner)), Ty::Ref(inner) => format!("ref_{}", mangle_type(inner)), Ty::Param(name) => format!("param_{name}"), Ty::Path(name, args) => { + if name == "sys.string.string" || name == "string" { + return "string".to_string(); + } let mut base = name.replace('.', "_"); if !args.is_empty() { let suffix = args diff --git a/capc/tests/run.rs b/capc/tests/run.rs index 94cb3be..2da9547 100644 --- a/capc/tests/run.rs +++ b/capc/tests/run.rs @@ -642,3 +642,317 @@ fn run_scoping_assign() { assert_eq!(code, 0); assert!(stdout.contains("scoping assign test ok"), "stdout was: {stdout:?}"); } + +#[test] +fn run_break_basic() { + let out_dir = make_out_dir("break_basic"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/break_basic.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("break ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_continue_basic() { + let out_dir = make_out_dir("continue_basic"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/continue_basic.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("continue ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_break_nested() { + let out_dir = make_out_dir("break_nested"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/break_nested.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("break nested ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_nested_match() { + let out_dir = make_out_dir("nested_match"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/nested_match.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("nested match ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_basic() { + let out_dir = make_out_dir("for_basic"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_basic.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_basic ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_break() { + let out_dir = make_out_dir("for_break"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_break.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_break ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_continue() { + let out_dir = make_out_dir("for_continue"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_continue.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_continue ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_nested() { + let out_dir = make_out_dir("for_nested"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_nested.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_nested ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_sum() { + let out_dir = make_out_dir("for_sum"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_sum.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_sum ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_empty_range() { + let out_dir = make_out_dir("for_empty_range"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_empty_range.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_empty_range ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_break_nested() { + let out_dir = make_out_dir("for_break_nested"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_break_nested.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_break_nested ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_continue_nested() { + let out_dir = make_out_dir("for_continue_nested"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/for_continue_nested.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("for_continue_nested ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_string_compare() { + let out_dir = make_out_dir("string_compare"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/string_compare.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("string_compare ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_string_index() { + let out_dir = make_out_dir("string_index"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/string_index.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("string_index ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_generic_and_index() { + let out_dir = make_out_dir("generic_and_index"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/generic_and_index.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("generic_and_index ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_result_is_ok_is_err() { + let out_dir = make_out_dir("result_is_ok_is_err"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_result_is_ok_is_err.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("is_ok_is_err ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_result_ok_err() { + let out_dir = make_out_dir("result_ok_err"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_result_ok_err.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("ok_err ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_defer() { + let out_dir = make_out_dir("defer"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_defer.cap", + ]); + assert_eq!(code, 0); + assert!( + stdout.contains("start\nend\nsecond\nfirst\n"), + "stdout was: {stdout:?}" + ); +} + +#[test] +fn run_defer_scope() { + let out_dir = make_out_dir("defer_scope"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_defer_scope.cap", + ]); + assert_eq!(code, 0); + assert!( + stdout.contains("start\nin if\nif defer\nafter if\nin loop\nloop defer\nafter loop\n"), + "stdout was: {stdout:?}" + ); +} + +#[test] +fn run_defer_return() { + let out_dir = make_out_dir("defer_return"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_defer_return.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("start\ninner\nouter\n"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_if_let() { + let out_dir = make_out_dir("if_let"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_if_let.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("ok\nerr\nif_let ok\n"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_for_forever() { + let out_dir = make_out_dir("for_forever"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/should_pass_for_forever.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("0\n1\n2\nfor_forever ok\n"), "stdout was: {stdout:?}"); +} diff --git a/capc/tests/typecheck.rs b/capc/tests/typecheck.rs index d7d5756..905ba93 100644 --- a/capc/tests/typecheck.rs +++ b/capc/tests/typecheck.rs @@ -127,7 +127,23 @@ fn typecheck_result_unwrap_or_mismatch_fails() { let module = parse_module(&source).expect("parse module"); let stdlib = load_stdlib().expect("load stdlib"); let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); - assert!(err.to_string().contains("unwrap_or type mismatch")); + assert!(err.to_string().contains("argument type mismatch")); +} + +#[test] +fn typecheck_result_is_ok_is_err() { + let source = load_program("should_pass_result_is_ok_is_err.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + type_check_program(&module, &stdlib, &[]).expect("typecheck module"); +} + +#[test] +fn typecheck_result_ok_err() { + let source = load_program("should_pass_result_ok_err.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + type_check_program(&module, &stdlib, &[]).expect("typecheck module"); } #[test] @@ -221,7 +237,7 @@ fn typecheck_console_wrong_type() { let stdlib = load_stdlib().expect("load stdlib"); let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); let text = err.to_string(); - assert!(text.contains("method receiver must be a struct value")); + assert!(text.contains("method receiver must be a struct or enum value")); } #[test] @@ -231,7 +247,7 @@ fn typecheck_mint_without_system() { let stdlib = load_stdlib().expect("load stdlib"); let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); let text = err.to_string(); - assert!(text.contains("method receiver must be a struct value")); + assert!(text.contains("method receiver must be a struct or enum value")); } #[test] @@ -240,7 +256,7 @@ fn typecheck_reserved_type_name_fails() { let module = parse_module(&source).expect("parse module"); let stdlib = load_stdlib().expect("load stdlib"); let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); - assert!(err.to_string().contains("type name `string` is reserved")); + assert!(err.to_string().contains("type name `i32` is reserved")); } #[test] @@ -881,3 +897,57 @@ fn typecheck_impl_wrong_module() { "expected error about impl module, got: {msg}" ); } + +#[test] +fn typecheck_break_outside_loop_fails() { + let source = load_program("should_fail_break_outside_loop.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); + assert!(err.to_string().contains("break statement outside of loop")); +} + +#[test] +fn typecheck_continue_outside_loop_fails() { + let source = load_program("should_fail_continue_outside_loop.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); + assert!(err.to_string().contains("continue statement outside of loop")); +} + +#[test] +fn typecheck_break_in_function_fails() { + let source = load_program("should_fail_break_in_function.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); + assert!(err.to_string().contains("break statement outside of loop")); +} + +#[test] +fn typecheck_for_non_i32_start_fails() { + let source = load_program("should_fail_for_non_i32_start.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); + assert!(err.to_string().contains("for loop range start must be i32")); +} + +#[test] +fn typecheck_for_non_i32_end_fails() { + let source = load_program("should_fail_for_non_i32_end.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); + assert!(err.to_string().contains("for loop range end must be i32")); +} + +#[test] +fn typecheck_for_loop_move_fails() { + let source = load_program("should_fail_for_loop_move.cap"); + let module = parse_module(&source).expect("parse module"); + let stdlib = load_stdlib().expect("load stdlib"); + let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); + assert!(err.to_string().contains("moved inside loop")); +} diff --git a/docs/ABI.md b/docs/ABI.md new file mode 100644 index 0000000..af9c34b --- /dev/null +++ b/docs/ABI.md @@ -0,0 +1,38 @@ +# ABI Notes + +This doc captures current ABI/lowering rules used by the compiler and runtime. +These rules are intentionally simple and may evolve. + +## Struct returns (sret) + +Capable does not currently return non-opaque structs in registers. Instead it +uses an explicit "structure return" (sret) out-parameter: + +- A function that returns a non-opaque struct is lowered to: + - an extra first parameter: `out: *T` + - return type: `unit` +- The callee writes the struct into `out`. +- Callers allocate stack space and pass `&out`. + +This is a common ABI strategy used by many toolchains for larger values. + +## Result out-params for struct payloads + +When a `Result` payload is a non-opaque struct, the return is lowered to +out-params: + +- The function takes extra `ok_out` / `err_out` pointer params for the + struct payloads. +- The function returns only the Result tag (`Ok`/`Err` discriminator). +- Scalar payloads still use the normal in-register `Result` layout. + +## Runtime wrappers + +Runtime-backed intrinsics keep their original ABI (no sret) and are wrapped by +compiler-generated stubs when needed. + +## Status + +- Inline-by-value struct returns are not implemented yet. +- These rules apply to non-opaque structs only. Opaque/capability types remain + handles and return directly. diff --git a/ATTENUATION.md b/docs/ATTENUATION.md similarity index 100% rename from ATTENUATION.md rename to docs/ATTENUATION.md diff --git a/docs/DEFER.md b/docs/DEFER.md new file mode 100644 index 0000000..2d0ed7a --- /dev/null +++ b/docs/DEFER.md @@ -0,0 +1,35 @@ +# Defer + +`defer` schedules a function or method call to run when the current scope +exits. Defers run in last-in, first-out order. + +```cap +fn example(c: Console) -> unit { + c.println("start") + defer c.println("first") + defer c.println("second") + c.println("end") + return () +} +``` + +Output order: + +``` +start +end +second +first +``` + +## Semantics + +- Defers run when the current scope ends, including on `return`, `break`, + `continue`, or normal fallthrough. +- Arguments are evaluated at the `defer` site and captured for later use. +- Defers are executed in LIFO order. + +## Restrictions (current) + +- The deferred expression must be a function or method call. +- Defers are not run if the function traps (e.g., `panic()`). diff --git a/docs/POLICY.md b/docs/POLICY.md index 248ed43..a7d1749 100644 --- a/docs/POLICY.md +++ b/docs/POLICY.md @@ -45,5 +45,9 @@ Keep these as invariants: ## Result Helpers -- `Result.unwrap_or(default)` returns `Ok` or the default. -- `Result.unwrap_err_or(default)` returns `Err` or the default. +- `Result.is_ok()` returns `true` if `Ok`, `false` if `Err`. +- `Result.is_err()` returns `true` if `Err`, `false` if `Ok`. +- `Result.ok()` returns the `Ok` value (traps if `Err`). +- `Result.err()` returns the `Err` value (traps if `Ok`). +- `Result.unwrap_or(default)` returns `Ok` value or the default. +- `Result.unwrap_err_or(default)` returns `Err` value or the default. diff --git a/TUTORIAL.md b/docs/TUTORIAL.md similarity index 87% rename from TUTORIAL.md rename to docs/TUTORIAL.md index 6e6722a..d0c71e3 100644 --- a/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -39,10 +39,11 @@ pub fn main() -> i32 { } ``` -- Statements: `let`, assignment, `if`, `while`, `return`, `match`. +- Statements: `let`, assignment, `if`, `while`, `for`, `return`, `match`, `defer`. - Expressions: literals, calls, binary ops, unary ops, method calls. - Modules + imports: `module ...` and `use ...` (aliases by last path segment). - If a function returns `unit`, you can omit the `-> unit` annotation. +- `for { ... }` is an infinite loop (Go style); `for i in a..b` is range. - Integer arithmetic traps on overflow. - Variable shadowing is not allowed. @@ -58,7 +59,24 @@ enum Color { Red, Green, Blue } Structs and enums are nominal types. Enums are currently unit variants only. -## 4) Methods +## 4) Defer + +`defer` schedules a function or method call to run when the current scope +exits (LIFO order). Arguments are evaluated at the defer site. + +```cap +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.println("start") + defer c.println("cleanup") + c.println("end") + return 0 +} +``` + +Current restriction: the deferred expression must be a call. + +## 5) Methods Methods are defined in `impl` blocks and lower to `Type__method` at compile time. @@ -76,7 +94,7 @@ impl Pair { Method receivers can be `self` (move) or `self: &T` (borrow‑lite, read‑only). -## 5) Results, match, and `?` +## 6) Results, match, and `?` ```cap module results @@ -123,7 +141,17 @@ match flag { } ``` -## 6) Capabilities and attenuation +You can also use `if let` as a single-arm `match`: + +```cap +if let Ok(x) = make() { + return x +} else { + return 0 +} +``` + +## 7) Capabilities and attenuation Capabilities live in `sys.*` and are declared with the `capability` keyword (capability types are opaque). You can only get them from `RootCap`. @@ -171,7 +199,7 @@ Why this is rejected: So methods that return capabilities must take `self` by value, which consumes the old capability. -## 7) Capability, opaque, copy, affine, linear +## 8) Capability, opaque, copy, affine, linear `capability struct` is the explicit “this is an authority token” marker. Capability types are always opaque (no public fields, no user construction) and default to affine unless marked `copy` or `linear`. This exists so the capability surface is obvious in code and the compiler can enforce one‑way attenuation (methods returning capabilities must take `self` by value). @@ -198,7 +226,7 @@ In the current stdlib: - `capability` (affine): `ReadFS`, `Filesystem`, `Dir`, `Stdin` - `linear capability`: `FileRead` -## 8) Moves and use-after-move +## 9) Moves and use-after-move ```cap module moves @@ -215,7 +243,7 @@ pub fn main() -> i32 { Affine and linear values cannot be used after move. If you move in one branch, it's moved after the join. -## 9) Linear must be consumed +## 10) Linear must be consumed ```cap module linear @@ -231,7 +259,7 @@ pub fn main() -> i32 { Linear values must be consumed along every path. You can consume them with a terminal method (like `FileRead.close()` or `read_to_string()`), or with `drop(x)` as a last resort. -## 10) Borrow-lite: &T parameters +## 11) Borrow-lite: &T parameters There is a small borrow feature for read-only access in function parameters and locals. @@ -261,7 +289,7 @@ Rules: This avoids a full borrow checker while making non-consuming observers ergonomic. -## 11) Safety boundary +## 12) Safety boundary `package safe` is default. Raw pointers and extern calls require `package unsafe`. @@ -272,7 +300,7 @@ module ffi extern fn some_ffi(x: i32) -> i32 ``` -## 12) Raw pointers and unsafe +## 13) Raw pointers and unsafe Raw pointers are available as `*T`, but **only** in `package unsafe`. @@ -290,7 +318,7 @@ pub fn main(rc: RootCap) -> i32 { There is no borrow checker for pointers. Use them only inside `package unsafe`. -## 13) What exists today (quick list) +## 14) What exists today (quick list) - Methods, modules, enums, match, while, if - Opaque capability handles in `sys.*` diff --git a/examples/config_loader/config_loader.cap b/examples/config_loader/config_loader.cap index d1f3274..2af5662 100644 --- a/examples/config_loader/config_loader.cap +++ b/examples/config_loader/config_loader.cap @@ -14,61 +14,53 @@ fn print_kv(c: Console, key: string, val: string) -> unit { c.println(val) } -fn parse_line(c: Console, alloc: Alloc, line: string) -> Result[unit, VecErr] { - if line.len() == 0 { +fn parse_line(c: Console, alloc: Alloc, line: string) -> Result { + if (line.len() == 0) { return Ok(()) } - if line.starts_with("#") { + if (line.starts_with("#")) { return Ok(()) } let parts = line.split(61u8) - if parts.len() == 2 { - let key = parts.get(0)? - let val = parts.get(1)? + if (parts.len() == 2) { + let key = parts[0]? + let val = parts[1]? print_kv(c, key, val) } alloc.vec_string_free(parts) return Ok(()) } -fn parse_config(c: Console, alloc: Alloc, contents: string) -> Result[unit, VecErr] { +fn parse_config(c: Console, alloc: Alloc, contents: string) -> Result { let lines = contents.lines() - let i = 0 - while i < lines.len() { - let line = lines.get(i)? + let n = lines.len() + for i in 0..n { + let line = lines[i]? parse_line(c, alloc, line)? - i = i + 1 } alloc.vec_string_free(lines) return Ok(()) } -fn run(c: Console, alloc: Alloc, fs: ReadFS) -> Result[unit, FsErr] { +fn run(c: Console, alloc: Alloc, fs: ReadFS) -> Result { let contents = fs.read_to_string("app.conf")? - match (parse_config(c, alloc, contents)) { - Ok(_) => { - return Ok(()) - } - Err(_) => { - return Err(fs::FsErr::IoError) - } + let result = parse_config(c, alloc, contents) + if (result.is_err()) { + return Err(fs::FsErr::IoError) } + return Ok(()) } pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let fs = rc.mint_readfs("examples/config_loader") let alloc = rc.mint_alloc_default() - let res = run(c, alloc, fs) - match (res) { - Ok(_) => { - c.println("config ok") - return 0 - } - Err(_) => { - c.println("config read failed") - return 1 - } + let result = run(c, alloc, fs) + if (result.is_ok()) { + c.println("config ok") + return 0 } + c.println("config read failed") + return 1 } diff --git a/examples/http_server/http_server.cap b/examples/http_server/http_server.cap index b71dd76..2b7ea7c 100644 --- a/examples/http_server/http_server.cap +++ b/examples/http_server/http_server.cap @@ -12,27 +12,21 @@ fn arg_or_default(args: Args, index: i32, default: string) -> string { if (args.len() <= index) { return default } - match (args.at(index)) { - Ok(value) => { return value } - Err(_) => { return default } - } + return args.at(index).unwrap_or(default) } fn strip_query(raw_path: string) -> string { - match (raw_path.split(63u8).get(0)) { - Ok(path) => { return path } - Err(_) => { return "" } - } + return raw_path.split(63u8)[0].unwrap_or("") } -fn sanitize_segment(parts: VecString, i: i32, acc: string, seg: string) -> Result[string, unit] { +fn sanitize_segment(parts: Vec, i: i32, acc: string, seg: string) -> Result { if (seg.len() == 0) { return sanitize_parts(parts, i + 1, acc) } - if (seg.eq(".")) { + if (seg == ".") { return sanitize_parts(parts, i + 1, acc) } - if (seg.eq("..")) { + if (seg == "..") { return Err(()) } if (acc.len() == 0) { @@ -41,18 +35,20 @@ fn sanitize_segment(parts: VecString, i: i32, acc: string, seg: string) -> Resul return sanitize_parts(parts, i + 1, fs::join(acc, seg)) } -fn sanitize_parts(parts: VecString, i: i32, acc: string) -> Result[string, unit] { +fn sanitize_parts(parts: Vec, i: i32, acc: string) -> Result { if (i >= parts.len()) { return Ok(acc) } - match (parts.get(i)) { + let seg_result = parts[i] + match (seg_result) { Ok(seg) => { return sanitize_segment(parts, i, acc, seg) } Err(_) => { return Err(()) } } } -fn sanitize_path(raw_path: string) -> Result[string, unit] { - match (sanitize_parts(raw_path.split(47u8), 0, "")) { +fn sanitize_path(raw_path: string) -> Result { + let result = sanitize_parts(raw_path.split(47u8), 0, "") + match (result) { Ok(path) => { if (path.len() == 0) { return Ok("index.html") @@ -63,59 +59,67 @@ fn sanitize_path(raw_path: string) -> Result[string, unit] { } } -fn parse_request_line(line: string) -> Result[string, unit] { - let parts = line.trim().split(32u8) - match (parts.get(0)) { - Ok(method) => { - if (!method.eq("GET")) { +fn parse_request_line(line: string) -> Result { + let trimmed = line.trim() + match (trimmed.split_once(32u8)) { + Ok(parts) => { + if (parts.left != "GET") { return Err(()) } + match (parts.right.split_once(32u8)) { + Ok(rest) => { return sanitize_path(strip_query(rest.left)) } + Err(_) => { return Err(()) } + } } Err(_) => { return Err(()) } } - match (parts.get(1)) { - Ok(raw_path) => { return sanitize_path(strip_query(raw_path)) } - Err(_) => { return Err(()) } - } } -fn parse_request_path(req: string) -> Result[string, unit] { - match (req.lines().get(0)) { +fn parse_request_path(req: string) -> Result { + let line_result = req.lines()[0] + match (line_result) { Ok(line) => { return parse_request_line(line) } Err(_) => { return Err(()) } } } -fn respond_ok(conn: &TcpConn, body: string) -> Result[unit, NetErr] { +fn respond_ok(conn: &TcpConn, body: string) -> Result { conn.write("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n")? conn.write(body)? return Ok(()) } -fn respond_not_found(conn: &TcpConn) -> Result[unit, NetErr] { +fn respond_not_found(conn: &TcpConn) -> Result { return conn.write("HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\nnot found\n") } -fn respond_bad_request(conn: &TcpConn) -> Result[unit, NetErr] { +fn respond_bad_request(conn: &TcpConn) -> Result { return conn.write("HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nbad request\n") } -fn handle_request(conn: &TcpConn, readfs: ReadFS, path: string) -> Result[unit, NetErr] { - match (readfs.read_to_string(path)) { - Ok(body) => { return respond_ok(conn, body) } - Err(_) => { return respond_not_found(conn) } +fn handle_request(conn: &TcpConn, readfs: ReadFS, req: string) -> Result { + match (parse_request_path(req)) { + Ok(path) => { + match (readfs.read_to_string(path)) { + Ok(body) => { return respond_ok(conn, body) } + Err(_) => { return respond_not_found(conn) } + } + } + Err(_) => { return respond_bad_request(conn) } } } -fn serve_once(c: Console, net: Net, readfs: ReadFS) -> Result[unit, NetErr] { +fn serve_forever(c: Console, net: Net, rc: RootCap, root: string) -> Result { let listener = net.listen("127.0.0.1", 8080)? - let conn = listener.accept()? - let req = conn.read(4096)? - match (parse_request_path(req)) { - Ok(path) => { handle_request(conn, readfs, path)? } - Err(_) => { respond_bad_request(conn)? } + defer listener.close() + while (true) { + let conn = listener.accept()? + defer conn.close() + let req = conn.read_to_string()? + let readfs = rc.mint_readfs(root) + handle_request(conn, readfs, req)? + defer c.println("request complete") } - conn.close() return Ok(()) } @@ -124,11 +128,10 @@ pub fn main(rc: RootCap) -> i32 { let net = rc.mint_net() let args = rc.mint_args() let root = arg_or_default(args, 1, ".") - let readfs = rc.mint_readfs(root) c.println("listening on 127.0.0.1:8080") - match (serve_once(c, net, readfs)) { - Ok(_) => {} - Err(_) => { c.println("server error") } + let result = serve_forever(c, net, rc, root) + if (result.is_err()) { + c.println("server error") } return 0 } diff --git a/examples/sort/sort.cap b/examples/sort/sort.cap index c360f07..679c504 100644 --- a/examples/sort/sort.cap +++ b/examples/sort/sort.cap @@ -7,20 +7,6 @@ use sys::io use sys::string use sys::vec -fn get_line(lines: VecString, i: i32) -> string { - match (lines.get(i)) { - Ok(line) => { return line } - Err(_) => { return "" } - } -} - -fn get_idx(indices: VecI32, i: i32) -> i32 { - match (indices.get(i)) { - Ok(idx) => { return idx } - Err(_) => { return 0 } - } -} - fn min_i32(a: i32, b: i32) -> i32 { if (a < b) { return a @@ -47,65 +33,61 @@ fn str_lt(a: string, b: string) -> bool { } // Compare lines at indices i and j -fn line_lt(lines: VecString, i: i32, j: i32) -> bool { - return str_lt(get_line(lines, i), get_line(lines, j)) +fn line_lt(lines: Vec, i: i32, j: i32) -> bool { + let line_i = lines[i].unwrap_or("") + let line_j = lines[j].unwrap_or("") + return str_lt(line_i, line_j) } // Insertion sort on indices -fn sort_indices(lines: VecString, indices: VecI32) -> unit { +fn sort_indices(lines: Vec, indices: Vec) -> unit { let n = indices.len() - let i = 1 - while (i < n) { + for i in 1..n { let j = i while (j > 0) { - let curr_idx = get_idx(indices, j) - let prev_idx = get_idx(indices, j - 1) + let curr_idx = indices[j].unwrap_or(0) + let prev_idx = indices[j - 1].unwrap_or(0) if (line_lt(lines, curr_idx, prev_idx)) { // swap indices[j] and indices[j-1] match (indices.set(j, prev_idx)) { - Ok(_) => {} - Err(_) => {} + Ok(ok) => {} + Err(e) => {} } match (indices.set(j - 1, curr_idx)) { - Ok(_) => {} - Err(_) => {} + Ok(ok) => {} + Err(e) => {} } j = j - 1 } else { - j = 0 // break + break } } - i = i + 1 } } -fn run(c: Console, alloc: Alloc, input: Stdin) -> Result[unit, io::IoErr] { +fn run(c: Console, alloc: Alloc, input: Stdin) -> Result { let contents = input.read_to_string()? let lines = contents.lines() let n = lines.len() // Create index array [0, 1, 2, ...] let indices = alloc.vec_i32_new() - let i = 0 - while (i < n) { + for i in 0..n { match (indices.push(i)) { - Ok(_) => {} - Err(_) => {} + Ok(ok) => {} + Err(e) => {} } - i = i + 1 } sort_indices(lines, indices) // Print lines in sorted order (skip empty lines) - i = 0 - while (i < n) { - let idx = get_idx(indices, i) - let line = get_line(lines, idx) + for i in 0..n { + let idx = indices[i].unwrap_or(0) + let line = lines[idx].unwrap_or("") if (line.len() > 0) { c.println(line) } - i = i + 1 } alloc.vec_i32_free(indices) @@ -117,11 +99,10 @@ pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let input = rc.mint_stdin() let alloc = rc.mint_alloc_default() - match (run(c, alloc, input)) { - Ok(_) => { return 0 } - Err(_) => { - c.println("error reading input") - return 1 - } + let result = run(c, alloc, input) + if (result.is_err()) { + c.println("error reading input") + return 1 } + return 0 } diff --git a/examples/uniq/uniq.cap b/examples/uniq/uniq.cap index 9b9bb52..0ff818f 100644 --- a/examples/uniq/uniq.cap +++ b/examples/uniq/uniq.cap @@ -7,31 +7,24 @@ use sys::io use sys::string use sys::vec -fn get_line(lines: VecString, i: i32) -> string { - match (lines.get(i)) { - Ok(line) => { return line } - Err(_) => { return "" } - } -} - -fn should_print(lines: VecString, i: i32) -> bool { +fn should_print(lines: Vec, i: i32) -> bool { if (i == 0) { return true } - let curr = get_line(lines, i) - let prev = get_line(lines, i - 1) - return !prev.eq(curr) + let curr = lines[i].unwrap_or("") + let prev = lines[i - 1].unwrap_or("") + return prev != curr } -fn run(c: Console, alloc: Alloc, input: Stdin) -> Result[unit, io::IoErr] { +fn run(c: Console, alloc: Alloc, input: Stdin) -> Result { let contents = input.read_to_string()? let lines = contents.lines() - let i = 0 - while (i < lines.len()) { + let n = lines.len() + for i in 0..n { if (should_print(lines, i)) { - c.println(get_line(lines, i)) + let line = lines[i].unwrap_or("") + c.println(line) } - i = i + 1 } alloc.vec_string_free(lines) return Ok(()) @@ -41,11 +34,10 @@ pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let input = rc.mint_stdin() let alloc = rc.mint_alloc_default() - match (run(c, alloc, input)) { - Ok(_) => { return 0 } - Err(_) => { - c.println("error reading input") - return 1 - } + let result = run(c, alloc, input) + if (result.is_err()) { + c.println("error reading input") + return 1 } + return 0 } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 1a6752f..f9c024e 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -67,9 +67,8 @@ struct FileReadState { } #[repr(C)] -pub struct RawString { - ptr: *const u8, - len: u64, +pub struct CapString { + bytes: Handle, } #[derive(Copy, Clone, Debug)] @@ -133,11 +132,21 @@ fn with_table( f(&mut table) } -fn to_raw_string(value: String) -> RawString { +fn to_slice_handle(value: String) -> Handle { let bytes = value.into_bytes().into_boxed_slice(); - let len = bytes.len() as u64; - let ptr = Box::into_raw(bytes) as *const u8; - RawString { ptr, len } + let len = bytes.len(); + let ptr = Box::into_raw(bytes) as *mut u8; + let handle = new_handle(); + with_table(&SLICES, "slice table", |table| { + table.insert( + handle, + SliceState { + ptr: ptr as usize, + len, + }, + ); + }); + handle } fn write_handle_result( @@ -249,13 +258,12 @@ pub extern "C" fn capable_rt_mint_net(_sys: Handle) -> Handle { #[no_mangle] pub extern "C" fn capable_rt_mint_readfs( _sys: Handle, - root_ptr: *const u8, - root_len: usize, + root: *const CapString, ) -> Handle { if !has_handle(&ROOT_CAPS, _sys, "root cap table") { return 0; } - let root = unsafe { read_str(root_ptr, root_len) }; + let root = unsafe { read_cap_string(root) }; let root_path = match root { Some(path) => normalize_root(Path::new(&path)), None => normalize_root(Path::new("")), @@ -271,13 +279,12 @@ pub extern "C" fn capable_rt_mint_readfs( #[no_mangle] pub extern "C" fn capable_rt_mint_filesystem( _sys: Handle, - root_ptr: *const u8, - root_len: usize, + root: *const CapString, ) -> Handle { if !has_handle(&ROOT_CAPS, _sys, "root cap table") { return 0; } - let root = unsafe { read_str(root_ptr, root_len) }; + let root = unsafe { read_cap_string(root) }; let root_path = match root { Some(path) => normalize_root(Path::new(&path)), None => normalize_root(Path::new("")), @@ -322,10 +329,9 @@ pub extern "C" fn capable_rt_fs_filesystem_close(fs: Handle) { #[no_mangle] pub extern "C" fn capable_rt_fs_subdir( dir: Handle, - name_ptr: *const u8, - name_len: usize, + name: *const CapString, ) -> Handle { - let name = unsafe { read_str(name_ptr, name_len) }; + let name = unsafe { read_cap_string(name) }; let state = take_handle(&DIRS, dir, "dir table"); let (Some(state), Some(name)) = (state, name) else { return 0; @@ -353,10 +359,9 @@ pub extern "C" fn capable_rt_fs_subdir( #[no_mangle] pub extern "C" fn capable_rt_fs_open_read( dir: Handle, - name_ptr: *const u8, - name_len: usize, + name: *const CapString, ) -> Handle { - let name = unsafe { read_str(name_ptr, name_len) }; + let name = unsafe { read_cap_string(name) }; let state = take_handle(&DIRS, dir, "dir table"); let (Some(state), Some(name)) = (state, name) else { return 0; @@ -389,10 +394,9 @@ pub extern "C" fn capable_rt_fs_dir_close(dir: Handle) { #[no_mangle] pub extern "C" fn capable_rt_fs_exists( fs: Handle, - path_ptr: *const u8, - path_len: usize, + path: *const CapString, ) -> u8 { - let path = unsafe { read_str(path_ptr, path_len) }; + let path = unsafe { read_cap_string(path) }; let state = take_handle(&READ_FS, fs, "readfs table"); let (Some(state), Some(path)) = (state, path) else { return 0; @@ -410,12 +414,11 @@ pub extern "C" fn capable_rt_fs_exists( #[no_mangle] pub extern "C" fn capable_rt_fs_read_bytes( fs: Handle, - path_ptr: *const u8, - path_len: usize, + path: *const CapString, out_ok: *mut Handle, out_err: *mut i32, ) -> u8 { - let path = unsafe { read_str(path_ptr, path_len) }; + let path = unsafe { read_cap_string(path) }; let state = take_handle(&READ_FS, fs, "readfs table"); let (Some(state), Some(path)) = (state, path) else { return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); @@ -442,12 +445,11 @@ pub extern "C" fn capable_rt_fs_read_bytes( #[no_mangle] pub extern "C" fn capable_rt_fs_list_dir( fs: Handle, - path_ptr: *const u8, - path_len: usize, + path: *const CapString, out_ok: *mut Handle, out_err: *mut i32, ) -> u8 { - let path = unsafe { read_str(path_ptr, path_len) }; + let path = unsafe { read_cap_string(path) }; let state = take_handle(&READ_FS, fs, "readfs table"); let (Some(state), Some(path)) = (state, path) else { return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); @@ -481,10 +483,9 @@ pub extern "C" fn capable_rt_fs_list_dir( #[no_mangle] pub extern "C" fn capable_rt_fs_dir_exists( dir: Handle, - name_ptr: *const u8, - name_len: usize, + name: *const CapString, ) -> u8 { - let name = unsafe { read_str(name_ptr, name_len) }; + let name = unsafe { read_cap_string(name) }; let state = take_handle(&DIRS, dir, "dir table"); let (Some(state), Some(name)) = (state, name) else { return 0; @@ -502,12 +503,11 @@ pub extern "C" fn capable_rt_fs_dir_exists( #[no_mangle] pub extern "C" fn capable_rt_fs_dir_read_bytes( dir: Handle, - name_ptr: *const u8, - name_len: usize, + name: *const CapString, out_ok: *mut Handle, out_err: *mut i32, ) -> u8 { - let name = unsafe { read_str(name_ptr, name_len) }; + let name = unsafe { read_cap_string(name) }; let state = take_handle(&DIRS, dir, "dir table"); let (Some(state), Some(name)) = (state, name) else { return write_handle_result(out_ok, out_err, Err(FsErr::PermissionDenied)); @@ -567,16 +567,25 @@ pub extern "C" fn capable_rt_fs_dir_list_dir( #[no_mangle] pub extern "C" fn capable_rt_fs_join( - a_ptr: *const u8, - a_len: usize, - b_ptr: *const u8, - b_len: usize, -) -> RawString { - let (Some(a), Some(b)) = (unsafe { read_str(a_ptr, a_len) }, unsafe { read_str(b_ptr, b_len) }) else { - return RawString { ptr: std::ptr::null(), len: 0 }; + out: *mut CapString, + a: *const CapString, + b: *const CapString, +) { + let (Some(a), Some(b)) = (unsafe { read_cap_string(a) }, unsafe { read_cap_string(b) }) else { + unsafe { + if !out.is_null() { + (*out).bytes = 0; + } + } + return; }; let joined = Path::new(&a).join(&b); - to_raw_string(joined.to_string_lossy().to_string()) + let handle = to_slice_handle(joined.to_string_lossy().to_string()); + unsafe { + if !out.is_null() { + (*out).bytes = handle; + } + } } #[no_mangle] @@ -591,19 +600,27 @@ pub extern "C" fn capable_rt_assert(_sys: Handle, cond: u8) { } #[no_mangle] -pub extern "C" fn capable_rt_console_print(_console: Handle, ptr: *const u8, len: usize) { +pub extern "C" fn capable_rt_console_print(_console: Handle, s: *const CapString) { if !has_handle(&CONSOLES, _console, "console table") { return; } - unsafe { write_bytes(ptr, len, false) }; + let handle = unsafe { if s.is_null() { 0 } else { (*s).bytes } }; + let Some(state) = slice_state(handle) else { + return; + }; + unsafe { write_bytes(state.ptr as *const u8, state.len, false) }; } #[no_mangle] -pub extern "C" fn capable_rt_console_println(_console: Handle, ptr: *const u8, len: usize) { +pub extern "C" fn capable_rt_console_println(_console: Handle, s: *const CapString) { if !has_handle(&CONSOLES, _console, "console table") { return; } - unsafe { write_bytes(ptr, len, true) }; + let handle = unsafe { if s.is_null() { 0 } else { (*s).bytes } }; + let Some(state) = slice_state(handle) else { + return; + }; + unsafe { write_bytes(state.ptr as *const u8, state.len, true) }; } #[no_mangle] @@ -674,37 +691,35 @@ pub extern "C" fn capable_rt_math_mul_wrap_u8(a: u8, b: u8) -> u8 { #[no_mangle] pub extern "C" fn capable_rt_fs_read_to_string( fs: Handle, - path_ptr: *const u8, - path_len: usize, - out_ptr: *mut *const u8, - out_len: *mut u64, + path: *const CapString, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { - let path = unsafe { read_str(path_ptr, path_len) }; + let path = unsafe { read_cap_string(path) }; let state = take_handle(&READ_FS, fs, "readfs table"); let Some(state) = state else { - return write_result(out_ptr, out_len, out_err, Err(FsErr::PermissionDenied)); + return write_result(out_ok, out_err, Err(FsErr::PermissionDenied)); }; let Some(path) = path else { - return write_result(out_ptr, out_len, out_err, Err(FsErr::InvalidPath)); + return write_result(out_ok, out_err, Err(FsErr::InvalidPath)); }; let relative = match normalize_relative(Path::new(&path)) { Some(path) => path, - None => return write_result(out_ptr, out_len, out_err, Err(FsErr::InvalidPath)), + None => return write_result(out_ok, out_err, Err(FsErr::InvalidPath)), }; let full = state.root.join(relative); let full = match full.canonicalize() { Ok(path) => path, - Err(err) => return write_result(out_ptr, out_len, out_err, Err(map_fs_err(err))), + Err(err) => return write_result(out_ok, out_err, Err(map_fs_err(err))), }; if !full.starts_with(&state.root) { - return write_result(out_ptr, out_len, out_err, Err(FsErr::InvalidPath)); + return write_result(out_ok, out_err, Err(FsErr::InvalidPath)); } match std::fs::read_to_string(&full) { - Ok(contents) => write_result(out_ptr, out_len, out_err, Ok(contents)), - Err(err) => write_result(out_ptr, out_len, out_err, Err(map_fs_err(err))), + Ok(contents) => write_result(out_ok, out_err, Ok(contents)), + Err(err) => write_result(out_ok, out_err, Err(map_fs_err(err))), } } @@ -716,27 +731,26 @@ pub extern "C" fn capable_rt_fs_readfs_close(fs: Handle) { #[no_mangle] pub extern "C" fn capable_rt_fs_file_read_to_string( file: Handle, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { let state = take_handle(&FILE_READS, file, "file read table"); let Some(state) = state else { - return write_result(out_ptr, out_len, out_err, Err(FsErr::PermissionDenied)); + return write_result(out_ok, out_err, Err(FsErr::PermissionDenied)); }; let full = state.root.join(state.rel); let full = match full.canonicalize() { Ok(path) => path, - Err(err) => return write_result(out_ptr, out_len, out_err, Err(map_fs_err(err))), + Err(err) => return write_result(out_ok, out_err, Err(map_fs_err(err))), }; if !full.starts_with(&state.root) { - return write_result(out_ptr, out_len, out_err, Err(FsErr::InvalidPath)); + return write_result(out_ok, out_err, Err(FsErr::InvalidPath)); } match std::fs::read_to_string(&full) { - Ok(contents) => write_result(out_ptr, out_len, out_err, Ok(contents)), - Err(err) => write_result(out_ptr, out_len, out_err, Err(map_fs_err(err))), + Ok(contents) => write_result(out_ok, out_err, Ok(contents)), + Err(err) => write_result(out_ok, out_err, Err(map_fs_err(err))), } } @@ -748,8 +762,7 @@ pub extern "C" fn capable_rt_fs_file_read_close(file: Handle) { #[no_mangle] pub extern "C" fn capable_rt_net_connect( net: Handle, - host_ptr: *const u8, - host_len: usize, + host: *const CapString, port: i32, out_ok: *mut Handle, out_err: *mut i32, @@ -757,7 +770,7 @@ pub extern "C" fn capable_rt_net_connect( if !has_handle(&NET_CAPS, net, "net table") { return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32)); } - let host = unsafe { read_str(host_ptr, host_len) }; + let host = unsafe { read_cap_string(host) }; let Some(host) = host else { return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32)); }; @@ -777,8 +790,7 @@ pub extern "C" fn capable_rt_net_connect( #[no_mangle] pub extern "C" fn capable_rt_net_listen( net: Handle, - host_ptr: *const u8, - host_len: usize, + host: *const CapString, port: i32, out_ok: *mut Handle, out_err: *mut i32, @@ -786,7 +798,7 @@ pub extern "C" fn capable_rt_net_listen( if !has_handle(&NET_CAPS, net, "net table") { return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32)); } - let host = unsafe { read_str(host_ptr, host_len) }; + let host = unsafe { read_cap_string(host) }; let Some(host) = host else { return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32)); }; @@ -826,8 +838,7 @@ pub extern "C" fn capable_rt_net_accept( #[no_mangle] pub extern "C" fn capable_rt_net_read_to_string( conn: Handle, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { let result = with_table(&TCP_CONNS, "tcp conn table", |table| { @@ -840,20 +851,14 @@ pub extern "C" fn capable_rt_net_read_to_string( Err(err) => Err(map_net_err(err)), } }); - write_string_result( - out_ptr, - out_len, - out_err, - result.map_err(|err| err as i32), - ) + write_string_result(out_ok, out_err, result.map_err(|err| err as i32)) } #[no_mangle] pub extern "C" fn capable_rt_net_read( conn: Handle, max_size: i32, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { let result = with_table(&TCP_CONNS, "tcp conn table", |table| { @@ -873,22 +878,16 @@ pub extern "C" fn capable_rt_net_read( Err(err) => Err(map_net_err(err)), } }); - write_string_result( - out_ptr, - out_len, - out_err, - result.map_err(|err| err as i32), - ) + write_string_result(out_ok, out_err, result.map_err(|err| err as i32)) } #[no_mangle] pub extern "C" fn capable_rt_net_write( conn: Handle, - data_ptr: *const u8, - data_len: usize, + data: *const CapString, out_err: *mut i32, ) -> u8 { - let data = unsafe { read_str(data_ptr, data_len) }; + let data = unsafe { read_cap_string(data) }; let Some(data) = data else { unsafe { if !out_err.is_null() { @@ -1089,6 +1088,15 @@ pub extern "C" fn capable_rt_buffer_new( 0 } +#[no_mangle] +pub extern "C" fn capable_rt_buffer_new_default( + initial_len: i32, + out_ok: *mut Handle, + out_err: *mut i32, +) -> u8 { + capable_rt_buffer_new(0, initial_len, out_ok, out_err) +} + #[no_mangle] pub extern "C" fn capable_rt_buffer_len(buffer: Handle) -> i32 { with_table(&BUFFERS, "buffer table", |table| { @@ -1782,6 +1790,11 @@ pub extern "C" fn capable_rt_vec_string_new(_alloc: Handle) -> Handle { handle } +#[no_mangle] +pub extern "C" fn capable_rt_vec_string_new_default() -> Handle { + capable_rt_vec_string_new(0) +} + #[no_mangle] pub extern "C" fn capable_rt_vec_string_len(vec: Handle) -> i32 { with_table(&VECS_STRING, "vec string table", |table| { @@ -1796,35 +1809,33 @@ pub extern "C" fn capable_rt_vec_string_len(vec: Handle) -> i32 { pub extern "C" fn capable_rt_vec_string_get( vec: Handle, index: i32, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { let idx = match usize::try_from(index) { Ok(idx) => idx, Err(_) => { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); + return write_string_result(out_ok, out_err, Err(VecErr::OutOfRange as i32)); } }; with_table(&VECS_STRING, "vec string table", |table| { let Some(data) = table.get(&vec) else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); + return write_string_result(out_ok, out_err, Err(VecErr::OutOfRange as i32)); }; let Some(value) = data.get(idx) else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::OutOfRange as i32)); + return write_string_result(out_ok, out_err, Err(VecErr::OutOfRange as i32)); }; - write_string_result(out_ptr, out_len, out_err, Ok(value.clone())) + write_string_result(out_ok, out_err, Ok(value.clone())) }) } #[no_mangle] pub extern "C" fn capable_rt_vec_string_push( vec: Handle, - ptr: *const u8, - len: usize, + value: *const CapString, out_err: *mut i32, ) -> u8 { - let value = unsafe { read_str(ptr, len) }; + let value = unsafe { read_cap_string(value) }; let Some(value) = value else { unsafe { if !out_err.is_null() { @@ -1894,18 +1905,17 @@ pub extern "C" fn capable_rt_vec_string_extend( #[no_mangle] pub extern "C" fn capable_rt_vec_string_pop( vec: Handle, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { with_table(&VECS_STRING, "vec string table", |table| { let Some(data) = table.get_mut(&vec) else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::Empty as i32)); + return write_string_result(out_ok, out_err, Err(VecErr::Empty as i32)); }; let Some(value) = data.pop() else { - return write_string_result(out_ptr, out_len, out_err, Err(VecErr::Empty as i32)); + return write_string_result(out_ok, out_err, Err(VecErr::Empty as i32)); }; - write_string_result(out_ptr, out_len, out_err, Ok(value)) + write_string_result(out_ok, out_err, Ok(value)) }) } @@ -1916,104 +1926,6 @@ pub extern "C" fn capable_rt_vec_string_free(_alloc: Handle, vec: Handle) { }); } -#[no_mangle] -pub extern "C" fn capable_rt_string_split_whitespace( - ptr: *const u8, - len: usize, -) -> Handle { - let value = unsafe { read_str(ptr, len) }; - let mut vec = Vec::new(); - if let Some(value) = value { - vec.extend(value.split_whitespace().map(|s| s.to_string())); - } - let handle = new_handle(); - with_table(&VECS_STRING, "vec string table", |table| { - table.insert(handle, vec); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_split( - ptr: *const u8, - len: usize, - delim: u8, -) -> Handle { - let value = unsafe { read_str(ptr, len) }; - let mut vec = Vec::new(); - if let Some(value) = value { - let bytes = value.as_bytes(); - let mut start = 0usize; - for (idx, byte) in bytes.iter().enumerate() { - if *byte == delim { - let part = &value[start..idx]; - vec.push(part.to_string()); - start = idx + 1; - } - } - let part = &value[start..]; - vec.push(part.to_string()); - } - let handle = new_handle(); - with_table(&VECS_STRING, "vec string table", |table| { - table.insert(handle, vec); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_split_lines(ptr: *const u8, len: usize) -> Handle { - let value = unsafe { read_str(ptr, len) }; - let mut vec = Vec::new(); - if let Some(value) = value { - for line in value.split('\n') { - let line = line.strip_suffix('\r').unwrap_or(line); - vec.push(line.to_string()); - } - } - let handle = new_handle(); - with_table(&VECS_STRING, "vec string table", |table| { - table.insert(handle, vec); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_trim(ptr: *const u8, len: usize) -> RawString { - let value = unsafe { read_str(ptr, len) }; - let trimmed = value.as_deref().unwrap_or("").trim().to_string(); - to_raw_string(trimmed) -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_trim_start(ptr: *const u8, len: usize) -> RawString { - let value = unsafe { read_str(ptr, len) }; - let trimmed = value.as_deref().unwrap_or("").trim_start().to_string(); - to_raw_string(trimmed) -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_trim_end(ptr: *const u8, len: usize) -> RawString { - let value = unsafe { read_str(ptr, len) }; - let trimmed = value.as_deref().unwrap_or("").trim_end().to_string(); - to_raw_string(trimmed) -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_starts_with( - ptr: *const u8, - len: usize, - prefix_ptr: *const u8, - prefix_len: usize, -) -> u8 { - let value = unsafe { read_str(ptr, len) }; - let prefix = unsafe { read_str(prefix_ptr, prefix_len) }; - match (value, prefix) { - (Some(value), Some(prefix)) => u8::from(value.starts_with(&prefix)), - _ => 0, - } -} - #[no_mangle] pub extern "C" fn capable_rt_args_len(_sys: Handle) -> i32 { if !has_handle(&ARGS_CAPS, _sys, "args table") { @@ -2026,8 +1938,7 @@ pub extern "C" fn capable_rt_args_len(_sys: Handle) -> i32 { pub extern "C" fn capable_rt_args_at( _sys: Handle, index: i32, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { if !has_handle(&ARGS_CAPS, _sys, "args table") { @@ -2057,70 +1968,52 @@ pub extern "C" fn capable_rt_args_at( } return 1; }; - unsafe { - if !out_ptr.is_null() { - *out_ptr = arg.as_ptr(); - } - if !out_len.is_null() { - *out_len = arg.len() as u64; - } - } - 0 + write_string_result(out_ok, out_err, Ok(arg.clone())) } #[no_mangle] pub extern "C" fn capable_rt_read_stdin_to_string( _sys: Handle, - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { if !has_handle(&STDIN_CAPS, _sys, "stdin table") { - return write_string_result(out_ptr, out_len, out_err, Err(0)); + return write_string_result(out_ok, out_err, Err(0)); } let mut input = String::new(); let result = io::stdin().read_to_string(&mut input); match result { - Ok(_) => write_string_result(out_ptr, out_len, out_err, Ok(input)), - Err(_) => write_string_result(out_ptr, out_len, out_err, Err(0)), + Ok(_) => write_string_result(out_ok, out_err, Ok(input)), + Err(_) => write_string_result(out_ok, out_err, Err(0)), } } #[no_mangle] -pub extern "C" fn capable_rt_string_len(ptr: *const u8, len: usize) -> i32 { - let _ = ptr; - len.min(i32::MAX as usize) as i32 -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_byte_at( - ptr: *const u8, - len: usize, - index: i32, -) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => return 0, +pub extern "C" fn capable_rt_string_eq( + left: *const CapString, + right: *const CapString, +) -> i8 { + let left_handle = unsafe { if left.is_null() { 0 } else { (*left).bytes } }; + let right_handle = unsafe { if right.is_null() { 0 } else { (*right).bytes } }; + let left_state = slice_state(left_handle); + let right_state = slice_state(right_handle); + let (Some(left_state), Some(right_state)) = (left_state, right_state) else { + return 0; }; - if ptr.is_null() || idx >= len { + if left_state.len != right_state.len { return 0; } - unsafe { *ptr.add(idx) } -} - -#[no_mangle] -pub extern "C" fn capable_rt_string_as_slice(ptr: *const u8, len: usize) -> Handle { - let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert( - handle, - SliceState { - ptr: ptr as usize, - len, - }, - ); - }); - handle + if left_state.len == 0 { + return 1; + } + if left_state.ptr == 0 || right_state.ptr == 0 { + return 0; + } + let s1 = + unsafe { std::slice::from_raw_parts(left_state.ptr as *const u8, left_state.len) }; + let s2 = + unsafe { std::slice::from_raw_parts(right_state.ptr as *const u8, right_state.len) }; + i8::from(s1 == s2) } #[no_mangle] @@ -2189,18 +2082,10 @@ fn resolve_rooted_path(root: &Path, rel: &Path) -> Result { Ok(full) } -fn write_result( - out_ptr: *mut *const u8, - out_len: *mut u64, - out_err: *mut i32, - result: Result, -) -> u8 { +fn write_result(out_ok: *mut CapString, out_err: *mut i32, result: Result) -> u8 { unsafe { - if !out_ptr.is_null() { - *out_ptr = std::ptr::null(); - } - if !out_len.is_null() { - *out_len = 0; + if !out_ok.is_null() { + (*out_ok).bytes = 0; } if !out_err.is_null() { *out_err = 0; @@ -2208,18 +2093,10 @@ fn write_result( } match result { Ok(contents) => { - let bytes = contents.into_bytes().into_boxed_slice(); - let len = bytes.len() as u64; - let ptr = Box::into_raw(bytes) as *const u8; + let handle = to_slice_handle(contents); unsafe { - if !out_ptr.is_null() { - *out_ptr = ptr; - } - if !out_len.is_null() { - *out_len = len; - } - if !out_err.is_null() { - *out_err = 0; + if !out_ok.is_null() { + (*out_ok).bytes = handle; } } 0 @@ -2235,19 +2112,14 @@ fn write_result( } } -/// Write a Result[String, i32] payload using the ResultString ABI (u64 length). fn write_string_result( - out_ptr: *mut *const u8, - out_len: *mut u64, + out_ok: *mut CapString, out_err: *mut i32, result: Result, ) -> u8 { unsafe { - if !out_ptr.is_null() { - *out_ptr = std::ptr::null(); - } - if !out_len.is_null() { - *out_len = 0; + if !out_ok.is_null() { + (*out_ok).bytes = 0; } if !out_err.is_null() { *out_err = 0; @@ -2255,15 +2127,10 @@ fn write_string_result( } match result { Ok(s) => { - let len = s.len(); - let ptr = s.as_ptr(); - std::mem::forget(s); + let handle = to_slice_handle(s); unsafe { - if !out_ptr.is_null() { - *out_ptr = ptr; - } - if !out_len.is_null() { - *out_len = len as u64; + if !out_ok.is_null() { + (*out_ok).bytes = handle; } } 0 @@ -2279,12 +2146,27 @@ fn write_string_result( } } -unsafe fn read_str(ptr: *const u8, len: usize) -> Option { +fn slice_state(handle: Handle) -> Option { + with_table(&SLICES, "slice table", |table| table.get(&handle).copied()) +} + +fn read_slice_string(handle: Handle) -> Option { + if handle == 0 { + return Some(String::new()); + } + let state = slice_state(handle)?; + if state.ptr == 0 { + return Some(String::new()); + } + let bytes = unsafe { std::slice::from_raw_parts(state.ptr as *const u8, state.len) }; + std::str::from_utf8(bytes).ok().map(|s| s.to_string()) +} + +unsafe fn read_cap_string(ptr: *const CapString) -> Option { if ptr.is_null() { return None; } - let bytes = std::slice::from_raw_parts(ptr, len); - std::str::from_utf8(bytes).ok().map(|s| s.to_string()) + read_slice_string((*ptr).bytes) } fn normalize_root(root: &Path) -> Option { diff --git a/stdlib/sys/args.cap b/stdlib/sys/args.cap index 5cf7d17..64703fd 100644 --- a/stdlib/sys/args.cap +++ b/stdlib/sys/args.cap @@ -12,7 +12,7 @@ impl Args { return 0 } - pub fn at(self, index: i32) -> Result[string, ArgsErr] { + pub fn at(self, index: i32) -> Result { return Err(ArgsErr::OutOfRange) } } diff --git a/stdlib/sys/buffer.cap b/stdlib/sys/buffer.cap index 469a549..6cff58d 100644 --- a/stdlib/sys/buffer.cap +++ b/stdlib/sys/buffer.cap @@ -1,16 +1,27 @@ package safe module sys::buffer use sys::vec +use sys::string pub copy opaque struct Alloc pub copy opaque struct Buffer -pub copy opaque struct Slice[T] -pub copy opaque struct MutSlice[T] +pub copy opaque struct Slice +pub copy opaque struct MutSlice pub enum AllocErr { Oom } +/// Intrinsic; implemented by the runtime. +pub fn new(initial_len: i32) -> Result { + return Err(AllocErr::Oom) +} + +/// Intrinsic; implemented by the runtime. +pub fn vec_string_new() -> vec::Vec { + return () +} + impl Alloc { pub fn malloc(self, size: i32) -> *u8 { return () @@ -28,15 +39,15 @@ impl Alloc { return () } - pub fn slice_from_ptr(self, ptr: *u8, len: i32) -> Slice[u8] { + pub fn slice_from_ptr(self, ptr: *u8, len: i32) -> Slice { return () } - pub fn mut_slice_from_ptr(self, ptr: *u8, len: i32) -> MutSlice[u8] { + pub fn mut_slice_from_ptr(self, ptr: *u8, len: i32) -> MutSlice { return () } - pub fn buffer_new(self, initial_len: i32) -> Result[Buffer, AllocErr] { + pub fn buffer_new(self, initial_len: i32) -> Result { return Err(AllocErr::Oom) } @@ -44,27 +55,27 @@ impl Alloc { return () } - pub fn vec_u8_new(self) -> vec::VecU8 { + pub fn vec_u8_new(self) -> vec::Vec { return () } - pub fn vec_u8_free(self, v: vec::VecU8) -> unit { + pub fn vec_u8_free(self, v: vec::Vec) -> unit { return () } - pub fn vec_i32_new(self) -> vec::VecI32 { + pub fn vec_i32_new(self) -> vec::Vec { return () } - pub fn vec_i32_free(self, v: vec::VecI32) -> unit { + pub fn vec_i32_free(self, v: vec::Vec) -> unit { return () } - pub fn vec_string_new(self) -> vec::VecString { + pub fn vec_string_new(self) -> vec::Vec { return () } - pub fn vec_string_free(self, v: vec::VecString) -> unit { + pub fn vec_string_free(self, v: vec::Vec) -> unit { return () } } @@ -74,28 +85,36 @@ impl Buffer { return 0 } - pub fn push(self, x: u8) -> Result[unit, AllocErr] { + pub fn push(self, x: u8) -> Result { return Err(AllocErr::Oom) } - pub fn extend(self, data: Slice[u8]) -> Result[unit, AllocErr] { + pub fn extend(self, data: Slice) -> Result { return Err(AllocErr::Oom) } + pub fn push_str(self, s: string) -> Result { + return self.extend(s.as_slice()) + } + pub fn is_empty(self) -> bool { return false } - pub fn as_slice(self) -> Slice[u8] { + pub fn as_slice(self) -> Slice { return () } - pub fn as_mut_slice(self) -> MutSlice[u8] { + pub fn as_mut_slice(self) -> MutSlice { return () } + + pub fn to_string(self) -> Result { + return string::from_bytes(self.as_slice()) + } } -impl Slice[u8] { +impl Slice { pub fn len(self) -> i32 { return 0 } @@ -105,7 +124,7 @@ impl Slice[u8] { } } -impl MutSlice[u8] { +impl MutSlice { pub fn at(self, i: i32) -> u8 { return 0 } diff --git a/stdlib/sys/fs.cap b/stdlib/sys/fs.cap index 0ecf095..3882215 100644 --- a/stdlib/sys/fs.cap +++ b/stdlib/sys/fs.cap @@ -11,15 +11,15 @@ pub linear capability struct FileRead pub enum FsErr { NotFound, PermissionDenied, InvalidPath, IoError } impl ReadFS { - pub fn read_to_string(self, path: string) -> Result[string, FsErr] { + pub fn read_to_string(self, path: string) -> Result { return () } - pub fn read_bytes(self, path: string) -> Result[vec::VecU8, FsErr] { + pub fn read_bytes(self, path: string) -> Result, FsErr> { return Err(FsErr::IoError) } - pub fn list_dir(self, path: string) -> Result[vec::VecString, FsErr] { + pub fn list_dir(self, path: string) -> Result, FsErr> { return Err(FsErr::IoError) } @@ -51,11 +51,11 @@ impl Dir { return () } - pub fn read_bytes(self, name: string) -> Result[vec::VecU8, FsErr] { + pub fn read_bytes(self, name: string) -> Result, FsErr> { return Err(FsErr::IoError) } - pub fn list_dir(self) -> Result[vec::VecString, FsErr] { + pub fn list_dir(self) -> Result, FsErr> { return Err(FsErr::IoError) } @@ -63,7 +63,7 @@ impl Dir { return false } - pub fn read_to_string(self, name: string) -> Result[string, FsErr] { + pub fn read_to_string(self, name: string) -> Result { let file = self.open_read(name) return file.read_to_string() } @@ -74,7 +74,7 @@ impl Dir { } impl FileRead { - pub fn read_to_string(self) -> Result[string, FsErr] { + pub fn read_to_string(self) -> Result { return () } diff --git a/stdlib/sys/net.cap b/stdlib/sys/net.cap index 9bbcf76..16a2cf9 100644 --- a/stdlib/sys/net.cap +++ b/stdlib/sys/net.cap @@ -2,7 +2,7 @@ package unsafe module sys::net pub copy capability struct Net -pub linear capability struct TcpListener +pub copy capability struct TcpListener pub linear capability struct TcpConn pub enum NetErr { @@ -12,17 +12,17 @@ pub enum NetErr { } impl Net { - pub fn listen(self, host: string, port: i32) -> Result[TcpListener, NetErr] { + pub fn listen(self, host: string, port: i32) -> Result { return Err(NetErr::IoError) } - pub fn connect(self, host: string, port: i32) -> Result[TcpConn, NetErr] { + pub fn connect(self, host: string, port: i32) -> Result { return Err(NetErr::IoError) } } impl TcpListener { - pub fn accept(self) -> Result[TcpConn, NetErr] { + pub fn accept(self: &TcpListener) -> Result { return Err(NetErr::IoError) } @@ -32,15 +32,15 @@ impl TcpListener { } impl TcpConn { - pub fn read_to_string(self: &TcpConn) -> Result[string, NetErr] { + pub fn read_to_string(self: &TcpConn) -> Result { return Err(NetErr::IoError) } - pub fn read(self: &TcpConn, max_size: i32) -> Result[string, NetErr] { + pub fn read(self: &TcpConn, max_size: i32) -> Result { return Err(NetErr::IoError) } - pub fn write(self: &TcpConn, data: string) -> Result[unit, NetErr] { + pub fn write(self: &TcpConn, data: string) -> Result { return Err(NetErr::IoError) } diff --git a/stdlib/sys/result.cap b/stdlib/sys/result.cap new file mode 100644 index 0000000..7488497 --- /dev/null +++ b/stdlib/sys/result.cap @@ -0,0 +1,51 @@ +package safe +module sys::result + +pub enum Result { + Ok(T), + Err(E) +} + +impl Result { + pub fn is_ok(self) -> bool { + match self { + Result::Ok(_) => { return true } + Result::Err(_) => { return false } + } + } + + pub fn is_err(self) -> bool { + match self { + Result::Ok(_) => { return false } + Result::Err(_) => { return true } + } + } + + pub fn unwrap_or(self, default: T) -> T { + match self { + Result::Ok(val) => { return val } + Result::Err(_) => { return default } + } + } + + pub fn unwrap_err_or(self, default: E) -> E { + match self { + Result::Ok(_) => { return default } + Result::Err(err) => { return err } + } + } + + pub fn ok(self) -> T { + match self { + Result::Ok(val) => { return val } + Result::Err(_) => { panic() } + } + } + + pub fn err(self) -> E { + match self { + Result::Ok(_) => { panic() } + Result::Err(err) => { return err } + } + } +} diff --git a/stdlib/sys/stdin.cap b/stdlib/sys/stdin.cap index 38faf70..e798b6c 100644 --- a/stdlib/sys/stdin.cap +++ b/stdlib/sys/stdin.cap @@ -6,7 +6,7 @@ use sys::io pub capability struct Stdin impl Stdin { - pub fn read_to_string(self) -> Result[string, io::IoErr] { + pub fn read_to_string(self) -> Result { return Err(io::IoErr::IoError) } } diff --git a/stdlib/sys/string.cap b/stdlib/sys/string.cap index e5f9c3e..4814525 100644 --- a/stdlib/sys/string.cap +++ b/stdlib/sys/string.cap @@ -5,59 +5,287 @@ use sys::buffer use sys::bytes use sys::vec +pub copy struct string { + bytes: Slice +} + +pub struct SplitOnce { + left: string, + right: string +} + +pub fn from_bytes(bytes: Slice) -> Result { + return Ok(string { bytes: bytes }) +} + +fn build_range(s: string, start: i32, end: i32) -> string { + if (end <= start) { + return "" + } + let buf_result = buffer::new(0) + match (buf_result) { + Ok(buf) => { + let i = start + while (i < end) { + match (buf.push(s.byte_at(i))) { + Ok(_) => { } + Err(_) => { panic() } + } + i = i + 1 + } + match (buf.to_string()) { + Ok(out) => { return out } + Err(_) => { panic() } + } + } + Err(_) => { panic() } + } +} + +fn lower_ascii_byte(b: u8) -> u8 { + match (b) { + 'A' => { return 'a' } + 'B' => { return 'b' } + 'C' => { return 'c' } + 'D' => { return 'd' } + 'E' => { return 'e' } + 'F' => { return 'f' } + 'G' => { return 'g' } + 'H' => { return 'h' } + 'I' => { return 'i' } + 'J' => { return 'j' } + 'K' => { return 'k' } + 'L' => { return 'l' } + 'M' => { return 'm' } + 'N' => { return 'n' } + 'O' => { return 'o' } + 'P' => { return 'p' } + 'Q' => { return 'q' } + 'R' => { return 'r' } + 'S' => { return 's' } + 'T' => { return 't' } + 'U' => { return 'u' } + 'V' => { return 'v' } + 'W' => { return 'w' } + 'X' => { return 'x' } + 'Y' => { return 'y' } + 'Z' => { return 'z' } + _ => { return b } + } +} + +fn upper_ascii_byte(b: u8) -> u8 { + match (b) { + 'a' => { return 'A' } + 'b' => { return 'B' } + 'c' => { return 'C' } + 'd' => { return 'D' } + 'e' => { return 'E' } + 'f' => { return 'F' } + 'g' => { return 'G' } + 'h' => { return 'H' } + 'i' => { return 'I' } + 'j' => { return 'J' } + 'k' => { return 'K' } + 'l' => { return 'L' } + 'm' => { return 'M' } + 'n' => { return 'N' } + 'o' => { return 'O' } + 'p' => { return 'P' } + 'q' => { return 'Q' } + 'r' => { return 'R' } + 's' => { return 'S' } + 't' => { return 'T' } + 'u' => { return 'U' } + 'v' => { return 'V' } + 'w' => { return 'W' } + 'x' => { return 'X' } + 'y' => { return 'Y' } + 'z' => { return 'Z' } + _ => { return b } + } +} + impl string { - /// Intrinsic; implemented by the runtime. pub fn len(self) -> i32 { - return 0 + return self.bytes.len() } - /// Intrinsic; implemented by the runtime. pub fn byte_at(self, index: i32) -> u8 { - return 0u8 + return self.bytes.at(index) } - /// Intrinsic; implemented by the runtime. - pub fn as_slice(self) -> Slice[u8] { - return () + pub fn as_slice(self) -> Slice { + return self.bytes } /// bytes() is an alias for as_slice(). - pub fn bytes(self) -> Slice[u8] { + pub fn bytes(self) -> Slice { return self.as_slice() } - /// Intrinsic; implemented by the runtime. - pub fn split_whitespace(self) -> VecString { - return () + pub fn split_whitespace(self) -> Vec { + let out = buffer::vec_string_new() + let bytes = self.as_slice() + let len = bytes.len() + let i = 0 + while (i < len) { + while (i < len && bytes.at(i).is_whitespace()) { + i = i + 1 + } + if (i >= len) { + break + } + let start = i + while (i < len && !bytes.at(i).is_whitespace()) { + i = i + 1 + } + let part = build_range(self, start, i) + match (out.push(part)) { + Ok(_) => { } + Err(_) => { panic() } + } + } + return out } - /// Intrinsic; implemented by the runtime. - pub fn lines(self) -> VecString { - return () + pub fn lines(self) -> Vec { + let out = buffer::vec_string_new() + let bytes = self.as_slice() + let len = bytes.len() + let start = 0 + let i = 0 + while (i < len) { + if (bytes.at(i) == '\n') { + let end = i + if (end > start && bytes.at(end - 1) == '\r') { + end = end - 1 + } + let part = build_range(self, start, end) + match (out.push(part)) { + Ok(_) => { } + Err(_) => { panic() } + } + start = i + 1 + } + i = i + 1 + } + if (start < len) { + let end = len + if (end > start && bytes.at(end - 1) == '\r') { + end = end - 1 + } + let part = build_range(self, start, end) + match (out.push(part)) { + Ok(_) => { } + Err(_) => { panic() } + } + } + return out + } + + pub fn split(self, delim: u8) -> Vec { + let out = buffer::vec_string_new() + let bytes = self.as_slice() + let len = bytes.len() + let start = 0 + let i = 0 + while (i < len) { + if (bytes.at(i) == delim) { + let part = build_range(self, start, i) + match (out.push(part)) { + Ok(_) => { } + Err(_) => { panic() } + } + start = i + 1 + } + i = i + 1 + } + let part = build_range(self, start, len) + match (out.push(part)) { + Ok(_) => { } + Err(_) => { panic() } + } + return out } - /// Intrinsic; implemented by the runtime. - pub fn split(self, delim: u8) -> VecString { - return () + pub fn split_once(self, delim: u8) -> Result { + let bytes = self.as_slice() + let len = bytes.len() + let i = 0 + while (i < len) { + if (bytes.at(i) == delim) { + let left = build_range(self, 0, i) + let right = build_range(self, i + 1, len) + return Ok(SplitOnce { + left: left, + right: right + }) + } + i = i + 1 + } + return Err(()) } - /// Intrinsic; implemented by the runtime. pub fn trim(self) -> string { - return "" + let start_trimmed = self.trim_start() + return start_trimmed.trim_end() } - /// Intrinsic; implemented by the runtime. pub fn trim_start(self) -> string { - return "" + let bytes = self.as_slice() + let len = bytes.len() + let i = 0 + while (i < len) { + if (!bytes.at(i).is_whitespace()) { + break + } + i = i + 1 + } + if (i == 0) { + return self + } + return build_range(self, i, len) } - /// Intrinsic; implemented by the runtime. pub fn trim_end(self) -> string { - return "" + let bytes = self.as_slice() + let len = bytes.len() + if (len == 0) { + return self + } + let i = len + while (i > 0) { + if (!bytes.at(i - 1).is_whitespace()) { + break + } + i = i - 1 + } + if (i == len) { + return self + } + if (i == 0) { + return "" + } + return build_range(self, 0, i) + } + + pub fn trim_prefix(self, prefix: string) -> string { + if (self.starts_with(prefix)) { + return build_range(self, prefix.len(), self.len()) + } + return self + } + + pub fn trim_suffix(self, suffix: string) -> string { + if (self.ends_with(suffix)) { + return build_range(self, 0, self.len() - suffix.len()) + } + return self } /// split_lines() is an alias for lines(). - pub fn split_lines(self) -> VecString { + pub fn split_lines(self) -> Vec { return self.lines() } @@ -124,4 +352,146 @@ impl string { } return true } + + pub fn is_empty(self) -> bool { + return self.len() == 0 + } + + pub fn byte_at_checked(self, index: i32) -> Result { + if (index < 0) { + return Err(()) + } + if (index >= self.len()) { + return Err(()) + } + return Ok(self.byte_at(index)) + } + + pub fn index_of_byte(self, needle: u8) -> Result { + let bytes = self.as_slice() + let len = bytes.len() + let i = 0 + while (i < len) { + if (bytes.at(i) == needle) { + return Ok(i) + } + i = i + 1 + } + return Err(()) + } + + pub fn last_index_of_byte(self, needle: u8) -> Result { + let bytes = self.as_slice() + let len = bytes.len() + if (len == 0) { + return Err(()) + } + let i = len - 1 + while (true) { + if (bytes.at(i) == needle) { + return Ok(i) + } + if (i == 0) { + break + } + i = i - 1 + } + return Err(()) + } + + pub fn contains_byte(self, needle: u8) -> bool { + match (self.index_of_byte(needle)) { + Ok(_) => { return true } + Err(_) => { return false } + } + } + + pub fn count_byte(self, needle: u8) -> i32 { + let bytes = self.as_slice() + let len = bytes.len() + let i = 0 + let count = 0 + while (i < len) { + if (bytes.at(i) == needle) { + count = count + 1 + } + i = i + 1 + } + return count + } + + pub fn is_ascii(self) -> bool { + let bytes = self.as_slice() + let len = bytes.len() + let i = 0 + while (i < len) { + if (bytes.at(i) > '\x7f') { + return false + } + i = i + 1 + } + return true + } + + pub fn to_lower_ascii(self) -> string { + let bytes = self.as_slice() + let len = bytes.len() + let buf_result = buffer::new(0) + match (buf_result) { + Ok(buf) => { + let i = 0 + while (i < len) { + let b = bytes.at(i) + let lower = lower_ascii_byte(b) + match (buf.push(lower)) { + Ok(_) => { } + Err(_) => { panic() } + } + i = i + 1 + } + match (buf.to_string()) { + Ok(out) => { return out } + Err(_) => { panic() } + } + } + Err(_) => { panic() } + } + } + + pub fn to_upper_ascii(self) -> string { + let bytes = self.as_slice() + let len = bytes.len() + let buf_result = buffer::new(0) + match (buf_result) { + Ok(buf) => { + let i = 0 + while (i < len) { + let b = bytes.at(i) + let upper = upper_ascii_byte(b) + match (buf.push(upper)) { + Ok(_) => { } + Err(_) => { panic() } + } + i = i + 1 + } + match (buf.to_string()) { + Ok(out) => { return out } + Err(_) => { panic() } + } + } + Err(_) => { panic() } + } + } + + pub fn trim_ascii(self) -> string { + return self.trim() + } + + pub fn find_byte(self, needle: u8) -> Result { + return self.index_of_byte(needle) + } + + pub fn rfind_byte(self, needle: u8) -> Result { + return self.last_index_of_byte(needle) + } } diff --git a/stdlib/sys/vec.cap b/stdlib/sys/vec.cap index 71c15c6..c083760 100644 --- a/stdlib/sys/vec.cap +++ b/stdlib/sys/vec.cap @@ -3,6 +3,7 @@ module sys::vec use sys::buffer +pub copy opaque struct Vec pub copy opaque struct VecU8 pub copy opaque struct VecI32 pub copy opaque struct VecString @@ -17,22 +18,40 @@ impl VecU8 { return 0 } - pub fn get(self, i: i32) -> Result[u8, VecErr] { + pub fn is_empty(self) -> bool { + return self.len() == 0 + } + + pub fn get(self, i: i32) -> Result { return Err(VecErr::OutOfRange) } - pub fn set(self, i: i32, x: u8) -> Result[unit, VecErr] { + pub fn set(self, i: i32, x: u8) -> Result { return Err(VecErr::OutOfRange) } - pub fn push(self, x: u8) -> Result[unit, buffer::AllocErr] { + pub fn push(self, x: u8) -> Result { return Err(buffer::AllocErr::Oom) } - pub fn extend(self, other: VecU8) -> Result[unit, buffer::AllocErr] { + pub fn extend(self, other: VecU8) -> Result { return Err(buffer::AllocErr::Oom) } + pub fn extend_slice(self, data: Slice) -> Result { + let len = data.len() + let i = 0 + while (i < len) { + self.push(data.at(i))? + i = i + 1 + } + return Ok(()) + } + + pub fn push_all(self, other: VecU8) -> Result { + return self.extend(other) + } + pub fn filter(self, value: u8) -> VecU8 { return () } @@ -41,15 +60,25 @@ impl VecU8 { return () } - pub fn slice(self, start: i32, len: i32) -> Result[Slice[u8], VecErr] { + pub fn slice(self, start: i32, len: i32) -> Result, VecErr> { return Err(VecErr::OutOfRange) } - pub fn pop(self) -> Result[u8, VecErr] { + pub fn pop(self) -> Result { return Err(VecErr::Empty) } - pub fn as_slice(self) -> Slice[u8] { + pub fn as_slice(self) -> Slice { + return () + } + + pub fn clear(self) -> unit { + while (true) { + match (self.pop()) { + Ok(_) => { } + Err(_) => { break } + } + } return () } } @@ -59,22 +88,30 @@ impl VecI32 { return 0 } - pub fn get(self, i: i32) -> Result[i32, VecErr] { + pub fn is_empty(self) -> bool { + return self.len() == 0 + } + + pub fn get(self, i: i32) -> Result { return Err(VecErr::OutOfRange) } - pub fn set(self, i: i32, x: i32) -> Result[unit, VecErr] { + pub fn set(self, i: i32, x: i32) -> Result { return Err(VecErr::OutOfRange) } - pub fn push(self, x: i32) -> Result[unit, buffer::AllocErr] { + pub fn push(self, x: i32) -> Result { return Err(buffer::AllocErr::Oom) } - pub fn extend(self, other: VecI32) -> Result[unit, buffer::AllocErr] { + pub fn extend(self, other: VecI32) -> Result { return Err(buffer::AllocErr::Oom) } + pub fn push_all(self, other: VecI32) -> Result { + return self.extend(other) + } + pub fn filter(self, value: i32) -> VecI32 { return () } @@ -83,9 +120,19 @@ impl VecI32 { return () } - pub fn pop(self) -> Result[i32, VecErr] { + pub fn pop(self) -> Result { return Err(VecErr::Empty) } + + pub fn clear(self) -> unit { + while (true) { + match (self.pop()) { + Ok(_) => { } + Err(_) => { break } + } + } + return () + } } impl VecString { @@ -93,19 +140,55 @@ impl VecString { return 0 } - pub fn get(self, i: i32) -> Result[string, VecErr] { + pub fn is_empty(self) -> bool { + return self.len() == 0 + } + + pub fn get(self, i: i32) -> Result { return Err(VecErr::OutOfRange) } - pub fn push(self, x: string) -> Result[unit, buffer::AllocErr] { + pub fn push(self, x: string) -> Result { return Err(buffer::AllocErr::Oom) } - pub fn extend(self, other: VecString) -> Result[unit, buffer::AllocErr] { + pub fn extend(self, other: VecString) -> Result { return Err(buffer::AllocErr::Oom) } - pub fn pop(self) -> Result[string, VecErr] { + pub fn push_all(self, other: VecString) -> Result { + return self.extend(other) + } + + pub fn pop(self) -> Result { return Err(VecErr::Empty) } + + pub fn clear(self) -> unit { + while (true) { + match (self.pop()) { + Ok(_) => { } + Err(_) => { break } + } + } + return () + } + + pub fn join(self, sep: string) -> Result { + let len = self.len() + let buf = buffer::new(0)? + let i = 0 + while (i < len) { + let part = match (self.get(i)) { + Ok(v) => { v } + Err(_) => { panic() } + } + if (i > 0) { + buf.push_str(sep)? + } + buf.push_str(part)? + i = i + 1 + } + return buf.to_string() + } } diff --git a/tests/programs/break_basic.cap b/tests/programs/break_basic.cap new file mode 100644 index 0000000..dc2e116 --- /dev/null +++ b/tests/programs/break_basic.cap @@ -0,0 +1,16 @@ +module break_basic +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let i = 0 + while (i < 10) { + if (i == 5) { + break + } + i = i + 1 + } + c.println("break ok") + return 0 +} diff --git a/tests/programs/break_nested.cap b/tests/programs/break_nested.cap new file mode 100644 index 0000000..ca69d36 --- /dev/null +++ b/tests/programs/break_nested.cap @@ -0,0 +1,28 @@ +module break_nested +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let outer_count = 0 + let inner_sum = 0 + let i = 0 + while (i < 5) { + let j = 0 + while (j < 10) { + if (j == 3) { + break + } + inner_sum = inner_sum + 1 + j = j + 1 + } + outer_count = outer_count + 1 + i = i + 1 + } + if (outer_count == 5) { + if (inner_sum == 15) { + c.println("break nested ok") + } + } + return 0 +} diff --git a/tests/programs/continue_basic.cap b/tests/programs/continue_basic.cap new file mode 100644 index 0000000..92df805 --- /dev/null +++ b/tests/programs/continue_basic.cap @@ -0,0 +1,23 @@ +module continue_basic +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let sum = 0 + let i = 0 + while (i < 10) { + i = i + 1 + if (i == 3) { + continue + } + if (i == 7) { + continue + } + sum = sum + i + } + if (sum == 45) { + c.println("continue ok") + } + return 0 +} diff --git a/tests/programs/for_basic.cap b/tests/programs/for_basic.cap new file mode 100644 index 0000000..635bf66 --- /dev/null +++ b/tests/programs/for_basic.cap @@ -0,0 +1,15 @@ +module for_basic +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Basic for loop: should print 0 1 2 3 4 + for i in 0..5 { + c.println_i32(i) + } + + c.println("for_basic ok") + return 0 +} diff --git a/tests/programs/for_break.cap b/tests/programs/for_break.cap new file mode 100644 index 0000000..143f721 --- /dev/null +++ b/tests/programs/for_break.cap @@ -0,0 +1,18 @@ +module for_break +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // For loop with break: should exit when i == 5 + for i in 0..100 { + if i == 5 { + break + } + c.println_i32(i) + } + + c.println("for_break ok") + return 0 +} diff --git a/tests/programs/for_break_nested.cap b/tests/programs/for_break_nested.cap new file mode 100644 index 0000000..2c0451d --- /dev/null +++ b/tests/programs/for_break_nested.cap @@ -0,0 +1,31 @@ +module for_break_nested +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Break in nested loop: only breaks inner loop + let outer_count = 0 + let inner_count = 0 + + for i in 0..5 { + outer_count = outer_count + 1 + for j in 0..10 { + if j == 3 { + break + } + inner_count = inner_count + 1 + } + } + + // outer_count should be 5 (all iterations) + // inner_count should be 5 * 3 = 15 (break at j=3 each time) + if outer_count == 5 { + if inner_count == 15 { + c.println("for_break_nested ok") + } + } + + return 0 +} diff --git a/tests/programs/for_continue.cap b/tests/programs/for_continue.cap new file mode 100644 index 0000000..2f5b728 --- /dev/null +++ b/tests/programs/for_continue.cap @@ -0,0 +1,25 @@ +module for_continue +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // For loop with continue: skip specific values + // Should print 0 1 3 4 6 7 9 (skip 2, 5, 8) + for i in 0..10 { + if i == 2 { + continue + } + if i == 5 { + continue + } + if i == 8 { + continue + } + c.println_i32(i) + } + + c.println("for_continue ok") + return 0 +} diff --git a/tests/programs/for_continue_nested.cap b/tests/programs/for_continue_nested.cap new file mode 100644 index 0000000..9ab600d --- /dev/null +++ b/tests/programs/for_continue_nested.cap @@ -0,0 +1,28 @@ +module for_continue_nested +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Continue in nested loop: only affects inner loop + let sum = 0 + + for i in 0..3 { + for j in 0..5 { + // Skip when j is 2 + if j == 2 { + continue + } + sum = sum + 1 + } + } + + // Each inner loop runs 5 times but skips j=2, so 4 iterations + // Total: 3 * 4 = 12 + if sum == 12 { + c.println("for_continue_nested ok") + } + + return 0 +} diff --git a/tests/programs/for_empty_range.cap b/tests/programs/for_empty_range.cap new file mode 100644 index 0000000..1e63a0e --- /dev/null +++ b/tests/programs/for_empty_range.cap @@ -0,0 +1,24 @@ +module for_empty_range +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Empty range (start >= end): body should not execute + let executed = 0 + for i in 5..5 { + executed = 1 + } + + // Also test negative range + for i in 10..5 { + executed = 1 + } + + if executed == 0 { + c.println("for_empty_range ok") + } + + return 0 +} diff --git a/tests/programs/for_nested.cap b/tests/programs/for_nested.cap new file mode 100644 index 0000000..99a532a --- /dev/null +++ b/tests/programs/for_nested.cap @@ -0,0 +1,22 @@ +module for_nested +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Nested for loops: count total iterations + let count = 0 + for i in 0..3 { + for j in 0..4 { + count = count + 1 + } + } + + // Should be 3 * 4 = 12 + if count == 12 { + c.println("for_nested ok") + } + + return 0 +} diff --git a/tests/programs/for_sum.cap b/tests/programs/for_sum.cap new file mode 100644 index 0000000..45ae5c4 --- /dev/null +++ b/tests/programs/for_sum.cap @@ -0,0 +1,20 @@ +module for_sum +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Sum numbers 0 to 9 + let sum = 0 + for i in 0..10 { + sum = sum + i + } + + // 0+1+2+3+4+5+6+7+8+9 = 45 + if sum == 45 { + c.println("for_sum ok") + } + + return 0 +} diff --git a/tests/programs/generic_and_index.cap b/tests/programs/generic_and_index.cap new file mode 100644 index 0000000..9e81080 --- /dev/null +++ b/tests/programs/generic_and_index.cap @@ -0,0 +1,55 @@ +module generic_and_index +use sys::system +use sys::console + +struct Wrapper { + value: T, +} + +fn identity(x: T) -> T { + return x +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Test generics with <> + let w = Wrapper{ value: 42 } + let v = identity(w.value) + if (v != 42) { + c.println("FAIL: generic") + return 1 + } + c.println("generic ok") + + // Test indexing with [] + let s = "hello" + let first = s[0] + if (first != 104u8) { // 'h' is 104 + c.println("FAIL: index") + return 1 + } + c.println("index ok") + + // Test comparison with < (should not be confused with generics) + let x = 5 + let y = 10 + if (x >= y) { + c.println("FAIL: compare") + return 1 + } + c.println("compare ok") + + // Test both in same expression context + let s2 = "world" + let idx = identity(1) + let ch = s2[idx] + if (ch != 111u8) { // 'o' is 111 + c.println("FAIL: combo") + return 1 + } + c.println("combo ok") + + c.println("generic_and_index ok") + return 0 +} diff --git a/tests/programs/generics_basic.cap b/tests/programs/generics_basic.cap index 56ba111..bfa59ce 100644 --- a/tests/programs/generics_basic.cap +++ b/tests/programs/generics_basic.cap @@ -3,18 +3,18 @@ module generics_basic use sys::console use sys::system -struct Box[T] { +struct Box { value: T, } -fn id[T](value: T) -> T { +fn id(value: T) -> T { return value } pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() - let b = Box[i32]{ value: 42 } - let v = id[i32](b.value) + let b = Box{ value: 42 } + let v = id(b.value) c.print_i32(v) return 0 } diff --git a/tests/programs/nested_match.cap b/tests/programs/nested_match.cap new file mode 100644 index 0000000..62281bf --- /dev/null +++ b/tests/programs/nested_match.cap @@ -0,0 +1,30 @@ +module nested_match +use sys::system +use sys::console + +fn classify(x: i32) -> i32 { + match (x > 0) { + true => { + match (x > 10) { + true => { return 2 } + false => { return 1 } + } + } + false => { return 0 } + } +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let a = classify(-5) + let b = classify(5) + let d = classify(15) + if (a == 0) { + if (b == 1) { + if (d == 2) { + c.println("nested match ok") + } + } + } + return 0 +} diff --git a/tests/programs/result_construct.cap b/tests/programs/result_construct.cap index 25e9b03..31a18da 100644 --- a/tests/programs/result_construct.cap +++ b/tests/programs/result_construct.cap @@ -35,7 +35,7 @@ pub fn main(rc: RootCap) -> i32 { return 0 } -fn make(ok: bool) -> Result[string, i32] { +fn make(ok: bool) -> Result { if (ok) { return Ok("hello") } else { diff --git a/tests/programs/should_fail_break_in_function.cap b/tests/programs/should_fail_break_in_function.cap new file mode 100644 index 0000000..842e3c9 --- /dev/null +++ b/tests/programs/should_fail_break_in_function.cap @@ -0,0 +1,18 @@ +module should_fail_break_in_function +use sys::system +use sys::console + +fn helper() -> i32 { + break + return 0 +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let i = 0 + while (i < 10) { + let x = helper() + i = i + 1 + } + return 0 +} diff --git a/tests/programs/should_fail_break_outside_loop.cap b/tests/programs/should_fail_break_outside_loop.cap new file mode 100644 index 0000000..ccd78cb --- /dev/null +++ b/tests/programs/should_fail_break_outside_loop.cap @@ -0,0 +1,11 @@ +module should_fail_break_outside_loop +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.println("before") + break + c.println("after") + return 0 +} diff --git a/tests/programs/should_fail_capability_borrow_return_result.cap b/tests/programs/should_fail_capability_borrow_return_result.cap index 6dc0357..48c48a5 100644 --- a/tests/programs/should_fail_capability_borrow_return_result.cap +++ b/tests/programs/should_fail_capability_borrow_return_result.cap @@ -4,7 +4,7 @@ module should_fail_capability_borrow_return_result capability struct Cap impl Cap { - pub fn try_dup(self: &Cap) -> Result[Cap, i32] { + pub fn try_dup(self: &Cap) -> Result { return Ok(Cap{}) } } diff --git a/tests/programs/should_fail_continue_outside_loop.cap b/tests/programs/should_fail_continue_outside_loop.cap new file mode 100644 index 0000000..2ef463f --- /dev/null +++ b/tests/programs/should_fail_continue_outside_loop.cap @@ -0,0 +1,11 @@ +module should_fail_continue_outside_loop +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.println("before") + continue + c.println("after") + return 0 +} diff --git a/tests/programs/should_fail_for_loop_move.cap b/tests/programs/should_fail_for_loop_move.cap new file mode 100644 index 0000000..11ee623 --- /dev/null +++ b/tests/programs/should_fail_for_loop_move.cap @@ -0,0 +1,12 @@ +package safe +module should_fail_for_loop_move + +capability struct Cap + +pub fn main() -> i32 { + let c = Cap{} + for i in 0..5 { + let x = c + } + return 0 +} diff --git a/tests/programs/should_fail_for_non_i32_end.cap b/tests/programs/should_fail_for_non_i32_end.cap new file mode 100644 index 0000000..37cc38a --- /dev/null +++ b/tests/programs/should_fail_for_non_i32_end.cap @@ -0,0 +1,14 @@ +module should_fail_for_non_i32_end +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Error: for loop range end must be i32 + for i in 0..true { + c.println_i32(i) + } + + return 0 +} diff --git a/tests/programs/should_fail_for_non_i32_start.cap b/tests/programs/should_fail_for_non_i32_start.cap new file mode 100644 index 0000000..8748f2c --- /dev/null +++ b/tests/programs/should_fail_for_non_i32_start.cap @@ -0,0 +1,14 @@ +module should_fail_for_non_i32_start +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Error: for loop range start must be i32 + for i in true..5 { + c.println_i32(i) + } + + return 0 +} diff --git a/tests/programs/should_fail_match_result_non_exhaustive.cap b/tests/programs/should_fail_match_result_non_exhaustive.cap index ab35377..283e7e1 100644 --- a/tests/programs/should_fail_match_result_non_exhaustive.cap +++ b/tests/programs/should_fail_match_result_non_exhaustive.cap @@ -1,6 +1,6 @@ module should_fail_match_result_non_exhaustive -fn make() -> Result[i32, i32] { +fn make() -> Result { return Ok(1) } diff --git a/tests/programs/should_fail_reserved_type_name.cap b/tests/programs/should_fail_reserved_type_name.cap index adbbcd0..05d53a7 100644 --- a/tests/programs/should_fail_reserved_type_name.cap +++ b/tests/programs/should_fail_reserved_type_name.cap @@ -1,6 +1,6 @@ module should_fail_reserved_type_name -struct string { } +struct i32 { } pub fn main(rc: RootCap) -> i32 { return 0 diff --git a/tests/programs/should_fail_result_unwrap_or_mismatch.cap b/tests/programs/should_fail_result_unwrap_or_mismatch.cap index b66d04f..12fb500 100644 --- a/tests/programs/should_fail_result_unwrap_or_mismatch.cap +++ b/tests/programs/should_fail_result_unwrap_or_mismatch.cap @@ -1,6 +1,6 @@ module should_fail_result_unwrap_or_mismatch -fn make() -> Result[i32, i32] { +fn make() -> Result { return Ok(1) } diff --git a/tests/programs/should_fail_try_err_mismatch.cap b/tests/programs/should_fail_try_err_mismatch.cap index 5c19036..9cf7167 100644 --- a/tests/programs/should_fail_try_err_mismatch.cap +++ b/tests/programs/should_fail_try_err_mismatch.cap @@ -1,11 +1,11 @@ package safe module should_fail_try_err_mismatch -fn make() -> Result[i32, bool] { +fn make() -> Result { return Err(false) } -fn use_try() -> Result[i32, i32] { +fn use_try() -> Result { let v = make()? return Ok(v) } diff --git a/tests/programs/should_fail_try_non_result.cap b/tests/programs/should_fail_try_non_result.cap index 1d5ad1b..3a60a16 100644 --- a/tests/programs/should_fail_try_non_result.cap +++ b/tests/programs/should_fail_try_non_result.cap @@ -1,7 +1,7 @@ package safe module should_fail_try_non_result -fn make() -> Result[i32, i32] { +fn make() -> Result { return Ok(1) } diff --git a/tests/programs/should_pass_defer.cap b/tests/programs/should_pass_defer.cap new file mode 100644 index 0000000..5ab3f09 --- /dev/null +++ b/tests/programs/should_pass_defer.cap @@ -0,0 +1,12 @@ +module should_pass_defer +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.println("start") + defer c.println("first") + defer c.println("second") + c.println("end") + return 0 +} diff --git a/tests/programs/should_pass_defer_return.cap b/tests/programs/should_pass_defer_return.cap new file mode 100644 index 0000000..53bd3a5 --- /dev/null +++ b/tests/programs/should_pass_defer_return.cap @@ -0,0 +1,14 @@ +module should_pass_defer_return +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.println("start") + defer c.println("outer") + if (true) { + defer c.println("inner") + return 0 + } + return 1 +} diff --git a/tests/programs/should_pass_defer_scope.cap b/tests/programs/should_pass_defer_scope.cap new file mode 100644 index 0000000..12ac427 --- /dev/null +++ b/tests/programs/should_pass_defer_scope.cap @@ -0,0 +1,21 @@ +module should_pass_defer_scope +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.println("start") + if (true) { + defer c.println("if defer") + c.println("in if") + } + c.println("after if") + let i = 0 + while (i < 1) { + defer c.println("loop defer") + c.println("in loop") + break + } + c.println("after loop") + return 0 +} diff --git a/tests/programs/should_pass_for_forever.cap b/tests/programs/should_pass_for_forever.cap new file mode 100644 index 0000000..50018b8 --- /dev/null +++ b/tests/programs/should_pass_for_forever.cap @@ -0,0 +1,17 @@ +module should_pass_for_forever +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let i = 0 + for { + c.println_i32(i) + if (i == 2) { + break + } + i = i + 1 + } + c.println("for_forever ok") + return 0 +} diff --git a/tests/programs/should_pass_if_let.cap b/tests/programs/should_pass_if_let.cap new file mode 100644 index 0000000..e07a9fc --- /dev/null +++ b/tests/programs/should_pass_if_let.cap @@ -0,0 +1,41 @@ +module should_pass_if_let +use sys::system +use sys::console + +fn make_ok() -> Result { + return Ok(7) +} + +fn make_err() -> Result { + return Err(9) +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let ok = make_ok() + if let Ok(x) = ok { + if (x != 7) { + c.println("bad ok") + return 1 + } + c.println("ok") + } else { + c.println("unexpected err") + return 1 + } + + let err = make_err() + if let Err(e) = err { + if (e != 9) { + c.println("bad err") + return 2 + } + c.println("err") + } else { + c.println("unexpected ok") + return 2 + } + + c.println("if_let ok") + return 0 +} diff --git a/tests/programs/should_pass_result_is_ok_is_err.cap b/tests/programs/should_pass_result_is_ok_is_err.cap new file mode 100644 index 0000000..5382c11 --- /dev/null +++ b/tests/programs/should_pass_result_is_ok_is_err.cap @@ -0,0 +1,31 @@ +module should_pass_result_is_ok_is_err +use sys::system + +fn make_ok() -> Result { + return Ok(1) +} + +fn make_err() -> Result { + return Err(2) +} + +pub fn main(rc: RootCap) -> i32 { + let con = rc.mint_console() + let ok_result = make_ok() + let err_result = make_err() + + if ok_result.is_ok() == false { + return 1 + } + if ok_result.is_err() == true { + return 2 + } + if err_result.is_ok() == true { + return 3 + } + if err_result.is_err() == false { + return 4 + } + con.print("is_ok_is_err ok") + return 0 +} diff --git a/tests/programs/should_pass_result_ok_err.cap b/tests/programs/should_pass_result_ok_err.cap new file mode 100644 index 0000000..9c4bad3 --- /dev/null +++ b/tests/programs/should_pass_result_ok_err.cap @@ -0,0 +1,33 @@ +module should_pass_result_ok_err +use sys::system + +fn make_ok() -> Result { + return Ok(42) +} + +fn make_err() -> Result { + return Err(99) +} + +pub fn main(rc: RootCap) -> i32 { + let con = rc.mint_console() + let ok_result = make_ok() + let err_result = make_err() + + if ok_result.is_ok() { + let val = ok_result.ok() + if val != 42 { + return 1 + } + } + + if err_result.is_err() { + let e = err_result.err() + if e != 99 { + return 2 + } + } + + con.print("ok_err ok") + return 0 +} diff --git a/tests/programs/should_pass_result_unwrap_err_or.cap b/tests/programs/should_pass_result_unwrap_err_or.cap index 405652d..2b2c8ff 100644 --- a/tests/programs/should_pass_result_unwrap_err_or.cap +++ b/tests/programs/should_pass_result_unwrap_err_or.cap @@ -1,10 +1,10 @@ module should_pass_result_unwrap_err_or -fn make_ok() -> Result[i32, i32] { +fn make_ok() -> Result { return Ok(1) } -fn make_err() -> Result[i32, i32] { +fn make_err() -> Result { return Err(2) } diff --git a/tests/programs/should_pass_result_unwrap_or.cap b/tests/programs/should_pass_result_unwrap_or.cap index 952b89a..f4ff486 100644 --- a/tests/programs/should_pass_result_unwrap_or.cap +++ b/tests/programs/should_pass_result_unwrap_or.cap @@ -1,10 +1,10 @@ module should_pass_result_unwrap_or -fn make_ok() -> Result[i32, i32] { +fn make_ok() -> Result { return Ok(1) } -fn make_err() -> Result[i32, i32] { +fn make_err() -> Result { return Err(2) } diff --git a/tests/programs/should_pass_try_question.cap b/tests/programs/should_pass_try_question.cap index ef99399..b8e1ed4 100644 --- a/tests/programs/should_pass_try_question.cap +++ b/tests/programs/should_pass_try_question.cap @@ -1,11 +1,11 @@ package safe module should_pass_try_question -fn make(x: i32) -> Result[i32, i32] { +fn make(x: i32) -> Result { return Ok(x) } -fn use_try() -> Result[i32, i32] { +fn use_try() -> Result { let v = make(7)? return Ok(v) } diff --git a/tests/programs/slice_safe.cap b/tests/programs/slice_safe.cap index f9ae391..d3e8d95 100644 --- a/tests/programs/slice_safe.cap +++ b/tests/programs/slice_safe.cap @@ -2,7 +2,7 @@ package safe module slice_safe use sys::buffer -fn parse_bytes(data: Slice[u8]) -> i32 { +fn parse_bytes(data: Slice) -> i32 { return 0 } diff --git a/tests/programs/string_compare.cap b/tests/programs/string_compare.cap new file mode 100644 index 0000000..c374975 --- /dev/null +++ b/tests/programs/string_compare.cap @@ -0,0 +1,43 @@ +module string_compare +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Test equal strings + let a = "hello" + let b = "hello" + if (a == b) { + c.println("equal ok") + } + + // Test not equal strings + let x = "hello" + let y = "world" + if (x != y) { + c.println("not equal ok") + } + + // Test equal empty strings + let empty1 = "" + let empty2 = "" + if (empty1 == empty2) { + c.println("empty ok") + } + + // Test different lengths + let short = "hi" + let long = "hello" + if (short != long) { + c.println("diff len ok") + } + + // Test string literals directly + if ("test" == "test") { + c.println("literal ok") + } + + c.println("string_compare ok") + return 0 +} diff --git a/tests/programs/string_helpers.cap b/tests/programs/string_helpers.cap index 34781e4..0824dba 100644 --- a/tests/programs/string_helpers.cap +++ b/tests/programs/string_helpers.cap @@ -13,6 +13,9 @@ pub fn main(rc: RootCap) -> i32 { let trimmed = " hi \n".trim() let trimmed_start = " hi ".trim_start() let trimmed_end = " hi ".trim_end() + let trimmed_ascii = " \tHi\n".trim_ascii() + let lower = "AbC".to_lower_ascii() + let upper = "AbC".to_upper_ascii() let lines = "a\nb\n".split_lines() let alloc = rc.mint_alloc_default() alloc.vec_string_free(words) @@ -23,8 +26,47 @@ pub fn main(rc: RootCap) -> i32 { c.assert(trimmed.ends_with_byte(105u8)) c.assert(trimmed_start.starts_with("hi")) c.assert(trimmed_end.ends_with("hi")) - c.assert("abc".starts_with_byte(97u8)) - c.assert("abc".ends_with_byte(99u8)) + c.assert(trimmed_ascii.eq("Hi")) + c.assert(lower.eq("abc")) + c.assert(upper.eq("ABC")) + c.assert("abc".starts_with_byte('a')) + c.assert("abc".ends_with_byte('c')) + c.assert("".is_empty()) + c.assert(!"a".is_empty()) + c.assert("abc".contains_byte(98u8)) + c.assert(!"abc".contains_byte(120u8)) + match ("abc".index_of_byte('c')) { + Ok(i) => { c.assert(i == 2) } + Err(_) => { c.assert(false) } + } + match ("abca".last_index_of_byte('a')) { + Ok(i) => { c.assert(i == 3) } + Err(_) => { c.assert(false) } + } + match ("abca".find_byte('a')) { + Ok(i) => { c.assert(i == 0) } + Err(_) => { c.assert(false) } + } + match ("abca".rfind_byte('a')) { + Ok(i) => { c.assert(i == 3) } + Err(_) => { c.assert(false) } + } + c.assert("abca".count_byte('a') == 2) + c.assert("abc".is_ascii()) + c.assert("abc".byte_at_checked(10).is_err()) + match ("a,b,c".split_once(',')) { + Ok(parts) => { + c.assert(parts.left.eq("a")) + c.assert(parts.right.eq("b,c")) + } + Err(_) => { c.assert(false) } + } + let pieces = "a,b,c".split(',') + c.assert(pieces.len() == 3) + match (pieces.join(",")) { + Ok(joined) => { c.assert(joined.eq("a,b,c")) } + Err(_) => { c.assert(false) } + } c.println("string ok") return 0 } diff --git a/tests/programs/string_index.cap b/tests/programs/string_index.cap new file mode 100644 index 0000000..992f3f6 --- /dev/null +++ b/tests/programs/string_index.cap @@ -0,0 +1,44 @@ +module string_index +use sys::system +use sys::console + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + + // Test basic string indexing + let s = "hello" + let first = s[0] + if (first != 104u8) { // 'h' is 104 + c.println("FAIL: first") + return 1 + } + c.println("first ok") + + // Test middle character + let middle = s[2] + if (middle != 108u8) { // 'l' is 108 + c.println("FAIL: middle") + return 1 + } + c.println("middle ok") + + // Test last character + let last = s[4] + if (last != 111u8) { // 'o' is 111 + c.println("FAIL: last") + return 1 + } + c.println("last ok") + + // Test indexing with variable + let i = 1 + let at_i = s[i] + if (at_i != 101u8) { // 'e' is 101 + c.println("FAIL: var index") + return 1 + } + c.println("var index ok") + + c.println("string_index ok") + return 0 +} diff --git a/tests/programs/untrusted_logs.cap b/tests/programs/untrusted_logs.cap index 95ce8eb..dcf3e82 100644 --- a/tests/programs/untrusted_logs.cap +++ b/tests/programs/untrusted_logs.cap @@ -1,7 +1,7 @@ module untrusted_logs use sys::fs -pub fn read_log(dir: Dir) -> Result[string, FsErr] { +pub fn read_log(dir: Dir) -> Result { let file = dir.open_read("app.log") return file.read_to_string() }