diff --git a/.gitignore b/.gitignore index 04de141..c069562 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ target/ .hermit/ vscode/node_modules/ vscode/package-lock.json +docs/code-browser/index.html +docs/code-browser/files/ diff --git a/FIXME.md b/FIXME.md index 00eb975..a53b46d 100644 --- a/FIXME.md +++ b/FIXME.md @@ -37,3 +37,9 @@ Fixes applied from review: - 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. + +Current known issues/workarounds: + +- Result<*T> still trips a typechecker mismatch (Param vs Path) in some generic + cases. Workaround: sys.vec allows limited arithmetic on type params so Vec + can compute sizes; see tests/programs/result_ptr_generic.cap for coverage. diff --git a/TODO-MEMORY.md b/TODO-MEMORY.md new file mode 100644 index 0000000..85dc622 --- /dev/null +++ b/TODO-MEMORY.md @@ -0,0 +1,27 @@ +TODO-MEMORY + +1) Make Alloc real in runtime + - Done: all allocating runtime paths take an Alloc handle and use it for + malloc/free; current backend is libc, but the ABI keeps alloc explicit. + +2) Fix Result<*T> typechecker bug + - Done: type equivalence treats type params vs unqualified paths consistently + in enum payload matching and Ok/Err checks. + +3) Remove remaining runtime Vec intrinsics + - Done: runtime Vec tables removed; Vec/Slice operations are pure Cap. + +4) Owned string and slice memory model + - string is a view over Slice; Text is owned Vec. + - Done: Safe non-stdlib modules may not return or store Slice/MutSlice to reduce + use-after-free hazards until a real lifetime model exists. + - Done: typecheck coverage for slice returns and struct/enum fields. + +5) Unsafe pointer API completeness + - Done: memcpy/memmove added to sys::unsafe_ptr and lowered in codegen. + - Done: ptr_is_null/ownership expectations documented. + +6) ABI + lowering docs update + - Done: opaque structs lowered to handles, field access is special-cased + in codegen, sret + ResultOut behavior, alloc-passing convention, and + Slice/string layout. diff --git a/TODO.md b/TODO.md index 347d1ca..63593f3 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +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). +- Stdlib uses Vec directly (u8/i32/string supported). - Codex reviewed Claude-generated commits. diff --git a/capc/src/codegen/emit.rs b/capc/src/codegen/emit.rs index 57c9d49..9a62c70 100644 --- a/capc/src/codegen/emit.rs +++ b/capc/src/codegen/emit.rs @@ -16,10 +16,10 @@ use crate::ast::{BinaryOp, Literal, UnaryOp}; use super::{ CodegenError, EnumIndex, Flow, FnInfo, LocalValue, ResultKind, ResultShape, - StructLayoutIndex, TypeLayout, ValueRepr, + StructLayout, StructLayoutIndex, TypeLayout, ValueRepr, }; use super::abi_quirks; -use super::layout::{align_to, resolve_struct_layout, type_layout_for_abi}; +use super::layout::{align_to, resolve_struct_layout, type_layout_from_index}; use super::sig_to_clif; /// Target blocks for break/continue inside a loop. @@ -275,11 +275,42 @@ fn emit_hir_stmt_inner( module, data_counter, )?; + if let crate::typeck::Ty::Path(name, _) = &let_stmt.ty.ty { + if let Some(layout) = enum_index.layouts.get(name) { + let align = layout.align.max(1); + let slot_size = aligned_slot_size(layout.size, align); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + slot_size, + )); + let base_ptr = aligned_stack_addr( + builder, + slot, + align, + module.isa().pointer_type(), + ); + store_value_by_ty( + builder, + base_ptr, + 0, + &let_stmt.ty, + value, + enum_index, + struct_layouts, + module, + )?; + locals.insert( + let_stmt.local_id, + LocalValue::StructSlot(slot, let_stmt.ty.clone(), align), + ); + return Ok(Flow::Continues); + } + } if let Some(layout) = resolve_struct_layout(&let_stmt.ty.ty, "", &struct_layouts.layouts) { let align = layout.align.max(1); - let slot_size = layout.size.max(1).saturating_add(align - 1); + let slot_size = aligned_slot_size(layout.size, align); let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( ir::StackSlotKind::ExplicitSlot, slot_size, @@ -296,6 +327,7 @@ fn emit_hir_stmt_inner( 0, &let_stmt.ty, value, + enum_index, struct_layouts, module, )?; @@ -346,6 +378,7 @@ fn emit_hir_stmt_inner( 0, ty, value, + enum_index, struct_layouts, module, )?; @@ -398,6 +431,7 @@ fn emit_hir_stmt_inner( *out_ptr, ret_ty, value, + enum_index, struct_layouts, module, )?; @@ -430,6 +464,7 @@ fn emit_hir_stmt_inner( *out_ok, ok_ty, *ok, + enum_index, struct_layouts, module, )?; @@ -440,6 +475,7 @@ fn emit_hir_stmt_inner( *out_err, err_ty, *err, + enum_index, struct_layouts, module, )?; @@ -996,6 +1032,7 @@ fn emit_hir_expr_inner( builder, &ok_ty, ptr_ty, + enum_index, Some(struct_layouts), module, )? @@ -1004,6 +1041,7 @@ fn emit_hir_expr_inner( builder, &err_ty, ptr_ty, + enum_index, Some(struct_layouts), module, )?; @@ -1014,6 +1052,7 @@ fn emit_hir_expr_inner( builder, &ok_ty, ptr_ty, + enum_index, Some(struct_layouts), module, )?; @@ -1034,6 +1073,7 @@ fn emit_hir_expr_inner( builder, &err_ty, ptr_ty, + enum_index, Some(struct_layouts), module, )? @@ -1056,6 +1096,76 @@ fn emit_hir_expr_inner( } } + if let crate::typeck::Ty::Path(ty_name, _) = &variant.enum_ty.ty { + if let Some(layout) = enum_index.layouts.get(ty_name) { + let variants = enum_index + .variants + .get(ty_name) + .ok_or_else(|| { + CodegenError::Codegen(format!( + "unknown enum variant: {}.{}", + ty_name, variant.variant_name + )) + })?; + let discr = variants.get(&variant.variant_name).ok_or_else(|| { + CodegenError::Codegen(format!( + "unknown enum variant: {}.{}", + ty_name, variant.variant_name + )) + })?; + let tag_val = builder.ins().iconst(ir::types::I32, i64::from(*discr)); + let ptr_ty = module.isa().pointer_type(); + let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( + ir::StackSlotKind::ExplicitSlot, + aligned_slot_size(layout.size, layout.align), + )); + let base_ptr = aligned_stack_addr(builder, slot, layout.align, ptr_ty); + builder.ins().store(MemFlags::new(), tag_val, base_ptr, 0); + if layout.payload_size > 0 { + if let Some(payloads) = enum_index.payloads.get(ty_name) { + let payload_ty = payloads.get(&variant.variant_name).cloned().flatten(); + if let Some(payload_ty) = payload_ty { + let payload = if let Some(payload_expr) = &variant.payload { + emit_hir_expr( + builder, + payload_expr, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? + } else { + zero_value_for_ty( + builder, + &payload_ty, + ptr_ty, + enum_index, + Some(struct_layouts), + module, + )? + }; + store_value_by_ty( + builder, + base_ptr, + layout.payload_offset, + &payload_ty, + payload, + enum_index, + struct_layouts, + module, + )?; + } else { + zero_bytes(builder, base_ptr, layout.payload_offset, layout.payload_size); + } + } + } + return Ok(ValueRepr::Single(base_ptr)); + } + } + // For non-Result enums or variants without payload, emit just the discriminant let qualified = match &variant.enum_ty.ty { crate::typeck::Ty::Path(path, _) => path.clone(), @@ -1117,6 +1227,7 @@ fn emit_hir_expr_inner( builder, &ok_ty, ptr_ty, + enum_index, Some(struct_layouts), module, )?; @@ -1144,6 +1255,7 @@ fn emit_hir_expr_inner( *out_ptr, ret_ty, ret_value, + enum_index, struct_layouts, module, )?; @@ -1166,6 +1278,7 @@ fn emit_hir_expr_inner( *out_ok, ok_ty, *ok, + enum_index, struct_layouts, module, )?; @@ -1176,6 +1289,7 @@ fn emit_hir_expr_inner( *out_err, err_ty, *err, + enum_index, struct_layouts, module, )?; @@ -1221,6 +1335,22 @@ fn emit_hir_expr_inner( } }; + if module_path == "sys.unsafe_ptr" { + if let Some(value) = emit_unsafe_ptr_call( + builder, + module, + call, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + data_counter, + )? { + return Ok(value); + } + } + // Lookup in fn_map by module.function key let key = format!("{}.{}", module_path, func_name); let info = fn_map @@ -1235,15 +1365,26 @@ fn emit_hir_expr_inner( 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) + && (is_non_opaque_struct_type(&call.ret_ty, struct_layouts) + || matches!(&call.ret_ty.ty, crate::typeck::Ty::Path(name, _) if enum_index.layouts.contains_key(name))) { - 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 (size, align) = if let Some(layout) = + resolve_struct_layout(&call.ret_ty.ty, "", &struct_layouts.layouts) + { + (layout.size, layout.align) + } else if let crate::typeck::Ty::Path(name, _) = &call.ret_ty.ty { + let layout = enum_index.layouts.get(name).ok_or_else(|| { + CodegenError::Unsupported("enum layout missing".to_string()) + })?; + (layout.size, layout.align) + } else { + return Err(CodegenError::Unsupported( + "sret return layout missing".to_string(), + )); + }; + let align = align.max(1); + let slot_size = aligned_slot_size(size, align); let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( ir::StackSlotKind::ExplicitSlot, slot_size, @@ -1877,39 +2018,46 @@ fn emit_hir_index( let field = layout.fields.get("bytes").ok_or_else(|| { CodegenError::Unsupported("string.bytes field missing".to_string()) })?; + let slice_layout = + resolve_struct_layout(&field.ty.ty, "", &struct_layouts.layouts).ok_or_else( + || CodegenError::Unsupported("Slice layout 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)?; + let result = emit_slice_index(builder, module, addr, slice_layout, 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, + // For Slice[u8], index into the raw slice. + let base_ptr = match object { + ValueRepr::Single(ptr) => ptr, _ => { return Err(CodegenError::Codegen( - "expected Slice to be a handle".to_string(), + "expected Slice to be a struct pointer".to_string(), )) } }; - - let result = emit_slice_at(builder, module, handle, index_val)?; + let layout = + resolve_struct_layout(object_ty, "", &struct_layouts.layouts).ok_or_else(|| { + CodegenError::Unsupported("Slice layout missing".to_string()) + })?; + let result = emit_slice_index(builder, module, base_ptr, layout, 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, + // For MutSlice[u8], index into the raw slice. + let base_ptr = match object { + ValueRepr::Single(ptr) => ptr, _ => { return Err(CodegenError::Codegen( - "expected MutSlice to be a handle".to_string(), + "expected MutSlice to be a struct pointer".to_string(), )) } }; - - let result = emit_mut_slice_at(builder, module, handle, index_val)?; + let layout = + resolve_struct_layout(object_ty, "", &struct_layouts.layouts).ok_or_else(|| { + CodegenError::Unsupported("MutSlice layout missing".to_string()) + })?; + let result = emit_slice_index(builder, module, base_ptr, layout, index_val)?; Ok(ValueRepr::Single(result)) } _ => Err(CodegenError::Codegen(format!( @@ -1919,92 +2067,63 @@ fn emit_hir_index( } } -/// Emit a call to the runtime slice at function. -/// Returns a u8 value at the given index. -fn emit_slice_at( +/// Emit a bounds-checked slice index. +fn emit_slice_index( builder: &mut FunctionBuilder, - module: &mut ObjectModule, - handle: Value, + module: &ObjectModule, + slice_base: Value, + slice_layout: &StructLayout, index: Value, ) -> Result { - use cranelift_codegen::ir::{AbiParam, Signature}; - let ptr_ty = module.isa().pointer_type(); + let ptr_field = slice_layout + .fields + .get("ptr") + .ok_or_else(|| CodegenError::Unsupported("Slice.ptr field missing".to_string()))?; + let len_field = slice_layout + .fields + .get("len") + .ok_or_else(|| CodegenError::Unsupported("Slice.len field missing".to_string()))?; + let ptr_addr = ptr_add(builder, slice_base, ptr_field.offset); + let len_addr = ptr_add(builder, slice_base, len_field.offset); + let raw_ptr = builder.ins().load(ptr_ty, MemFlags::new(), ptr_addr, 0); + let len_val = builder + .ins() + .load(ir::types::I32, MemFlags::new(), len_addr, 0); + let zero_i32 = builder.ins().iconst(ir::types::I32, 0); + let idx_nonneg = builder + .ins() + .icmp(IntCC::SignedGreaterThanOrEqual, index, zero_i32); + let idx_lt = builder + .ins() + .icmp(IntCC::SignedLessThan, index, len_val); + let ptr_nonnull = builder.ins().icmp_imm(IntCC::NotEqual, raw_ptr, 0); + let in_bounds = builder.ins().band(idx_nonneg, idx_lt); + let in_bounds = builder.ins().band(in_bounds, ptr_nonnull); - // 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]) -} - -/// Emit a call to the runtime slice_from_ptr helper. -fn emit_slice_from_ptr( - builder: &mut FunctionBuilder, - module: &mut ObjectModule, - ptr: Value, - len: Value, -) -> Result { - 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]) -} - -/// 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(); + let ok_block = builder.create_block(); + let err_block = builder.create_block(); + let done_block = builder.create_block(); + builder.append_block_param(done_block, ir::types::I8); + builder.ins().brif(in_bounds, ok_block, &[], err_block, &[]); - // 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)); + builder.switch_to_block(ok_block); + builder.seal_block(ok_block); + let idx_ptr = builder.ins().uextend(ptr_ty, index); + let addr = builder.ins().iadd(raw_ptr, idx_ptr); + let value = builder + .ins() + .load(ir::types::I8, MemFlags::new(), addr, 0); + builder.ins().jump(done_block, &[value]); - // 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); + builder.switch_to_block(err_block); + builder.seal_block(err_block); + let zero_i8 = builder.ins().iconst(ir::types::I8, 0); + builder.ins().jump(done_block, &[zero_i8]); - // Call the function - let call_inst = builder.ins().call(local_func, &[handle, index]); - let results = builder.inst_results(call_inst); - Ok(results[0]) + builder.switch_to_block(done_block); + builder.seal_block(done_block); + Ok(builder.block_params(done_block)[0]) } fn cmp_cc(expr: &crate::hir::HirExpr, signed: IntCC, unsigned: IntCC) -> IntCC { @@ -2041,6 +2160,26 @@ fn emit_hir_struct_literal( module: &mut ObjectModule, data_counter: &mut u32, ) -> Result { + if matches!(literal.struct_ty.abi, AbiType::Handle) { + if literal.fields.len() != 1 { + return Err(CodegenError::Unsupported(format!( + "opaque struct literal expects 1 field, got {}", + literal.fields.len() + ))); + } + let value = emit_hir_expr( + builder, + &literal.fields[0].expr, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + return Ok(value); + } let layout = resolve_struct_layout(&literal.struct_ty.ty, "", &struct_layouts.layouts) .ok_or_else(|| { CodegenError::Unsupported(format!( @@ -2081,6 +2220,7 @@ fn emit_hir_struct_literal( field_layout.offset, &field_layout.ty, value, + enum_index, struct_layouts, module, )?; @@ -2102,6 +2242,25 @@ fn emit_hir_field_access( data_counter: &mut u32, ) -> Result { let object_ty = field_access.object.ty(); + if matches!(object_ty.abi, AbiType::Handle) { + let object_value = emit_hir_expr( + builder, + &field_access.object, + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + if matches!(field_access.field_ty.abi, AbiType::Ptr | AbiType::Handle) { + return Ok(object_value); + } + return Err(CodegenError::Unsupported( + "field access on opaque non-pointer field".to_string(), + )); + } let layout = resolve_struct_layout(&object_ty.ty, "", &struct_layouts.layouts).ok_or_else(|| { CodegenError::Unsupported(format!( @@ -2141,6 +2300,7 @@ fn emit_hir_field_access( base_ptr, field_layout.offset, &field_layout.ty, + enum_index, struct_layouts, module, ) @@ -2158,16 +2318,22 @@ fn store_out_value( out_ptr: ir::Value, ty: &crate::hir::HirType, value: ValueRepr, + enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, module: &mut ObjectModule, ) -> Result<(), CodegenError> { - if is_non_opaque_struct_type(ty, struct_layouts) { + let is_enum_payload = match &ty.ty { + crate::typeck::Ty::Path(name, _) => enum_index.layouts.contains_key(name), + _ => false, + }; + if is_non_opaque_struct_type(ty, struct_layouts) || is_enum_payload { return store_value_by_ty( builder, out_ptr, 0, ty, value, + enum_index, struct_layouts, module, ); @@ -2198,6 +2364,7 @@ fn store_value_by_ty( offset: u32, ty: &crate::hir::HirType, value: ValueRepr, + enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, module: &mut ObjectModule, ) -> Result<(), CodegenError> { @@ -2242,6 +2409,7 @@ fn store_value_by_ty( offset, &inner_ty, value, + enum_index, struct_layouts, module, ) @@ -2277,6 +2445,7 @@ fn store_value_by_ty( offset + ok_off, &ok_ty, *ok, + enum_index, struct_layouts, module, )?; @@ -2286,12 +2455,22 @@ fn store_value_by_ty( offset + err_off, &err_ty, *err, + enum_index, struct_layouts, module, )?; return Ok(()); } + if let Some(layout) = enum_index.layouts.get(name) { + let ValueRepr::Single(src_ptr) = value else { + return Err(CodegenError::Unsupported("store enum".to_string())); + }; + let dst_ptr = ptr_add(builder, base_ptr, offset); + copy_bytes(builder, dst_ptr, src_ptr, layout.size); + return Ok(()); + } + if let Some(layout) = resolve_struct_layout(&ty.ty, "", &struct_layouts.layouts) { let ValueRepr::Single(src_ptr) = value else { return Err(CodegenError::Unsupported("store struct".to_string())); @@ -2305,6 +2484,7 @@ fn store_value_by_ty( src_ptr, field.offset, &field.ty, + enum_index, struct_layouts, module, )?; @@ -2314,6 +2494,7 @@ fn store_value_by_ty( offset + field.offset, &field.ty, field_value, + enum_index, struct_layouts, module, )?; @@ -2354,6 +2535,7 @@ fn load_value_by_ty( base_ptr: ir::Value, offset: u32, ty: &crate::hir::HirType, + enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, module: &mut ObjectModule, ) -> Result { @@ -2385,6 +2567,7 @@ fn load_value_by_ty( base_ptr, offset, &inner_ty, + enum_index, struct_layouts, module, ) @@ -2416,6 +2599,7 @@ fn load_value_by_ty( base_ptr, offset + ok_off, &ok_ty, + enum_index, struct_layouts, module, )?; @@ -2424,6 +2608,7 @@ fn load_value_by_ty( base_ptr, offset + err_off, &err_ty, + enum_index, struct_layouts, module, )?; @@ -2434,6 +2619,11 @@ fn load_value_by_ty( }); } + if enum_index.layouts.contains_key(name) { + let ptr = ptr_add(builder, base_ptr, offset); + return Ok(ValueRepr::Single(ptr)); + } + if resolve_struct_layout(&ty.ty, "", &struct_layouts.layouts).is_some() { let ptr = ptr_add(builder, base_ptr, offset); return Ok(ValueRepr::Single(ptr)); @@ -2476,6 +2666,33 @@ fn ptr_add(builder: &mut FunctionBuilder, base: ir::Value, offset: u32) -> ir::V } } +fn copy_bytes(builder: &mut FunctionBuilder, dst_ptr: ir::Value, src_ptr: ir::Value, size: u32) { + let mut offset = 0u32; + while offset < size { + let byte = builder + .ins() + .load(ir::types::I8, MemFlags::new(), src_ptr, offset as i32); + builder + .ins() + .store(MemFlags::new(), byte, dst_ptr, offset as i32); + offset += 1; + } +} + +fn zero_bytes(builder: &mut FunctionBuilder, base_ptr: ir::Value, offset: u32, size: u32) { + if size == 0 { + return; + } + let zero = builder.ins().iconst(ir::types::I8, 0); + let mut i = 0u32; + while i < size { + builder + .ins() + .store(MemFlags::new(), zero, base_ptr, (offset + i) as i32); + i += 1; + } +} + /// Compute an aligned stack address for a struct slot. fn aligned_stack_addr( builder: &mut FunctionBuilder, @@ -2506,21 +2723,6 @@ fn result_offsets(ok: TypeLayout, err: TypeLayout) -> (u32, u32, u32) { (tag_offset, ok_offset, err_offset) } -/// Lookup a layout for a resolved HIR type from the struct layout index. -fn type_layout_from_index( - ty: &crate::hir::HirType, - struct_layouts: &StructLayoutIndex, - ptr_ty: Type, -) -> Result { - if let Some(layout) = resolve_struct_layout(&ty.ty, "", &struct_layouts.layouts) { - return Ok(TypeLayout { - size: layout.size, - align: layout.align, - }); - } - type_layout_for_abi(&ty.abi, ptr_ty) -} - /// Emit short-circuit logic for `&&` and `||`. fn emit_hir_short_circuit_expr( builder: &mut FunctionBuilder, @@ -2604,7 +2806,15 @@ fn emit_hir_match_stmt( )?; let (match_val, match_result) = match value.clone() { - ValueRepr::Single(v) => (v, None), + ValueRepr::Single(v) => { + let tag = match &match_expr.expr.ty().ty { + crate::typeck::Ty::Path(name, _) if enum_index.layouts.contains_key(name) => { + builder.ins().load(ir::types::I32, MemFlags::new(), v, 0) + } + _ => v, + }; + (tag, None) + } ValueRepr::Result { tag, ok, err } => (tag, Some((*ok, *err))), ValueRepr::Unit => (builder.ins().iconst(ir::types::I32, 0), None), }; @@ -2656,6 +2866,10 @@ fn emit_hir_match_stmt( &arm.pattern, &value, match_result.as_ref(), + match_expr.expr.ty(), + enum_index, + struct_layouts, + module, &mut arm_locals, )?; @@ -2742,7 +2956,15 @@ fn emit_hir_match_expr( )?; let (match_val, match_result) = match value.clone() { - ValueRepr::Single(v) => (v, None), + ValueRepr::Single(v) => { + let tag = match &match_expr.expr.ty().ty { + crate::typeck::Ty::Path(name, _) if enum_index.layouts.contains_key(name) => { + builder.ins().load(ir::types::I32, MemFlags::new(), v, 0) + } + _ => v, + }; + (tag, None) + } ValueRepr::Result { tag, ok, err } => (tag, Some((*ok, *err))), ValueRepr::Unit => (builder.ins().iconst(ir::types::I32, 0), None), }; @@ -2784,6 +3006,10 @@ fn emit_hir_match_expr( &arm.pattern, &value, match_result.as_ref(), + match_expr.expr.ty(), + enum_index, + struct_layouts, + module, &mut arm_locals, )?; @@ -3020,6 +3246,10 @@ fn hir_bind_match_pattern_value( pattern: &crate::hir::HirPattern, value: &ValueRepr, result: Option<&(ValueRepr, ValueRepr)>, + match_ty: &crate::hir::HirType, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + module: &mut ObjectModule, locals: &mut HashMap, ) -> Result<(), CodegenError> { use crate::hir::HirPattern; @@ -3035,16 +3265,54 @@ fn hir_bind_match_pattern_value( HirPattern::Variant { variant_name, binding, .. } => { if let Some(local_id) = binding { // Bind the inner value based on variant - let Some((ok_val, err_val)) = result else { + if let Some((ok_val, err_val)) = result { + if variant_name == "Ok" { + locals.insert(*local_id, store_local(builder, ok_val.clone())); + } else if variant_name == "Err" { + locals.insert(*local_id, store_local(builder, err_val.clone())); + } + return Ok(()); + } + let enum_name = match &match_ty.ty { + crate::typeck::Ty::Path(path, _) => path, + _ => { + return Err(CodegenError::Unsupported( + "variant binding on non-enum".to_string(), + )) + } + }; + let Some(layout) = enum_index.layouts.get(enum_name) else { return Err(CodegenError::Unsupported( - "variant binding without result value".to_string(), + "variant binding without payload".to_string(), )); }; - if variant_name == "Ok" { - locals.insert(*local_id, store_local(builder, ok_val.clone())); - } else if variant_name == "Err" { - locals.insert(*local_id, store_local(builder, err_val.clone())); - } + let Some(payloads) = enum_index.payloads.get(enum_name) else { + return Err(CodegenError::Unsupported( + "missing enum payload info".to_string(), + )); + }; + let payload_ty = payloads + .get(variant_name) + .cloned() + .flatten() + .ok_or_else(|| { + CodegenError::Unsupported("variant binding without payload".to_string()) + })?; + let ValueRepr::Single(base_ptr) = value else { + return Err(CodegenError::Unsupported( + "variant binding expects enum storage".to_string(), + )); + }; + let payload_val = load_value_by_ty( + builder, + *base_ptr, + layout.payload_offset, + &payload_ty, + enum_index, + struct_layouts, + module, + )?; + locals.insert(*local_id, store_local(builder, payload_val)); } Ok(()) } @@ -3093,7 +3361,6 @@ fn emit_string( .ins() .global_value(module.isa().pointer_type(), global); 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(|| { @@ -3102,6 +3369,16 @@ fn emit_string( let field = layout.fields.get("bytes").ok_or_else(|| { CodegenError::Unsupported("string.bytes field missing".to_string()) })?; + let slice_layout = + resolve_struct_layout(&field.ty.ty, "", &struct_layouts.layouts).ok_or_else(|| { + CodegenError::Unsupported("Slice layout missing".to_string()) + })?; + let slice_ptr = slice_layout.fields.get("ptr").ok_or_else(|| { + CodegenError::Unsupported("Slice.ptr field missing".to_string()) + })?; + let slice_len = slice_layout.fields.get("len").ok_or_else(|| { + CodegenError::Unsupported("Slice.len 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); @@ -3111,9 +3388,12 @@ fn emit_string( )); let base_ptr = aligned_stack_addr(builder, slot, align, ptr_ty); let addr = ptr_add(builder, base_ptr, field.offset); + let ptr_addr = ptr_add(builder, addr, slice_ptr.offset); + let len_addr = ptr_add(builder, addr, slice_len.offset); builder .ins() - .store(MemFlags::new(), slice_handle, addr, 0); + .store(MemFlags::new(), ptr, ptr_addr, 0); + builder.ins().store(MemFlags::new(), len, len_addr, 0); Ok(ValueRepr::Single(base_ptr)) } @@ -3207,6 +3487,7 @@ fn zero_value_for_ty( builder: &mut FunctionBuilder, ty: &crate::hir::HirType, ptr_ty: Type, + enum_index: &EnumIndex, struct_layouts: Option<&StructLayoutIndex>, module: &mut ObjectModule, ) -> Result { @@ -3219,7 +3500,7 @@ fn zero_value_for_ty( ty: *inner.clone(), abi: ty.abi.clone(), }; - zero_value_for_ty(builder, &inner_ty, ptr_ty, struct_layouts, module) + zero_value_for_ty(builder, &inner_ty, ptr_ty, enum_index, struct_layouts, module) } Ty::Param(_) => Err(CodegenError::Unsupported( "generic type parameters must be monomorphized before codegen".to_string(), @@ -3240,8 +3521,10 @@ 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, module)?; - let err_val = zero_value_for_ty(builder, &err_ty, ptr_ty, struct_layouts, module)?; + let ok_val = + zero_value_for_ty(builder, &ok_ty, ptr_ty, enum_index, struct_layouts, module)?; + let err_val = + zero_value_for_ty(builder, &err_ty, ptr_ty, enum_index, struct_layouts, module)?; return Ok(ValueRepr::Result { tag, ok: Box::new(ok_val), @@ -3261,14 +3544,21 @@ fn zero_value_for_ty( 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)?; + let field_zero = zero_value_for_ty( + builder, + &field.ty, + ptr_ty, + enum_index, + Some(struct_layouts), + module, + )?; store_value_by_ty( builder, base_ptr, field.offset, &field.ty, field_zero, + enum_index, struct_layouts, module, )?; @@ -3359,6 +3649,7 @@ pub(super) fn emit_runtime_wrapper_call( info: &FnInfo, args: Vec, ret_ty: &crate::hir::HirType, + enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, ) -> Result { ensure_abi_sig_handled(info)?; @@ -3374,14 +3665,26 @@ pub(super) fn emit_runtime_wrapper_call( if info.sig.ret == AbiType::Ptr && abi_sig.ret == AbiType::Unit - && is_non_opaque_struct_type(ret_ty, struct_layouts) + && (is_non_opaque_struct_type(ret_ty, struct_layouts) + || matches!(&ret_ty.ty, crate::typeck::Ty::Path(name, _) if enum_index.layouts.contains_key(name))) { - 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 align = layout.align.max(1); - let slot_size = layout.size.max(1).saturating_add(align - 1); + let (size, align) = if let Some(layout) = + resolve_struct_layout(&ret_ty.ty, "", &struct_layouts.layouts) + { + (layout.size, layout.align) + } else if let crate::typeck::Ty::Path(name, _) = &ret_ty.ty { + let layout = enum_index.layouts.get(name).ok_or_else(|| { + CodegenError::Unsupported("enum layout missing".to_string()) + })?; + (layout.size, layout.align) + } else { + return Err(CodegenError::Unsupported( + "sret return layout missing".to_string(), + )); + }; + let align = align.max(1); + let slot_size = aligned_slot_size(size, align); let slot = builder.create_sized_stack_slot(ir::StackSlotData::new( ir::StackSlotKind::ExplicitSlot, slot_size, @@ -3514,6 +3817,385 @@ pub(super) fn emit_runtime_wrapper_call( value_from_results(builder, &info.sig.ret, &results, &mut idx) } +fn emit_unsafe_ptr_call( + builder: &mut FunctionBuilder, + module: &mut ObjectModule, + call: &crate::hir::HirCall, + locals: &HashMap, + fn_map: &HashMap, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + return_lowering: &ReturnLowering, + data_counter: &mut u32, +) -> Result, CodegenError> { + let (module_path, func_name) = match &call.callee { + crate::hir::ResolvedCallee::Function { module, name, .. } => (module, name), + _ => return Ok(None), + }; + if module_path != "sys.unsafe_ptr" { + return Ok(None); + } + let base_name = func_name.split("__").next().unwrap_or(func_name); + if call.type_args.len() != 1 { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects one type argument" + ))); + } + let elem_ty = &call.type_args[0]; + let ptr_ty = module.isa().pointer_type(); + let elem_hir = hir_type_from_ty(elem_ty, enum_index, struct_layouts, ptr_ty)?; + let layout = type_layout_from_index(&elem_hir, struct_layouts, ptr_ty)?; + match base_name { + "sizeof" => { + let size = builder + .ins() + .iconst(ir::types::I32, layout.size as i64); + return Ok(Some(ValueRepr::Single(size))); + } + "alignof" => { + let align = builder + .ins() + .iconst(ir::types::I32, layout.align as i64); + return Ok(Some(ValueRepr::Single(align))); + } + "ptr_cast" | "ptr_cast_u8" => { + if call.args.len() != 1 { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects (ptr)" + ))); + } + let base_ptr = match emit_hir_expr( + builder, + &call.args[0], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects a pointer value" + ))) + } + }; + return Ok(Some(ValueRepr::Single(base_ptr))); + } + "ptr_is_null" => { + if call.args.len() != 1 { + return Err(CodegenError::Unsupported( + "ptr_is_null expects (ptr)".to_string(), + )); + } + let base_ptr = match emit_hir_expr( + builder, + &call.args[0], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported( + "ptr_is_null expects a pointer value".to_string(), + )) + } + }; + let is_null = builder.ins().icmp_imm( + ir::condcodes::IntCC::Equal, + base_ptr, + 0, + ); + return Ok(Some(ValueRepr::Single(is_null))); + } + "ptr_add" => { + if call.args.len() != 2 { + return Err(CodegenError::Unsupported( + "ptr_add expects (ptr, offset)".to_string(), + )); + } + let base_ptr = match emit_hir_expr( + builder, + &call.args[0], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported( + "ptr_add expects a pointer value".to_string(), + )) + } + }; + let offset_val = match emit_hir_expr( + builder, + &call.args[1], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(val) => val, + _ => { + return Err(CodegenError::Unsupported( + "ptr_add expects an i32 offset".to_string(), + )) + } + }; + let offset = if ptr_ty != ir::types::I32 { + builder.ins().sextend(ptr_ty, offset_val) + } else { + offset_val + }; + let stride = builder.ins().iconst(ptr_ty, layout.size as i64); + let byte_offset = if layout.size == 1 { + offset + } else { + builder.ins().imul(offset, stride) + }; + let addr = builder.ins().iadd(base_ptr, byte_offset); + return Ok(Some(ValueRepr::Single(addr))); + } + "ptr_read" => { + if call.args.len() != 1 { + return Err(CodegenError::Unsupported( + "ptr_read expects (ptr)".to_string(), + )); + } + let base_ptr = match emit_hir_expr( + builder, + &call.args[0], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported( + "ptr_read expects a pointer value".to_string(), + )) + } + }; + let value = load_value_by_ty( + builder, + base_ptr, + 0, + &elem_hir, + enum_index, + struct_layouts, + module, + )?; + return Ok(Some(value)); + } + "ptr_write" => { + if call.args.len() != 2 { + return Err(CodegenError::Unsupported( + "ptr_write expects (ptr, value)".to_string(), + )); + } + let base_ptr = match emit_hir_expr( + builder, + &call.args[0], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported( + "ptr_write expects a pointer value".to_string(), + )) + } + }; + let value = emit_hir_expr( + builder, + &call.args[1], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )?; + store_value_by_ty( + builder, + base_ptr, + 0, + &elem_hir, + value, + enum_index, + struct_layouts, + module, + )?; + return Ok(Some(ValueRepr::Unit)); + } + "memcpy" | "memmove" => { + if call.args.len() != 3 { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects (dst, src, count)" + ))); + } + let dst_ptr = match emit_hir_expr( + builder, + &call.args[0], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects a pointer dst" + ))) + } + }; + let src_ptr = match emit_hir_expr( + builder, + &call.args[1], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(ptr) => ptr, + _ => { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects a pointer src" + ))) + } + }; + let count_val = match emit_hir_expr( + builder, + &call.args[2], + locals, + fn_map, + enum_index, + struct_layouts, + return_lowering, + module, + data_counter, + )? { + ValueRepr::Single(val) => val, + _ => { + return Err(CodegenError::Unsupported(format!( + "{base_name} expects an i32 count" + ))) + } + }; + + let zero_i32 = builder.ins().iconst(ir::types::I32, 0); + let should_copy = builder + .ins() + .icmp(IntCC::SignedGreaterThan, count_val, zero_i32); + let copy_block = builder.create_block(); + let done_block = builder.create_block(); + builder.ins().brif(should_copy, copy_block, &[], done_block, &[]); + + builder.switch_to_block(copy_block); + builder.seal_block(copy_block); + let count_ptr = if ptr_ty != ir::types::I32 { + builder.ins().sextend(ptr_ty, count_val) + } else { + count_val + }; + let stride = builder.ins().iconst(ptr_ty, layout.size as i64); + let byte_count = if layout.size == 1 { + count_ptr + } else { + builder.ins().imul(count_ptr, stride) + }; + let config = module.isa().frontend_config(); + if base_name == "memcpy" { + builder.call_memcpy(config, dst_ptr, src_ptr, byte_count); + } else { + builder.call_memmove(config, dst_ptr, src_ptr, byte_count); + } + builder.ins().jump(done_block, &[]); + + builder.switch_to_block(done_block); + builder.seal_block(done_block); + return Ok(Some(ValueRepr::Unit)); + } + _ => {} + } + Ok(None) +} + +fn hir_type_from_ty( + ty: &crate::typeck::Ty, + enum_index: &EnumIndex, + struct_layouts: &StructLayoutIndex, + ptr_ty: Type, +) -> Result { + use crate::typeck::{BuiltinType, Ty}; + let abi = match ty { + Ty::Builtin(b) => match b { + BuiltinType::I32 => AbiType::I32, + BuiltinType::I64 => { + return Err(CodegenError::Unsupported( + "i64 is not supported by the current codegen backend".to_string(), + )) + } + BuiltinType::U32 => AbiType::U32, + BuiltinType::U8 => AbiType::U8, + BuiltinType::Bool => AbiType::Bool, + BuiltinType::Unit | BuiltinType::Never => AbiType::Unit, + }, + Ty::Ptr(_) => AbiType::Ptr, + Ty::Ref(inner) => { + return hir_type_from_ty(inner, enum_index, struct_layouts, ptr_ty); + } + Ty::Param(_) => { + return Err(CodegenError::Unsupported( + "generic type parameters must be monomorphized before codegen".to_string(), + )) + } + Ty::Path(name, _args) => { + if resolve_struct_layout(ty, "", &struct_layouts.layouts).is_some() { + AbiType::Ptr + } else if enum_index.layouts.contains_key(name) { + AbiType::Ptr + } else if enum_index.variants.contains_key(name) { + AbiType::I32 + } else { + AbiType::Handle + } + } + }; + Ok(crate::hir::HirType { ty: ty.clone(), abi }) +} + fn ensure_abi_sig_handled(info: &FnInfo) -> Result<(), CodegenError> { let Some(abi_sig) = info.abi_sig.as_ref() else { return Ok(()); diff --git a/capc/src/codegen/intrinsics.rs b/capc/src/codegen/intrinsics.rs index 12c1c5b..94e5dad 100644 --- a/capc/src/codegen/intrinsics.rs +++ b/capc/src/codegen/intrinsics.rs @@ -42,19 +42,25 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Handle, }; let fs_read_to_string = FnSig { - params: vec![AbiType::Handle, AbiType::Ptr], + params: vec![AbiType::Handle, 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::Ptr, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + params: vec![ + AbiType::Handle, + 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::Ptr], + params: vec![AbiType::Handle, 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::Handle, AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), @@ -62,11 +68,12 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_list_dir = FnSig { - params: vec![AbiType::Handle, AbiType::Ptr], + params: vec![AbiType::Handle, 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::Handle, AbiType::Ptr, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), @@ -90,11 +97,15 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Unit, }; let fs_file_read_to_string = FnSig { - params: vec![AbiType::Handle], + params: vec![AbiType::Handle, AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let fs_file_read_to_string_abi = FnSig { - params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + params: vec![ + AbiType::Handle, + 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 { @@ -102,22 +113,23 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Unit, }; let fs_dir_list_dir = FnSig { - params: vec![AbiType::Handle], + params: vec![AbiType::Handle, AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_dir_list_dir_abi = FnSig { params: vec![ + AbiType::Handle, AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), ], ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let fs_join = FnSig { - params: vec![AbiType::Ptr, AbiType::Ptr], + params: vec![AbiType::Handle, AbiType::Ptr, AbiType::Ptr], ret: AbiType::Ptr, }; let fs_join_abi = FnSig { - params: vec![AbiType::Ptr, AbiType::Ptr, AbiType::Ptr], + params: vec![AbiType::Ptr, AbiType::Handle, AbiType::Ptr, AbiType::Ptr], ret: AbiType::Unit, }; // Console. @@ -146,7 +158,7 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::U8, AbiType::U8], ret: AbiType::U8, }; - // Buffer + slices. + // Alloc + slices. let mem_malloc = FnSig { params: vec![AbiType::Handle, AbiType::I32], ret: AbiType::Ptr, @@ -203,19 +215,28 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), }; let net_read_to_string = FnSig { - params: vec![AbiType::Handle], + params: vec![AbiType::Handle, AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let net_read_to_string_abi = FnSig { - params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + params: vec![ + AbiType::Handle, + 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], + params: vec![AbiType::Handle, AbiType::Handle, 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::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + params: vec![ + AbiType::Handle, + 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 { @@ -254,317 +275,13 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }; let args_at_abi = FnSig { - 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 { - params: vec![AbiType::Handle, AbiType::Ptr, AbiType::I32], - ret: AbiType::Handle, - }; - let mem_slice_len = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::I32, - }; - let mem_slice_at = FnSig { - params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::U8, - }; - // Vecs. - let mem_buffer_new = FnSig { - params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), - }; - let mem_buffer_new_abi = FnSig { - params: vec![ - AbiType::Handle, - 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_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, - }; - let mem_buffer_push = FnSig { - params: vec![AbiType::Handle, AbiType::U8], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let mem_buffer_push_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::U8, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let mem_buffer_extend = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let mem_buffer_extend_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::Handle, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let mem_buffer_is_empty = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Bool, - }; - let mem_buffer_as_slice = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Handle, - }; - let mem_buffer_as_mut_slice = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Handle, - }; - let mem_buffer_free = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Unit, - }; - let vec_new = FnSig { - 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)), - }; - let vec_u8_get_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::I32, - AbiType::ResultOut(Box::new(AbiType::U8), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::U8), Box::new(AbiType::I32)), - }; - let vec_u8_set = FnSig { - params: vec![AbiType::Handle, AbiType::I32, AbiType::U8], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_u8_set_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::I32, - AbiType::U8, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_u8_push = FnSig { - params: vec![AbiType::Handle, AbiType::U8], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_u8_push_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::U8, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_u8_extend = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_u8_extend_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::Handle, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_u8_filter = FnSig { - params: vec![AbiType::Handle, AbiType::U8], - ret: AbiType::Handle, - }; - let vec_u8_map_add = FnSig { - params: vec![AbiType::Handle, AbiType::U8], - ret: AbiType::Handle, - }; - let vec_u8_slice = FnSig { - params: vec![AbiType::Handle, AbiType::I32, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)), - }; - let vec_u8_slice_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::I32, - AbiType::I32, - AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)), - }; - let vec_u8_pop = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::U8), Box::new(AbiType::I32)), - }; - let vec_u8_pop_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::ResultOut(Box::new(AbiType::U8), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::U8), Box::new(AbiType::I32)), - }; - let vec_u8_as_slice = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Handle, - }; - let vec_u8_free = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Unit, - }; - let vec_i32_get = FnSig { - params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::I32), Box::new(AbiType::I32)), - }; - let vec_i32_get_abi = FnSig { params: vec![ AbiType::Handle, AbiType::I32, - AbiType::ResultOut(Box::new(AbiType::I32), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::I32), Box::new(AbiType::I32)), - }; - let vec_i32_set = FnSig { - params: vec![AbiType::Handle, AbiType::I32, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_i32_set_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::I32, - AbiType::I32, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_i32_push = FnSig { - params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_i32_push_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::I32, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_i32_extend = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_i32_extend_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::Handle, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_i32_filter = FnSig { - params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Handle, - }; - let vec_i32_map_add = FnSig { - params: vec![AbiType::Handle, AbiType::I32], - ret: AbiType::Handle, - }; - let vec_i32_pop = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::I32), Box::new(AbiType::I32)), - }; - let vec_i32_pop_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::ResultOut(Box::new(AbiType::I32), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::I32), Box::new(AbiType::I32)), - }; - let vec_i32_free = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Unit, - }; - let vec_string_len = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::I32, - }; - let vec_string_get = FnSig { - params: vec![AbiType::Handle, 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::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::Ptr], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_string_push_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::Ptr, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_string_extend = FnSig { - params: vec![AbiType::Handle, AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_string_extend_abi = FnSig { - params: vec![ - AbiType::Handle, - AbiType::Handle, - AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), + AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), ], - ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)), - }; - let vec_string_pop = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), - }; - let vec_string_pop_abi = FnSig { - 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, - }; - // Vec lengths. - let vec_u8_len = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::I32, - }; - let vec_i32_len = FnSig { - params: vec![AbiType::Handle], - ret: AbiType::I32, - }; - // === System + args === map.insert( "sys.system.RootCap__mint_console".to_string(), @@ -654,11 +371,15 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { "sys.stdin.Stdin__read_to_string".to_string(), FnInfo { sig: FnSig { - params: vec![AbiType::Handle], + params: vec![AbiType::Handle, AbiType::Handle], ret: AbiType::Result(Box::new(AbiType::Ptr), Box::new(AbiType::I32)), }, abi_sig: Some(FnSig { - params: vec![AbiType::Handle, AbiType::ResultOut(Box::new(AbiType::Ptr), Box::new(AbiType::I32))], + params: vec![ + AbiType::Handle, + 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(), @@ -1064,53 +785,23 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); - // === Buffer + slices === + // === Slices === map.insert( - "sys.buffer.new".to_string(), + "sys.buffer.Alloc__malloc".to_string(), FnInfo { - sig: mem_buffer_new_default, - abi_sig: Some(mem_buffer_new_default_abi), - symbol: "capable_rt_buffer_new_default".to_string(), + sig: mem_malloc, + abi_sig: None, + symbol: "capable_rt_malloc".to_string(), runtime_symbol: None, is_runtime: true, }, ); map.insert( - "sys.buffer.Alloc__buffer_new".to_string(), + "sys.buffer.Alloc__free".to_string(), FnInfo { - sig: mem_buffer_new, - abi_sig: Some(mem_buffer_new_abi), - symbol: "capable_rt_buffer_new".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__buffer_free".to_string(), - FnInfo { - sig: mem_buffer_free, - abi_sig: None, - symbol: "capable_rt_buffer_free".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__malloc".to_string(), - FnInfo { - sig: mem_malloc, - abi_sig: None, - symbol: "capable_rt_malloc".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__free".to_string(), - FnInfo { - sig: mem_free, - abi_sig: None, - symbol: "capable_rt_free".to_string(), + sig: mem_free, + abi_sig: None, + symbol: "capable_rt_free".to_string(), runtime_symbol: None, is_runtime: true, }, @@ -1135,417 +826,6 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); - map.insert( - "sys.buffer.Alloc__slice_from_ptr".to_string(), - FnInfo { - sig: mem_slice_from_ptr.clone(), - abi_sig: None, - symbol: "capable_rt_slice_from_ptr".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__mut_slice_from_ptr".to_string(), - FnInfo { - sig: mem_slice_from_ptr, - abi_sig: None, - symbol: "capable_rt_mut_slice_from_ptr".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Buffer__len".to_string(), - FnInfo { - sig: mem_buffer_len, - abi_sig: None, - symbol: "capable_rt_buffer_len".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Buffer__push".to_string(), - FnInfo { - sig: mem_buffer_push, - abi_sig: Some(mem_buffer_push_abi), - symbol: "capable_rt_buffer_push".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Buffer__extend".to_string(), - FnInfo { - sig: mem_buffer_extend, - abi_sig: Some(mem_buffer_extend_abi), - symbol: "capable_rt_buffer_extend".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Buffer__is_empty".to_string(), - FnInfo { - sig: mem_buffer_is_empty, - abi_sig: None, - symbol: "capable_rt_buffer_is_empty".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Buffer__as_slice".to_string(), - FnInfo { - sig: mem_buffer_as_slice, - abi_sig: None, - symbol: "capable_rt_buffer_as_slice".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Buffer__as_mut_slice".to_string(), - FnInfo { - sig: mem_buffer_as_mut_slice, - abi_sig: None, - symbol: "capable_rt_buffer_as_mut_slice".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Slice__len".to_string(), - FnInfo { - sig: mem_slice_len, - abi_sig: None, - symbol: "capable_rt_slice_len".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Slice__at".to_string(), - FnInfo { - sig: mem_slice_at.clone(), - abi_sig: None, - symbol: "capable_rt_slice_at".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.MutSlice__at".to_string(), - FnInfo { - sig: mem_slice_at, - abi_sig: None, - symbol: "capable_rt_mut_slice_at".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - // === Vec === - map.insert( - "sys.buffer.Alloc__vec_u8_new".to_string(), - FnInfo { - sig: vec_new.clone(), - abi_sig: None, - symbol: "capable_rt_vec_u8_new".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__vec_u8_free".to_string(), - FnInfo { - sig: vec_u8_free, - abi_sig: None, - symbol: "capable_rt_vec_u8_free".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__vec_i32_new".to_string(), - FnInfo { - sig: vec_new.clone(), - abi_sig: None, - symbol: "capable_rt_vec_i32_new".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__vec_i32_free".to_string(), - FnInfo { - sig: vec_i32_free, - abi_sig: None, - symbol: "capable_rt_vec_i32_free".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.buffer.Alloc__vec_string_new".to_string(), - FnInfo { - sig: vec_new, - abi_sig: None, - symbol: "capable_rt_vec_string_new".to_string(), - runtime_symbol: None, - 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 { - sig: vec_string_free, - abi_sig: None, - symbol: "capable_rt_vec_string_free".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__len".to_string(), - FnInfo { - sig: vec_u8_len, - abi_sig: None, - symbol: "capable_rt_vec_u8_len".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__get".to_string(), - FnInfo { - sig: vec_u8_get, - abi_sig: Some(vec_u8_get_abi), - symbol: "capable_rt_vec_u8_get".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__set".to_string(), - FnInfo { - sig: vec_u8_set, - abi_sig: Some(vec_u8_set_abi), - symbol: "capable_rt_vec_u8_set".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__push".to_string(), - FnInfo { - sig: vec_u8_push, - abi_sig: Some(vec_u8_push_abi), - symbol: "capable_rt_vec_u8_push".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__extend".to_string(), - FnInfo { - sig: vec_u8_extend, - abi_sig: Some(vec_u8_extend_abi), - symbol: "capable_rt_vec_u8_extend".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__filter".to_string(), - FnInfo { - sig: vec_u8_filter, - abi_sig: None, - symbol: "capable_rt_vec_u8_filter".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__map_add".to_string(), - FnInfo { - sig: vec_u8_map_add, - abi_sig: None, - symbol: "capable_rt_vec_u8_map_add".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__slice".to_string(), - FnInfo { - sig: vec_u8_slice, - abi_sig: Some(vec_u8_slice_abi), - symbol: "capable_rt_vec_u8_slice".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__pop".to_string(), - FnInfo { - sig: vec_u8_pop, - abi_sig: Some(vec_u8_pop_abi), - symbol: "capable_rt_vec_u8_pop".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecU8__as_slice".to_string(), - FnInfo { - sig: vec_u8_as_slice, - abi_sig: None, - symbol: "capable_rt_vec_u8_as_slice".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__len".to_string(), - FnInfo { - sig: vec_i32_len, - abi_sig: None, - symbol: "capable_rt_vec_i32_len".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__get".to_string(), - FnInfo { - sig: vec_i32_get, - abi_sig: Some(vec_i32_get_abi), - symbol: "capable_rt_vec_i32_get".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__set".to_string(), - FnInfo { - sig: vec_i32_set, - abi_sig: Some(vec_i32_set_abi), - symbol: "capable_rt_vec_i32_set".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__push".to_string(), - FnInfo { - sig: vec_i32_push, - abi_sig: Some(vec_i32_push_abi), - symbol: "capable_rt_vec_i32_push".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__extend".to_string(), - FnInfo { - sig: vec_i32_extend, - abi_sig: Some(vec_i32_extend_abi), - symbol: "capable_rt_vec_i32_extend".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__filter".to_string(), - FnInfo { - sig: vec_i32_filter, - abi_sig: None, - symbol: "capable_rt_vec_i32_filter".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__map_add".to_string(), - FnInfo { - sig: vec_i32_map_add, - abi_sig: None, - symbol: "capable_rt_vec_i32_map_add".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecI32__pop".to_string(), - FnInfo { - sig: vec_i32_pop, - abi_sig: Some(vec_i32_pop_abi), - symbol: "capable_rt_vec_i32_pop".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecString__len".to_string(), - FnInfo { - sig: vec_string_len, - abi_sig: None, - symbol: "capable_rt_vec_string_len".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecString__get".to_string(), - FnInfo { - sig: vec_string_get, - abi_sig: Some(vec_string_get_abi), - symbol: "capable_rt_vec_string_get".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecString__push".to_string(), - FnInfo { - sig: vec_string_push, - abi_sig: Some(vec_string_push_abi), - symbol: "capable_rt_vec_string_push".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecString__extend".to_string(), - FnInfo { - sig: vec_string_extend, - abi_sig: Some(vec_string_extend_abi), - symbol: "capable_rt_vec_string_extend".to_string(), - runtime_symbol: None, - is_runtime: true, - }, - ); - map.insert( - "sys.vec.VecString__pop".to_string(), - FnInfo { - sig: vec_string_pop, - abi_sig: Some(vec_string_pop_abi), - symbol: "capable_rt_vec_string_pop".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 5cafc23..975feb2 100644 --- a/capc/src/codegen/layout.rs +++ b/capc/src/codegen/layout.rs @@ -7,8 +7,8 @@ use std::collections::{HashMap, HashSet}; use cranelift_codegen::ir::Type; use super::{ - abi_quirks, CodegenError, EnumIndex, StructFieldLayout, StructLayout, StructLayoutIndex, - TypeLayout, + abi_quirks, CodegenError, EnumIndex, EnumLayout, StructFieldLayout, StructLayout, + StructLayoutIndex, TypeLayout, }; use crate::abi::AbiType; @@ -17,8 +17,12 @@ pub(super) fn build_enum_index( entry: &crate::hir::HirModule, user_modules: &[crate::hir::HirModule], stdlib: &[crate::hir::HirModule], -) -> EnumIndex { + struct_layouts: &StructLayoutIndex, + ptr_ty: Type, +) -> Result { let mut variants = HashMap::new(); + let mut payloads = HashMap::new(); + let mut layouts = HashMap::new(); let entry_name = &entry.name; let modules = stdlib .iter() @@ -28,17 +32,53 @@ pub(super) fn build_enum_index( let module_name = &module.name; for en in &module.enums { let mut map = HashMap::new(); + let mut payload_map = HashMap::new(); + let mut payload_size = 0u32; + let mut payload_align = 1u32; + let mut has_payload = false; for (idx, variant) in en.variants.iter().enumerate() { map.insert(variant.name.clone(), idx as i32); + payload_map.insert(variant.name.clone(), variant.payload.clone()); + if let Some(payload_ty) = &variant.payload { + let layout = type_layout_from_index(payload_ty, struct_layouts, ptr_ty)?; + payload_size = payload_size.max(layout.size); + payload_align = payload_align.max(layout.align); + has_payload = true; + } } let qualified = format!("{}.{}", module_name, en.name); - variants.insert(qualified, map.clone()); + variants.insert(qualified.clone(), map.clone()); + payloads.insert(qualified.clone(), payload_map.clone()); + if has_payload { + let tag_size = 4u32; + let tag_align = 4u32; + let payload_offset = align_to(tag_size, payload_align); + let align = tag_align.max(payload_align); + let size = align_to(payload_offset.saturating_add(payload_size), align); + layouts.insert( + qualified.clone(), + EnumLayout { + payload_offset, + payload_size, + size, + align, + }, + ); + } if module_name == entry_name { variants.insert(en.name.clone(), map); + payloads.insert(en.name.clone(), payload_map.clone()); + if let Some(layout) = layouts.get(&qualified).cloned() { + layouts.insert(en.name.clone(), layout); + } } } } - EnumIndex { variants } + Ok(EnumIndex { + variants, + payloads, + layouts, + }) } /// Compute struct layouts for all non-opaque structs. @@ -264,6 +304,21 @@ pub(super) fn type_layout_for_abi( } } +/// Lookup a layout for a resolved HIR type from the struct layout index. +pub(super) fn type_layout_from_index( + ty: &crate::hir::HirType, + struct_layouts: &StructLayoutIndex, + ptr_ty: Type, +) -> Result { + if let Some(layout) = resolve_struct_layout(&ty.ty, "", &struct_layouts.layouts) { + return Ok(TypeLayout { + size: layout.size, + align: layout.align, + }); + } + type_layout_for_abi(&ty.abi, ptr_ty) +} + /// Resolve a struct layout by name (qualified or unqualified). pub(super) fn resolve_struct_layout<'a>( ty: &crate::typeck::Ty, diff --git a/capc/src/codegen/mod.rs b/capc/src/codegen/mod.rs index b63f3e9..e291f6b 100644 --- a/capc/src/codegen/mod.rs +++ b/capc/src/codegen/mod.rs @@ -123,6 +123,17 @@ struct FnInfo { #[derive(Clone, Debug)] struct EnumIndex { variants: HashMap>, + payloads: HashMap>>, + layouts: HashMap, +} + +/// Layout metadata for enums with payloads (tag + payload). +#[derive(Clone, Debug)] +struct EnumLayout { + payload_offset: u32, + payload_size: u32, + size: u32, + align: u32, } /// Struct layout index used for field offsets and sizes. @@ -208,13 +219,19 @@ pub fn build_object( .map_err(|err| CodegenError::Codegen(err.to_string()))?, ); - let enum_index = build_enum_index(&program.entry, &program.user_modules, &program.stdlib); let struct_layouts = build_struct_layout_index( &program.entry, &program.user_modules, &program.stdlib, module.isa().pointer_type(), )?; + let enum_index = build_enum_index( + &program.entry, + &program.user_modules, + &program.stdlib, + &struct_layouts, + module.isa().pointer_type(), + )?; let runtime_intrinsics = register_runtime_intrinsics(module.isa().pointer_type()); let mut fn_map = HashMap::new(); @@ -261,7 +278,7 @@ pub fn build_object( .collect::>(); for module_ref in &all_modules { - register_extern_functions_from_hir(module_ref, &mut fn_map, &struct_layouts)?; + register_extern_functions_from_hir(module_ref, &mut fn_map, &enum_index, &struct_layouts)?; } let mut data_counter = 0u32; @@ -318,6 +335,7 @@ pub fn build_object( &info, args, &func.ret_ty, + &enum_index, &struct_layouts, )?; match value { @@ -345,9 +363,14 @@ pub fn build_object( let mut param_index = 0; let mut return_lowering = ReturnLowering::Direct; + let is_enum_payload = match &func.ret_ty.ty { + crate::typeck::Ty::Path(name, _) => enum_index.layouts.contains_key(name), + _ => false, + }; if info.sig.ret == AbiType::Ptr && abi_sig.ret == AbiType::Unit - && resolve_struct_layout(&func.ret_ty.ty, "", &struct_layouts.layouts).is_some() + && (resolve_struct_layout(&func.ret_ty.ty, "", &struct_layouts.layouts).is_some() + || is_enum_payload) { let out_ptr = params .get(0) @@ -548,15 +571,25 @@ fn is_non_opaque_struct_type( resolve_struct_layout(ty, "", &struct_layouts.layouts).is_some() } +fn is_payload_enum_type(ty: &crate::typeck::Ty, enum_index: &EnumIndex) -> bool { + match ty { + crate::typeck::Ty::Path(name, _) => enum_index.layouts.contains_key(name), + _ => false, + } +} + fn lowered_abi_sig_for_return( sig: &FnSig, ret_ty: &crate::hir::HirType, + enum_index: &EnumIndex, 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); + let ok_is_struct = is_non_opaque_struct_type(&args[0], struct_layouts) + || is_payload_enum_type(&args[0], enum_index); + let err_is_struct = is_non_opaque_struct_type(&args[1], struct_layouts) + || is_payload_enum_type(&args[1], enum_index); if ok_is_struct || err_is_struct { let AbiType::Result(ok_abi, err_abi) = &ret_ty.abi else { return None; @@ -571,7 +604,9 @@ fn lowered_abi_sig_for_return( } } - if is_non_opaque_struct_type(&ret_ty.ty, struct_layouts) { + if is_non_opaque_struct_type(&ret_ty.ty, struct_layouts) + || is_payload_enum_type(&ret_ty.ty, enum_index) + { let mut params = Vec::with_capacity(sig.params.len() + 1); params.push(AbiType::Ptr); params.extend(sig.params.iter().cloned()); @@ -588,7 +623,7 @@ fn lowered_abi_sig_for_return( fn register_user_functions( module: &crate::hir::HirModule, entry: &crate::hir::HirModule, - _enum_index: &EnumIndex, + enum_index: &EnumIndex, map: &mut HashMap, runtime_intrinsics: &HashMap, struct_layouts: &StructLayoutIndex, @@ -624,7 +659,9 @@ 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)); + let abi_sig = abi_sig.or_else(|| { + lowered_abi_sig_for_return(&sig, &func.ret_ty, enum_index, struct_layouts) + }); map.insert( key, FnInfo { @@ -643,6 +680,7 @@ fn register_user_functions( fn register_extern_functions_from_hir( module: &crate::hir::HirModule, map: &mut HashMap, + enum_index: &EnumIndex, struct_layouts: &StructLayoutIndex, ) -> Result<(), CodegenError> { let module_name = &module.name; @@ -656,7 +694,7 @@ 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); + let abi_sig = lowered_abi_sig_for_return(&sig, &func.ret_ty, enum_index, struct_layouts); map.insert( key, FnInfo { @@ -676,18 +714,6 @@ fn validate_intrinsics( fn_map: &HashMap, runtime_intrinsics: &HashMap, ) -> Result<(), CodegenError> { - let missing_wrappers = runtime_intrinsics - .keys() - .filter(|key| !fn_map.contains_key(*key)) - .cloned() - .collect::>(); - if !missing_wrappers.is_empty() { - return Err(CodegenError::Codegen(format!( - "runtime intrinsics missing stdlib wrappers: {}", - missing_wrappers.join(", ") - ))); - } - let unknown_wrappers = fn_map .iter() .filter(|(key, info)| info.runtime_symbol.is_some() && !runtime_intrinsics.contains_key(*key)) diff --git a/capc/src/parser.rs b/capc/src/parser.rs index d764ec9..c1272be 100644 --- a/capc/src/parser.rs +++ b/capc/src/parser.rs @@ -377,10 +377,10 @@ impl Parser { } } let end = self.expect(TokenKind::RBrace)?.span.end; - if (is_opaque || is_capability) && !fields.is_empty() { + if is_capability && !fields.is_empty() { return Err(self.error_at( Span::new(start, end), - "opaque/capability struct cannot declare fields".to_string(), + "capability struct cannot declare fields".to_string(), )); } end diff --git a/capc/src/typeck/check.rs b/capc/src/typeck/check.rs index 2ee65b0..304ceec 100644 --- a/capc/src/typeck/check.rs +++ b/capc/src/typeck/check.rs @@ -37,8 +37,208 @@ fn record_expr_type( Ok(ty) } +fn infer_enum_args(template: &Ty, actual: &Ty, inferred: &mut HashMap) -> bool { + match template { + Ty::Param(name) => match inferred.get(name) { + Some(existing) => { + existing == actual + || matches!(actual, Ty::Path(actual_name, args) if actual_name == name && args.is_empty()) + } + None => { + inferred.insert(name.clone(), actual.clone()); + true + } + }, + Ty::Builtin(b) => matches!(actual, Ty::Builtin(other) if other == b), + Ty::Ptr(inner) => matches!(actual, Ty::Ptr(other) if infer_enum_args(inner, other, inferred)), + Ty::Ref(inner) => matches!(actual, Ty::Ref(other) if infer_enum_args(inner, other, inferred)), + Ty::Path(name, args) => match actual { + Ty::Path(other_name, other_args) if other_name == name && args.len() == other_args.len() => { + args.iter() + .zip(other_args.iter()) + .all(|(a, b)| infer_enum_args(a, b, inferred)) + } + _ => false, + }, + } +} + +fn resolve_enum_type_args( + enum_name: &str, + type_params: &[String], + inferred: &HashMap, + ret_ty: &Ty, +) -> Vec { + if type_params.is_empty() { + return Vec::new(); + } + let ret_args = match ret_ty { + Ty::Path(ret_name, args) if ret_name == enum_name && args.len() == type_params.len() => { + Some(args) + } + _ => None, + }; + type_params + .iter() + .enumerate() + .map(|(idx, param)| { + if let Some(ty) = inferred.get(param) { + return ty.clone(); + } + if let Some(args) = ret_args { + return args[idx].clone(); + } + Ty::Builtin(BuiltinType::Unit) + }) + .collect() +} + +fn apply_enum_type_args(ty: &Ty, type_params: &[String], type_args: &[Ty]) -> Ty { + match ty { + Ty::Param(name) => { + if let Some(idx) = type_params.iter().position(|p| p == name) { + return type_args.get(idx).cloned().unwrap_or_else(|| ty.clone()); + } + ty.clone() + } + Ty::Builtin(_) => ty.clone(), + Ty::Ptr(inner) => Ty::Ptr(Box::new(apply_enum_type_args(inner, type_params, type_args))), + Ty::Ref(inner) => Ty::Ref(Box::new(apply_enum_type_args(inner, type_params, type_args))), + Ty::Path(name, args) => Ty::Path( + name.clone(), + args.iter() + .map(|arg| apply_enum_type_args(arg, type_params, type_args)) + .collect(), + ), + } +} + +fn enum_payload_matches(payload: &Ty, arg_ty: &Ty, type_params: &[String], type_args: &[Ty]) -> bool { + let expected = apply_enum_type_args(payload, type_params, type_args); + ty_equivalent_for_params(&expected, arg_ty, type_params) +} + +fn ty_equivalent_for_params(left: &Ty, right: &Ty, type_params: &[String]) -> bool { + match (left, right) { + (Ty::Param(name), Ty::Path(other, args)) + if args.is_empty() && name == other && type_params.contains(name) => + { + true + } + (Ty::Path(name, args), Ty::Param(other)) + if args.is_empty() && name == other && type_params.contains(other) => + { + true + } + (Ty::Ptr(l), Ty::Ptr(r)) | (Ty::Ref(l), Ty::Ref(r)) => { + ty_equivalent_for_params(l, r, type_params) + } + (Ty::Path(name, args), Ty::Path(other, other_args)) + if name == other && args.len() == other_args.len() => + { + args.iter() + .zip(other_args.iter()) + .all(|(a, b)| ty_equivalent_for_params(a, b, type_params)) + } + _ => left == right, + } +} + +fn ty_equivalent_for_set(left: &Ty, right: &Ty, type_params: &HashSet) -> bool { + match (left, right) { + (Ty::Param(name), Ty::Path(other, args)) + if args.is_empty() && name == other && type_params.contains(name) => + { + true + } + (Ty::Path(name, args), Ty::Param(other)) + if args.is_empty() && name == other && type_params.contains(other) => + { + true + } + (Ty::Ptr(l), Ty::Ptr(r)) | (Ty::Ref(l), Ty::Ref(r)) => { + ty_equivalent_for_set(l, r, type_params) + } + (Ty::Path(name, args), Ty::Path(other, other_args)) + if name == other && args.len() == other_args.len() => + { + args.iter() + .zip(other_args.iter()) + .all(|(a, b)| ty_equivalent_for_set(a, b, type_params)) + } + _ => left == right, + } +} + +fn enforce_vec_method_constraints( + receiver_ty: &Ty, + method: &str, + span: Span, +) -> Result<(), TypeError> { + let base = match receiver_ty { + Ty::Ref(inner) | Ty::Ptr(inner) => inner.as_ref(), + _ => receiver_ty, + }; + let Ty::Path(name, args) = base else { + return Ok(()); + }; + if name != "Vec" && name != "sys.vec.Vec" { + return Ok(()); + } + if args.len() != 1 { + return Err(TypeError::new( + "Vec expects exactly one type argument".to_string(), + span, + )); + } + let elem = &args[0]; + let is_u8 = matches!(elem, Ty::Builtin(BuiltinType::U8)); + let is_i32 = matches!(elem, Ty::Builtin(BuiltinType::I32)); + let is_string = is_string_ty(elem); + let is_param = matches!(elem, Ty::Param(_)); + match method { + "as_slice" | "slice" | "extend_slice" | "to_string" => { + if !is_u8 { + return Err(TypeError::new( + format!("Vec<{elem:?}> does not support `{method}`"), + span, + )); + } + } + "capacity" | "reserve" | "shrink_to_fit" => { + if !is_u8 && !is_i32 && !is_string && !is_param { + return Err(TypeError::new( + format!("Vec<{elem:?}> does not support `{method}`"), + span, + )); + } + } + "filter" | "map_add" | "set" => { + if !is_u8 && !is_i32 { + return Err(TypeError::new( + format!("Vec<{elem:?}> does not support `{method}`"), + span, + )); + } + } + "join" => { + if !is_string { + return Err(TypeError::new( + format!("Vec<{elem:?}> does not support `{method}`"), + span, + )); + } + } + _ => {} + } + Ok(()) +} + /// Safe packages cannot mention externs or raw pointer types anywhere. -pub(super) fn validate_package_safety(module: &Module) -> Result<(), TypeError> { +pub(super) fn validate_package_safety( + module: &Module, + is_stdlib: bool, +) -> Result<(), TypeError> { if module.package != PackageSafety::Safe { return Ok(()); } @@ -51,14 +251,25 @@ pub(super) fn validate_package_safety(module: &Module) -> Result<(), TypeError> )); } Item::Function(func) => { - if let Some(span) = type_contains_ptr_fn(func) { - return Err(TypeError::new( - "raw pointer types require `package unsafe`".to_string(), - span, - )); + if !is_stdlib { + if let Some(span) = type_contains_ptr_fn(func) { + return Err(TypeError::new( + "raw pointer types require `package unsafe`".to_string(), + span, + )); + } + if let Some(span) = type_contains_slice(&func.ret) { + return Err(TypeError::new( + "Slice types cannot be returned from safe modules".to_string(), + span, + )); + } } } Item::Impl(impl_block) => { + if is_stdlib { + continue; + } for method in &impl_block.methods { if let Some(span) = type_contains_ptr_fn(method) { return Err(TypeError::new( @@ -66,24 +277,78 @@ pub(super) fn validate_package_safety(module: &Module) -> Result<(), TypeError> span, )); } + if let Some(span) = type_contains_slice(&method.ret) { + return Err(TypeError::new( + "Slice types cannot be returned from safe modules".to_string(), + span, + )); + } } } Item::Struct(decl) => { + if is_stdlib { + continue; + } if let Some(span) = type_contains_ptr_struct(decl) { return Err(TypeError::new( "raw pointer types require `package unsafe`".to_string(), span, )); } - } - Item::Enum(decl) => { - if let Some(span) = type_contains_ptr_enum(decl) { + if let Some(span) = type_contains_slice_struct(decl) { return Err(TypeError::new( - "raw pointer types require `package unsafe`".to_string(), + "Slice types cannot appear in structs in safe modules".to_string(), span, )); } } + Item::Enum(decl) => { + if !is_stdlib { + if let Some(span) = type_contains_ptr_enum(decl) { + return Err(TypeError::new( + "raw pointer types require `package unsafe`".to_string(), + span, + )); + } + if let Some(span) = type_contains_slice_enum(decl) { + return Err(TypeError::new( + "Slice types cannot appear in enums in safe modules".to_string(), + span, + )); + } + } + } + } + } + Ok(()) +} + +pub(super) fn validate_import_safety( + module: &Module, + package_map: &HashMap, + stdlib_names: &HashSet, +) -> Result<(), TypeError> { + if module.package != PackageSafety::Safe { + return Ok(()); + } + for use_decl in &module.uses { + let mut name = String::new(); + for (i, seg) in use_decl.path.segments.iter().enumerate() { + if i > 0 { + name.push('.'); + } + name.push_str(&seg.item); + } + if let Some(pkg) = package_map.get(&name) { + if *pkg == PackageSafety::Unsafe { + if stdlib_names.contains(&name) { + continue; + } + return Err(TypeError::new( + format!("safe module cannot import unsafe module `{name}`"), + use_decl.span, + )); + } } } Ok(()) @@ -138,6 +403,61 @@ fn type_contains_ptr_enum(decl: &EnumDecl) -> Option { None } +fn is_slice_type_path(path: &Path) -> bool { + let Some(last) = path.segments.last() else { + return false; + }; + if last.item != "Slice" && last.item != "MutSlice" { + return false; + } + if path.segments.len() == 1 { + return true; + } + if path.segments.len() == 3 { + return path.segments[0].item == "sys" + && path.segments[1].item == "buffer" + && (last.item == "Slice" || last.item == "MutSlice"); + } + false +} + +fn type_contains_slice(ty: &Type) -> Option { + match ty { + Type::Path { path, args, span } => { + if is_slice_type_path(path) { + return Some(*span); + } + for arg in args { + if let Some(span) = type_contains_slice(arg) { + return Some(span); + } + } + None + } + Type::Ptr { target, .. } | Type::Ref { target, .. } => type_contains_slice(target), + } +} + +fn type_contains_slice_struct(decl: &StructDecl) -> Option { + for field in &decl.fields { + if let Some(span) = type_contains_slice(&field.ty) { + return Some(span); + } + } + None +} + +fn type_contains_slice_enum(decl: &EnumDecl) -> Option { + for variant in &decl.variants { + if let Some(payload) = &variant.payload { + if let Some(span) = type_contains_slice(payload) { + return Some(span); + } + } + } + None +} + fn block_contains_ptr(block: &Block) -> Option { for stmt in &block.stmts { match stmt { @@ -1062,8 +1382,29 @@ pub(super) fn check_expr( return record_expr_type(recorder, expr, ty); } } - if let Some(ty) = resolve_enum_variant(path, use_map, enum_map, module_name) { - return record_expr_type(recorder, expr, ty); + if let Some(Ty::Path(enum_name, _)) = + resolve_enum_variant(path, use_map, enum_map, module_name) + { + if let Some(info) = enum_map.get(&enum_name) { + let ty = if info.type_params.is_empty() { + Ty::Path(enum_name, Vec::new()) + } else if let Ty::Path(ret_name, ret_args) = ret_ty { + if ret_name == &enum_name && ret_args.len() == info.type_params.len() { + Ty::Path(enum_name, ret_args.clone()) + } else { + Ty::Path( + enum_name, + vec![Ty::Builtin(BuiltinType::Unit); info.type_params.len()], + ) + } + } else { + Ty::Path( + enum_name, + vec![Ty::Builtin(BuiltinType::Unit); info.type_params.len()], + ) + }; + return record_expr_type(recorder, expr, ty); + } } Err(TypeError::new( format!("unknown value `{path}`"), @@ -1137,7 +1478,7 @@ pub(super) fn check_expr( if let Ty::Path(ty_name, args) = ret_ty { if ty_name == "sys.result.Result" && args.len() == 2 { let expected = if name == "Ok" { &args[0] } else { &args[1] }; - if &arg_ty != expected { + if !ty_equivalent_for_set(&arg_ty, expected, type_params) { return Err(TypeError::new( format!("{name} argument type mismatch: expected {expected:?}, got {arg_ty:?}"), call.args[0].span(), @@ -1150,17 +1491,96 @@ pub(super) fn check_expr( recorder, expr, Ty::Path( - "sys.result.Result".to_string(), - if name == "Ok" { - vec![arg_ty, Ty::Builtin(BuiltinType::Unit)] - } else { - vec![Ty::Builtin(BuiltinType::Unit), arg_ty] - }, + "sys.result.Result".to_string(), + if name == "Ok" { + vec![arg_ty, Ty::Builtin(BuiltinType::Unit)] + } else { + vec![Ty::Builtin(BuiltinType::Unit), arg_ty] + }, ), ); } } + if let Some(Ty::Path(enum_name, _)) = + resolve_enum_variant(&path, use_map, enum_map, module_name) + { + let Some(info) = enum_map.get(&enum_name) else { + return Err(TypeError::new( + "unknown enum variant".to_string(), + call.span, + )); + }; + let variant = path + .segments + .last() + .map(|s| s.item.clone()) + .unwrap_or_else(|| "unknown".to_string()); + let payload = info.payloads.get(&variant).cloned().unwrap_or(None); + + if payload.is_none() && !call.args.is_empty() { + return Err(TypeError::new( + format!("{variant} takes no arguments"), + call.span, + )); + } + if payload.is_some() && call.args.len() != 1 { + return Err(TypeError::new( + format!("{variant} takes exactly one argument"), + call.span, + )); + } + + let mut inferred: HashMap = HashMap::new(); + let arg_ty = if let Some(payload_ty) = payload.clone() { + let arg_ty = check_expr( + &call.args[0], + functions, + scopes, + UseMode::Move, + recorder, + use_map, + struct_map, + enum_map, + stdlib, + ret_ty, + module_name, + type_params, + )?; + if !infer_enum_args(&payload_ty, &arg_ty, &mut inferred) { + return Err(TypeError::new( + format!( + "variant argument type mismatch: expected {payload_ty:?}, got {arg_ty:?}" + ), + call.args[0].span(), + )); + } + Some(arg_ty) + } else { + None + }; + + let type_args = resolve_enum_type_args( + &enum_name, + &info.type_params, + &inferred, + ret_ty, + ); + + if let Some(payload_ty) = payload { + if let Some(arg_ty) = arg_ty { + if !enum_payload_matches(&payload_ty, &arg_ty, &info.type_params, &type_args) { + return Err(TypeError::new( + "variant argument type mismatch".to_string(), + call.args[0].span(), + )); + } + } + } + + return record_expr_type(recorder, expr, Ty::Path(enum_name, type_args)); + } + let resolved = resolve_path(&path, use_map); let key = resolved.join("."); @@ -1378,6 +1798,11 @@ pub(super) fn check_expr( module_name, type_params, )?; + enforce_vec_method_constraints( + &receiver_ty, + &method_call.method.item, + method_call.method.span, + )?; let (method_module, type_name, receiver_args) = resolve_method_target( &receiver_ty, module_name, @@ -1664,6 +2089,11 @@ pub(super) fn check_expr( || left == Ty::Builtin(BuiltinType::I64)) { Ok(left) + } else if left == right + && matches!(left, Ty::Param(_)) + && module_name == "sys.vec" + { + Ok(left) } else if matches!(left, Ty::Builtin(BuiltinType::Never)) || matches!(right, Ty::Builtin(BuiltinType::Never)) { @@ -1984,29 +2414,17 @@ pub(super) fn check_expr( 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" => { + Ty::Path(name, args) if name == "Vec" || name == "sys.vec.Vec" => { + if args.len() != 1 { + return Err(TypeError::new( + "Vec expects exactly one type argument".to_string(), + index_expr.span, + )); + } Ok(Ty::Path( "sys.result.Result".to_string(), vec![ - Ty::Builtin(BuiltinType::U8), + args[0].clone(), Ty::Path("sys.vec.VecErr".to_string(), vec![]), ], )) @@ -2316,7 +2734,12 @@ fn check_match_exhaustive( }; let mut seen = HashSet::new(); for arm in arms { - if let Pattern::Path(path) = &arm.pattern { + let path = match &arm.pattern { + Pattern::Path(path) => Some(path), + Pattern::Call { path, .. } => Some(path), + _ => None, + }; + if let Some(path) = path { if let Some(ty) = resolve_enum_variant(path, use_map, enum_map, module_name) { if same_type_constructor(&ty, match_ty) { if let Some(seg) = path.segments.last() { @@ -2385,9 +2808,20 @@ fn check_struct_literal( } else { type_name.clone() }; - let info = struct_map.get(&key).ok_or_else(|| { - TypeError::new(format!("unknown struct `{}`", key), lit.span) - })?; + let (key, info) = match struct_map.get(&key) { + Some(info) => (key, info), + None => { + let qualified = if lit.path.segments.len() == 1 { + format!("{}.{}", module_name, key) + } else { + key.clone() + }; + let info = struct_map.get(&qualified).ok_or_else(|| { + TypeError::new(format!("unknown struct `{}`", key), lit.span) + })?; + (qualified, info) + } + }; if info.type_params.is_empty() { if !type_args.is_empty() { return Err(TypeError::new( @@ -2629,9 +3063,6 @@ fn bind_pattern( ) -> Result<(), TypeError> { match pattern { Pattern::Call { path, binding, .. } => { - let Some(binding) = binding else { - return Ok(()); - }; let name = path .segments .iter() @@ -2640,19 +3071,64 @@ fn bind_pattern( .join("."); if let Ty::Path(ty_name, args) = match_ty { if ty_name == "sys.result.Result" && args.len() == 2 { - let ty = if name == "Ok" { - args[0].clone() - } else if name == "Err" { - args[1].clone() - } else { - return Ok(()); - }; - scopes.insert_local(binding.item.clone(), ty); + if let Some(binding) = binding { + let ty = if name == "Ok" { + args[0].clone() + } else if name == "Err" { + args[1].clone() + } else { + return Ok(()); + }; + scopes.insert_local(binding.item.clone(), ty); + } return Ok(()); } } + if let Some(Ty::Path(enum_name, _)) = + resolve_enum_variant(path, use_map, enum_map, module_name) + { + let Ty::Path(match_name, match_args) = match_ty else { + return Err(TypeError::new( + format!("pattern type mismatch: expected {match_ty:?}, found {enum_name:?}"), + path.span, + )); + }; + if match_name != &enum_name { + return Err(TypeError::new( + format!("pattern type mismatch: expected {match_ty:?}, found {enum_name:?}"), + path.span, + )); + } + if let Some(binding) = binding { + let Some(info) = enum_map.get(&enum_name) else { + return Err(TypeError::new("unknown enum variant".to_string(), path.span)); + }; + let variant = path + .segments + .last() + .map(|s| s.item.clone()) + .unwrap_or_else(|| "unknown".to_string()); + let payload = info.payloads.get(&variant).cloned().unwrap_or(None); + let Some(payload_ty) = payload else { + return Err(TypeError::new( + format!("variant `{name}` has no payload"), + path.span, + )); + }; + if info.type_params.len() != match_args.len() { + return Err(TypeError::new( + "pattern type mismatch".to_string(), + path.span, + )); + } + let payload_ty = + apply_enum_type_args(&payload_ty, &info.type_params, match_args); + scopes.insert_local(binding.item.clone(), payload_ty); + } + return Ok(()); + } Err(TypeError::new( - "pattern binding requires a Result match".to_string(), + "pattern binding requires an enum match".to_string(), path.span, )) } diff --git a/capc/src/typeck/lower.rs b/capc/src/typeck/lower.rs index 77d0b4b..dfe609a 100644 --- a/capc/src/typeck/lower.rs +++ b/capc/src/typeck/lower.rs @@ -605,7 +605,7 @@ fn type_of_ast_expr( } let mut scopes = super::Scopes::from_flat_map(ctx.local_types.clone()); let mut recorder = super::check::TypeRecorder::new(None); - let type_params = HashSet::new(); + let type_params = ctx.type_params.clone(); check::check_expr( expr, ctx.functions, @@ -770,6 +770,31 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result 1 { + return Err(TypeError::new( + "enum variant takes at most one argument".to_string(), + call.span, + )); + } + let variant_name = path + .segments + .last() + .map(|s| s.item.clone()) + .unwrap_or_else(|| String::from("unknown")); + let payload = if call.args.is_empty() { + None + } else { + Some(Box::new(lower_expr(&call.args[0], ctx, ret_ty)?)) + }; + return Ok(HirExpr::EnumVariant(HirEnumVariantExpr { + enum_ty: hir_ty.clone(), + variant_name, + payload, + span: call.span, + })); + } + let mut resolved = super::resolve_path(&path, ctx.use_map); if resolved.len() == 1 { @@ -965,9 +990,20 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result (key, info), + None => { + let qualified = if lit.path.segments.len() == 1 { + format!("{}.{}", ctx.module_name, key) + } else { + key.clone() + }; + let info = ctx.structs.get(&qualified).ok_or_else(|| { + TypeError::new(format!("unknown struct `{}`", key), lit.span) + })?; + (qualified, info) + } + }; if info.is_opaque && info.module != ctx.module_name { return Err(TypeError::new( @@ -1050,34 +1086,30 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result { - if name.starts_with("sys.vec.") { - name.strip_prefix("sys.vec.").unwrap() - } else { - name.as_str() - } - } - _ => unreachable!(), + let type_args = match object_ty { + Ty::Path(_, args) => args.clone(), + _ => Vec::new(), }; - let key = format!("sys.vec.{}__get", type_name); - let symbol = format!("capable_{}", key.replace('.', "_").replace("__", "_")); + if type_args.len() != 1 { + return Err(TypeError::new( + "Vec expects exactly one type argument".to_string(), + index_expr.span, + )); + } + let symbol = "capable_sys_vec_Vec__get".to_string(); Ok(HirExpr::Call(crate::hir::HirCall { callee: ResolvedCallee::Function { module: "sys.vec".to_string(), - name: format!("{}__get", type_name), + name: "Vec__get".to_string(), symbol, }, - type_args: vec![], + type_args, args: vec![object, index], ret_ty: hir_ty, span: index_expr.span, diff --git a/capc/src/typeck/mod.rs b/capc/src/typeck/mod.rs index 44a65b6..8b467a5 100644 --- a/capc/src/typeck/mod.rs +++ b/capc/src/typeck/mod.rs @@ -366,6 +366,17 @@ fn resolve_path(path: &Path, use_map: &UseMap) -> Vec { path.segments.iter().map(|seg| seg.item.clone()).collect() } +fn path_to_string(path: &Path) -> String { + let mut out = String::new(); + for (i, seg) in path.segments.iter().enumerate() { + if i > 0 { + out.push('.'); + } + out.push_str(&seg.item); + } + out +} + /// Resolve a method receiver type to (module, type name, type args). /// Builtins with methods (string/u8) are mapped to their stdlib modules. fn resolve_method_target( @@ -726,18 +737,15 @@ fn lower_type( )); } 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())); + if !matches!(elem, Ty::Builtin(BuiltinType::U8) | Ty::Builtin(BuiltinType::I32) | Ty::Param(_)) + && !is_string_ty(elem) + { + return Err(TypeError::new( + "Vec only supports u8, i32, and string element types".to_string(), + path.span, + )); + } + return Ok(Ty::Path("sys.vec.Vec".to_string(), args)); } return Ok(Ty::Path(joined, args)); } @@ -750,18 +758,15 @@ fn lower_type( )); } 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())); + if !matches!(elem, Ty::Builtin(BuiltinType::U8) | Ty::Builtin(BuiltinType::I32) | Ty::Param(_)) + && !is_string_ty(elem) + { + return Err(TypeError::new( + "Vec only supports u8, i32, and string element types".to_string(), + path.span, + )); + } + return Ok(Ty::Path("sys.vec.Vec".to_string(), args)); } Ok(Ty::Path(joined, args)) } @@ -1028,6 +1033,18 @@ pub fn type_check_program( user_modules: &[Module], ) -> Result { let use_map = UseMap::new(module); + let stdlib_names: HashSet = stdlib + .iter() + .map(|m| path_to_string(&m.name)) + .collect(); + let mut package_map: HashMap = HashMap::new(); + for m in stdlib { + package_map.insert(path_to_string(&m.name), m.package); + } + for m in user_modules { + package_map.insert(path_to_string(&m.name), m.package); + } + package_map.insert(path_to_string(&module.name), module.package); let stdlib_index = collect::build_stdlib_index(stdlib)?; let modules = stdlib .iter() @@ -1035,11 +1052,31 @@ pub fn type_check_program( .chain(std::iter::once(module)) .collect::>(); let module_name = module.name.to_string(); - check::validate_package_safety(module) + check::validate_package_safety(module, false) + .map_err(|err| err.with_context(format!("in module `{}`", module.name)))?; + check::validate_import_safety(module, &package_map, &stdlib_names) .map_err(|err| err.with_context(format!("in module `{}`", module.name)))?; for user_module in user_modules { - check::validate_package_safety(user_module) + check::validate_package_safety(user_module, false) .map_err(|err| err.with_context(format!("in module `{}`", user_module.name)))?; + check::validate_import_safety( + user_module, + &package_map, + &stdlib_names, + ) + .map_err(|err| err.with_context(format!("in module `{}`", user_module.name)))?; + } + for stdlib_module in stdlib { + check::validate_package_safety(stdlib_module, true) + .map_err(|err| err.with_context(format!("in module `{}`", stdlib_module.name)))?; + if stdlib_module.package == PackageSafety::Safe { + check::validate_import_safety( + stdlib_module, + &package_map, + &stdlib_names, + ) + .map_err(|err| err.with_context(format!("in module `{}`", stdlib_module.name)))?; + } } let struct_map = collect::collect_structs(&modules, &module_name, &stdlib_index) .map_err(|err| err.with_context("while collecting structs"))?; diff --git a/capc/src/typeck/monomorphize.rs b/capc/src/typeck/monomorphize.rs index f8d2a1a..00e631f 100644 --- a/capc/src/typeck/monomorphize.rs +++ b/capc/src/typeck/monomorphize.rs @@ -535,7 +535,7 @@ impl MonoCtx { let (new_name, symbol, type_args) = self.mono_callee( module, &func, - &args, + &call.args, &call.type_args, subs, )?; @@ -556,7 +556,7 @@ impl MonoCtx { let (new_name, symbol, type_args) = self.mono_callee( module, &func, - &args, + &call.args, &call.type_args, subs, )?; @@ -684,8 +684,8 @@ impl MonoCtx { let explicit_args: Vec = explicit_type_args .iter() - .map(|arg| self.mono_ty(module, arg, subs)) - .collect::>()?; + .map(|arg| substitute_ty(arg, subs)) + .collect(); let mut inferred = HashMap::new(); if !explicit_args.is_empty() { @@ -694,7 +694,8 @@ impl MonoCtx { } } for (param, arg) in func.params().iter().zip(args.iter()) { - match_type_params(¶m.ty.ty, &arg.ty().ty, &mut inferred, DUMMY_SPAN)?; + let actual = substitute_ty(&arg.ty().ty, subs); + match_type_params(¶m.ty.ty, &actual, &mut inferred, DUMMY_SPAN)?; } for name in func.type_params() { if !inferred.contains_key(name) { @@ -716,7 +717,7 @@ impl MonoCtx { base_name: func.name().to_string(), type_args: ordered_args.clone(), }); - Ok((new_name, symbol, Vec::new())) + Ok((new_name, symbol, ordered_args)) } fn mono_hir_type( @@ -884,7 +885,10 @@ impl MonoCtx { }); } if let Some(info) = self.enums.get(&qualified) { - let _ = info; + let has_payload = info.variants.iter().any(|variant| variant.payload.is_some()); + if has_payload { + return Ok(AbiType::Ptr); + } return Ok(AbiType::I32); } Err(TypeError::new( @@ -980,6 +984,19 @@ fn build_substitution( Ok(map) } +fn substitute_ty(ty: &Ty, subs: &HashMap) -> Ty { + match ty { + Ty::Param(name) => subs.get(name).cloned().unwrap_or_else(|| ty.clone()), + Ty::Builtin(_) => ty.clone(), + Ty::Ptr(inner) => Ty::Ptr(Box::new(substitute_ty(inner, subs))), + Ty::Ref(inner) => Ty::Ref(Box::new(substitute_ty(inner, subs))), + Ty::Path(name, args) => Ty::Path( + name.clone(), + args.iter().map(|arg| substitute_ty(arg, subs)).collect(), + ), + } +} + fn match_type_params( expected: &Ty, actual: &Ty, diff --git a/capc/tests/run.rs b/capc/tests/run.rs index 2da9547..f4db5eb 100644 --- a/capc/tests/run.rs +++ b/capc/tests/run.rs @@ -316,43 +316,160 @@ fn run_slice_unsafe() { } #[test] -fn run_buffer_unsafe() { - let out_dir = make_out_dir("buffer_unsafe"); +fn run_unsafe_ptr_unsafe() { + let out_dir = make_out_dir("unsafe_ptr_unsafe"); let out_dir = out_dir.to_str().expect("utf8 out dir"); let (code, stdout, _stderr) = run_capc(&[ "run", "--out-dir", out_dir, - "tests/programs/buffer_unsafe.cap", + "tests/programs/unsafe_ptr_unsafe.cap", ]); assert_eq!(code, 0); - assert!(stdout.contains("buffer ok"), "stdout was: {stdout:?}"); + assert!(stdout.contains("unsafe ptr ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_text_unsafe() { + let out_dir = make_out_dir("text_unsafe"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/text_unsafe.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("text ok"), "stdout was: {stdout:?}"); assert!(stdout.contains("slice ok"), "stdout was: {stdout:?}"); } #[test] -fn run_buffer_safe() { - let out_dir = make_out_dir("buffer_safe"); +fn run_text_safe() { + let out_dir = make_out_dir("text_safe"); let out_dir = out_dir.to_str().expect("utf8 out dir"); let (code, stdout, _stderr) = run_capc(&[ "run", "--out-dir", out_dir, - "tests/programs/buffer_safe.cap", + "tests/programs/text_safe.cap", ]); assert_eq!(code, 0); - assert!(stdout.contains("buffer ok"), "stdout was: {stdout:?}"); + assert!(stdout.contains("text ok"), "stdout was: {stdout:?}"); } #[test] -fn run_buffer_push_safe() { - let out_dir = make_out_dir("buffer_push_safe"); +fn run_text_to_string() { + let out_dir = make_out_dir("text_to_string"); let out_dir = out_dir.to_str().expect("utf8 out dir"); let (code, stdout, _stderr) = run_capc(&[ "run", "--out-dir", out_dir, - "tests/programs/buffer_push_safe.cap", + "tests/programs/text_to_string.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("hi"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_text_helpers_more() { + let out_dir = make_out_dir("text_helpers_more"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/text_helpers_more.cap", + ]); + assert_eq!(code, 0); + assert!( + stdout.contains("text helpers ok"), + "stdout was: {stdout:?}" + ); +} + +#[test] +fn run_text_basic() { + let out_dir = make_out_dir("text_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/text_basic.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("text basic ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_option_basic() { + let out_dir = make_out_dir("option_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/option_basic.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("ok"), "stdout was: {stdout:?}"); + assert!(stdout.contains("default"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_enum_payload_basic() { + let out_dir = make_out_dir("enum_payload_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/enum_payload_basic.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("some"), "stdout was: {stdout:?}"); + assert!(stdout.contains("none"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_enum_payload_struct() { + let out_dir = make_out_dir("enum_payload_struct"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/enum_payload_struct.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("pair"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_enum_payload_string() { + let out_dir = make_out_dir("enum_payload_string"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/enum_payload_string.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("left"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_text_push_safe() { + let out_dir = make_out_dir("text_push_safe"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/text_push_safe.cap", ]); assert_eq!(code, 0); assert!(stdout.contains("push ok"), "stdout was: {stdout:?}"); @@ -473,6 +590,48 @@ fn run_vec_helpers() { assert!(stdout.contains("vec ok"), "stdout was: {stdout:?}"); } +#[test] +fn run_vec_i32_helpers() { + let out_dir = make_out_dir("vec_i32_helpers"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/vec_i32_helpers.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("vec i32 ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_vec_string_helpers() { + let out_dir = make_out_dir("vec_string_helpers"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/vec_string_helpers.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("vec string ok"), "stdout was: {stdout:?}"); +} + +#[test] +fn run_vec_search_helpers() { + let out_dir = make_out_dir("vec_search_helpers"); + let out_dir = out_dir.to_str().expect("utf8 out dir"); + let (code, stdout, _stderr) = run_capc(&[ + "run", + "--out-dir", + out_dir, + "tests/programs/vec_search_helpers.cap", + ]); + assert_eq!(code, 0); + assert!(stdout.contains("vec search ok"), "stdout was: {stdout:?}"); +} + #[test] fn run_net_helpers() { let out_dir = make_out_dir("net_helpers"); diff --git a/capc/tests/snapshots/parser__snapshot_struct_and_match.snap b/capc/tests/snapshots/parser__snapshot_struct_and_match.snap index 9b8b4c0..8a493ee 100644 --- a/capc/tests/snapshots/parser__snapshot_struct_and_match.snap +++ b/capc/tests/snapshots/parser__snapshot_struct_and_match.snap @@ -172,10 +172,60 @@ Module { Let( LetStmt { name: Spanned { - item: "rfs", + item: "alloc", span: Span { start: 100, - end: 103, + end: 105, + }, + }, + ty: None, + expr: MethodCall( + MethodCallExpr { + receiver: Path( + Path { + segments: [ + Spanned { + item: "rc", + span: Span { + start: 108, + end: 110, + }, + }, + ], + span: Span { + start: 108, + end: 110, + }, + }, + ), + method: Spanned { + item: "mint_alloc_default", + span: Span { + start: 111, + end: 129, + }, + }, + type_args: [], + args: [], + span: Span { + start: 108, + end: 131, + }, + }, + ), + span: Span { + start: 96, + end: 131, + }, + }, + ), + Let( + LetStmt { + name: Spanned { + item: "rfs", + span: Span { + start: 138, + end: 141, }, }, ty: None, @@ -187,22 +237,22 @@ Module { Spanned { item: "rc", span: Span { - start: 106, - end: 108, + start: 144, + end: 146, }, }, ], span: Span { - start: 106, - end: 108, + start: 144, + end: 146, }, }, ), method: Spanned { item: "mint_readfs", span: Span { - start: 109, - end: 120, + start: 147, + end: 158, }, }, type_args: [], @@ -213,21 +263,21 @@ Module { "./config", ), span: Span { - start: 121, - end: 131, + start: 159, + end: 169, }, }, ), ], span: Span { - start: 106, - end: 132, + start: 144, + end: 170, }, }, ), span: Span { - start: 96, - end: 132, + start: 134, + end: 170, }, }, ), @@ -243,41 +293,58 @@ Module { Spanned { item: "rfs", span: Span { - start: 142, - end: 145, + start: 180, + end: 183, }, }, ], span: Span { - start: 142, - end: 145, + start: 180, + end: 183, }, }, ), method: Spanned { item: "read_to_string", span: Span { - start: 146, - end: 160, + start: 184, + end: 198, }, }, type_args: [], args: [ + Path( + Path { + segments: [ + Spanned { + item: "alloc", + span: Span { + start: 199, + end: 204, + }, + }, + ], + span: Span { + start: 199, + end: 204, + }, + }, + ), Literal( LiteralExpr { value: String( "app.txt", ), span: Span { - start: 161, - end: 170, + start: 206, + end: 215, }, }, ), ], span: Span { - start: 142, - end: 171, + start: 180, + end: 216, }, }, ), @@ -289,28 +356,28 @@ Module { Spanned { item: "Ok", span: Span { - start: 178, - end: 180, + start: 223, + end: 225, }, }, ], span: Span { - start: 178, - end: 180, + start: 223, + end: 225, }, }, binding: Some( Spanned { item: "s", span: Span { - start: 181, - end: 182, + start: 226, + end: 227, }, }, ), span: Span { - start: 178, - end: 183, + start: 223, + end: 228, }, }, body: Block { @@ -325,22 +392,22 @@ Module { Spanned { item: "c", span: Span { - start: 189, - end: 190, + start: 234, + end: 235, }, }, ], span: Span { - start: 189, - end: 190, + start: 234, + end: 235, }, }, ), method: Spanned { item: "println", span: Span { - start: 191, - end: 198, + start: 236, + end: 243, }, }, type_args: [], @@ -351,27 +418,27 @@ Module { Spanned { item: "s", span: Span { - start: 199, - end: 200, + start: 244, + end: 245, }, }, ], span: Span { - start: 199, - end: 200, + start: 244, + end: 245, }, }, ), ], span: Span { - start: 189, - end: 201, + start: 234, + end: 246, }, }, ), span: Span { - start: 189, - end: 202, + start: 234, + end: 247, }, }, ), @@ -384,27 +451,27 @@ Module { 0, ), span: Span { - start: 210, - end: 211, + start: 255, + end: 256, }, }, ), ), span: Span { - start: 203, - end: 213, + start: 248, + end: 258, }, }, ), ], span: Span { - start: 187, - end: 213, + start: 232, + end: 258, }, }, span: Span { - start: 136, - end: 213, + start: 174, + end: 258, }, }, MatchArm { @@ -414,28 +481,28 @@ Module { Spanned { item: "Err", span: Span { - start: 218, - end: 221, + start: 263, + end: 266, }, }, ], span: Span { - start: 218, - end: 221, + start: 263, + end: 266, }, }, binding: Some( Spanned { item: "e", span: Span { - start: 222, - end: 223, + start: 267, + end: 268, }, }, ), span: Span { - start: 218, - end: 224, + start: 263, + end: 269, }, }, body: Block { @@ -450,22 +517,22 @@ Module { Spanned { item: "c", span: Span { - start: 230, - end: 231, + start: 275, + end: 276, }, }, ], span: Span { - start: 230, - end: 231, + start: 275, + end: 276, }, }, ), method: Spanned { item: "println", span: Span { - start: 232, - end: 239, + start: 277, + end: 284, }, }, type_args: [], @@ -476,21 +543,21 @@ Module { "read failed", ), span: Span { - start: 240, - end: 253, + start: 285, + end: 298, }, }, ), ], span: Span { - start: 230, - end: 254, + start: 275, + end: 299, }, }, ), span: Span { - start: 230, - end: 255, + start: 275, + end: 300, }, }, ), @@ -503,63 +570,63 @@ Module { 1, ), span: Span { - start: 263, - end: 264, + start: 308, + end: 309, }, }, ), ), span: Span { - start: 256, - end: 266, + start: 301, + end: 311, }, }, ), ], span: Span { - start: 228, - end: 266, + start: 273, + end: 311, }, }, span: Span { - start: 136, - end: 266, + start: 174, + end: 311, }, }, ], span: Span { - start: 136, - end: 270, + start: 174, + end: 315, }, match_span: Span { - start: 136, - end: 141, + start: 174, + end: 179, }, }, ), span: Span { - start: 136, - end: 270, + start: 174, + end: 315, }, }, ), ], span: Span { start: 64, - end: 272, + end: 317, }, }, is_pub: true, doc: None, span: Span { start: 36, - end: 272, + end: 317, }, }, ), ], span: Span { start: 0, - end: 272, + end: 317, }, } diff --git a/capc/tests/typecheck.rs b/capc/tests/typecheck.rs index 905ba93..ff9366c 100644 --- a/capc/tests/typecheck.rs +++ b/capc/tests/typecheck.rs @@ -206,7 +206,7 @@ fn typecheck_console_as_alloc_fails() { let err = type_check_program(&module, &stdlib, &[]).expect_err("expected type error"); assert!(err .to_string() - .contains("unknown method `sys.console.Console__buffer_new`")); + .contains("unknown method `sys.console.Console__vec_u8_new`")); } #[test] @@ -690,6 +690,14 @@ fn typecheck_pointer_unsafe_ok() { type_check_program(&module, &stdlib, &[]).expect("typecheck module"); } +#[test] +fn typecheck_result_ptr_generic_ok() { + let source = load_program("result_ptr_generic.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_slice_safe_ok() { let source = load_program("slice_safe.cap"); @@ -698,6 +706,39 @@ fn typecheck_slice_safe_ok() { type_check_program(&module, &stdlib, &[]).expect("typecheck module"); } +#[test] +fn typecheck_slice_return_fails() { + let source = load_program("should_fail_slice_return.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("Slice types cannot be returned from safe modules")); +} + +#[test] +fn typecheck_slice_struct_field_fails() { + let source = load_program("should_fail_slice_struct_field.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("Slice types cannot appear in structs in safe modules")); +} + +#[test] +fn typecheck_slice_enum_field_fails() { + let source = load_program("should_fail_slice_enum_field.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("Slice types cannot appear in enums in safe modules")); +} + #[test] fn typecheck_slice_unsafe_ok() { let source = load_program("slice_unsafe.cap"); @@ -707,8 +748,8 @@ fn typecheck_slice_unsafe_ok() { } #[test] -fn typecheck_buffer_safe_ok() { - let source = load_program("buffer_safe.cap"); +fn typecheck_text_safe_ok() { + let source = load_program("text_safe.cap"); let module = parse_module(&source).expect("parse module"); let stdlib = load_stdlib().expect("load stdlib"); type_check_program(&module, &stdlib, &[]).expect("typecheck module"); @@ -723,8 +764,8 @@ fn typecheck_slice_safe_read_ok() { } #[test] -fn typecheck_buffer_push_safe_ok() { - let source = load_program("buffer_push_safe.cap"); +fn typecheck_text_push_safe_ok() { + let source = load_program("text_push_safe.cap"); let module = parse_module(&source).expect("parse module"); let stdlib = load_stdlib().expect("load stdlib"); type_check_program(&module, &stdlib, &[]).expect("typecheck module"); diff --git a/docs/ABI.md b/docs/ABI.md index af9c34b..5cf69af 100644 --- a/docs/ABI.md +++ b/docs/ABI.md @@ -31,8 +31,24 @@ out-params: Runtime-backed intrinsics keep their original ABI (no sret) and are wrapped by compiler-generated stubs when needed. +## Allocation convention + +APIs that allocate accept an explicit `Alloc` handle. The handle is passed +through to the runtime and currently backed by libc `malloc`/`free`, but the ABI +keeps the allocator explicit for future custom allocator support. + ## 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. + +## Slice + string layout + +`Slice` and `string` are plain structs in the stdlib and are passed by value: + +- `Slice` layout: `{ ptr: *T, len: i32 }` +- `string` layout: `{ bytes: Slice }` + +`string` values are views into UTF-8 byte storage; they do not imply ownership +in the ABI. Owned text is represented by `string::Text` (backed by `Vec`). diff --git a/docs/README.md b/docs/README.md index 5f2375e..3be00d2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,6 +8,7 @@ This is an experimental and toy project. Inspired by [Austral](https://austral-l fn main(rc: RootCap) { // Mint a capability from the root let console = rc.mint_console(); + let alloc = rc.mint_alloc_default(); // Acquire a ReadFS capability at ./here. // We pass this capability struct to functions that require @@ -16,7 +17,7 @@ fn main(rc: RootCap) { let fs = rc.mint_readfs("./here"); // Attempt to read beyond the capability's scopes: this will print "blocked". - match fs.read_to_string("../etc/passwd") { + match fs.read_to_string(alloc, "../etc/passwd") { Ok(_) => console.println("BUG: escaped"), Err(_) => console.println("blocked"), } diff --git a/docs/TUTORIAL.md b/docs/TUTORIAL.md index d0c71e3..d0f9a55 100644 --- a/docs/TUTORIAL.md +++ b/docs/TUTORIAL.md @@ -1,12 +1,18 @@ -# Capable in 15 Minutes +# Capable Tutorial -Capable is a small capability-secure systems language. The main idea: authority is a value. If you didn't receive a capability, you can't do the thing. +Capable is a small, capability-secure systems language. Authority is a value: if +you did not receive a capability, you cannot perform that action. The language +leans Go-like in surface syntax while keeping a tighter, explicit memory and +capability model. -This tutorial is a quick tour of the current language slice and the capability model. +This tutorial is a cohesive walk-through of the language as it exists today. +It focuses on how to write real programs, how the capability model works, and +how memory is managed. ## 1) Hello, console ```cap +package safe module hello use sys::system @@ -17,18 +23,20 @@ pub fn main(rc: RootCap) -> i32 { } ``` -`RootCap` is the root authority passed to `main`. It can mint narrower capabilities (console, filesystem, etc.). +`RootCap` is the root authority passed to `main`. It can mint narrower +capabilities (console, filesystem, network, allocators). -## 2) Basic syntax +## 2) Language basics ```cap +package safe module basics pub fn add(a: i32, b: i32) -> i32 { return a + b } -pub fn main() -> i32 { +pub fn main(rc: RootCap) -> i32 { let x = 1 let y: i32 = 2 if (x < y) { @@ -39,171 +47,113 @@ pub fn main() -> i32 { } ``` -- Statements: `let`, assignment, `if`, `while`, `for`, `return`, `match`, `defer`. +Key syntax: +- Statements: `let`, assignment, `if`, `while`, `for`, `match`, `return`, `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. +- Modules + imports: `module ...` and `use ...` (alias by last path segment). +- `for { ... }` is an infinite loop; `for i in a..b` is a range loop. - Integer arithmetic traps on overflow. - Variable shadowing is not allowed. -## 3) Structs and enums +## 3) Control flow and pattern matching ```cap -module types - -struct Pair { left: i32, right: i32 } +package safe +module match_demo -enum Color { Red, Green, Blue } +pub fn main(rc: RootCap) -> i32 { + let flag = true + match flag { + true => { return 1 } + false => { return 0 } + } +} ``` -Structs and enums are nominal types. Enums are currently unit variants only. - -## 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. +Matches must be exhaustive; use `_` to cover the rest. `if let` is a +single-arm match: ```cap -pub fn main(rc: RootCap) -> i32 { - let c = rc.mint_console() - c.println("start") - defer c.println("cleanup") - c.println("end") +if let Ok(x) = make() { + return x +} else { 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. +## 4) Structs, enums, methods ```cap -module methods +package safe +module types struct Pair { left: i32, right: i32 } +enum Color { Red, Green, Blue } + impl Pair { pub fn sum(self) -> i32 { return self.left + self.right } - pub fn add(self, x: i32) -> i32 { return self.sum() + x } pub fn peek(self: &Pair) -> i32 { return self.left } } ``` -Method receivers can be `self` (move) or `self: &T` (borrow‑lite, read‑only). +- Structs and enums are nominal types. +- Methods are defined in `impl` blocks and lower to `Type__method` in codegen. +- Method receivers can be `self` (move) or `self: &T` (borrow-lite, read-only). -## 6) Results, match, and `?` +## 5) Results and error flow -```cap -module results - -pub fn main() -> i32 { - let ok: Result[i32, i32] = Ok(10) - match ok { - Ok(x) => { return x } - Err(e) => { return 0 } - } -} -``` - -`Result[T, E]` is the only generic type today and is special-cased by the compiler. - -Inside a function that returns `Result`, you can use `?` to unwrap or return early: +Capable uses `Result[T, E]` for recoverable errors. ```cap -module results_try +package safe +module results -fn read_value() -> Result[i32, i32] { +fn parse() -> Result { return Ok(7) } -fn use_value() -> Result[i32, i32] { - let v = read_value()? +fn use_value() -> Result { + let v = parse()? return Ok(v + 1) } ``` -You can also unwrap with defaults: +Other helpers: ```cap let v = make().unwrap_or(0) let e = make().unwrap_err_or(0) ``` -Matches must be exhaustive; use `_` to cover the rest: - -```cap -match flag { - true => { } - false => { } -} -``` +## 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`. +Capabilities live in `sys.*` and are declared with `capability` (opaque, no +public fields, no user construction). You only get them from `RootCap`. ```cap +package safe module read_config use sys::system use sys::fs pub fn main(rc: RootCap) -> i32 { - let fs = rc.mint_filesystem("./config") - let dir = fs.root_dir() - let file = dir.open_read("app.txt") - - match file.read_to_string() { + let fs = rc.mint_readfs("./config") + let alloc = rc.mint_alloc_default() + match fs.read_to_string(alloc, "app.txt") { Ok(s) => { rc.mint_console().println(s); return 0 } - Err(e) => { return 1 } + Err(_) => { return 1 } } } ``` -This is attenuation: each step narrows authority. There is no safe API to widen back. +Attenuation is one-way: methods that return capabilities must take `self` by +value, so you give up the more powerful capability when you derive a narrower +one. This is enforced by the compiler. -To make attenuation one-way at compile time, any method that returns a capability must take `self` by value. Methods that take `&self` cannot return capabilities. +## 7) Kinds: copy, affine, linear -Example of what is rejected (and why): - -```cap -capability struct Dir -capability struct FileRead - -impl Dir { - pub fn open(self: &Dir, name: string) -> FileRead { - let file = self.open_read(name) - return file - } -} -``` - -Why this is rejected: - -- `Dir` can read many files (more power). -- `FileRead` can read one file (less power). -- The bad example lets you keep the more powerful `Dir` and also get a `FileRead`. -- We want ā€œone-wayā€ attenuation: when you make something less powerful, you give up the more powerful one. - -So methods that return capabilities must take `self` by value, which consumes the old capability. - -## 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). - -Structs can declare their kind: +Types can declare how they move: ```cap capability struct Token // affine by default (move-only) @@ -212,97 +162,121 @@ linear capability struct FileRead // must be consumed ``` Kinds: - -- **Unrestricted** (copy): can be reused freely. -- **Affine** (default for capability/opaque): move-only, dropping is OK. +- **Copy**: reusable. +- **Affine**: move-only, dropping is allowed. - **Linear**: move-only and must be consumed on all paths. -Use `capability struct` for authority-bearing tokens. Use `opaque struct` for unforgeable data types that aren’t capabilities. +## 8) Borrow-lite references: `&T` -In the current stdlib: - -- `copy capability`: `RootCap`, `Console`, `Args` -- `copy opaque`: `Alloc`, `Buffer`, `Slice`, `MutSlice`, `VecU8`, `VecI32`, `VecString` -- `capability` (affine): `ReadFS`, `Filesystem`, `Dir`, `Stdin` -- `linear capability`: `FileRead` - -## 9) Moves and use-after-move +Capable has a minimal borrow system for read-only access: ```cap -module moves - -capability struct Token +impl Cap { + pub fn ping(self: &Cap) -> i32 { return 1 } +} -pub fn main() -> i32 { - let t = Token{} - let u = t - let v = t // error: use of moved value - return 0 +pub fn twice(c: &Cap) -> i32 { + let a = c.ping() + let b = c.ping() + return a + b } ``` -Affine and linear values cannot be used after move. If you move in one branch, it's moved after the join. +Rules: +- `&T` is allowed on parameters and locals. +- References cannot be stored in structs/enums or returned. +- References are read-only. + +This keeps the language simple without a full borrow checker. -## 10) Linear must be consumed +## 9) Memory model (explicit ownership) -```cap -module linear +Capable has explicit memory management. Owned heap types must be freed. +Non-owning views must not outlive their backing storage. -linear capability struct Ticket +Owned types: +- `Vec` (heap-backed) +- `Text` (owned UTF-8 bytes, backed by `Vec`) -pub fn main() -> i32 { - let t = Ticket{} - drop(t) // consumes t - return 0 -} -``` +Views: +- `string` (non-owning view of bytes) +- `Slice`, `MutSlice` (non-owning views) -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. +Safe code cannot return or store slices in structs/enums, which keeps slice +lifetimes local until a full lifetime model exists. -## 11) Borrow-lite: &T parameters +### Allocators -There is a small borrow feature for read-only access in function parameters and locals. +Allocation is explicit. Functions that allocate accept an `Alloc` handle: ```cap -module borrow +let alloc = rc.mint_alloc_default() +let v = alloc.vec_u8_new() +... +alloc.vec_u8_free(v) +``` -capability struct Cap +Use `defer` to simplify cleanup. -impl Cap { - pub fn ping(self: &Cap) -> i32 { return 1 } -} +## 10) Strings: `string` vs `Text` -pub fn twice(c: &Cap) -> i32 { - let a = c.ping() - let b = c.ping() - return a + b +`string` is a view. `Text` is owned. + +```cap +fn build_greeting(alloc: Alloc) -> Result { + let s = "hello" + let _bytes = s.as_slice() + let _sub = s.slice_range(0, 5)? + + let t = alloc.text_new() + defer t.free(alloc) + t.push_str("hello")? + t.push_byte(' ')? + t.append("text")? + let out = t.to_string()? + return Ok(out) } ``` -Rules: +Helpers: +- `string.split`, `split_once`, `trim_*`, `contains`, `index_of_*`. +- `string.concat(alloc, other)` creates a new owned string view. +- `Text.slice_range` returns a `string` view into its buffer. -- `&T` is allowed on parameters and locals. -- Reference locals must be initialized from another local value. -- References cannot be stored in structs, enums, or returned. -- References are read-only: they can only satisfy `&T` parameters. -- Passing a value to `&T` implicitly borrows it. +## 11) Slices and indexing -This avoids a full borrow checker while making non-consuming observers ergonomic. +Slices are bounds-checked in safe code. Indexing out of bounds traps. +If you need a checked version, use `slice_range` or `byte_at_checked`. -## 12) Safety boundary +```cap +fn use_tail(s: string) -> Result { + let buf = s.as_slice() + let b0 = buf.at(0) // traps if out of bounds + let tail = buf.slice_range(1, 3)? + let _b1 = tail.at(0) + return Ok(()) +} +``` + +## 12) Defer (scope-based cleanup) -`package safe` is default. Raw pointers and extern calls require `package unsafe`. +`defer` schedules a call to run when the current scope exits (LIFO order): ```cap -package unsafe -module ffi +let c = rc.mint_console() +let alloc = rc.mint_alloc_default() +let v = alloc.vec_u8_new() -extern fn some_ffi(x: i32) -> i32 +// ensure we free on all paths + defer alloc.vec_u8_free(v) ``` -## 13) Raw pointers and unsafe +Deferred expressions must be calls; arguments are evaluated at the defer site. + +## 13) Safe vs unsafe -Raw pointers are available as `*T`, but **only** in `package unsafe`. +`package safe` is default. Raw pointers and extern calls require +`package unsafe`. ```cap package unsafe @@ -310,21 +284,53 @@ module pointers pub fn main(rc: RootCap) -> i32 { let alloc = rc.mint_alloc_default() - let ptr: *u8 = alloc.malloc(16) - alloc.free(ptr) + let p = alloc.malloc(16) + alloc.free(p) return 0 } ``` -There is no borrow checker for pointers. Use them only inside `package unsafe`. +Unsafe is auditable: `--safe-only` rejects unsafe dependencies, and `audit` +reports unsafe packages. + +## 14) Putting it together: a small parser -## 14) What exists today (quick list) +```cap +fn parse_key_value(line: string, alloc: Alloc) -> Result { + match (line.index_of_byte('=')) { + Ok(i) => { + let key = match (line.slice_range(0, i)) { + Ok(v) => { v } + Err(_) => { return Err(()) } + } + let val = match (line.slice_range(i + 1, line.len())) { + Ok(v) => { v } + Err(_) => { return Err(()) } + } + let mid = match (key.concat(alloc, "=")) { + Ok(v) => { v } + Err(_) => { return Err(()) } + } + match (mid.concat(alloc, val)) { + Ok(out) => { return Ok(out) } + Err(_) => { return Err(()) } + } + } + Err(_) => { return Err(()) } + } +} +``` -- Methods, modules, enums, match, while, if -- Opaque capability handles in `sys.*` -- Linear/affine checking with control-flow joins -- Borrow-lite `&T` parameters +This pattern (find delimiter, slice views, optionally allocate) is the common +way to parse without raw pointers. --- -That should be enough to read and write small Capable programs, and understand how attenuation and linearity fit together. +## Where to go next + +- Read `docs/memory.md` and `docs/ABI.md` for deeper internals. +- Explore `examples/` for real programs. +- Use `defer` aggressively to keep ownership clear. + +Capable is intentionally small. The goal is to make capability-based security +and explicit memory ownership the default, not an advanced feature. diff --git a/docs/caps.md b/docs/caps.md index dd3d7db..50872af 100644 --- a/docs/caps.md +++ b/docs/caps.md @@ -81,7 +81,7 @@ impl Dir { } ``` -Not every opaque handle is a capability. Use `capability` for authority-bearing tokens (filesystem, console, stdin); use `opaque` for unforgeable data handles (buffers, slices, vectors). +Not every opaque handle is a capability. Use `capability` for authority-bearing tokens (filesystem, console, stdin); use `opaque` for unforgeable data handles (allocators, vectors). Slices are plain structs in the stdlib, but pointer types remain unsafe for user code. ### 3) Root authority comes from `RootCap` The entrypoint receives a root capability: diff --git a/docs/code-browser/README.md b/docs/code-browser/README.md new file mode 100644 index 0000000..277f70b --- /dev/null +++ b/docs/code-browser/README.md @@ -0,0 +1,14 @@ +# Code Browser + +This folder is generated by `tools/gen_code_browser.py` and contains a +docstring-first browser for stdlib `.cap` files. + +Usage: + +```bash +python3 tools/gen_code_browser.py +``` + +Output: + +- `docs/code-browser/index.html` diff --git a/docs/extern_demo.md b/docs/extern_demo.md index db50f47..8461441 100644 --- a/docs/extern_demo.md +++ b/docs/extern_demo.md @@ -32,5 +32,5 @@ capable still works ## Notes - The extern function is declared as `extern fn demo_log(msg: string) -> unit`. -- `demo_log` expects a raw pointer + length; the compiler lowers `string` as `(ptr, len)`. +- `demo_log` expects a raw pointer + length; the compiler lowers `string` as a struct with a `bytes` slice `{ ptr, len }`. - Only `package unsafe` modules may declare `extern`. diff --git a/docs/memory.md b/docs/memory.md new file mode 100644 index 0000000..54da59f --- /dev/null +++ b/docs/memory.md @@ -0,0 +1,70 @@ +# Memory Model + +This document describes how Capable implements memory safety without a full +borrow checker, and how that shapes the stdlib and ABI. + +## Goals +- Safe code is memory-safe, so capability security remains meaningful. +- Unsafe operations are explicit and auditable. +- Allocation is explicit (Zig-like) and testable. +- The core model is simple enough to keep the language small and predictable. + +## Safe vs unsafe +Safe code cannot: +- dereference or do arithmetic on raw pointers +- call FFI or inline assembly +- access unchecked memcpy/memmove helpers + +Unsafe code (`package unsafe`) may do all of the above. Tooling can audit and +reject unsafe dependencies (`--safe-only`, `audit`). + +## Allocators are explicit +- APIs that allocate take an explicit `Alloc` handle (or are methods that already + carry one). +- The runtime currently backs `Alloc` with libc malloc/free, but the ABI keeps + allocator passing explicit for future custom allocators. + +## Owned vs borrowed data +Capable separates owned buffers from borrowed views. + +### Owned +- `Vec` is an owned, growable buffer. +- `Text` is an owned UTF-8 buffer backed by `Vec`. +- Owned types are move-only to reduce double-free patterns. + +### Borrowed +- `Slice` and `MutSlice` are non-owning views. +- Safe indexing and slicing are bounds-checked. + +Because Capable does not have a full lifetime system, safe code is restricted +from letting slices escape: +- Safe non-stdlib modules may not return `Slice` / `MutSlice`. +- Safe non-stdlib modules may not store slices inside structs or enums. + +This keeps slice lifetimes local until we introduce a richer lifetime model. + +## Unsafe pointer surface +The stdlib exposes low-level pointer helpers in `sys::unsafe_ptr` for unsafe +code. The surface is intentionally small and explicit: +- `ptr_is_null` only checks nullness; it does not imply ownership or validity. +- `memcpy` and `memmove` are available for unsafe code and lowered in codegen. +- `sizeof`/`alignof` are compile-time layout queries exposed as intrinsics so the + compiler, not user code, defines layout and ABI details. + +## ABI and lowering notes +- Opaque structs lower to handles; field access is special-cased in codegen. +- `sret` and `ResultOut` conventions are documented. +- Slice/string layout and alloc-passing conventions are stable and documented. + +## Design trade-offs +- We avoid a borrow checker to keep the language small and predictable. +- We still preserve memory safety in safe code by restricting raw pointers and + slice escapes. +- The stdlib is trusted to use unsafe internals where needed; user code remains + safe by default. + +## Roadmap +- Introduce a lightweight lifetime model or scoped borrows to relax slice + escape restrictions without losing safety. +- Expand allocator ergonomics without losing explicit allocation. +- Keep the unsafe surface small and auditable. diff --git a/docs/memory_safety.md b/docs/memory_safety.md index 6b23520..c624a5f 100644 --- a/docs/memory_safety.md +++ b/docs/memory_safety.md @@ -63,6 +63,8 @@ We want explicit, Zig-like control without GC, but without letting safe libs cor ### 3.1 Bounds-checked slices/arrays in safe code - Safe indexing is bounds-checked (at least in debug / safe mode). - No ā€œescape hatchā€ from slice to raw pointer in safe code. +- `Slice[T]` is implemented as `{ ptr, len }` in the stdlib, but pointer types remain unsafe for user code. +- Safe non-stdlib modules may not return or store `Slice` / `MutSlice` values. ### 3.2 Explicit allocators (optional dependency, not a security boundary) Capable may expose explicit allocation via an `Alloc` handle: diff --git a/docs/slice_design.md b/docs/slice_design.md index ea7f6a0..b9da079 100644 --- a/docs/slice_design.md +++ b/docs/slice_design.md @@ -1,6 +1,6 @@ -# Safe Raw Buffers Without Raw Pointers +# Safe Bytes Without Raw Pointers -This document explains how Capable safe code can work with ā€œraw buffersā€ (bytes, packets, file contents) **without** exposing raw pointers or requiring a borrow checker. +This document explains how Capable safe code can work with raw bytes (packets, file contents) **without** exposing raw pointers or requiring a borrow checker. Goal: keep **capability security** meaningful by ensuring **safe code is memory-safe**, while still supporting systems-style byte manipulation. @@ -11,7 +11,7 @@ Goal: keep **capability security** meaningful by ensuring **safe code is memory- Safe code never performs pointer arithmetic or raw pointer dereference. Instead, safe code manipulates raw data using: -- **Owning buffer types** (heap-backed, move-only) +- **Owning vectors** (heap-backed, move-only) - **Slice views** (read-only and mutable, bounds-checked) - `sys.*` APIs that accept slices and perform OS/FFI pointer work internally @@ -21,16 +21,20 @@ Unsafe code exists for FFI/asm/special cases, but is quarantined (`package unsaf ## 2. Types -### 2.1 `Buffer` (owned, heap-backed) +### 2.1 `Vec` (owned, heap-backed) An owning contiguous byte buffer. Conceptually similar to Rust `Vec` or Go `[]byte` when used as an owned value. Properties: - owns its allocation - **move-only** (non-copyable) to prevent double-free patterns -- provides safe operations (push/resize/etc.) +- provides safe operations (push/extend/etc.) - can be converted to slices for reading/writing -### 2.2 `Slice[T]` and `MutSlice[T]` (non-owning views) +### 2.2 `Text` (owned UTF-8, heap-backed) +An owning, growable UTF-8 string builder backed by `Vec`. +Conceptually similar to Rust `String` and Go `strings.Builder`. + +### 2.3 `Slice[T]` and `MutSlice[T]` (non-owning views) Non-owning views into contiguous memory. Properties: @@ -41,7 +45,7 @@ Properties: - safe code cannot access the underlying pointer or do arithmetic Implementation detail: -- internally may be `{ ptr, len }`, but pointer is not exposed to safe code. +- implemented as `{ ptr, len }` in the stdlib, but pointer types remain unsafe for user code. --- @@ -52,26 +56,30 @@ Allocation is explicit (Zig-like). Functions that allocate accept an allocator v ```cap opaque struct Alloc -opaque struct Buffer // move-only owner +opaque struct Vec[T] // move-only owner -fn buffer_new(a: Alloc, initial_len: usize) -> Result[Buffer, AllocErr] -fn buffer_len(b: &Buffer) -> usize -fn buffer_push(b: &mut Buffer, x: u8) -> Result[unit, AllocErr] -fn buffer_free(a: Alloc, b: Buffer) -> unit // consumes b +impl Alloc { + fn vec_u8_new(self) -> Vec + fn vec_u8_with_capacity(self, cap: i32) -> Result, AllocErr> + fn vec_u8_free(self, v: Vec) -> unit +} ```` ### 3.2 Views ```cap -opaque struct Slice[T] -opaque struct MutSlice[T] +struct Slice[T] { ptr: *T, len: i32 } +struct MutSlice[T] { ptr: *T, len: i32 } -fn buffer_as_slice(b: &Buffer) -> Slice[u8] -fn buffer_as_mut_slice(b: &mut Buffer) -> MutSlice[u8] +impl Vec { + fn as_slice(self) -> Slice +} -fn slice_len[T](s: Slice[T]) -> usize -fn slice_at[T](s: Slice[T], i: usize) -> &T // bounds-checked -fn slice_sub[T](s: Slice[T], start: usize, n: usize) -> Slice[T] // bounds-checked +impl Slice { + fn len(self) -> i32 + fn at(self, i: i32) -> u8 // bounds-checked + fn slice(self, start: i32, len: i32) -> Result, SliceErr> +} ``` Note: For `T` that are Copy, `slice_get` may return `Option[T]`. @@ -85,25 +93,25 @@ Note: For `T` that are Copy, `slice_get` may return `Option[T]`. Safe indexing gives ā€œsystems parsingā€ without unsafe pointer math: ```cap -fn parse_u16_be(buf: Slice[u8], off: usize) -> Result[u16, Err] { +fn parse_u16_be(buf: Slice, off: i32) -> Result { let b0 = buf.at(off) // bounds checked let b1 = buf.at(off + 1) Ok(((b0 as u16) << 8) | (b1 as u16)) } ``` -### 4.2 Reading a file into a buffer +### 4.2 Reading a file into bytes -`sys.fs` provides methods that return a `Buffer` (owned) and/or `string`: +`sys.fs` provides methods that return owned bytes (`Vec`) and/or `string`: ```cap -fn ReadFS.read_all(self, path: string) -> Result[Buffer, FsErr] +fn ReadFS.read_bytes(self, alloc: Alloc, path: string) -> Result, FsErr> ``` Usage: -* safe code receives `Buffer` -* parses it via `Slice[u8]` +* safe code receives `Vec` +* parses it via `Slice` * frees it (explicitly or with `defer`) --- @@ -114,14 +122,14 @@ OS APIs typically want `(ptr, len)`. In Capable: -* safe code supplies `Slice[u8]` / `MutSlice[u8]` +* safe code supplies `Slice` / `MutSlice` * `sys.*` converts internally and calls the OS Example shapes: ```cap -Socket.send(data: Slice[u8]) -> Result[usize, Err] -File.write(data: Slice[u8]) -> Result[usize, Err] +Socket.send(data: Slice) -> Result +File.write(data: Slice) -> Result ``` Safe code never manipulates the raw pointers. @@ -134,16 +142,21 @@ Capable does **not** implement a full borrow checker. Instead we keep safe opera * safe code cannot do pointer arithmetic or unchecked deref * slice indexing/subslicing is bounds-checked -* freeing consumes the owner (`Buffer` is move-only), reducing double-free risk +* freeing consumes the owner (`Vec` is move-only), reducing double-free risk -Remaining hazard without lifetimes: use-after-free by keeping a slice after freeing its `Buffer`. +Remaining hazard without lifetimes: use-after-free by keeping a slice after freeing its `Vec`. Mitigations (recommended): -1. **Debug validation:** `Buffer` carries a generation cookie; slices carry `(id,cookie)`; deref checks cookie in debug builds. -2. **Simple restriction (v0.2):** slices derived from a `Buffer` may not escape the current function scope (easy rule). +1. **Debug validation:** `Vec` carries a generation cookie; slices carry `(id,cookie)`; deref checks cookie in debug builds. +2. **Simple restriction (v0.2):** slices derived from a `Vec` may not escape the current function scope (easy rule). 3. Prefer **arenas** for complex lifetimes: allocate many objects, free once. +Current rule (implemented): +- Safe non-stdlib modules may not return `Slice` / `MutSlice` values or + embed them in structs/enums. This keeps view lifetimes local until a richer + lifetime system exists. + --- ## 7. Security rationale @@ -154,7 +167,7 @@ If safe code could corrupt memory, it could forge or tamper with capability toke Therefore: * raw pointers and FFI are **unsafe-only** -* safe buffer work is done via `Buffer` + `Slice` APIs +* safe byte work is done via `Vec` + `Slice` APIs * `sys.*` performs pointer work internally This preserves the guarantee: diff --git a/examples/config_loader/config_loader.cap b/examples/config_loader/config_loader.cap index 2af5662..6566938 100644 --- a/examples/config_loader/config_loader.cap +++ b/examples/config_loader/config_loader.cap @@ -7,14 +7,34 @@ use sys::string use sys::vec use sys::buffer -fn print_kv(c: Console, key: string, val: string) -> unit { - c.print("key: ") - c.println(key) - c.print("value: ") - c.println(val) +fn print_kv(c: Console, alloc: Alloc, key: string, val: string) -> unit { + let out = alloc.text_new() + match (out.push_str("key: ")) { + Ok(_) => { } + Err(_) => { out.free(alloc); return () } + } + match (out.push_str(key)) { + Ok(_) => { } + Err(_) => { out.free(alloc); return () } + } + match (out.push_str("\nvalue: ")) { + Ok(_) => { } + Err(_) => { out.free(alloc); return () } + } + match (out.push_str(val)) { + Ok(_) => { } + Err(_) => { out.free(alloc); return () } + } + match (out.push_byte('\n')) { + Ok(_) => { } + Err(_) => { out.free(alloc); return () } + } + c.print(out.as_string()) + out.free(alloc) + return () } -fn parse_line(c: Console, alloc: Alloc, line: string) -> Result { +fn parse_line(c: Console, alloc: Alloc, line: string) -> Result { if (line.len() == 0) { return Ok(()) } @@ -22,18 +42,27 @@ fn parse_line(c: Console, alloc: Alloc, line: string) -> Result { return Ok(()) } - let parts = line.split(61u8) - if (parts.len() == 2) { - let key = parts[0]? - let val = parts[1]? - print_kv(c, key, val) + match (line.index_of_byte('=')) { + Ok(eq) => { + let key_result = line.slice_range(0, eq) + match (key_result) { + Ok(key) => { + let val_result = line.slice_range(eq + 1, line.len()) + match (val_result) { + Ok(val) => { print_kv(c, alloc, key, val) } + Err(_) => { return Err(vec::VecErr::OutOfRange) } + } + } + Err(_) => { return Err(vec::VecErr::OutOfRange) } + } + } + Err(_) => { } } - alloc.vec_string_free(parts) return Ok(()) } -fn parse_config(c: Console, alloc: Alloc, contents: string) -> Result { - let lines = contents.lines() +fn parse_config(c: Console, alloc: Alloc, contents: string) -> Result { + let lines = contents.lines(alloc) let n = lines.len() for i in 0..n { let line = lines[i]? @@ -44,7 +73,7 @@ fn parse_config(c: Console, alloc: Alloc, contents: string) -> Result Result { - let contents = fs.read_to_string("app.conf")? + let contents = fs.read_to_string(alloc, "app.conf")? let result = parse_config(c, alloc, contents) if (result.is_err()) { return Err(fs::FsErr::IoError) diff --git a/examples/how_to_string/how_to_string.cap b/examples/how_to_string/how_to_string.cap new file mode 100644 index 0000000..144a7e5 --- /dev/null +++ b/examples/how_to_string/how_to_string.cap @@ -0,0 +1,87 @@ +package safe +module how_to_string +use sys::system +use sys::console +use sys::string +use sys::buffer + +fn demo_string_view(c: Console, alloc: Alloc) -> unit { + let s = "hello,world" + c.println("-- string view --") + c.println(s) + c.println("len:") + c.println_i32(s.len()) + + match (s.index_of_byte(',')) { + Ok(i) => { + match (s.slice_range(0, i)) { + Ok(left) => { c.println(left) } + Err(_) => { c.println("slice failed") } + } + } + Err(_) => { c.println("comma not found") } + } + + let words = s.split(alloc, ',') + c.println("split count:") + c.println_i32(words.len()) + match (words[0]) { + Ok(w0) => { c.println(w0) } + Err(_) => { c.println("word0 missing") } + } + match (words[1]) { + Ok(w1) => { c.println(w1) } + Err(_) => { c.println("word1 missing") } + } + alloc.vec_string_free(words) +} + +fn demo_text_builder(c: Console, alloc: Alloc) -> unit { + c.println("-- Text builder --") + let t = alloc.text_new() + match (t.push_str("hello")) { + Ok(_) => { } + Err(_) => { c.println("push_str failed"); return () } + } + match (t.push_byte(' ')) { + Ok(_) => { } + Err(_) => { c.println("push_byte failed"); return () } + } + match (t.append("text")) { + Ok(_) => { } + Err(_) => { c.println("append failed"); return () } + } + match (t.slice_range(0, 5)) { + Ok(view) => { c.println(view) } + Err(_) => { c.println("slice_range failed") } + } + match (t.to_string()) { + Ok(out) => { c.println(out) } + Err(_) => { c.println("to_string failed") } + } + t.free(alloc) + + match (alloc.text_from("owned")) { + Ok(t2) => { + match (t2.to_string()) { + Ok(out) => { c.println(out) } + Err(_) => { c.println("to_string failed") } + } + t2.free(alloc) + } + Err(_) => { c.println("text_from failed") } + } + + match ("a".concat(alloc, "b")) { + Ok(out) => { c.println(out) } + Err(_) => { c.println("concat failed") } + } +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + demo_string_view(c, alloc) + demo_text_builder(c, alloc) + return 0 +} diff --git a/examples/http_server/http_server.cap b/examples/http_server/http_server.cap index 2b7ea7c..0891e32 100644 --- a/examples/http_server/http_server.cap +++ b/examples/http_server/http_server.cap @@ -15,39 +15,49 @@ fn arg_or_default(args: Args, index: i32, default: string) -> string { return args.at(index).unwrap_or(default) } -fn strip_query(raw_path: string) -> string { - return raw_path.split(63u8)[0].unwrap_or("") +fn strip_query(raw_path: string, alloc: Alloc) -> string { + match (raw_path.index_of_byte('?')) { + Ok(i) => { + match (raw_path.slice_range(0, i)) { + Ok(view) => { return view } + Err(_) => { return raw_path } + } + } + Err(_) => { return raw_path } + } } -fn sanitize_segment(parts: Vec, i: i32, acc: string, seg: string) -> Result { +fn sanitize_segment(parts: Vec, i: i32, acc: string, seg: string, alloc: Alloc) -> Result { if (seg.len() == 0) { - return sanitize_parts(parts, i + 1, acc) + return sanitize_parts(parts, i + 1, acc, alloc) } if (seg == ".") { - return sanitize_parts(parts, i + 1, acc) + return sanitize_parts(parts, i + 1, acc, alloc) } if (seg == "..") { return Err(()) } if (acc.len() == 0) { - return sanitize_parts(parts, i + 1, seg) + return sanitize_parts(parts, i + 1, seg, alloc) } - return sanitize_parts(parts, i + 1, fs::join(acc, seg)) + return sanitize_parts(parts, i + 1, fs::join(alloc, acc, seg), alloc) } -fn sanitize_parts(parts: Vec, i: i32, acc: string) -> Result { +fn sanitize_parts(parts: Vec, i: i32, acc: string, alloc: Alloc) -> Result { if (i >= parts.len()) { return Ok(acc) } let seg_result = parts[i] match (seg_result) { - Ok(seg) => { return sanitize_segment(parts, i, acc, seg) } + Ok(seg) => { return sanitize_segment(parts, i, acc, seg, alloc) } Err(_) => { return Err(()) } } } -fn sanitize_path(raw_path: string) -> Result { - let result = sanitize_parts(raw_path.split(47u8), 0, "") +fn sanitize_path(raw_path: string, alloc: Alloc) -> Result { + let parts = raw_path.split(alloc, '/') + let result = sanitize_parts(parts, 0, "", alloc) + alloc.vec_string_free(parts) match (result) { Ok(path) => { if (path.len() == 0) { @@ -59,15 +69,30 @@ fn sanitize_path(raw_path: string) -> Result { } } -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)) } +fn parse_request_line(line: string, alloc: Alloc) -> Result { + let trimmed = line.trim(alloc) + match (trimmed.index_of_byte(' ')) { + Ok(space0) => { + match (trimmed.slice_range(0, space0)) { + Ok(method) => { + if (method != "GET") { + return Err(()) + } + match (trimmed.slice_range(space0 + 1, trimmed.len())) { + Ok(rest) => { + match (rest.index_of_byte(' ')) { + Ok(space1) => { + match (rest.slice_range(0, space1)) { + Ok(path) => { return sanitize_path(strip_query(path, alloc), alloc) } + Err(_) => { return Err(()) } + } + } + Err(_) => { return Err(()) } + } + } + Err(_) => { return Err(()) } + } + } Err(_) => { return Err(()) } } } @@ -75,10 +100,12 @@ fn parse_request_line(line: string) -> Result { } } -fn parse_request_path(req: string) -> Result { - let line_result = req.lines()[0] +fn parse_request_path(req: string, alloc: Alloc) -> Result { + let lines = req.lines(alloc) + let line_result = lines[0] + alloc.vec_string_free(lines) match (line_result) { - Ok(line) => { return parse_request_line(line) } + Ok(line) => { return parse_request_line(line, alloc) } Err(_) => { return Err(()) } } } @@ -97,10 +124,10 @@ 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, req: string) -> Result { - match (parse_request_path(req)) { +fn handle_request(conn: &TcpConn, readfs: ReadFS, alloc: Alloc, req: string) -> Result { + match (parse_request_path(req, alloc)) { Ok(path) => { - match (readfs.read_to_string(path)) { + match (readfs.read_to_string(alloc, path)) { Ok(body) => { return respond_ok(conn, body) } Err(_) => { return respond_not_found(conn) } } @@ -109,15 +136,15 @@ fn handle_request(conn: &TcpConn, readfs: ReadFS, req: string) -> Result Result { +fn serve_forever(c: Console, net: Net, rc: RootCap, alloc: Alloc, root: string) -> Result { let listener = net.listen("127.0.0.1", 8080)? defer listener.close() while (true) { let conn = listener.accept()? defer conn.close() - let req = conn.read_to_string()? + let req = conn.read_to_string(alloc)? let readfs = rc.mint_readfs(root) - handle_request(conn, readfs, req)? + handle_request(conn, readfs, alloc, req)? defer c.println("request complete") } return Ok(()) @@ -127,9 +154,10 @@ pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let net = rc.mint_net() let args = rc.mint_args() + let alloc = rc.mint_alloc_default() let root = arg_or_default(args, 1, ".") c.println("listening on 127.0.0.1:8080") - let result = serve_forever(c, net, rc, root) + let result = serve_forever(c, net, rc, alloc, root) if (result.is_err()) { c.println("server error") } diff --git a/examples/sort/sort.cap b/examples/sort/sort.cap index 679c504..c155ae4 100644 --- a/examples/sort/sort.cap +++ b/examples/sort/sort.cap @@ -16,11 +16,13 @@ fn min_i32(a: i32, b: i32) -> i32 { // Returns true if a < b lexicographically fn str_lt(a: string, b: string) -> bool { - let len = min_i32(a.len(), b.len()) + let a_bytes = a.as_slice() + let b_bytes = b.as_slice() + let len = min_i32(a_bytes.len(), b_bytes.len()) let i = 0 while (i < len) { - let ca = a.byte_at(i) - let cb = b.byte_at(i) + let ca = a_bytes.at(i) + let cb = b_bytes.at(i) if (ca < cb) { return true } @@ -66,8 +68,8 @@ fn sort_indices(lines: Vec, indices: Vec) -> unit { } fn run(c: Console, alloc: Alloc, input: Stdin) -> Result { - let contents = input.read_to_string()? - let lines = contents.lines() + let contents = input.read_to_string(alloc)? + let lines = contents.lines(alloc) let n = lines.len() // Create index array [0, 1, 2, ...] diff --git a/examples/uniq/uniq.cap b/examples/uniq/uniq.cap index 0ff818f..4f8fcd5 100644 --- a/examples/uniq/uniq.cap +++ b/examples/uniq/uniq.cap @@ -17,8 +17,8 @@ fn should_print(lines: Vec, i: i32) -> bool { } fn run(c: Console, alloc: Alloc, input: Stdin) -> Result { - let contents = input.read_to_string()? - let lines = contents.lines() + let contents = input.read_to_string(alloc)? + let lines = contents.lines(alloc) let n = lines.len() for i in 0..n { if (should_print(lines, i)) { diff --git a/justfile b/justfile index 7813be0..e23626e 100644 --- a/justfile +++ b/justfile @@ -56,3 +56,8 @@ lsp: website: python3 website/gen_website.py + open website/index.html + +code-browser: + python3 tools/gen_code_browser.py + open docs/code-browser/index.html diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f9c024e..1138d66 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -28,20 +28,12 @@ static STDIN_CAPS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static NET_CAPS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +static ALLOCS: LazyLock>> = + LazyLock::new(|| Mutex::new(HashMap::new())); static TCP_LISTENERS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); static TCP_CONNS: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); -static SLICES: LazyLock>> = - LazyLock::new(|| Mutex::new(HashMap::new())); -static BUFFERS: LazyLock>>> = - LazyLock::new(|| Mutex::new(HashMap::new())); -static VECS_U8: LazyLock>>> = - LazyLock::new(|| Mutex::new(HashMap::new())); -static VECS_I32: LazyLock>>> = - LazyLock::new(|| Mutex::new(HashMap::new())); -static VECS_STRING: LazyLock>>> = - LazyLock::new(|| Mutex::new(HashMap::new())); static ARGS: LazyLock> = LazyLock::new(|| std::env::args().collect()); #[derive(Debug, Clone)] @@ -67,22 +59,27 @@ struct FileReadState { } #[repr(C)] -pub struct CapString { - bytes: Handle, +#[derive(Copy, Clone)] +pub struct CapSlice { + ptr: *mut u8, + len: i32, } -#[derive(Copy, Clone, Debug)] -struct SliceState { - ptr: usize, - len: usize, +#[repr(C)] +pub struct CapString { + bytes: CapSlice, } -#[derive(Copy, Clone, Debug)] -enum VecErr { - OutOfRange = 0, - Empty = 1, +#[repr(C)] +struct VecHeader { + raw: *mut u8, + len: i32, + cap: i32, + elem_size: i32, + alloc: Handle, } + fn new_handle() -> Handle { let mut buf = [0u8; 8]; loop { @@ -132,21 +129,111 @@ fn with_table( f(&mut table) } -fn to_slice_handle(value: String) -> Handle { - let bytes = value.into_bytes().into_boxed_slice(); - 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 alloc_malloc(alloc: Handle, size: usize) -> Option<*mut u8> { + if !has_handle(&ALLOCS, alloc, "alloc table") { + return None; + } + let ptr = unsafe { libc::malloc(size) as *mut u8 }; + if ptr.is_null() && size != 0 { + return None; + } + Some(ptr) +} + +fn alloc_free(alloc: Handle, ptr: *mut u8) { + if !has_handle(&ALLOCS, alloc, "alloc table") { + return; + } + if ptr.is_null() { + return; + } + unsafe { libc::free(ptr as *mut libc::c_void) }; +} + +fn to_cap_slice_bytes_with_alloc(alloc: Handle, bytes: Vec) -> Option { + if !has_handle(&ALLOCS, alloc, "alloc table") { + return None; + } + let len = bytes.len().min(i32::MAX as usize) as i32; + let ptr = if len == 0 { + std::ptr::null_mut() + } else { + let ptr = alloc_malloc(alloc, len as usize)?; + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, len as usize); + } + ptr + }; + Some(CapSlice { ptr, len }) +} + +fn to_cap_slice_with_alloc(alloc: Handle, value: String) -> Option { + to_cap_slice_bytes_with_alloc(alloc, value.into_bytes()) +} + +fn make_vec_header( + raw: *mut u8, + len: i32, + cap: i32, + elem_size: i32, + alloc: Handle, +) -> Option { + let header = + alloc_malloc(alloc, std::mem::size_of::())? as *mut VecHeader; + if header.is_null() { + return None; + } + unsafe { + *header = VecHeader { + raw, + len, + cap, + elem_size, + alloc, + }; + } + Some(header as Handle) +} + +fn vec_from_bytes(alloc: Handle, bytes: Vec) -> Option { + let len = bytes.len().min(i32::MAX as usize) as i32; + let raw = if len == 0 { + std::ptr::null_mut() + } else { + let ptr = alloc_malloc(alloc, len as usize)?; + if ptr.is_null() { + return None; + } + unsafe { + std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr, len as usize); + } + ptr + }; + make_vec_header(raw, len, len, 1, alloc) +} + +fn vec_from_strings(alloc: Handle, items: Vec) -> Option { + let len = items.len().min(i32::MAX as usize) as i32; + let elem_size = std::mem::size_of::() as i32; + let raw = if len == 0 { + std::ptr::null_mut() + } else { + let total = (len as usize).saturating_mul(elem_size as usize); + let ptr = alloc_malloc(alloc, total)? as *mut CapString; + if ptr.is_null() { + return None; + } + for (i, item) in items.into_iter().take(len as usize).enumerate() { + let Some(slice) = to_cap_slice_with_alloc(alloc, item) else { + return None; + }; + unsafe { + std::ptr::write(ptr.add(i), CapString { bytes: slice }); + } + } + ptr as *mut u8 + }; + make_vec_header(raw, len, len, elem_size, alloc) } fn write_handle_result( @@ -414,6 +501,7 @@ pub extern "C" fn capable_rt_fs_exists( #[no_mangle] pub extern "C" fn capable_rt_fs_read_bytes( fs: Handle, + _alloc: Handle, path: *const CapString, out_ok: *mut Handle, out_err: *mut i32, @@ -431,13 +519,10 @@ pub extern "C" fn capable_rt_fs_read_bytes( Err(err) => return write_handle_result(out_ok, out_err, Err(err)), }; match std::fs::read(&full) { - Ok(bytes) => { - let handle = new_handle(); - with_table(&VECS_U8, "vec u8 table", |table| { - table.insert(handle, bytes); - }); - write_handle_result(out_ok, out_err, Ok(handle)) - } + Ok(bytes) => match vec_from_bytes(_alloc, bytes) { + Some(handle) => write_handle_result(out_ok, out_err, Ok(handle)), + None => write_handle_result(out_ok, out_err, Err(FsErr::IoError)), + }, Err(err) => write_handle_result(out_ok, out_err, Err(map_fs_err(err))), } } @@ -445,6 +530,7 @@ pub extern "C" fn capable_rt_fs_read_bytes( #[no_mangle] pub extern "C" fn capable_rt_fs_list_dir( fs: Handle, + _alloc: Handle, path: *const CapString, out_ok: *mut Handle, out_err: *mut i32, @@ -473,11 +559,10 @@ pub extern "C" fn capable_rt_fs_list_dir( }; names.push(entry.file_name().to_string_lossy().to_string()); } - let handle = new_handle(); - with_table(&VECS_STRING, "vec string table", |table| { - table.insert(handle, names); - }); - write_handle_result(out_ok, out_err, Ok(handle)) + match vec_from_strings(_alloc, names) { + Some(handle) => write_handle_result(out_ok, out_err, Ok(handle)), + None => write_handle_result(out_ok, out_err, Err(FsErr::IoError)), + } } #[no_mangle] @@ -503,6 +588,7 @@ pub extern "C" fn capable_rt_fs_dir_exists( #[no_mangle] pub extern "C" fn capable_rt_fs_dir_read_bytes( dir: Handle, + _alloc: Handle, name: *const CapString, out_ok: *mut Handle, out_err: *mut i32, @@ -521,13 +607,10 @@ pub extern "C" fn capable_rt_fs_dir_read_bytes( Err(err) => return write_handle_result(out_ok, out_err, Err(err)), }; match std::fs::read(&full) { - Ok(bytes) => { - let handle = new_handle(); - with_table(&VECS_U8, "vec u8 table", |table| { - table.insert(handle, bytes); - }); - write_handle_result(out_ok, out_err, Ok(handle)) - } + Ok(bytes) => match vec_from_bytes(_alloc, bytes) { + Some(handle) => write_handle_result(out_ok, out_err, Ok(handle)), + None => write_handle_result(out_ok, out_err, Err(FsErr::IoError)), + }, Err(err) => write_handle_result(out_ok, out_err, Err(map_fs_err(err))), } } @@ -535,6 +618,7 @@ pub extern "C" fn capable_rt_fs_dir_read_bytes( #[no_mangle] pub extern "C" fn capable_rt_fs_dir_list_dir( dir: Handle, + _alloc: Handle, out_ok: *mut Handle, out_err: *mut i32, ) -> u8 { @@ -558,32 +642,45 @@ pub extern "C" fn capable_rt_fs_dir_list_dir( }; names.push(entry.file_name().to_string_lossy().to_string()); } - let handle = new_handle(); - with_table(&VECS_STRING, "vec string table", |table| { - table.insert(handle, names); - }); - write_handle_result(out_ok, out_err, Ok(handle)) + match vec_from_strings(_alloc, names) { + Some(handle) => write_handle_result(out_ok, out_err, Ok(handle)), + None => write_handle_result(out_ok, out_err, Err(FsErr::IoError)), + } } #[no_mangle] pub extern "C" fn capable_rt_fs_join( out: *mut CapString, + alloc: Handle, 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; + (*out).bytes = CapSlice { + ptr: std::ptr::null_mut(), + len: 0, + }; } } return; }; let joined = Path::new(&a).join(&b); - let handle = to_slice_handle(joined.to_string_lossy().to_string()); + let Some(slice) = to_cap_slice_with_alloc(alloc, joined.to_string_lossy().to_string()) else { + unsafe { + if !out.is_null() { + (*out).bytes = CapSlice { + ptr: std::ptr::null_mut(), + len: 0, + }; + } + } + return; + }; unsafe { if !out.is_null() { - (*out).bytes = handle; + (*out).bytes = slice; } } } @@ -604,11 +701,23 @@ pub extern "C" fn capable_rt_console_print(_console: Handle, s: *const CapString if !has_handle(&CONSOLES, _console, "console table") { return; } - let handle = unsafe { if s.is_null() { 0 } else { (*s).bytes } }; - let Some(state) = slice_state(handle) else { - return; + let slice = unsafe { + if s.is_null() { + CapSlice { + ptr: std::ptr::null_mut(), + len: 0, + } + } else { + (*s).bytes + } }; - unsafe { write_bytes(state.ptr as *const u8, state.len, false) }; + if slice.len <= 0 { + return; + } + if slice.ptr.is_null() { + return; + } + unsafe { write_bytes(slice.ptr as *const u8, slice.len as usize, false) }; } #[no_mangle] @@ -616,11 +725,24 @@ pub extern "C" fn capable_rt_console_println(_console: Handle, s: *const CapStri if !has_handle(&CONSOLES, _console, "console table") { return; } - let handle = unsafe { if s.is_null() { 0 } else { (*s).bytes } }; - let Some(state) = slice_state(handle) else { - return; + let slice = unsafe { + if s.is_null() { + CapSlice { + ptr: std::ptr::null_mut(), + len: 0, + } + } else { + (*s).bytes + } }; - unsafe { write_bytes(state.ptr as *const u8, state.len, true) }; + if slice.len == 0 { + unsafe { write_bytes(std::ptr::null(), 0, true) }; + return; + } + if slice.ptr.is_null() { + return; + } + unsafe { write_bytes(slice.ptr as *const u8, slice.len as usize, true) }; } #[no_mangle] @@ -691,6 +813,7 @@ 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, + _alloc: Handle, path: *const CapString, out_ok: *mut CapString, out_err: *mut i32, @@ -699,27 +822,27 @@ pub extern "C" fn capable_rt_fs_read_to_string( let state = take_handle(&READ_FS, fs, "readfs table"); let Some(state) = state else { - return write_result(out_ok, out_err, Err(FsErr::PermissionDenied)); + return write_result_with_alloc(_alloc, out_ok, out_err, Err(FsErr::PermissionDenied)); }; let Some(path) = path else { - return write_result(out_ok, out_err, Err(FsErr::InvalidPath)); + return write_result_with_alloc(_alloc, out_ok, out_err, Err(FsErr::InvalidPath)); }; let relative = match normalize_relative(Path::new(&path)) { Some(path) => path, - None => return write_result(out_ok, out_err, Err(FsErr::InvalidPath)), + None => return write_result_with_alloc(_alloc, 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_ok, out_err, Err(map_fs_err(err))), + Err(err) => return write_result_with_alloc(_alloc, out_ok, out_err, Err(map_fs_err(err))), }; if !full.starts_with(&state.root) { - return write_result(out_ok, out_err, Err(FsErr::InvalidPath)); + return write_result_with_alloc(_alloc, out_ok, out_err, Err(FsErr::InvalidPath)); } match std::fs::read_to_string(&full) { - Ok(contents) => write_result(out_ok, out_err, Ok(contents)), - Err(err) => write_result(out_ok, out_err, Err(map_fs_err(err))), + Ok(contents) => write_result_with_alloc(_alloc, out_ok, out_err, Ok(contents)), + Err(err) => write_result_with_alloc(_alloc, out_ok, out_err, Err(map_fs_err(err))), } } @@ -731,26 +854,27 @@ 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, + _alloc: Handle, 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_ok, out_err, Err(FsErr::PermissionDenied)); + return write_result_with_alloc(_alloc, 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_ok, out_err, Err(map_fs_err(err))), + Err(err) => return write_result_with_alloc(_alloc, out_ok, out_err, Err(map_fs_err(err))), }; if !full.starts_with(&state.root) { - return write_result(out_ok, out_err, Err(FsErr::InvalidPath)); + return write_result_with_alloc(_alloc, out_ok, out_err, Err(FsErr::InvalidPath)); } match std::fs::read_to_string(&full) { - Ok(contents) => write_result(out_ok, out_err, Ok(contents)), - Err(err) => write_result(out_ok, out_err, Err(map_fs_err(err))), + Ok(contents) => write_result_with_alloc(_alloc, out_ok, out_err, Ok(contents)), + Err(err) => write_result_with_alloc(_alloc, out_ok, out_err, Err(map_fs_err(err))), } } @@ -838,6 +962,7 @@ pub extern "C" fn capable_rt_net_accept( #[no_mangle] pub extern "C" fn capable_rt_net_read_to_string( conn: Handle, + alloc: Handle, out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { @@ -851,12 +976,13 @@ pub extern "C" fn capable_rt_net_read_to_string( Err(err) => Err(map_net_err(err)), } }); - write_string_result(out_ok, out_err, result.map_err(|err| err as i32)) + write_string_result_with_alloc(alloc, out_ok, out_err, result.map_err(|err| err as i32)) } #[no_mangle] pub extern "C" fn capable_rt_net_read( conn: Handle, + alloc: Handle, max_size: i32, out_ok: *mut CapString, out_err: *mut i32, @@ -878,7 +1004,7 @@ pub extern "C" fn capable_rt_net_read( Err(err) => Err(map_net_err(err)), } }); - write_string_result(out_ok, out_err, result.map_err(|err| err as i32)) + write_string_result_with_alloc(alloc, out_ok, out_err, result.map_err(|err| err as i32)) } #[no_mangle] @@ -941,20 +1067,13 @@ pub extern "C" fn capable_rt_start() -> i32 { #[no_mangle] pub extern "C" fn capable_rt_malloc(_sys: Handle, size: i32) -> *mut u8 { - if size <= 0 { - return std::ptr::null_mut(); - } - unsafe { libc::malloc(size as usize) as *mut u8 } + let size = size.max(0) as usize; + alloc_malloc(_sys, size).unwrap_or(std::ptr::null_mut()) } #[no_mangle] pub extern "C" fn capable_rt_free(_sys: Handle, ptr: *mut u8) { - if ptr.is_null() { - return; - } - unsafe { - libc::free(ptr as *mut libc::c_void); - } + alloc_free(_sys, ptr) } #[no_mangle] @@ -969,95 +1088,27 @@ pub extern "C" fn capable_rt_cast_u32_to_u8(_sys: Handle, ptr: *mut u32) -> *mut #[no_mangle] pub extern "C" fn capable_rt_alloc_default(_sys: Handle) -> Handle { - new_handle() -} - -#[no_mangle] -pub extern "C" fn capable_rt_slice_from_ptr(_sys: Handle, ptr: *mut u8, len: i32) -> Handle { - let len = len.max(0) as usize; - let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert( - handle, - SliceState { - ptr: ptr as usize, - len, - }, - ); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_mut_slice_from_ptr(_sys: Handle, ptr: *mut u8, len: i32) -> Handle { - let len = len.max(0) as usize; let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert( - handle, - SliceState { - ptr: ptr as usize, - len, - }, - ); - }); + insert_handle(&ALLOCS, handle, (), "alloc table"); handle } #[no_mangle] -pub extern "C" fn capable_rt_slice_len(slice: Handle) -> i32 { - with_table(&SLICES, "slice table", |table| { - table - .get(&slice) - .map(|state| state.len.min(i32::MAX as usize) as i32) - .unwrap_or(0) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_slice_at(slice: Handle, index: i32) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => return 0, - }; - with_table(&SLICES, "slice table", |table| { - let Some(state) = table.get(&slice) else { - return 0; - }; - if state.ptr == 0 || idx >= state.len { - return 0; - } - let ptr = state.ptr as *mut u8; - unsafe { *ptr.add(idx) } - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_mut_slice_at(slice: Handle, index: i32) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => return 0, - }; - with_table(&SLICES, "slice table", |table| { - let Some(state) = table.get(&slice) else { - return 0; - }; - if state.ptr == 0 || idx >= state.len { - return 0; - } - let ptr = state.ptr as *mut u8; - unsafe { *ptr.add(idx) } - }) +pub extern "C" fn capable_rt_args_len(_sys: Handle) -> i32 { + if !has_handle(&ARGS_CAPS, _sys, "args table") { + return 0; + } + ARGS.len().min(i32::MAX as usize) as i32 } #[no_mangle] -pub extern "C" fn capable_rt_buffer_new( - _alloc: Handle, - initial_len: i32, - out_ok: *mut Handle, +pub extern "C" fn capable_rt_args_at( + _sys: Handle, + index: i32, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { - if initial_len < 0 { + if !has_handle(&ARGS_CAPS, _sys, "args table") { unsafe { if !out_err.is_null() { *out_err = 0; @@ -1065,971 +1116,119 @@ pub extern "C" fn capable_rt_buffer_new( } return 1; } - let len = initial_len as usize; - let mut data = Vec::new(); - if data.try_reserve(len).is_err() { + let idx = match usize::try_from(index) { + Ok(idx) => idx, + Err(_) => { + unsafe { + if !out_err.is_null() { + *out_err = 0; + } + } + return 1; + } + }; + let Some(arg) = ARGS.get(idx) else { unsafe { if !out_err.is_null() { *out_err = 0; } } return 1; - } - data.resize(len, 0); - let handle = new_handle(); - with_table(&BUFFERS, "buffer table", |table| { - table.insert(handle, data); - }); + }; + let bytes = arg.as_bytes(); + let len = bytes.len().min(i32::MAX as usize) as i32; + let ptr = if len == 0 { + std::ptr::null_mut() + } else { + bytes.as_ptr() as *mut u8 + }; unsafe { if !out_ok.is_null() { - *out_ok = handle; + (*out_ok).bytes = CapSlice { ptr, len }; + } + if !out_err.is_null() { + *out_err = 0; } } 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| { - table - .get(&buffer) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_buffer_push( - buffer: Handle, - value: u8, +pub extern "C" fn capable_rt_read_stdin_to_string( + _sys: Handle, + alloc: Handle, + out_ok: *mut CapString, out_err: *mut i32, ) -> u8 { - with_table(&BUFFERS, "buffer table", |table| { - let Some(data) = table.get_mut(&buffer) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(1).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.push(value); - 0 - }) + if !has_handle(&STDIN_CAPS, _sys, "stdin table") { + return write_string_result_with_alloc(alloc, 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_with_alloc(alloc, out_ok, out_err, Ok(input)), + Err(_) => write_string_result_with_alloc(alloc, out_ok, out_err, Err(0)), + } } #[no_mangle] -pub extern "C" fn capable_rt_buffer_extend( - buffer: Handle, - slice: Handle, - out_err: *mut i32, -) -> u8 { - let slice_state = with_table(&SLICES, "slice table", |table| table.get(&slice).copied()); - let Some(slice_state) = slice_state else { - unsafe { - if !out_err.is_null() { - *out_err = 0; +pub extern "C" fn capable_rt_string_eq( + left: *const CapString, + right: *const CapString, +) -> i8 { + let left_slice = unsafe { + if left.is_null() { + CapSlice { + ptr: std::ptr::null_mut(), + len: 0, } + } else { + (*left).bytes } - return 1; }; - let bytes = unsafe { std::slice::from_raw_parts(slice_state.ptr as *const u8, slice_state.len) }; - with_table(&BUFFERS, "buffer table", |table| { - let Some(data) = table.get_mut(&buffer) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(bytes.len()).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } + let right_slice = unsafe { + if right.is_null() { + CapSlice { + ptr: std::ptr::null_mut(), + len: 0, } - return 1; + } else { + (*right).bytes } - data.extend_from_slice(bytes); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_buffer_is_empty(buffer: Handle) -> u8 { - with_table(&BUFFERS, "buffer table", |table| { - u8::from(table.get(&buffer).map(|data| data.is_empty()).unwrap_or(true)) - }) + }; + if left_slice.len != right_slice.len { + return 0; + } + if left_slice.len == 0 { + return 1; + } + if left_slice.ptr.is_null() || right_slice.ptr.is_null() { + return 0; + } + let len = left_slice.len as usize; + let s1 = unsafe { std::slice::from_raw_parts(left_slice.ptr as *const u8, len) }; + let s2 = unsafe { std::slice::from_raw_parts(right_slice.ptr as *const u8, len) }; + i8::from(s1 == s2) } #[no_mangle] -pub extern "C" fn capable_rt_buffer_free(_alloc: Handle, buffer: Handle) { - with_table(&BUFFERS, "buffer table", |table| { - table.remove(&buffer); - }); -} - -#[no_mangle] -pub extern "C" fn capable_rt_buffer_as_slice(buffer: Handle) -> Handle { - let Some((ptr, len)) = with_table(&BUFFERS, "buffer table", |table| { - let Some(data) = table.get(&buffer) else { - return None; - }; - let ptr = if data.is_empty() { 0 } else { data.as_ptr() as usize }; - Some((ptr, data.len())) - }) else { - return 0; - }; - let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert(handle, SliceState { ptr, len }); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_buffer_as_mut_slice(buffer: Handle) -> Handle { - let Some((ptr, len)) = with_table(&BUFFERS, "buffer table", |table| { - let Some(data) = table.get_mut(&buffer) else { - return None; - }; - let ptr = if data.is_empty() { 0 } else { data.as_mut_ptr() as usize }; - Some((ptr, data.len())) - }) else { - return 0; - }; - let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert(handle, SliceState { ptr, len }); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_new(_alloc: Handle) -> Handle { - let handle = new_handle(); - with_table(&VECS_U8, "vec u8 table", |table| { - table.insert(handle, Vec::new()); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_len(vec: Handle) -> i32 { - with_table(&VECS_U8, "vec u8 table", |table| { - table - .get(&vec) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_get( - vec: Handle, - index: i32, - out_ok: *mut u8, - out_err: *mut i32, -) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - }; - with_table(&VECS_U8, "vec u8 table", |table| { - let Some(data) = table.get(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - let Some(value) = data.get(idx) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = *value; - } - } - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_set( - vec: Handle, - index: i32, - value: u8, - out_err: *mut i32, -) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - }; - with_table(&VECS_U8, "vec u8 table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - if idx >= data.len() { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - data[idx] = value; - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_push( - vec: Handle, - value: u8, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_U8, "vec u8 table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(1).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.push(value); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_extend( - vec: Handle, - other: Handle, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_U8, "vec u8 table", |table| { - let Some(other_data) = table.get(&other).cloned() else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(other_data.len()).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.extend_from_slice(&other_data); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_filter(vec: Handle, value: u8) -> Handle { - let filtered = with_table(&VECS_U8, "vec u8 table", |table| { - table - .get(&vec) - .map(|data| data.iter().copied().filter(|b| *b == value).collect::>()) - }); - let Some(filtered) = filtered else { - return 0; - }; - let handle = new_handle(); - with_table(&VECS_U8, "vec u8 table", |table| { - table.insert(handle, filtered); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_map_add(vec: Handle, delta: u8) -> Handle { - let mapped = with_table(&VECS_U8, "vec u8 table", |table| { - table - .get(&vec) - .map(|data| data.iter().map(|b| b.wrapping_add(delta)).collect::>()) - }); - let Some(mapped) = mapped else { - return 0; - }; - let handle = new_handle(); - with_table(&VECS_U8, "vec u8 table", |table| { - table.insert(handle, mapped); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_slice( - vec: Handle, - start: i32, - len: i32, - out_ok: *mut Handle, - out_err: *mut i32, -) -> u8 { - let start = match usize::try_from(start) { - Ok(start) => start, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - }; - let len = match usize::try_from(len) { - Ok(len) => len, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - }; - let slice_state = with_table(&VECS_U8, "vec u8 table", |table| { - let Some(data) = table.get(&vec) else { - return None; - }; - if start > data.len() || start.saturating_add(len) > data.len() { - return None; - } - let ptr = if len == 0 { 0 } else { unsafe { data.as_ptr().add(start) as usize } }; - Some(SliceState { ptr, len }) - }); - let Some(slice_state) = slice_state else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert(handle, slice_state); - }); - unsafe { - if !out_ok.is_null() { - *out_ok = handle; - } - } - 0 -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_pop( - vec: Handle, - out_ok: *mut u8, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_U8, "vec u8 table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; - } - } - return 1; - }; - let Some(value) = data.pop() else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; - } - } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = value; - } - } - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_as_slice(vec: Handle) -> Handle { - let Some((ptr, len)) = with_table(&VECS_U8, "vec u8 table", |table| { - let Some(data) = table.get(&vec) else { - return None; - }; - let ptr = if data.is_empty() { 0 } else { data.as_ptr() as usize }; - Some((ptr, data.len())) - }) else { - return 0; - }; - let handle = new_handle(); - with_table(&SLICES, "slice table", |table| { - table.insert(handle, SliceState { ptr, len }); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_u8_free(_alloc: Handle, vec: Handle) { - with_table(&VECS_U8, "vec u8 table", |table| { - table.remove(&vec); - }); -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_new(_alloc: Handle) -> Handle { - let handle = new_handle(); - with_table(&VECS_I32, "vec i32 table", |table| { - table.insert(handle, Vec::new()); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_len(vec: Handle) -> i32 { - with_table(&VECS_I32, "vec i32 table", |table| { - table - .get(&vec) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_get( - vec: Handle, - index: i32, - out_ok: *mut i32, - out_err: *mut i32, -) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - }; - with_table(&VECS_I32, "vec i32 table", |table| { - let Some(data) = table.get(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - let Some(value) = data.get(idx) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = *value; - } - } - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_set( - vec: Handle, - index: i32, - value: i32, - out_err: *mut i32, -) -> u8 { - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - }; - with_table(&VECS_I32, "vec i32 table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - }; - if idx >= data.len() { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::OutOfRange as i32; - } - } - return 1; - } - data[idx] = value; - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_push( - vec: Handle, - value: i32, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_I32, "vec i32 table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(1).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.push(value); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_extend( - vec: Handle, - other: Handle, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_I32, "vec i32 table", |table| { - let Some(other_data) = table.get(&other).cloned() else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(other_data.len()).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.extend_from_slice(&other_data); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_filter(vec: Handle, value: i32) -> Handle { - let filtered = with_table(&VECS_I32, "vec i32 table", |table| { - table - .get(&vec) - .map(|data| data.iter().copied().filter(|b| *b == value).collect::>()) - }); - let Some(filtered) = filtered else { - return 0; - }; - let handle = new_handle(); - with_table(&VECS_I32, "vec i32 table", |table| { - table.insert(handle, filtered); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_map_add(vec: Handle, delta: i32) -> Handle { - let mapped = with_table(&VECS_I32, "vec i32 table", |table| { - table - .get(&vec) - .map(|data| data.iter().map(|b| b.wrapping_add(delta)).collect::>()) - }); - let Some(mapped) = mapped else { - return 0; - }; - let handle = new_handle(); - with_table(&VECS_I32, "vec i32 table", |table| { - table.insert(handle, mapped); - }); - handle -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_pop( - vec: Handle, - out_ok: *mut i32, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_I32, "vec i32 table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; - } - } - return 1; - }; - let Some(value) = data.pop() else { - unsafe { - if !out_err.is_null() { - *out_err = VecErr::Empty as i32; - } - } - return 1; - }; - unsafe { - if !out_ok.is_null() { - *out_ok = value; - } - } - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_i32_free(_alloc: Handle, vec: Handle) { - with_table(&VECS_I32, "vec i32 table", |table| { - table.remove(&vec); - }); -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_string_new(_alloc: Handle) -> Handle { - let handle = new_handle(); - with_table(&VECS_STRING, "vec string table", |table| { - table.insert(handle, Vec::new()); - }); - 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| { - table - .get(&vec) - .map(|data| data.len().min(i32::MAX as usize) as i32) - .unwrap_or(0) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_string_get( - vec: Handle, - index: i32, - 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_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_ok, out_err, Err(VecErr::OutOfRange as i32)); - }; - let Some(value) = data.get(idx) else { - return write_string_result(out_ok, out_err, Err(VecErr::OutOfRange as i32)); - }; - write_string_result(out_ok, out_err, Ok(value.clone())) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_string_push( - vec: Handle, - value: *const CapString, - out_err: *mut i32, -) -> u8 { - let value = unsafe { read_cap_string(value) }; - let Some(value) = value else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - with_table(&VECS_STRING, "vec string table", |table| { - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(1).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.push(value); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_string_extend( - vec: Handle, - other: Handle, - out_err: *mut i32, -) -> u8 { - with_table(&VECS_STRING, "vec string table", |table| { - let Some(other_data) = table.get(&other).cloned() else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - let Some(data) = table.get_mut(&vec) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - if data.try_reserve(other_data.len()).is_err() { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - data.extend(other_data); - 0 - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_string_pop( - vec: Handle, - 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_ok, out_err, Err(VecErr::Empty as i32)); - }; - let Some(value) = data.pop() else { - return write_string_result(out_ok, out_err, Err(VecErr::Empty as i32)); - }; - write_string_result(out_ok, out_err, Ok(value)) - }) -} - -#[no_mangle] -pub extern "C" fn capable_rt_vec_string_free(_alloc: Handle, vec: Handle) { - with_table(&VECS_STRING, "vec string table", |table| { - table.remove(&vec); - }); -} - -#[no_mangle] -pub extern "C" fn capable_rt_args_len(_sys: Handle) -> i32 { - if !has_handle(&ARGS_CAPS, _sys, "args table") { - return 0; - } - ARGS.len().min(i32::MAX as usize) as i32 -} - -#[no_mangle] -pub extern "C" fn capable_rt_args_at( - _sys: Handle, - index: i32, - out_ok: *mut CapString, - out_err: *mut i32, -) -> u8 { - if !has_handle(&ARGS_CAPS, _sys, "args table") { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - let idx = match usize::try_from(index) { - Ok(idx) => idx, - Err(_) => { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - } - }; - let Some(arg) = ARGS.get(idx) else { - unsafe { - if !out_err.is_null() { - *out_err = 0; - } - } - return 1; - }; - 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_ok: *mut CapString, - out_err: *mut i32, -) -> u8 { - if !has_handle(&STDIN_CAPS, _sys, "stdin table") { - 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_ok, out_err, Ok(input)), - Err(_) => write_string_result(out_ok, out_err, Err(0)), - } -} - -#[no_mangle] -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 left_state.len != right_state.len { - return 0; - } - 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] -pub extern "C" fn capable_rt_bytes_is_whitespace(value: u8) -> u8 { - match value { - b' ' | b'\t' | b'\n' | b'\r' => 1, - _ => 0, - } +pub extern "C" fn capable_rt_bytes_is_whitespace(value: u8) -> u8 { + match value { + b' ' | b'\t' | b'\n' | b'\r' => 1, + _ => 0, + } } unsafe fn write_bytes(ptr: *const u8, len: usize, newline: bool) { + let mut stdout = io::stdout().lock(); if ptr.is_null() { + if newline { + let _ = stdout.write_all(b"\n"); + let _ = stdout.flush(); + } return; } let slice = std::slice::from_raw_parts(ptr, len); - let mut stdout = io::stdout().lock(); let _ = stdout.write_all(slice); if newline { let _ = stdout.write_all(b"\n"); @@ -2082,10 +1281,18 @@ fn resolve_rooted_path(root: &Path, rel: &Path) -> Result { Ok(full) } -fn write_result(out_ok: *mut CapString, out_err: *mut i32, result: Result) -> u8 { +fn write_result_with_alloc( + alloc: Handle, + out_ok: *mut CapString, + out_err: *mut i32, + result: Result, +) -> u8 { unsafe { if !out_ok.is_null() { - (*out_ok).bytes = 0; + (*out_ok).bytes = CapSlice { + ptr: std::ptr::null_mut(), + len: 0, + }; } if !out_err.is_null() { *out_err = 0; @@ -2093,10 +1300,17 @@ fn write_result(out_ok: *mut CapString, out_err: *mut i32, result: Result { - let handle = to_slice_handle(contents); + let Some(slice) = to_cap_slice_with_alloc(alloc, contents) else { + unsafe { + if !out_err.is_null() { + *out_err = FsErr::IoError as i32; + } + } + return 1; + }; unsafe { if !out_ok.is_null() { - (*out_ok).bytes = handle; + (*out_ok).bytes = slice; } } 0 @@ -2112,14 +1326,18 @@ fn write_result(out_ok: *mut CapString, out_err: *mut i32, result: Result, ) -> u8 { unsafe { if !out_ok.is_null() { - (*out_ok).bytes = 0; + (*out_ok).bytes = CapSlice { + ptr: std::ptr::null_mut(), + len: 0, + }; } if !out_err.is_null() { *out_err = 0; @@ -2127,10 +1345,17 @@ fn write_string_result( } match result { Ok(s) => { - let handle = to_slice_handle(s); + let Some(slice) = to_cap_slice_with_alloc(alloc, s) else { + unsafe { + if !out_err.is_null() { + *out_err = 1; + } + } + return 1; + }; unsafe { if !out_ok.is_null() { - (*out_ok).bytes = handle; + (*out_ok).bytes = slice; } } 0 @@ -2146,27 +1371,17 @@ fn write_string_result( } } -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; } - read_slice_string((*ptr).bytes) + let slice = (*ptr).bytes; + if slice.ptr.is_null() || slice.len <= 0 { + return Some(String::new()); + } + let len = slice.len as usize; + let bytes = std::slice::from_raw_parts(slice.ptr as *const u8, len); + std::str::from_utf8(bytes).ok().map(|s| s.to_string()) } fn normalize_root(root: &Path) -> Option { diff --git a/stdlib/sys/args.cap b/stdlib/sys/args.cap index 64703fd..5661c32 100644 --- a/stdlib/sys/args.cap +++ b/stdlib/sys/args.cap @@ -1,17 +1,23 @@ +/// Program argument access. package safe module sys::args +/// Capability for reading argv. pub copy capability struct Args +/// Argument lookup errors. pub enum ArgsErr { + /// Index was out of range. OutOfRange } impl Args { + /// Number of arguments. pub fn len(self) -> i32 { return 0 } + /// Get an argument by index. pub fn at(self, index: i32) -> Result { return Err(ArgsErr::OutOfRange) } diff --git a/stdlib/sys/buffer.cap b/stdlib/sys/buffer.cap index 6cff58d..efc082e 100644 --- a/stdlib/sys/buffer.cap +++ b/stdlib/sys/buffer.cap @@ -1,131 +1,247 @@ +/// Slice and allocation utilities. +/// Slice/MutSlice are lightweight, non-owning views into existing bytes. package safe module sys::buffer use sys::vec use sys::string +use sys::unsafe_ptr +/// Alloc capability for heap operations (heap-backed allocations). pub copy opaque struct Alloc -pub copy opaque struct Buffer -pub copy opaque struct Slice -pub copy opaque struct MutSlice +/// Immutable slice view (non-owning). +pub copy struct Slice { + ptr: *T, + len: i32 +} +/// Mutable slice view (non-owning). +pub copy struct MutSlice { + ptr: *T, + len: i32 +} +/// Allocation errors. pub enum AllocErr { + /// Out of memory. Oom } -/// Intrinsic; implemented by the runtime. -pub fn new(initial_len: i32) -> Result { - return Err(AllocErr::Oom) +/// Slice errors. +pub enum SliceErr { + /// Index is out of range. + OutOfRange } -/// Intrinsic; implemented by the runtime. -pub fn vec_string_new() -> vec::Vec { - return () +/// Copy a slice into a new owned slice using the provided allocator. +pub fn copy_slice(alloc: Alloc, data: Slice) -> Result, AllocErr> { + let len = data.len() + let raw = alloc.malloc(len) + if (len > 0 && unsafe_ptr::ptr_is_null(raw)) { + return Err(AllocErr::Oom) + } + let i = 0 + while (i < len) { + let value = data.at(i) + unsafe_ptr::ptr_write(unsafe_ptr::ptr_add(raw, i), value) + i = i + 1 + } + return Ok(alloc.slice_from_ptr(raw, len)) } impl Alloc { + /// Allocate raw bytes. pub fn malloc(self, size: i32) -> *u8 { return () } + /// Free raw bytes. pub fn free(self, ptr: *u8) -> unit { return () } + /// Reinterpret a *u8 as *u32. pub fn cast_u8_to_u32(self, ptr: *u8) -> *u32 { return () } + /// Reinterpret a *u32 as *u8. pub fn cast_u32_to_u8(self, ptr: *u32) -> *u8 { return () } + /// Create an immutable slice from a raw pointer. pub fn slice_from_ptr(self, ptr: *u8, len: i32) -> Slice { - return () + return Slice { ptr: ptr, len: len } } + /// Create a mutable slice from a raw pointer. pub fn mut_slice_from_ptr(self, ptr: *u8, len: i32) -> MutSlice { - return () - } - - pub fn buffer_new(self, initial_len: i32) -> Result { - return Err(AllocErr::Oom) + return MutSlice { ptr: ptr, len: len } } - pub fn buffer_free(self, b: Buffer) -> unit { - return () + /// Create a Vec with this Alloc (heap-backed). + pub fn vec_u8_new(self) -> vec::Vec { + return vec::new_with(self) } - pub fn vec_u8_new(self) -> vec::Vec { - return () + /// Create a Vec with capacity using this Alloc. + pub fn vec_u8_with_capacity(self, capacity: i32) -> Result, AllocErr> { + return vec::with_capacity(self, capacity) } + /// Free a Vec created with this Alloc. pub fn vec_u8_free(self, v: vec::Vec) -> unit { + v.free() return () } + /// Create a Vec with this Alloc (heap-backed). pub fn vec_i32_new(self) -> vec::Vec { - return () + return vec::new_with(self) + } + + /// Create a Vec with capacity using this Alloc. + pub fn vec_i32_with_capacity(self, capacity: i32) -> Result, AllocErr> { + return vec::with_capacity(self, capacity) } + /// Free a Vec created with this Alloc. pub fn vec_i32_free(self, v: vec::Vec) -> unit { + v.free() return () } + /// Create a Vec with this Alloc (heap-backed). pub fn vec_string_new(self) -> vec::Vec { - return () + return vec::new_with(self) } - pub fn vec_string_free(self, v: vec::Vec) -> unit { - return () + /// Create a Vec with capacity using this Alloc. + pub fn vec_string_with_capacity(self, capacity: i32) -> Result, AllocErr> { + return vec::with_capacity(self, capacity) } -} -impl Buffer { - pub fn len(self) -> i32 { - return 0 + /// Free a Vec created with this Alloc. + pub fn vec_string_free(self, v: vec::Vec) -> unit { + v.free() + return () } - pub fn push(self, x: u8) -> Result { - return Err(AllocErr::Oom) + /// Allocate a new empty Text. + pub fn text_new(self) -> string::Text { + return string::text_new(self) } - pub fn extend(self, data: Slice) -> Result { - return Err(AllocErr::Oom) + /// Allocate a new empty Text with capacity. + pub fn text_with_capacity(self, capacity: i32) -> Result { + return string::text_with_capacity(self, capacity) } - pub fn push_str(self, s: string) -> Result { - return self.extend(s.as_slice()) + /// Copy a string view into a new Text. + pub fn text_from(self, s: string) -> Result { + return string::text_from(self, s) } - pub fn is_empty(self) -> bool { - return false + /// Create a Vec with this Alloc (heap-backed). + pub fn vec_new(self) -> vec::Vec { + return vec::new_with(self) } - pub fn as_slice(self) -> Slice { - return () + /// Create a Vec with capacity using this Alloc. + pub fn vec_with_capacity(self, capacity: i32) -> Result, AllocErr> { + return vec::with_capacity(self, capacity) } - pub fn as_mut_slice(self) -> MutSlice { + /// Free a Vec created with this Alloc. + pub fn vec_free(self, v: vec::Vec) -> unit { + v.free() return () } +} - pub fn to_string(self) -> Result { - return string::from_bytes(self.as_slice()) +impl Slice { + /// Length in elements. + pub fn len(self) -> i32 { + return self.len + } + + /// Create a subslice view. + pub fn slice(self, start: i32, len: i32) -> Result, SliceErr> { + if (start < 0 || len < 0) { + return Err(SliceErr::OutOfRange) + } + if (start > self.len) { + return Err(SliceErr::OutOfRange) + } + if (start + len > self.len) { + return Err(SliceErr::OutOfRange) + } + let ptr = unsafe_ptr::ptr_add(self.ptr, start) + return Ok(Slice { ptr: ptr, len: len }) + } + + /// Create a subslice view using start/end bounds (end is exclusive). + pub fn slice_range(self, start: i32, end: i32) -> Result, SliceErr> { + if (start < 0 || end < 0) { + return Err(SliceErr::OutOfRange) + } + if (start > end) { + return Err(SliceErr::OutOfRange) + } + return self.slice(start, end - start) } } -impl Slice { +impl MutSlice { + /// Length in elements. pub fn len(self) -> i32 { - return 0 + return self.len + } + + /// Create a mutable subslice view. + pub fn slice(self, start: i32, len: i32) -> Result, SliceErr> { + if (start < 0 || len < 0) { + return Err(SliceErr::OutOfRange) + } + if (start > self.len) { + return Err(SliceErr::OutOfRange) + } + if (start + len > self.len) { + return Err(SliceErr::OutOfRange) + } + let ptr = unsafe_ptr::ptr_add(self.ptr, start) + return Ok(MutSlice { ptr: ptr, len: len }) + } + + /// Create a mutable subslice view using start/end bounds (end is exclusive). + pub fn slice_range(self, start: i32, end: i32) -> Result, SliceErr> { + if (start < 0 || end < 0) { + return Err(SliceErr::OutOfRange) + } + if (start > end) { + return Err(SliceErr::OutOfRange) + } + return self.slice(start, end - start) } +} +impl Slice { + /// Index into the slice. pub fn at(self, i: i32) -> u8 { - return 0 + if (i < 0 || i >= self.len) { + panic() + } + let ptr = unsafe_ptr::ptr_add(self.ptr, i) + return unsafe_ptr::ptr_read(ptr) } } impl MutSlice { + /// Index into the mutable slice. pub fn at(self, i: i32) -> u8 { - return 0 + if (i < 0 || i >= self.len) { + panic() + } + let ptr = unsafe_ptr::ptr_add(self.ptr, i) + return unsafe_ptr::ptr_read(ptr) } } diff --git a/stdlib/sys/bytes.cap b/stdlib/sys/bytes.cap index 6b9cd3d..b59ef69 100644 --- a/stdlib/sys/bytes.cap +++ b/stdlib/sys/bytes.cap @@ -1,8 +1,10 @@ +/// Byte helpers and predicates. package safe module sys::bytes impl u8 { + /// True if the byte is ASCII whitespace. pub fn is_whitespace(self) -> bool { - if (self == 32u8 || self == 9u8 || self == 10u8 || self == 13u8) { + if (self == ' ' || self == '\t' || self == '\n' || self == '\r') { return true } return false diff --git a/stdlib/sys/console.cap b/stdlib/sys/console.cap index a1ceb20..2ae645f 100644 --- a/stdlib/sys/console.cap +++ b/stdlib/sys/console.cap @@ -1,25 +1,32 @@ +/// Console output utilities. package unsafe module sys::console +/// Capability for stdout access. pub copy capability struct Console impl Console { + /// Print a string without a newline. pub fn print(self, s: string) -> unit { return () } + /// Print a string with a newline. pub fn println(self, s: string) -> unit { return () } + /// Print an i32 without a newline. pub fn print_i32(self, v: i32) -> unit { return () } + /// Print an i32 with a newline. pub fn println_i32(self, v: i32) -> unit { return () } + /// Trap if condition is false. pub fn assert(self, cond: bool) -> unit { return () } diff --git a/stdlib/sys/fs.cap b/stdlib/sys/fs.cap index 3882215..0c507d8 100644 --- a/stdlib/sys/fs.cap +++ b/stdlib/sys/fs.cap @@ -1,88 +1,112 @@ +/// Filesystem capabilities and helpers. package unsafe module sys::fs +use sys::buffer use sys::vec +/// Read-only filesystem capability. pub capability struct ReadFS +/// Read/write filesystem capability. pub capability struct Filesystem +/// Directory capability. pub capability struct Dir +/// File handle for reading; linear to enforce close. pub linear capability struct FileRead +/// Filesystem error codes. pub enum FsErr { NotFound, PermissionDenied, InvalidPath, IoError } impl ReadFS { - pub fn read_to_string(self, path: string) -> Result { + /// Read an entire file into a string. + pub fn read_to_string(self, alloc: buffer::Alloc, path: string) -> Result { return () } - pub fn read_bytes(self, path: string) -> Result, FsErr> { + /// Read an entire file into a byte vec. + pub fn read_bytes(self, alloc: buffer::Alloc, path: string) -> Result, FsErr> { return Err(FsErr::IoError) } - pub fn list_dir(self, path: string) -> Result, FsErr> { + /// List directory contents as strings. + pub fn list_dir(self, alloc: buffer::Alloc, path: string) -> Result, FsErr> { return Err(FsErr::IoError) } + /// True if a path exists. pub fn exists(self, path: string) -> bool { return false } + /// Close the capability. pub fn close(self) -> unit { return () } } impl Filesystem { + /// Get a Dir for the filesystem root. pub fn root_dir(self) -> Dir { return () } + /// Close the capability. pub fn close(self) -> unit { return () } } impl Dir { + /// Open a subdirectory. pub fn subdir(self, name: string) -> Dir { return () } + /// Open a file for reading. pub fn open_read(self, name: string) -> FileRead { return () } - pub fn read_bytes(self, name: string) -> Result, FsErr> { + /// Read a file into a byte vec. + pub fn read_bytes(self, alloc: buffer::Alloc, name: string) -> Result, FsErr> { return Err(FsErr::IoError) } - pub fn list_dir(self) -> Result, FsErr> { + /// List directory contents. + pub fn list_dir(self, alloc: buffer::Alloc) -> Result, FsErr> { return Err(FsErr::IoError) } + /// True if a path exists. pub fn exists(self, name: string) -> bool { return false } - pub fn read_to_string(self, name: string) -> Result { + /// Read a file into a string. + pub fn read_to_string(self, alloc: buffer::Alloc, name: string) -> Result { let file = self.open_read(name) - return file.read_to_string() + return file.read_to_string(alloc) } + /// Close the capability. pub fn close(self) -> unit { return () } } impl FileRead { - pub fn read_to_string(self) -> Result { + /// Read the file into a string. + pub fn read_to_string(self, alloc: buffer::Alloc) -> Result { return () } + /// Close the file. pub fn close(self) -> unit { return () } } -pub fn join(a: string, b: string) -> string { +/// Join two path segments with a platform separator. +pub fn join(alloc: buffer::Alloc, a: string, b: string) -> string { return "" } diff --git a/stdlib/sys/io.cap b/stdlib/sys/io.cap index 30d5d52..16c2e4a 100644 --- a/stdlib/sys/io.cap +++ b/stdlib/sys/io.cap @@ -1,6 +1,9 @@ +/// IO error enums shared across modules. package safe module sys::io +/// Basic IO errors. pub enum IoErr { + /// Generic IO failure. IoError } diff --git a/stdlib/sys/math.cap b/stdlib/sys/math.cap index 12f637b..f726e42 100644 --- a/stdlib/sys/math.cap +++ b/stdlib/sys/math.cap @@ -1,14 +1,24 @@ +/// Wrapping arithmetic helpers. package safe module sys::math +/// Wrapping add for i32. pub fn add_wrap_i32(a: i32, b: i32) -> i32 { return 0 } +/// Wrapping subtract for i32. pub fn sub_wrap_i32(a: i32, b: i32) -> i32 { return 0 } +/// Wrapping multiply for i32. pub fn mul_wrap_i32(a: i32, b: i32) -> i32 { return 0 } +/// Wrapping add for u32. pub fn add_wrap_u32(a: u32, b: u32) -> u32 { return 0 } +/// Wrapping subtract for u32. pub fn sub_wrap_u32(a: u32, b: u32) -> u32 { return 0 } +/// Wrapping multiply for u32. pub fn mul_wrap_u32(a: u32, b: u32) -> u32 { return 0 } +/// Wrapping add for u8. pub fn add_wrap_u8(a: u8, b: u8) -> u8 { return 0 } +/// Wrapping subtract for u8. pub fn sub_wrap_u8(a: u8, b: u8) -> u8 { return 0 } +/// Wrapping multiply for u8. pub fn mul_wrap_u8(a: u8, b: u8) -> u8 { return 0 } diff --git a/stdlib/sys/mem.cap b/stdlib/sys/mem.cap index 3c8ecd9..5697918 100644 --- a/stdlib/sys/mem.cap +++ b/stdlib/sys/mem.cap @@ -1,2 +1,3 @@ +/// Memory helpers (reserved for future use). package unsafe module sys::mem diff --git a/stdlib/sys/net.cap b/stdlib/sys/net.cap index 16a2cf9..57d67f1 100644 --- a/stdlib/sys/net.cap +++ b/stdlib/sys/net.cap @@ -1,49 +1,66 @@ +/// Networking capabilities and helpers. package unsafe module sys::net +use sys::buffer +/// Network capability. pub copy capability struct Net +/// TCP listener capability. pub copy capability struct TcpListener +/// TCP connection capability (linear to enforce close). pub linear capability struct TcpConn +/// Network errors. pub enum NetErr { + /// Address parse/format error. InvalidAddress, + /// Generic IO failure. IoError, + /// Data parse/format error. InvalidData } impl Net { + /// Bind and listen on host:port. pub fn listen(self, host: string, port: i32) -> Result { return Err(NetErr::IoError) } + /// Connect to host:port. pub fn connect(self, host: string, port: i32) -> Result { return Err(NetErr::IoError) } } impl TcpListener { + /// Accept an incoming connection. pub fn accept(self: &TcpListener) -> Result { return Err(NetErr::IoError) } + /// Close the listener. pub fn close(self) -> unit { return () } } impl TcpConn { - pub fn read_to_string(self: &TcpConn) -> Result { + /// Read all available data into a string. + pub fn read_to_string(self: &TcpConn, alloc: buffer::Alloc) -> Result { return Err(NetErr::IoError) } - pub fn read(self: &TcpConn, max_size: i32) -> Result { + /// Read up to max_size bytes into a string. + pub fn read(self: &TcpConn, alloc: buffer::Alloc, max_size: i32) -> Result { return Err(NetErr::IoError) } + /// Write a string to the connection. pub fn write(self: &TcpConn, data: string) -> Result { return Err(NetErr::IoError) } + /// Close the connection. pub fn close(self) -> unit { return () } diff --git a/stdlib/sys/option.cap b/stdlib/sys/option.cap new file mode 100644 index 0000000..421ab7a --- /dev/null +++ b/stdlib/sys/option.cap @@ -0,0 +1,45 @@ +/// Option enum and helpers. +package safe +module sys::option + +/// Optional value. +pub enum Option { + /// Some value. + Some(T), + /// No value. + None +} + +impl Option { + /// True if Some. + pub fn is_some(self) -> bool { + match self { + Option::Some(_) => { return true } + Option::None => { return false } + } + } + + /// True if None. + pub fn is_none(self) -> bool { + match self { + Option::Some(_) => { return false } + Option::None => { return true } + } + } + + /// Return the inner value or a default. + pub fn unwrap_or(self, default: T) -> T { + match self { + Option::Some(val) => { return val } + Option::None => { return default } + } + } + + /// Unwrap the inner value or panic. + pub fn unwrap(self) -> T { + match self { + Option::Some(val) => { return val } + Option::None => { panic() } + } + } +} diff --git a/stdlib/sys/result.cap b/stdlib/sys/result.cap index 7488497..86d4c12 100644 --- a/stdlib/sys/result.cap +++ b/stdlib/sys/result.cap @@ -1,12 +1,17 @@ +/// Result enum and helpers. package safe module sys::result +/// Result for fallible operations. pub enum Result { + /// Success value. Ok(T), + /// Error value. Err(E) } impl Result { + /// True if Ok. pub fn is_ok(self) -> bool { match self { Result::Ok(_) => { return true } @@ -14,6 +19,7 @@ impl Result { } } + /// True if Err. pub fn is_err(self) -> bool { match self { Result::Ok(_) => { return false } @@ -21,6 +27,7 @@ impl Result { } } + /// Return Ok value or a default. pub fn unwrap_or(self, default: T) -> T { match self { Result::Ok(val) => { return val } @@ -28,6 +35,7 @@ impl Result { } } + /// Return Err value or a default. pub fn unwrap_err_or(self, default: E) -> E { match self { Result::Ok(_) => { return default } @@ -35,6 +43,7 @@ impl Result { } } + /// Unwrap Ok or panic. pub fn ok(self) -> T { match self { Result::Ok(val) => { return val } @@ -42,6 +51,7 @@ impl Result { } } + /// Unwrap Err or panic. pub fn err(self) -> E { match self { Result::Ok(_) => { panic() } diff --git a/stdlib/sys/stdin.cap b/stdlib/sys/stdin.cap index e798b6c..c3e53e5 100644 --- a/stdlib/sys/stdin.cap +++ b/stdlib/sys/stdin.cap @@ -1,12 +1,16 @@ +/// Stdin access. package safe module sys::stdin use sys::io +use sys::buffer +/// Capability for stdin. pub capability struct Stdin impl Stdin { - pub fn read_to_string(self) -> Result { + /// Read stdin into a string. + pub fn read_to_string(self, alloc: buffer::Alloc) -> Result { return Err(io::IoErr::IoError) } } diff --git a/stdlib/sys/string.cap b/stdlib/sys/string.cap index 4814525..7e1e707 100644 --- a/stdlib/sys/string.cap +++ b/stdlib/sys/string.cap @@ -1,3 +1,6 @@ +/// UTF-8-ish string types. +/// `string` is a lightweight view backed by a byte slice. +/// `Text` is an owned, growable UTF-8 buffer backed by Vec. package safe module sys::string @@ -5,39 +8,68 @@ use sys::buffer use sys::bytes use sys::vec +/// String view backed by a byte slice (non-owning). pub copy struct string { bytes: Slice } +/// Owned UTF-8 text backed by a Vec. +/// Text is a growable, heap-backed string builder. +pub struct Text { + bytes: vec::Vec +} + +/// Result payload for split_once. pub struct SplitOnce { left: string, right: string } +/// Wrap a slice as a string view (no copy). +/// The view is only valid while the slice's backing storage is alive. pub fn from_bytes(bytes: Slice) -> Result { return Ok(string { bytes: bytes }) } -fn build_range(s: string, start: i32, end: i32) -> string { +/// Copy a slice into a new owned string view using the provided allocator. +pub fn from_bytes_copy(alloc: buffer::Alloc, bytes: Slice) -> Result { + let owned = buffer::copy_slice(alloc, bytes)? + return Ok(string { bytes: owned }) +} + +/// Allocate a new empty Text using the provided allocator. +pub fn text_new(alloc: buffer::Alloc) -> Text { + return Text { bytes: alloc.vec_u8_new() } +} + +/// Allocate a new empty Text with capacity using the provided allocator. +pub fn text_with_capacity(alloc: buffer::Alloc, capacity: i32) -> Result { + let bytes = alloc.vec_u8_with_capacity(capacity)? + return Ok(Text { bytes: bytes }) +} + +/// Copy a string view into a new Text using the provided allocator. +pub fn text_from(alloc: buffer::Alloc, s: string) -> Result { + let out = text_new(alloc) + out.push_str(s)? + return Ok(out) +} + +fn build_range(alloc: buffer::Alloc, 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() } - } + let buf = text_new(alloc) + let i = start + while (i < end) { + match (buf.push_byte(s.byte_at(i))) { + Ok(_) => { } + Err(_) => { panic() } } + i = i + 1 + } + match (buf.to_string()) { + Ok(out) => { return out } Err(_) => { panic() } } } @@ -106,15 +138,123 @@ fn upper_ascii_byte(b: u8) -> u8 { } } +impl Text { + /// Current length in bytes. + pub fn len(self) -> i32 { + return self.bytes.len() + } + + /// True if empty. + pub fn is_empty(self) -> bool { + return self.bytes.is_empty() + } + + /// Append a single byte. + /// Bytes are not validated as UTF-8; use with ASCII or known UTF-8 bytes. + pub fn push_byte(self, b: u8) -> Result { + return self.bytes.push(b) + } + + /// Append bytes from a slice. + /// Bytes are not validated as UTF-8; use with ASCII or known UTF-8 bytes. + pub fn extend_slice(self, data: Slice) -> Result { + return self.bytes.extend_slice(data) + } + + /// Append bytes from a Vec. + pub fn extend_vec(self, data: vec::Vec) -> Result { + return self.extend_slice(data.as_slice()) + } + + /// Append bytes from a string view. + /// This preserves the UTF-8 bytes of the view. + pub fn push_str(self, s: string) -> Result { + return self.extend_slice(s.as_slice()) + } + + /// Append a string view (alias of push_str). + pub fn append(self, s: string) -> Result { + return self.push_str(s) + } + + /// Borrow as a byte slice view (no copy). + pub fn as_slice(self) -> Slice { + return self.bytes.as_slice() + } + + /// Borrow as a string view (no copy). + /// The view is invalid after freeing this Text. + pub fn as_string(self) -> string { + return string { bytes: self.as_slice() } + } + + /// Borrow a subslice of this Text as a string view (byte offsets, end exclusive). + /// The view is invalid after freeing this Text. + pub fn slice_range(self, start: i32, end: i32) -> Result { + let sub = self.as_slice().slice_range(start, end)? + return Ok(string { bytes: sub }) + } + + /// Copy contents into a new string view. + /// This allocates a new owned slice for the string view. + pub fn to_string(self) -> Result { + let owned = self.bytes.copy_slice()? + return from_bytes(owned) + } + + /// Free the underlying Vec. + /// All derived views become invalid after this call. + pub fn free(self, alloc: buffer::Alloc) -> unit { + alloc.vec_u8_free(self.bytes) + return () + } + + /// Remove all bytes from this Text. + pub fn clear(self) -> unit { + self.bytes.clear() + return () + } +} + impl string { + /// Return the length in bytes. pub fn len(self) -> i32 { return self.bytes.len() } + /// Create a subslice view of this string by byte offset. + /// The view is invalid if the backing storage is freed. + pub fn slice(self, start: i32, len: i32) -> Result { + let sub = self.as_slice().slice(start, len)? + return Ok(string { bytes: sub }) + } + + /// Create a subslice view of this string using start/end bounds (end exclusive). + /// Offsets are byte indices (no UTF-8 validation). + pub fn slice_range(self, start: i32, end: i32) -> Result { + let sub = self.as_slice().slice_range(start, end)? + return Ok(string { bytes: sub }) + } + + /// Copy this string view into a new owned Text with the provided allocator. + pub fn to_text(self, alloc: buffer::Alloc) -> Result { + return text_from(alloc, self) + } + + /// Concatenate another string into a new owned string view. + pub fn concat(self, alloc: buffer::Alloc, other: string) -> Result { + let out = text_new(alloc) + out.push_str(self)? + out.push_str(other)? + return out.to_string() + } + + /// Index into the string by byte. pub fn byte_at(self, index: i32) -> u8 { return self.bytes.at(index) } + /// Borrow the underlying byte slice (no copy). pub fn as_slice(self) -> Slice { return self.bytes } @@ -124,8 +264,9 @@ impl string { return self.as_slice() } - pub fn split_whitespace(self) -> Vec { - let out = buffer::vec_string_new() + /// Split on ASCII whitespace. + pub fn split_whitespace(self, alloc: buffer::Alloc) -> Vec { + let out = alloc.vec_string_new() let bytes = self.as_slice() let len = bytes.len() let i = 0 @@ -140,7 +281,7 @@ impl string { while (i < len && !bytes.at(i).is_whitespace()) { i = i + 1 } - let part = build_range(self, start, i) + let part = build_range(alloc, self, start, i) match (out.push(part)) { Ok(_) => { } Err(_) => { panic() } @@ -149,8 +290,8 @@ impl string { return out } - pub fn lines(self) -> Vec { - let out = buffer::vec_string_new() + pub fn lines(self, alloc: buffer::Alloc) -> Vec { + let out = alloc.vec_string_new() let bytes = self.as_slice() let len = bytes.len() let start = 0 @@ -161,7 +302,7 @@ impl string { if (end > start && bytes.at(end - 1) == '\r') { end = end - 1 } - let part = build_range(self, start, end) + let part = build_range(alloc, self, start, end) match (out.push(part)) { Ok(_) => { } Err(_) => { panic() } @@ -175,7 +316,7 @@ impl string { if (end > start && bytes.at(end - 1) == '\r') { end = end - 1 } - let part = build_range(self, start, end) + let part = build_range(alloc, self, start, end) match (out.push(part)) { Ok(_) => { } Err(_) => { panic() } @@ -184,15 +325,15 @@ impl string { return out } - pub fn split(self, delim: u8) -> Vec { - let out = buffer::vec_string_new() + pub fn split(self, alloc: buffer::Alloc, delim: u8) -> Vec { + let out = alloc.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) + let part = build_range(alloc, self, start, i) match (out.push(part)) { Ok(_) => { } Err(_) => { panic() } @@ -201,7 +342,7 @@ impl string { } i = i + 1 } - let part = build_range(self, start, len) + let part = build_range(alloc, self, start, len) match (out.push(part)) { Ok(_) => { } Err(_) => { panic() } @@ -209,14 +350,15 @@ impl string { return out } - pub fn split_once(self, delim: u8) -> Result { + /// Split once on the first matching delimiter. + pub fn split_once(self, alloc: buffer::Alloc, 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) + let left = build_range(alloc, self, 0, i) + let right = build_range(alloc, self, i + 1, len) return Ok(SplitOnce { left: left, right: right @@ -227,12 +369,14 @@ impl string { return Err(()) } - pub fn trim(self) -> string { - let start_trimmed = self.trim_start() - return start_trimmed.trim_end() + /// Trim ASCII whitespace from both ends. + pub fn trim(self, alloc: buffer::Alloc) -> string { + let start_trimmed = self.trim_start(alloc) + return start_trimmed.trim_end(alloc) } - pub fn trim_start(self) -> string { + /// Trim ASCII whitespace from the start. + pub fn trim_start(self, alloc: buffer::Alloc) -> string { let bytes = self.as_slice() let len = bytes.len() let i = 0 @@ -245,10 +389,11 @@ impl string { if (i == 0) { return self } - return build_range(self, i, len) + return build_range(alloc, self, i, len) } - pub fn trim_end(self) -> string { + /// Trim ASCII whitespace from the end. + pub fn trim_end(self, alloc: buffer::Alloc) -> string { let bytes = self.as_slice() let len = bytes.len() if (len == 0) { @@ -267,28 +412,31 @@ impl string { if (i == 0) { return "" } - return build_range(self, 0, i) + return build_range(alloc, self, 0, i) } - pub fn trim_prefix(self, prefix: string) -> string { + /// Remove a leading prefix if present. + pub fn trim_prefix(self, alloc: buffer::Alloc, prefix: string) -> string { if (self.starts_with(prefix)) { - return build_range(self, prefix.len(), self.len()) + return build_range(alloc, self, prefix.len(), self.len()) } return self } - pub fn trim_suffix(self, suffix: string) -> string { + /// Remove a trailing suffix if present. + pub fn trim_suffix(self, alloc: buffer::Alloc, suffix: string) -> string { if (self.ends_with(suffix)) { - return build_range(self, 0, self.len() - suffix.len()) + return build_range(alloc, self, 0, self.len() - suffix.len()) } return self } /// split_lines() is an alias for lines(). - pub fn split_lines(self) -> Vec { - return self.lines() + pub fn split_lines(self, alloc: buffer::Alloc) -> Vec { + return self.lines(alloc) } + /// True if the string starts with the prefix. pub fn starts_with(self, prefix: string) -> bool { let self_len = self.len() let prefix_len = prefix.len() @@ -305,6 +453,7 @@ impl string { return true } + /// True if the string ends with the suffix. pub fn ends_with(self, suffix: string) -> bool { let self_len = self.len() let suffix_len = suffix.len() @@ -322,6 +471,7 @@ impl string { return true } + /// True if the first byte matches. pub fn starts_with_byte(self, prefix: u8) -> bool { if (self.len() == 0) { return false @@ -329,6 +479,7 @@ impl string { return self.byte_at(0) == prefix } + /// True if the last byte matches. pub fn ends_with_byte(self, suffix: u8) -> bool { let len = self.len() if (len == 0) { @@ -337,6 +488,7 @@ impl string { return self.byte_at(len - 1) == suffix } + /// Byte-wise equality. pub fn eq(self, other: string) -> bool { let self_len = self.len() let other_len = other.len() @@ -353,10 +505,12 @@ impl string { return true } + /// True if empty. pub fn is_empty(self) -> bool { return self.len() == 0 } + /// Checked byte access. pub fn byte_at_checked(self, index: i32) -> Result { if (index < 0) { return Err(()) @@ -367,6 +521,7 @@ impl string { return Ok(self.byte_at(index)) } + /// Find the first matching byte. pub fn index_of_byte(self, needle: u8) -> Result { let bytes = self.as_slice() let len = bytes.len() @@ -380,6 +535,7 @@ impl string { return Err(()) } + /// Find the last matching byte. pub fn last_index_of_byte(self, needle: u8) -> Result { let bytes = self.as_slice() let len = bytes.len() @@ -399,6 +555,76 @@ impl string { return Err(()) } + /// Return the index of a substring if present. + pub fn index_of(self, needle: string) -> Result { + let hay = self.as_slice() + let hay_len = hay.len() + let needle_bytes = needle.as_slice() + let needle_len = needle_bytes.len() + if (needle_len == 0) { + return Ok(0) + } + if (needle_len > hay_len) { + return Err(()) + } + let i = 0 + while (i <= hay_len - needle_len) { + let j = 0 + while (j < needle_len) { + if (hay.at(i + j) != needle_bytes.at(j)) { + break + } + j = j + 1 + } + if (j == needle_len) { + return Ok(i) + } + i = i + 1 + } + return Err(()) + } + + /// Return the last index of a substring if present. + pub fn last_index_of(self, needle: string) -> Result { + let hay = self.as_slice() + let hay_len = hay.len() + let needle_bytes = needle.as_slice() + let needle_len = needle_bytes.len() + if (needle_len == 0) { + return Ok(hay_len) + } + if (needle_len > hay_len) { + return Err(()) + } + let i = hay_len - needle_len + while (true) { + let j = 0 + while (j < needle_len) { + if (hay.at(i + j) != needle_bytes.at(j)) { + break + } + j = j + 1 + } + if (j == needle_len) { + return Ok(i) + } + if (i == 0) { + break + } + i = i - 1 + } + return Err(()) + } + + /// True if the substring is present. + pub fn contains(self, needle: string) -> bool { + match (self.index_of(needle)) { + Ok(_) => { return true } + Err(_) => { return false } + } + } + + /// True if the byte is present. pub fn contains_byte(self, needle: u8) -> bool { match (self.index_of_byte(needle)) { Ok(_) => { return true } @@ -406,6 +632,7 @@ impl string { } } + /// Count occurrences of a byte. pub fn count_byte(self, needle: u8) -> i32 { let bytes = self.as_slice() let len = bytes.len() @@ -420,6 +647,7 @@ impl string { return count } + /// True if all bytes are ASCII. pub fn is_ascii(self) -> bool { let bytes = self.as_slice() let len = bytes.len() @@ -433,64 +661,59 @@ impl string { return true } - pub fn to_lower_ascii(self) -> string { + /// Lowercase ASCII letters. + pub fn to_lower_ascii(self, alloc: buffer::Alloc) -> 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() } - } + let buf = text_new(alloc) + let i = 0 + while (i < len) { + let b = bytes.at(i) + let lower = lower_ascii_byte(b) + match (buf.push_byte(lower)) { + Ok(_) => { } + Err(_) => { panic() } } + i = i + 1 + } + match (buf.to_string()) { + Ok(out) => { return out } Err(_) => { panic() } } } - pub fn to_upper_ascii(self) -> string { + /// Uppercase ASCII letters. + pub fn to_upper_ascii(self, alloc: buffer::Alloc) -> 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() } - } + let buf = text_new(alloc) + let i = 0 + while (i < len) { + let b = bytes.at(i) + let upper = upper_ascii_byte(b) + match (buf.push_byte(upper)) { + Ok(_) => { } + Err(_) => { panic() } } + i = i + 1 + } + match (buf.to_string()) { + Ok(out) => { return out } Err(_) => { panic() } } } - pub fn trim_ascii(self) -> string { - return self.trim() + /// Trim ASCII whitespace (alias of trim()). + pub fn trim_ascii(self, alloc: buffer::Alloc) -> string { + return self.trim(alloc) } + /// Alias for index_of_byte. pub fn find_byte(self, needle: u8) -> Result { return self.index_of_byte(needle) } + /// Alias for last_index_of_byte. pub fn rfind_byte(self, needle: u8) -> Result { return self.last_index_of_byte(needle) } diff --git a/stdlib/sys/system.cap b/stdlib/sys/system.cap index ac53258..de2af9d 100644 --- a/stdlib/sys/system.cap +++ b/stdlib/sys/system.cap @@ -1,3 +1,4 @@ +/// Root capability minting. package unsafe module sys::system use sys::console @@ -7,33 +8,41 @@ use sys::stdin use sys::buffer use sys::net +/// Root capability for minting sub-capabilities. pub copy capability struct RootCap impl RootCap { + /// Mint console access. pub fn mint_console(self) -> console::Console { return () } + /// Mint a read-only filesystem rooted at path. pub fn mint_readfs(self, root: string) -> fs::ReadFS { return () } + /// Mint a filesystem rooted at path. pub fn mint_filesystem(self, root: string) -> fs::Filesystem { return () } + /// Mint a default Alloc. pub fn mint_alloc_default(self) -> buffer::Alloc { return () } + /// Mint argument access. pub fn mint_args(self) -> args::Args { return () } + /// Mint stdin access. pub fn mint_stdin(self) -> stdin::Stdin { return () } + /// Mint network access. pub fn mint_net(self) -> net::Net { return () } diff --git a/stdlib/sys/unsafe_ptr.cap b/stdlib/sys/unsafe_ptr.cap new file mode 100644 index 0000000..a03519a --- /dev/null +++ b/stdlib/sys/unsafe_ptr.cap @@ -0,0 +1,35 @@ +/// Unsafe pointer and layout helpers for stdlib internals. +package unsafe +module sys::unsafe_ptr + +/// Return the size of T in bytes. +pub extern fn sizeof() -> i32 + +/// Return the alignment of T in bytes. +pub extern fn alignof() -> i32 + +/// Add element offset to a pointer. +pub extern fn ptr_add(ptr: *T, offset: i32) -> *T + +/// Read a value from a pointer (unchecked). +pub extern fn ptr_read(ptr: *T) -> T + +/// Write a value to a pointer (unchecked). +pub extern fn ptr_write(ptr: *T, value: T) -> unit + +/// Reinterpret a *u8 as a pointer to T. +pub extern fn ptr_cast(ptr: *u8) -> *T + +/// Reinterpret a pointer to T as *u8. +pub extern fn ptr_cast_u8(ptr: *T) -> *u8 + +/// Return true if the pointer is null. +/// This does not imply ownership or validity beyond null/non-null. +pub extern fn ptr_is_null(ptr: *T) -> bool + +/// Copy `count` elements from `src` to `dst` (no overlap). +/// Use memmove when regions might overlap. +pub extern fn memcpy(dst: *T, src: *T, count: i32) -> unit + +/// Copy `count` elements from `src` to `dst` (overlap-safe). +pub extern fn memmove(dst: *T, src: *T, count: i32) -> unit diff --git a/stdlib/sys/vec.cap b/stdlib/sys/vec.cap index c083760..69bfa7f 100644 --- a/stdlib/sys/vec.cap +++ b/stdlib/sys/vec.cap @@ -1,129 +1,322 @@ +/// Vec helpers for built-in element types. +/// Vec is an owned, heap-backed growable container (not a view). +/// Use Slice/MutSlice for non-owning views into byte data. package safe module sys::vec use sys::buffer +use sys::string +use sys::unsafe_ptr + +/// Internal Vec header stored on the heap. +copy struct VecHeader { + raw: *u8, + len: i32, + cap: i32, + elem_size: i32, + alloc: buffer::Alloc +} -pub copy opaque struct Vec -pub copy opaque struct VecU8 -pub copy opaque struct VecI32 -pub copy opaque struct VecString +/// Generic Vec marker type (owned, heap-backed container). +pub copy opaque struct Vec { + header: *u8 +} +/// Vec errors. pub enum VecErr { + /// Index is out of range. OutOfRange, + /// Vec is empty. Empty } -impl VecU8 { - pub fn len(self) -> i32 { - return 0 +/// Allocate a new Vec header. +fn alloc_header(alloc: buffer::Alloc) -> *u8 { + let bytes = unsafe_ptr::sizeof() + let header = alloc.malloc(bytes) + let header_ptr: *sys::vec::VecHeader = unsafe_ptr::ptr_cast(header) + if (unsafe_ptr::ptr_is_null(header_ptr)) { + panic() } + return header +} - pub fn is_empty(self) -> bool { - return self.len() == 0 +/// Allocate element storage for the given capacity. +fn alloc_elems(alloc: buffer::Alloc, cap: i32) -> *u8 { + if (cap <= 0) { + return alloc.malloc(0) } + let size = unsafe_ptr::sizeof() + let bytes = size * cap + return alloc.malloc(bytes) +} - pub fn get(self, i: i32) -> Result { - return Err(VecErr::OutOfRange) - } +/// Free element storage. +fn free_elems(alloc: buffer::Alloc, raw: *u8) -> unit { + alloc.free(raw) + return () +} - pub fn set(self, i: i32, x: u8) -> Result { - return Err(VecErr::OutOfRange) - } +/// Read the header value. +fn read_header(v: Vec) -> sys::vec::VecHeader { + let header_ptr: *sys::vec::VecHeader = unsafe_ptr::ptr_cast(v.header) + return unsafe_ptr::ptr_read(header_ptr) +} - pub fn push(self, x: u8) -> Result { - return Err(buffer::AllocErr::Oom) +/// Write the header value. +fn write_header(v: Vec, header: sys::vec::VecHeader) -> unit { + let header_ptr: *sys::vec::VecHeader = unsafe_ptr::ptr_cast(v.header) + unsafe_ptr::ptr_write(header_ptr, header) + return () +} + +/// Cast a raw pointer to a typed pointer at the given element index. +fn elem_ptr(raw: *u8, elem_size: i32, index: i32) -> *T { + let offset = elem_size * index + let ptr = unsafe_ptr::ptr_add(raw, offset) + return unsafe_ptr::ptr_cast(ptr) +} + +/// Copy elements from src to dst. +fn copy_elems(dst_raw: *u8, src_raw: *u8, elem_size: i32, count: i32) -> unit { + let i = 0 + while (i < count) { + let value = unsafe_ptr::ptr_read(elem_ptr(src_raw, elem_size, i)) + unsafe_ptr::ptr_write(elem_ptr(dst_raw, elem_size, i), value) + i = i + 1 } + return () +} - pub fn extend(self, other: VecU8) -> Result { +/// Grow to a new capacity. +fn grow_to(v: Vec, header: sys::vec::VecHeader, new_cap: i32) -> Result { + let new_raw = alloc_elems(header.alloc, new_cap) + if (new_cap > 0 && unsafe_ptr::ptr_is_null(new_raw)) { return Err(buffer::AllocErr::Oom) } + copy_elems(new_raw, header.raw, header.elem_size, header.len) + free_elems(header.alloc, header.raw) + let new_header = sys::vec::VecHeader { + raw: new_raw, + len: header.len, + cap: new_cap, + elem_size: header.elem_size, + alloc: header.alloc + } + write_header(v, new_header) + return Ok(()) +} - 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 - } +/// Ensure capacity for a target length. +fn ensure_capacity(v: Vec, header: sys::vec::VecHeader, needed: i32) -> Result { + if (needed <= header.cap) { return Ok(()) } - - pub fn push_all(self, other: VecU8) -> Result { - return self.extend(other) - } - - pub fn filter(self, value: u8) -> VecU8 { - return () + let new_cap = header.cap + if (new_cap < 4) { + new_cap = 4 + } else { + new_cap = new_cap * 2 } - - pub fn map_add(self, delta: u8) -> VecU8 { - return () + while (new_cap < needed) { + new_cap = new_cap * 2 } + return grow_to(v, header, new_cap) +} - pub fn slice(self, start: i32, len: i32) -> Result, VecErr> { - return Err(VecErr::OutOfRange) - } +/// Create a new Vec with the provided allocator. +pub fn new_with(alloc: buffer::Alloc) -> Vec { + let header_ptr = alloc_header(alloc) + let raw = alloc.malloc(0) + let header = sys::vec::VecHeader { + raw: raw, + len: 0, + cap: 0, + elem_size: unsafe_ptr::sizeof(), + alloc: alloc + } + write_header(Vec { header: header_ptr }, header) + return Vec { header: header_ptr } +} - pub fn pop(self) -> Result { - return Err(VecErr::Empty) +/// Create a new Vec with capacity using the provided allocator. +pub fn with_capacity(alloc: buffer::Alloc, capacity: i32) -> Result, buffer::AllocErr> { + let cap = capacity + if (cap < 0) { + cap = 0 } - - pub fn as_slice(self) -> Slice { - return () + let header_ptr = alloc_header(alloc) + let raw = alloc_elems(alloc, cap) + if (cap > 0 && unsafe_ptr::ptr_is_null(raw)) { + free_elems(alloc, header_ptr) + return Err(buffer::AllocErr::Oom) } - - pub fn clear(self) -> unit { - while (true) { - match (self.pop()) { - Ok(_) => { } - Err(_) => { break } - } - } - return () + let header = sys::vec::VecHeader { + raw: raw, + len: 0, + cap: cap, + elem_size: unsafe_ptr::sizeof(), + alloc: alloc } + write_header(Vec { header: header_ptr }, header) + return Ok(Vec { header: header_ptr }) } -impl VecI32 { +impl Vec { + /// Length in elements. pub fn len(self) -> i32 { - return 0 + let header = read_header(self) + return header.len } + /// Capacity in elements. + pub fn capacity(self) -> i32 { + let header = read_header(self) + return header.cap + } + + /// True if empty. pub fn is_empty(self) -> bool { return self.len() == 0 } - pub fn get(self, i: i32) -> Result { - return Err(VecErr::OutOfRange) + /// Get element at index. + pub fn get(self, i: i32) -> Result { + let header = read_header(self) + if (i < 0 || i >= header.len) { + return Err(VecErr::OutOfRange) + } + let ptr = elem_ptr(header.raw, header.elem_size, i) + return Ok(unsafe_ptr::ptr_read(ptr)) } - pub fn set(self, i: i32, x: i32) -> Result { - return Err(VecErr::OutOfRange) + /// Get the first element. + pub fn first(self) -> Result { + return self.get(0) } - pub fn push(self, x: i32) -> Result { - return Err(buffer::AllocErr::Oom) + /// Get the last element. + pub fn last(self) -> Result { + let len = self.len() + if (len == 0) { + return Err(VecErr::Empty) + } + return self.get(len - 1) } - pub fn extend(self, other: VecI32) -> Result { - return Err(buffer::AllocErr::Oom) + /// Set element at index. + pub fn set(self, i: i32, x: T) -> Result { + let header = read_header(self) + if (i < 0 || i >= header.len) { + return Err(VecErr::OutOfRange) + } + let ptr = elem_ptr(header.raw, header.elem_size, i) + unsafe_ptr::ptr_write(ptr, x) + return Ok(()) } - pub fn push_all(self, other: VecI32) -> Result { - return self.extend(other) + /// Append one element. + pub fn push(self, x: T) -> Result { + self.reserve(1)? + let header = read_header(self) + let ptr = elem_ptr(header.raw, header.elem_size, header.len) + unsafe_ptr::ptr_write(ptr, x) + let new_header = sys::vec::VecHeader { + raw: header.raw, + len: header.len + 1, + cap: header.cap, + elem_size: header.elem_size, + alloc: header.alloc + } + write_header(self, new_header) + return Ok(()) } - pub fn filter(self, value: i32) -> VecI32 { - return () + /// Append another Vec. + pub fn extend(self, other: Vec) -> Result { + let other_header = read_header(other) + self.reserve(other_header.len)? + let i = 0 + while (i < other_header.len) { + let value = unsafe_ptr::ptr_read(elem_ptr(other_header.raw, other_header.elem_size, i)) + self.push(value)? + i = i + 1 + } + return Ok(()) } - pub fn map_add(self, delta: i32) -> VecI32 { + /// Reserve additional capacity. + pub fn reserve(self, additional: i32) -> Result { + if (additional <= 0) { + return Ok(()) + } + let header = read_header(self) + let needed = header.len + additional + return ensure_capacity(self, header, needed) + } + + /// Shrink capacity to fit length. + pub fn shrink_to_fit(self) -> unit { + let header = read_header(self) + if (header.len == header.cap) { + return () + } + if (header.len == 0) { + free_elems(header.alloc, header.raw) + let raw = header.alloc.malloc(0) + let new_header = sys::vec::VecHeader { + raw: raw, + len: 0, + cap: 0, + elem_size: header.elem_size, + alloc: header.alloc + } + write_header(self, new_header) + return () + } + let new_raw = alloc_elems(header.alloc, header.len) + if (header.len > 0 && unsafe_ptr::ptr_is_null(new_raw)) { + return () + } + copy_elems(new_raw, header.raw, header.elem_size, header.len) + free_elems(header.alloc, header.raw) + let new_header = sys::vec::VecHeader { + raw: new_raw, + len: header.len, + cap: header.len, + elem_size: header.elem_size, + alloc: header.alloc + } + write_header(self, new_header) return () } - pub fn pop(self) -> Result { - return Err(VecErr::Empty) + /// Append another Vec (alias). + pub fn push_all(self, other: Vec) -> Result { + return self.extend(other) + } + + /// Pop last element. + pub fn pop(self) -> Result { + let header = read_header(self) + if (header.len == 0) { + return Err(VecErr::Empty) + } + let index = header.len - 1 + let ptr = elem_ptr(header.raw, header.elem_size, index) + let value = unsafe_ptr::ptr_read(ptr) + let new_header = sys::vec::VecHeader { + raw: header.raw, + len: index, + cap: header.cap, + elem_size: header.elem_size, + alloc: header.alloc + } + write_header(self, new_header) + return Ok(value) } + /// Remove all elements. pub fn clear(self) -> unit { while (true) { match (self.pop()) { @@ -133,50 +326,203 @@ impl VecI32 { } return () } -} -impl VecString { - pub fn len(self) -> i32 { - return 0 + /// True if the Vec contains value. + pub fn contains(self, value: T) -> bool { + let len = self.len() + let i = 0 + while (i < len) { + match (self.get(i)) { + Ok(v) => { + if (v == value) { + return true + } + } + Err(_) => { return false } + } + i = i + 1 + } + return false } - pub fn is_empty(self) -> bool { - return self.len() == 0 + /// Count occurrences of value. + pub fn count(self, value: T) -> i32 { + let len = self.len() + let i = 0 + let out = 0 + while (i < len) { + match (self.get(i)) { + Ok(v) => { + if (v == value) { + out = out + 1 + } + } + Err(_) => { return out } + } + i = i + 1 + } + return out } - pub fn get(self, i: i32) -> Result { + /// Find the first index of value. + pub fn index_of(self, value: T) -> Result { + let len = self.len() + let i = 0 + while (i < len) { + match (self.get(i)) { + Ok(v) => { + if (v == value) { + return Ok(i) + } + } + Err(_) => { return Err(VecErr::OutOfRange) } + } + i = i + 1 + } return Err(VecErr::OutOfRange) } - pub fn push(self, x: string) -> Result { - return Err(buffer::AllocErr::Oom) + /// Find the last index of value. + pub fn last_index_of(self, value: T) -> Result { + let len = self.len() + let i = len - 1 + while (i >= 0) { + match (self.get(i)) { + Ok(v) => { + if (v == value) { + return Ok(i) + } + } + Err(_) => { return Err(VecErr::OutOfRange) } + } + i = i - 1 + } + return Err(VecErr::OutOfRange) } - pub fn extend(self, other: VecString) -> Result { - return Err(buffer::AllocErr::Oom) + /// Filter elements equal to value (numeric vecs only). + pub fn filter(self, value: T) -> Vec { + let header = read_header(self) + let out = new_with(header.alloc) + let len = self.len() + let i = 0 + while (i < len) { + match (self.get(i)) { + Ok(v) => { + if (v == value) { + match (out.push(v)) { + Ok(_) => { } + Err(_) => { panic() } + } + } + } + Err(_) => { return out } + } + i = i + 1 + } + return out } - pub fn push_all(self, other: VecString) -> Result { - return self.extend(other) + /// Add delta to each element. + pub fn map_add(self, delta: T) -> Vec { + let header = read_header(self) + let out = new_with(header.alloc) + let len = self.len() + let i = 0 + while (i < len) { + match (self.get(i)) { + Ok(v) => { + match (out.push(v + delta)) { + Ok(_) => { } + Err(_) => { panic() } + } + } + Err(_) => { return out } + } + i = i + 1 + } + return out } - pub fn pop(self) -> Result { - return Err(VecErr::Empty) + /// Free the backing storage and header. + pub fn free(self) -> unit { + let header = read_header(self) + free_elems(header.alloc, header.raw) + free_elems(header.alloc, self.header) + return () } +} - pub fn clear(self) -> unit { - while (true) { - match (self.pop()) { - Ok(_) => { } - Err(_) => { break } - } +impl Vec { + /// Append a slice of bytes. + 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 () + return Ok(()) + } + + /// Create a slice view of a range. + pub fn slice(self, start: i32, len: i32) -> Result, VecErr> { + let header = read_header(self) + if (start < 0 || len < 0) { + return Err(VecErr::OutOfRange) + } + if (start + len > header.len) { + return Err(VecErr::OutOfRange) + } + let ptr = elem_ptr(header.raw, header.elem_size, start) + return Ok(header.alloc.slice_from_ptr(ptr, len)) + } + + /// Create a slice view of a range using start/end bounds (end exclusive). + pub fn slice_range(self, start: i32, end: i32) -> Result, VecErr> { + if (start < 0 || end < 0) { + return Err(VecErr::OutOfRange) + } + if (start > end) { + return Err(VecErr::OutOfRange) + } + return self.slice(start, end - start) } + /// Borrow as a slice. + pub fn as_slice(self) -> Slice { + let header = read_header(self) + return header.alloc.slice_from_ptr(header.raw, header.len) + } + + /// Copy contents into a new owned slice using the Vec allocator. + pub fn copy_slice(self) -> Result, buffer::AllocErr> { + let header = read_header(self) + if (header.len == 0) { + let raw = header.alloc.malloc(0) + return Ok(header.alloc.slice_from_ptr(raw, 0)) + } + let raw = alloc_elems(header.alloc, header.len) + if (unsafe_ptr::ptr_is_null(raw)) { + return Err(buffer::AllocErr::Oom) + } + copy_elems(raw, header.raw, header.elem_size, header.len) + return Ok(header.alloc.slice_from_ptr(raw, header.len)) + } + + /// Copy contents into a new string view. + pub fn to_string(self) -> Result { + let owned = self.copy_slice()? + return string::from_bytes(owned) + } +} + +impl Vec { + /// Join elements with a separator. pub fn join(self, sep: string) -> Result { let len = self.len() - let buf = buffer::new(0)? + let header = read_header(self) + let buf = string::text_new(header.alloc) let i = 0 while (i < len) { let part = match (self.get(i)) { diff --git a/tests/programs/attenuation_untrusted_fail.cap b/tests/programs/attenuation_untrusted_fail.cap index e171e9e..d3f8b7b 100644 --- a/tests/programs/attenuation_untrusted_fail.cap +++ b/tests/programs/attenuation_untrusted_fail.cap @@ -4,11 +4,12 @@ use sys::fs use untrusted_logs pub fn main(rc: RootCap) -> i32 { + let alloc = rc.mint_alloc_default() let fs = rc.mint_filesystem("./config") let d = fs.root_dir() - let res = untrusted_logs::read_log(d) + let res = untrusted_logs::read_log(alloc, d) let file = d.open_read("app.txt") - let tmp = file.read_to_string() + let tmp = file.read_to_string(alloc) match (res) { Ok(_) => { return 0 } Err(_) => { return 1 } diff --git a/tests/programs/attenuation_untrusted_pass.cap b/tests/programs/attenuation_untrusted_pass.cap index a726d98..a90d89d 100644 --- a/tests/programs/attenuation_untrusted_pass.cap +++ b/tests/programs/attenuation_untrusted_pass.cap @@ -4,9 +4,10 @@ use sys::fs use untrusted_logs pub fn main(rc: RootCap) -> i32 { + let alloc = rc.mint_alloc_default() let fs = rc.mint_filesystem("./config") let d = fs.root_dir() - let res = untrusted_logs::read_log(d) + let res = untrusted_logs::read_log(alloc, d) match (res) { Ok(_) => { return 0 } Err(_) => { return 1 } diff --git a/tests/programs/buffer_push_safe.cap b/tests/programs/buffer_push_safe.cap deleted file mode 100644 index 5c295fb..0000000 --- a/tests/programs/buffer_push_safe.cap +++ /dev/null @@ -1,55 +0,0 @@ -package safe -module buffer_push_safe -use sys::system - -pub fn main(rc: RootCap) -> i32 { - let c = rc.mint_console() - let alloc = rc.mint_alloc_default() - match alloc.buffer_new(1) { - Ok(b) => { - match b.push(7u8) { - Ok(u) => { u } - Err(e) => { - c.println("push err") - alloc.buffer_free(b) - return 1 - } - } - let v = alloc.vec_u8_new() - match v.push(9u8) { - Ok(u) => { u } - Err(e) => { - c.println("vec err") - alloc.vec_u8_free(v) - alloc.buffer_free(b) - return 1 - } - } - let slice = v.as_slice() - match b.extend(slice) { - Ok(u) => { u } - Err(e) => { - c.println("extend err") - alloc.vec_u8_free(v) - alloc.buffer_free(b) - return 1 - } - } - let len = b.len() - c.assert(len == 3) - c.assert(!b.is_empty()) - if (len == 3) { - c.println("push ok") - } else { - c.println("push bad") - } - alloc.vec_u8_free(v) - alloc.buffer_free(b) - return 0 - } - Err(e) => { - c.println("buffer err") - return 1 - } - } -} diff --git a/tests/programs/buffer_safe.cap b/tests/programs/buffer_safe.cap deleted file mode 100644 index af6eb20..0000000 --- a/tests/programs/buffer_safe.cap +++ /dev/null @@ -1,26 +0,0 @@ -package safe -module buffer_safe -use sys::system - -pub fn main(rc: RootCap) -> i32 { - let c = rc.mint_console() - let alloc = rc.mint_alloc_default() - match alloc.buffer_new(3) { - Ok(b) => { - let slice = b.as_slice() - let len = slice.len() - c.assert(len == 3) - if (len == 3) { - c.println("buffer ok") - } else { - c.println("buffer bad") - } - alloc.buffer_free(b) - return 0 - } - Err(e) => { - c.println("buffer err") - return 1 - } - } -} diff --git a/tests/programs/buffer_unsafe.cap b/tests/programs/buffer_unsafe.cap deleted file mode 100644 index d3473e7..0000000 --- a/tests/programs/buffer_unsafe.cap +++ /dev/null @@ -1,33 +0,0 @@ -package unsafe -module buffer_unsafe -use sys::system - -pub fn main(rc: RootCap) -> i32 { - let c = rc.mint_console() - let alloc = rc.mint_alloc_default() - match alloc.buffer_new(3) { - Ok(b) => { - let len = b.len() - c.assert(len == 3) - if (len == 3) { - c.println("buffer ok") - } else { - c.println("buffer bad") - } - let slice = b.as_slice() - let s_len = slice.len() - c.assert(s_len == 3) - if (s_len == 3) { - c.println("slice ok") - } else { - c.println("slice bad") - } - alloc.buffer_free(b) - return 0 - } - Err(e) => { - c.println("buffer err") - return 1 - } - } -} diff --git a/tests/programs/bytes_helpers.cap b/tests/programs/bytes_helpers.cap index 6bc54bc..580d1b9 100644 --- a/tests/programs/bytes_helpers.cap +++ b/tests/programs/bytes_helpers.cap @@ -8,6 +8,11 @@ pub fn main(rc: RootCap) -> i32 { let buf = s.bytes() let b0 = buf.at(0) let b1 = buf.at(1) + let tail = buf.slice_range(1, 2) + match (tail) { + Ok(t) => { c.assert(t.len() == 1 && t.at(0) == '\n') } + Err(_) => { c.assert(false) } + } c.assert(b0.is_whitespace() && b1.is_whitespace()) c.println("bytes ok") return 0 diff --git a/tests/programs/enum_payload_basic.cap b/tests/programs/enum_payload_basic.cap new file mode 100644 index 0000000..759f23a --- /dev/null +++ b/tests/programs/enum_payload_basic.cap @@ -0,0 +1,36 @@ +module enum_payload_basic +use sys::system + +enum Maybe { + Some(T), + None +} + +fn mk(flag: bool) -> Maybe { + if (flag) { + return Maybe::Some(7) + } + return Maybe::None +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + match mk(true) { + Maybe::Some(v) => { + c.assert(v == 7) + c.println("some") + } + Maybe::None => { + c.assert(false) + } + } + match mk(false) { + Maybe::Some(_) => { + c.assert(false) + } + Maybe::None => { + c.println("none") + } + } + return 0 +} diff --git a/tests/programs/enum_payload_string.cap b/tests/programs/enum_payload_string.cap new file mode 100644 index 0000000..509176f --- /dev/null +++ b/tests/programs/enum_payload_string.cap @@ -0,0 +1,36 @@ +module enum_payload_string +use sys::system + +enum Either { + Left(T), + Right(E) +} + +fn pick(flag: bool) -> Either { + if (flag) { + return Either::Left("hello") + } + return Either::Right(4) +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + match pick(true) { + Either::Left(s) => { + c.assert(s.len() == 5) + c.println("left") + } + Either::Right(_) => { + c.assert(false) + } + } + match pick(false) { + Either::Left(_) => { + c.assert(false) + } + Either::Right(n) => { + c.assert(n == 4) + } + } + return 0 +} diff --git a/tests/programs/enum_payload_struct.cap b/tests/programs/enum_payload_struct.cap new file mode 100644 index 0000000..ab88830 --- /dev/null +++ b/tests/programs/enum_payload_struct.cap @@ -0,0 +1,36 @@ +module enum_payload_struct +use sys::system + +struct Pair { + a: i32, + b: i32 +} + +enum Wrap { + One(i32), + Two(Pair) +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let w = Wrap::Two(Pair { a: 1, b: 2 }) + match w { + Wrap::Two(p) => { + c.assert(p.a + p.b == 3) + c.println("pair") + } + Wrap::One(_) => { + c.assert(false) + } + } + let w2 = Wrap::One(9) + match w2 { + Wrap::One(v) => { + c.assert(v == 9) + } + Wrap::Two(_) => { + c.assert(false) + } + } + return 0 +} diff --git a/tests/programs/fs_attenuation.cap b/tests/programs/fs_attenuation.cap index cb465d6..dc5d1a9 100644 --- a/tests/programs/fs_attenuation.cap +++ b/tests/programs/fs_attenuation.cap @@ -3,11 +3,12 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() + let alloc = rc.mint_alloc_default() let fs = rc.mint_filesystem("./config") let root = fs.root_dir() let file = root.open_read("app.txt") - match file.read_to_string() { + match file.read_to_string(alloc) { Ok(s) => { c.println(s); return 0 } Err(e) => { c.println("read failed"); return 1 } } diff --git a/tests/programs/fs_helpers.cap b/tests/programs/fs_helpers.cap index 7869340..1a2a1f6 100644 --- a/tests/programs/fs_helpers.cap +++ b/tests/programs/fs_helpers.cap @@ -16,7 +16,7 @@ pub fn main(rc: RootCap) -> i32 { } let rfs2 = rc.mint_readfs("./config") - match rfs2.read_bytes("app.txt") { + match rfs2.read_bytes(alloc, "app.txt") { Ok(bytes) => { c.assert(bytes.len() > 0) alloc.vec_u8_free(bytes) @@ -28,7 +28,7 @@ pub fn main(rc: RootCap) -> i32 { } let rfs3 = rc.mint_readfs("./config") - match rfs3.list_dir(".") { + match rfs3.list_dir(alloc, ".") { Ok(entries) => { c.assert(entries.len() > 0) alloc.vec_string_free(entries) @@ -39,7 +39,7 @@ pub fn main(rc: RootCap) -> i32 { } } - let joined = fs::join("config", "app.txt") + let joined = fs::join(alloc, "config", "app.txt") if !joined.starts_with("config") { c.println("join failed") return 1 diff --git a/tests/programs/fs_read.cap b/tests/programs/fs_read.cap index 368f5a6..b7a6e9f 100644 --- a/tests/programs/fs_read.cap +++ b/tests/programs/fs_read.cap @@ -3,9 +3,10 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() + let alloc = rc.mint_alloc_default() let rfs = rc.mint_readfs("./config") - match rfs.read_to_string("app.txt") { + match rfs.read_to_string(alloc, "app.txt") { Ok(s) => { c.println(s); return 0 } Err(e) => { c.println("read failed"); return 1 } } diff --git a/tests/programs/fs_symlink_escape.cap b/tests/programs/fs_symlink_escape.cap index 66e4529..b72b9cc 100644 --- a/tests/programs/fs_symlink_escape.cap +++ b/tests/programs/fs_symlink_escape.cap @@ -3,8 +3,9 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() + let alloc = rc.mint_alloc_default() let rfs = rc.mint_readfs("tests/fixtures/config") - match rfs.read_to_string("link.txt") { + match rfs.read_to_string(alloc, "link.txt") { Ok(_) => { c.println("BUG: symlink escape") return 2 diff --git a/tests/programs/fs_traversal_denied.cap b/tests/programs/fs_traversal_denied.cap index 433895d..b843498 100644 --- a/tests/programs/fs_traversal_denied.cap +++ b/tests/programs/fs_traversal_denied.cap @@ -3,9 +3,10 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() + let alloc = rc.mint_alloc_default() let rfs = rc.mint_readfs("./config") - match rfs.read_to_string("../secrets.txt") { + match rfs.read_to_string(alloc, "../secrets.txt") { Ok(s) => { c.assert(false) c.println("BUG: escaped") diff --git a/tests/programs/fs_traversal_kind.cap b/tests/programs/fs_traversal_kind.cap index 86a1df5..9655116 100644 --- a/tests/programs/fs_traversal_kind.cap +++ b/tests/programs/fs_traversal_kind.cap @@ -4,9 +4,10 @@ use sys::fs pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() + let alloc = rc.mint_alloc_default() let rfs = rc.mint_readfs("./config") - match rfs.read_to_string("../secrets.txt") { + match rfs.read_to_string(alloc, "../secrets.txt") { Ok(s) => { c.assert(false) c.println("BUG: escaped") diff --git a/tests/programs/generic_and_index.cap b/tests/programs/generic_and_index.cap index 9e81080..2bbc5d0 100644 --- a/tests/programs/generic_and_index.cap +++ b/tests/programs/generic_and_index.cap @@ -25,7 +25,7 @@ pub fn main(rc: RootCap) -> i32 { // Test indexing with [] let s = "hello" let first = s[0] - if (first != 104u8) { // 'h' is 104 + if (first != 'h') { // expect 'h' c.println("FAIL: index") return 1 } @@ -44,7 +44,7 @@ pub fn main(rc: RootCap) -> i32 { let s2 = "world" let idx = identity(1) let ch = s2[idx] - if (ch != 111u8) { // 'o' is 111 + if (ch != 'o') { // expect 'o' c.println("FAIL: combo") return 1 } diff --git a/tests/programs/match_expr.cap b/tests/programs/match_expr.cap index 0d523a5..5f1858a 100644 --- a/tests/programs/match_expr.cap +++ b/tests/programs/match_expr.cap @@ -9,9 +9,9 @@ pub fn main(rc: RootCap) -> i32 { } c.assert( v.len() == 3 && - v.byte_at(0) == 121u8 && - v.byte_at(1) == 101u8 && - v.byte_at(2) == 115u8 + v.byte_at(0) == 'y' && + v.byte_at(1) == 'e' && + v.byte_at(2) == 's' ) c.println(v) return 0 diff --git a/tests/programs/option_basic.cap b/tests/programs/option_basic.cap new file mode 100644 index 0000000..b7fc609 --- /dev/null +++ b/tests/programs/option_basic.cap @@ -0,0 +1,19 @@ +module option_basic +use sys::system +use sys::option + +fn get(flag: bool) -> option::Option { + if (flag) { + return option::Option::Some("ok") + } + return option::Option::None +} + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + c.assert(get(true).is_some()) + c.assert(get(false).is_none()) + c.println(get(true).unwrap_or("bad")) + c.println(get(false).unwrap_or("default")) + return 0 +} diff --git a/tests/programs/result_ptr_generic.cap b/tests/programs/result_ptr_generic.cap new file mode 100644 index 0000000..7807446 --- /dev/null +++ b/tests/programs/result_ptr_generic.cap @@ -0,0 +1,7 @@ +package unsafe +module result_ptr_generic +use sys::result + +pub fn id_ptr(x: *T) -> Result<*T, unit> { + return Ok(x) +} diff --git a/tests/programs/result_unit_ok.cap b/tests/programs/result_unit_ok.cap index 8dd2c3e..137a0b3 100644 --- a/tests/programs/result_unit_ok.cap +++ b/tests/programs/result_unit_ok.cap @@ -5,20 +5,13 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let alloc = rc.mint_alloc_default() - match alloc.buffer_new(1) { - Ok(b) => { - let ok = match b.push(42u8) { - Ok(unit_val) => { 1 } - Err(e) => { 0 } - } - c.assert(ok == 1) - c.println("result unit ok - got Ok(unit)") - alloc.buffer_free(b) - return 0 - } - Err(e) => { - c.println("buffer new failed") - return 1 - } + let v = alloc.vec_u8_new() + let ok = match v.push('*') { + Ok(unit_val) => { 1 } + Err(e) => { 0 } } + c.assert(ok == 1) + c.println("result unit ok - got Ok(unit)") + alloc.vec_u8_free(v) + return 0 } diff --git a/tests/programs/should_fail_attenuation_reuse_fileread.cap b/tests/programs/should_fail_attenuation_reuse_fileread.cap index fa8cf70..73310d6 100644 --- a/tests/programs/should_fail_attenuation_reuse_fileread.cap +++ b/tests/programs/should_fail_attenuation_reuse_fileread.cap @@ -3,10 +3,11 @@ module should_fail_attenuation_reuse_fileread use sys::system pub fn main(rc: RootCap) -> i32 { + let alloc = rc.mint_alloc_default() let fs = rc.mint_filesystem("./config") let d = fs.root_dir() let f = d.open_read("app.txt") - let s = f.read_to_string() - let t = f.read_to_string() + let s = f.read_to_string(alloc) + let t = f.read_to_string(alloc) return 0 } diff --git a/tests/programs/should_fail_console_as_alloc.cap b/tests/programs/should_fail_console_as_alloc.cap index 8679056..cfe1ff2 100644 --- a/tests/programs/should_fail_console_as_alloc.cap +++ b/tests/programs/should_fail_console_as_alloc.cap @@ -3,6 +3,6 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() - let ignored = c.buffer_new(1) + let ignored = c.vec_u8_new() return 0 } diff --git a/tests/programs/should_fail_numeric_add_mismatch.cap b/tests/programs/should_fail_numeric_add_mismatch.cap index 50eeb39..49e2e9d 100644 --- a/tests/programs/should_fail_numeric_add_mismatch.cap +++ b/tests/programs/should_fail_numeric_add_mismatch.cap @@ -2,6 +2,6 @@ module app fn main() -> unit { let a: i32 = 1 - let b = 2u8 + let b = '\x02' let _c = a + b } diff --git a/tests/programs/should_fail_numeric_cmp_mismatch.cap b/tests/programs/should_fail_numeric_cmp_mismatch.cap index 47f90cc..bd7643c 100644 --- a/tests/programs/should_fail_numeric_cmp_mismatch.cap +++ b/tests/programs/should_fail_numeric_cmp_mismatch.cap @@ -1,7 +1,7 @@ module app fn main() -> unit { - let a = 1u8 + let a = '\x01' let b: i32 = 2 if (a < b) { } diff --git a/tests/programs/should_fail_result_unwrap_or_mismatch.cap b/tests/programs/should_fail_result_unwrap_or_mismatch.cap index 12fb500..cc0498d 100644 --- a/tests/programs/should_fail_result_unwrap_or_mismatch.cap +++ b/tests/programs/should_fail_result_unwrap_or_mismatch.cap @@ -5,6 +5,6 @@ fn make() -> Result { } pub fn main() -> i32 { - let v = make().unwrap_or(1u8) + let v = make().unwrap_or('\x01') return 0 } diff --git a/tests/programs/should_fail_slice_enum_field.cap b/tests/programs/should_fail_slice_enum_field.cap new file mode 100644 index 0000000..e2a3146 --- /dev/null +++ b/tests/programs/should_fail_slice_enum_field.cap @@ -0,0 +1,23 @@ +package safe +module should_fail_slice_enum_field +use sys::system +use sys::buffer + +enum Holder { + Bytes(Slice) +} + +pub fn main(rc: RootCap) -> i32 { + let alloc = rc.mint_alloc_default() + let v = alloc.vec_u8_new() + let s = v.as_slice() + let h = Holder::Bytes(s) + match h { + Holder::Bytes(b) => { + if (b.len() == 0) { + return 0 + } + } + } + return 1 +} diff --git a/tests/programs/should_fail_slice_return.cap b/tests/programs/should_fail_slice_return.cap new file mode 100644 index 0000000..37a3785 --- /dev/null +++ b/tests/programs/should_fail_slice_return.cap @@ -0,0 +1,19 @@ +package safe +module should_fail_slice_return +use sys::system +use sys::buffer + +fn leak(alloc: Alloc) -> Slice { + let v = alloc.vec_u8_new() + return v.as_slice() +} + +pub fn main(rc: RootCap) -> i32 { + let alloc = rc.mint_alloc_default() + let s = leak(alloc) + let n = s.len() + if (n == 0) { + return 0 + } + return 1 +} diff --git a/tests/programs/should_fail_slice_struct_field.cap b/tests/programs/should_fail_slice_struct_field.cap new file mode 100644 index 0000000..3e6388d --- /dev/null +++ b/tests/programs/should_fail_slice_struct_field.cap @@ -0,0 +1,19 @@ +package safe +module should_fail_slice_struct_field +use sys::system +use sys::buffer + +struct Holder { + data: Slice +} + +pub fn main(rc: RootCap) -> i32 { + let alloc = rc.mint_alloc_default() + let v = alloc.vec_u8_new() + let s = v.as_slice() + let h = Holder { data: s } + if (h.data.len() == 0) { + return 0 + } + return 1 +} diff --git a/tests/programs/slice_safe_read.cap b/tests/programs/slice_safe_read.cap index cb4e37d..994dcd3 100644 --- a/tests/programs/slice_safe_read.cap +++ b/tests/programs/slice_safe_read.cap @@ -5,22 +5,19 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let alloc = rc.mint_alloc_default() - match alloc.buffer_new(1) { - Ok(b) => { - let slice = b.as_slice() - let v = slice.at(0) - c.assert(v == 0u8) - if (v == 0u8) { - c.println("slice read ok") - } else { - c.println("slice read bad") - } - alloc.buffer_free(b) - return 0 - } - Err(e) => { - c.println("buffer err") - return 1 - } + let v = alloc.vec_u8_new() + match (v.push('\x00')) { + Ok(_) => { } + Err(_) => { c.assert(false); alloc.vec_u8_free(v); return 1 } } + let slice = v.as_slice() + let b = slice.at(0) + c.assert(b == '\x00') + if (b == '\x00') { + c.println("slice read ok") + } else { + c.println("slice read bad") + } + alloc.vec_u8_free(v) + return 0 } diff --git a/tests/programs/stdin_safe.cap b/tests/programs/stdin_safe.cap index 6b20287..55cce9a 100644 --- a/tests/programs/stdin_safe.cap +++ b/tests/programs/stdin_safe.cap @@ -5,7 +5,8 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let stdin = rc.mint_stdin() - let code = match stdin.read_to_string() { + let alloc = rc.mint_alloc_default() + let code = match stdin.read_to_string(alloc) { Ok(s) => { let n = s.len() c.assert(n == 0) diff --git a/tests/programs/string_helpers.cap b/tests/programs/string_helpers.cap index 0824dba..28cfc0a 100644 --- a/tests/programs/string_helpers.cap +++ b/tests/programs/string_helpers.cap @@ -4,26 +4,27 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() + let alloc = rc.mint_alloc_default() let s = "abc" let buf = s.bytes() let n = buf.len() let b = buf.at(0) - let words = "a b c".split_whitespace() + let words = "a b c".split_whitespace(alloc) let count = words.len() - 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() + let trimmed = " hi \n".trim(alloc) + let trimmed_start = " hi ".trim_start(alloc) + let trimmed_end = " hi ".trim_end(alloc) + let trimmed_ascii = " \tHi\n".trim_ascii(alloc) + let lower = "AbC".to_lower_ascii(alloc) + let upper = "AbC".to_upper_ascii(alloc) + let sliced = "hello".slice_range(1, 4) + let lines = "a\nb\n".split_lines(alloc) alloc.vec_string_free(words) alloc.vec_string_free(lines) - c.assert(n == 3 && b == 97u8 && count == 3) + c.assert(n == 3 && b == 'a' && count == 3) c.assert(trimmed.len() == 2) - c.assert(trimmed.starts_with_byte(104u8)) - c.assert(trimmed.ends_with_byte(105u8)) + c.assert(trimmed.starts_with_byte('h')) + c.assert(trimmed.ends_with_byte('i')) c.assert(trimmed_start.starts_with("hi")) c.assert(trimmed_end.ends_with("hi")) c.assert(trimmed_ascii.eq("Hi")) @@ -33,8 +34,14 @@ pub fn main(rc: RootCap) -> i32 { 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)) + c.assert("abc".contains_byte('b')) + c.assert(!"abc".contains_byte('x')) + c.assert("abc".contains("b")) + c.assert(!"abc".contains("x")) + match (sliced) { + Ok(sub) => { c.assert(sub.eq("ell")) } + Err(_) => { c.assert(false) } + } match ("abc".index_of_byte('c')) { Ok(i) => { c.assert(i == 2) } Err(_) => { c.assert(false) } @@ -51,17 +58,37 @@ pub fn main(rc: RootCap) -> i32 { Ok(i) => { c.assert(i == 3) } Err(_) => { c.assert(false) } } + match ("ababa".index_of("ba")) { + Ok(i) => { c.assert(i == 1) } + Err(_) => { c.assert(false) } + } + match ("ababa".last_index_of("ba")) { + Ok(i) => { c.assert(i == 3) } + Err(_) => { c.assert(false) } + } + match ("abc".index_of("")) { + Ok(i) => { c.assert(i == 0) } + Err(_) => { c.assert(false) } + } + match ("abc".last_index_of("")) { + 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(',')) { + match ("a,b,c".split_once(alloc, ',')) { 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(',') + match ("ab".concat(alloc, "cd")) { + Ok(joined) => { c.assert(joined.eq("abcd")) } + Err(_) => { c.assert(false) } + } + let pieces = "a,b,c".split(alloc, ',') c.assert(pieces.len() == 3) match (pieces.join(",")) { Ok(joined) => { c.assert(joined.eq("a,b,c")) } diff --git a/tests/programs/string_index.cap b/tests/programs/string_index.cap index 992f3f6..f239f55 100644 --- a/tests/programs/string_index.cap +++ b/tests/programs/string_index.cap @@ -8,7 +8,7 @@ pub fn main(rc: RootCap) -> i32 { // Test basic string indexing let s = "hello" let first = s[0] - if (first != 104u8) { // 'h' is 104 + if (first != 'h') { // expect 'h' c.println("FAIL: first") return 1 } @@ -16,7 +16,7 @@ pub fn main(rc: RootCap) -> i32 { // Test middle character let middle = s[2] - if (middle != 108u8) { // 'l' is 108 + if (middle != 'l') { // expect 'l' c.println("FAIL: middle") return 1 } @@ -24,7 +24,7 @@ pub fn main(rc: RootCap) -> i32 { // Test last character let last = s[4] - if (last != 111u8) { // 'o' is 111 + if (last != 'o') { // expect 'o' c.println("FAIL: last") return 1 } @@ -33,7 +33,7 @@ pub fn main(rc: RootCap) -> i32 { // Test indexing with variable let i = 1 let at_i = s[i] - if (at_i != 101u8) { // 'e' is 101 + if (at_i != 'e') { // expect 'e' c.println("FAIL: var index") return 1 } diff --git a/tests/programs/string_split.cap b/tests/programs/string_split.cap index 57cd6b6..4d94dce 100644 --- a/tests/programs/string_split.cap +++ b/tests/programs/string_split.cap @@ -4,9 +4,9 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() - let parts = "a,b,c".split(44u8) - let count = parts.len() let alloc = rc.mint_alloc_default() + let parts = "a,b,c".split(alloc, ',') + let count = parts.len() alloc.vec_string_free(parts) c.assert(count == 3) c.println("split ok") diff --git a/tests/programs/text_basic.cap b/tests/programs/text_basic.cap new file mode 100644 index 0000000..0e3a31f --- /dev/null +++ b/tests/programs/text_basic.cap @@ -0,0 +1,27 @@ +package safe +module text_basic +use sys::system +use sys::string + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let text = string::text_new(alloc) + match (text.push_str("hi")) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + match (text.push_byte('!')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + let view = text.as_string() + c.assert(view.eq("hi!")) + match (text.to_string()) { + Ok(s) => { c.assert(s.eq("hi!")) } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + text.free(alloc) + c.println("text basic ok") + return 0 +} diff --git a/tests/programs/text_helpers_more.cap b/tests/programs/text_helpers_more.cap new file mode 100644 index 0000000..9bb7b72 --- /dev/null +++ b/tests/programs/text_helpers_more.cap @@ -0,0 +1,63 @@ +package safe +module text_helpers_more +use sys::system +use sys::string + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let text = alloc.text_new() + match (text.push_str("hi")) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (text.push_byte('\n')) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (text.push_str("ok")) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (text.slice_range(0, 2)) { + Ok(view) => { c.assert(view.eq("hi")) } + Err(_) => { c.assert(false); return 1 } + } + let v = alloc.vec_u8_new() + match (v.push('!')) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (text.extend_vec(v)) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (text.to_string()) { + Ok(s) => { c.assert(s.eq("hi\nok!")) } + Err(_) => { c.assert(false); return 1 } + } + match (alloc.text_from("cap")) { + Ok(t2) => { + match (t2.to_string()) { + Ok(s) => { c.assert(s.eq("cap")) } + Err(_) => { c.assert(false); return 1 } + } + t2.free(alloc) + } + Err(_) => { c.assert(false); return 1 } + } + match ("owned".to_text(alloc)) { + Ok(t3) => { + match (t3.to_string()) { + Ok(s) => { c.assert(s.eq("owned")) } + Err(_) => { c.assert(false); return 1 } + } + t3.free(alloc) + } + Err(_) => { c.assert(false); return 1 } + } + alloc.vec_u8_free(v) + text.free(alloc) + c.println("text helpers ok") + return 0 +} diff --git a/tests/programs/text_push_safe.cap b/tests/programs/text_push_safe.cap new file mode 100644 index 0000000..8e1868e --- /dev/null +++ b/tests/programs/text_push_safe.cap @@ -0,0 +1,57 @@ +package safe +module text_push_safe +use sys::system +use sys::string + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let text = string::text_new(alloc) + match text.push_byte('\x00') { + Ok(u) => { u } + Err(e) => { + c.assert(false) + text.free(alloc) + return 1 + } + } + match text.push_byte('\x07') { + Ok(u) => { u } + Err(e) => { + c.assert(false) + text.free(alloc) + return 1 + } + } + let v = alloc.vec_u8_new() + match v.push('\t') { + Ok(u) => { u } + Err(e) => { + c.assert(false) + alloc.vec_u8_free(v) + text.free(alloc) + return 1 + } + } + let slice = v.as_slice() + match text.extend_slice(slice) { + Ok(u) => { u } + Err(e) => { + c.assert(false) + alloc.vec_u8_free(v) + text.free(alloc) + return 1 + } + } + let len = text.len() + c.assert(len == 3) + c.assert(!text.is_empty()) + if (len == 3) { + c.println("push ok") + } else { + c.println("push bad") + } + alloc.vec_u8_free(v) + text.free(alloc) + return 0 +} diff --git a/tests/programs/text_safe.cap b/tests/programs/text_safe.cap new file mode 100644 index 0000000..f7bbbc3 --- /dev/null +++ b/tests/programs/text_safe.cap @@ -0,0 +1,32 @@ +package safe +module text_safe +use sys::system +use sys::string + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let text = string::text_new(alloc) + match (text.push_byte('a')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + match (text.push_byte('b')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + match (text.push_byte('c')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + let slice = text.as_slice() + let len = slice.len() + c.assert(len == 3) + if (len == 3) { + c.println("text ok") + } else { + c.println("text bad") + } + text.free(alloc) + return 0 +} diff --git a/tests/programs/text_to_string.cap b/tests/programs/text_to_string.cap new file mode 100644 index 0000000..28960b9 --- /dev/null +++ b/tests/programs/text_to_string.cap @@ -0,0 +1,25 @@ +package safe +module text_to_string +use sys::system +use sys::string + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let text = string::text_new(alloc) + match (text.push_byte('h')) { + Ok(_) => { } + Err(_) => { panic() } + } + match (text.push_byte('i')) { + Ok(_) => { } + Err(_) => { panic() } + } + let s = match (text.to_string()) { + Ok(v) => { v } + Err(_) => { panic() } + } + c.println(s) + text.free(alloc) + return 0 +} diff --git a/tests/programs/text_unsafe.cap b/tests/programs/text_unsafe.cap new file mode 100644 index 0000000..9476506 --- /dev/null +++ b/tests/programs/text_unsafe.cap @@ -0,0 +1,39 @@ +package unsafe +module text_unsafe +use sys::system +use sys::string + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let text = string::text_new(alloc) + match (text.push_byte('a')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + match (text.push_byte('b')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + match (text.push_byte('c')) { + Ok(_) => { } + Err(_) => { c.assert(false); text.free(alloc); return 1 } + } + let len = text.len() + c.assert(len == 3) + if (len == 3) { + c.println("text ok") + } else { + c.println("text bad") + } + let slice = text.as_slice() + let s_len = slice.len() + c.assert(s_len == 3) + if (s_len == 3) { + c.println("slice ok") + } else { + c.println("slice bad") + } + text.free(alloc) + return 0 +} diff --git a/tests/programs/unsafe_ptr_unsafe.cap b/tests/programs/unsafe_ptr_unsafe.cap new file mode 100644 index 0000000..c554d8e --- /dev/null +++ b/tests/programs/unsafe_ptr_unsafe.cap @@ -0,0 +1,33 @@ +package unsafe +module unsafe_ptr_unsafe +use sys::buffer +use sys::unsafe_ptr +use sys::system + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let size = unsafe_ptr::sizeof() + let align = unsafe_ptr::alignof() + c.assert(size == 1) + c.assert(align == 1) + let raw = alloc.malloc(4) + unsafe_ptr::ptr_write(raw, 42u8) + unsafe_ptr::ptr_write(unsafe_ptr::ptr_add(raw, 1), 7u8) + unsafe_ptr::ptr_write(unsafe_ptr::ptr_add(raw, 2), 9u8) + let v = unsafe_ptr::ptr_read(raw) + c.assert(v == 42u8) + let raw2 = alloc.malloc(4) + unsafe_ptr::memcpy(raw2, raw, 4) + let v2 = unsafe_ptr::ptr_read(raw2) + let v3 = unsafe_ptr::ptr_read(unsafe_ptr::ptr_add(raw2, 2)) + c.assert(v2 == 42u8) + c.assert(v3 == 9u8) + unsafe_ptr::memmove(unsafe_ptr::ptr_add(raw2, 1), raw2, 3) + let v4 = unsafe_ptr::ptr_read(unsafe_ptr::ptr_add(raw2, 1)) + c.assert(v4 == 42u8) + alloc.free(raw) + alloc.free(raw2) + c.println("unsafe ptr ok") + return 0 +} diff --git a/tests/programs/unsigned_compare.cap b/tests/programs/unsigned_compare.cap index 206fad8..1119a8e 100644 --- a/tests/programs/unsigned_compare.cap +++ b/tests/programs/unsigned_compare.cap @@ -3,8 +3,8 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() - let max = 255u8 - let zero = 0u8 + let max = '\xff' + let zero = '\x00' c.assert(max > zero) c.assert(zero < max) c.println("unsigned compare ok") diff --git a/tests/programs/untrusted_logs.cap b/tests/programs/untrusted_logs.cap index dcf3e82..b2aa93c 100644 --- a/tests/programs/untrusted_logs.cap +++ b/tests/programs/untrusted_logs.cap @@ -1,7 +1,8 @@ module untrusted_logs +use sys::buffer use sys::fs -pub fn read_log(dir: Dir) -> Result { +pub fn read_log(alloc: buffer::Alloc, dir: Dir) -> Result { let file = dir.open_read("app.log") - return file.read_to_string() + return file.read_to_string(alloc) } diff --git a/tests/programs/vec_helpers.cap b/tests/programs/vec_helpers.cap index 8154416..bfea188 100644 --- a/tests/programs/vec_helpers.cap +++ b/tests/programs/vec_helpers.cap @@ -6,16 +6,25 @@ pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let alloc = rc.mint_alloc_default() let v = alloc.vec_u8_new() + let with_cap = alloc.vec_with_capacity(8) let extra = alloc.vec_u8_new() - match v.push(65u8) { + match (with_cap) { + Ok(vc) => { + c.assert(vc.capacity() >= 8) + vc.clear() + alloc.vec_u8_free(vc) + } + Err(_) => { c.println("vec bad"); return 1 } + } + match v.push('A') { Ok(x) => {} Err(e) => { c.println("vec bad"); return 1 } } - match v.push(66u8) { + match v.push('B') { Ok(x) => {} Err(e) => { c.println("vec bad"); return 1 } } - match extra.push(67u8) { + match extra.push('C') { Ok(x) => {} Err(e) => { c.println("vec bad"); return 1 } } @@ -23,17 +32,21 @@ pub fn main(rc: RootCap) -> i32 { Ok(x) => {} Err(e) => { c.println("vec bad"); return 1 } } + match v.reserve(4) { + Ok(_) => { c.assert(v.capacity() >= 3) } + Err(_) => { c.println("vec bad"); return 1 } + } let len = v.len() let b = v.get(1) - let filtered = v.filter(66u8) - let mapped = v.map_add(1u8) - let slice = v.slice(1, 2) + let filtered = v.filter('B') + let mapped = v.map_add('\x01') + let slice = v.slice_range(1, 3) match (b) { Ok(x) => { - c.assert(len == 3 && x == 66u8) + c.assert(len == 3 && x == 'B') c.assert(filtered.len() == 1) match (mapped.get(0)) { - Ok(v0) => { c.assert(v0 == 66u8) } + Ok(v0) => { c.assert(v0 == 'B') } Err(e) => { c.println("vec bad"); return 1 } } match (slice) { diff --git a/tests/programs/vec_i32_helpers.cap b/tests/programs/vec_i32_helpers.cap new file mode 100644 index 0000000..fdf7712 --- /dev/null +++ b/tests/programs/vec_i32_helpers.cap @@ -0,0 +1,51 @@ +package safe +module vec_i32_helpers +use sys::system + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let v = alloc.vec_i32_new() + let extra = alloc.vec_i32_new() + match v.push(3) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match v.push(5) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match extra.push(7) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match v.extend(extra) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match v.set(1, 9) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match v.get(1) { + Ok(x) => { c.assert(x == 9) } + Err(_) => { c.assert(false); return 1 } + } + match v.pop() { + Ok(x) => { c.assert(x == 7) } + Err(_) => { c.assert(false); return 1 } + } + let filtered = v.filter(3) + let mapped = v.map_add(1) + match mapped.get(0) { + Ok(x) => { c.assert(x == 4) } + Err(_) => { c.assert(false); return 1 } + } + c.assert(filtered.len() == 1) + c.println("vec i32 ok") + alloc.vec_i32_free(filtered) + alloc.vec_i32_free(mapped) + alloc.vec_i32_free(v) + alloc.vec_i32_free(extra) + return 0 +} diff --git a/tests/programs/vec_search_helpers.cap b/tests/programs/vec_search_helpers.cap new file mode 100644 index 0000000..9d1c074 --- /dev/null +++ b/tests/programs/vec_search_helpers.cap @@ -0,0 +1,57 @@ +package safe +module vec_search_helpers +use sys::system + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let v = alloc.vec_i32_new() + match (v.push(1)) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (v.push(2)) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (v.push(1)) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + c.assert(v.contains(2)) + c.assert(!v.contains(3)) + c.assert(v.count(1) == 2) + match (v.index_of(1)) { + Ok(i) => { c.assert(i == 0) } + Err(_) => { c.assert(false); return 1 } + } + match (v.last_index_of(1)) { + Ok(i) => { c.assert(i == 2) } + Err(_) => { c.assert(false); return 1 } + } + match (v.first()) { + Ok(x) => { c.assert(x == 1) } + Err(_) => { c.assert(false); return 1 } + } + match (v.last()) { + Ok(x) => { c.assert(x == 1) } + Err(_) => { c.assert(false); return 1 } + } + let bytes = alloc.vec_u8_new() + match (bytes.push('h')) { + Ok(_) => { } + Err(_) => { c.assert(false); alloc.vec_i32_free(v); alloc.vec_u8_free(bytes); return 1 } + } + match (bytes.push('i')) { + Ok(_) => { } + Err(_) => { c.assert(false); alloc.vec_i32_free(v); alloc.vec_u8_free(bytes); return 1 } + } + match (bytes.to_string()) { + Ok(s) => { c.assert(s.eq("hi")) } + Err(_) => { c.assert(false); alloc.vec_i32_free(v); alloc.vec_u8_free(bytes); return 1 } + } + alloc.vec_i32_free(v) + alloc.vec_u8_free(bytes) + c.println("vec search ok") + return 0 +} diff --git a/tests/programs/vec_string_helpers.cap b/tests/programs/vec_string_helpers.cap new file mode 100644 index 0000000..a09d682 --- /dev/null +++ b/tests/programs/vec_string_helpers.cap @@ -0,0 +1,38 @@ +package safe +module vec_string_helpers +use sys::system + +pub fn main(rc: RootCap) -> i32 { + let c = rc.mint_console() + let alloc = rc.mint_alloc_default() + let v = alloc.vec_string_new() + let extra = alloc.vec_string_new() + match v.push("a") { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match v.push("b") { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match extra.push("c") { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match v.extend(extra) { + Ok(_) => { } + Err(_) => { c.assert(false); return 1 } + } + match (v.join(",")) { + Ok(s) => { c.assert(s.eq("a,b,c")) } + Err(_) => { c.assert(false); return 1 } + } + match v.pop() { + Ok(x) => { c.assert(x.eq("c")) } + Err(_) => { c.assert(false); return 1 } + } + c.println("vec string ok") + alloc.vec_string_free(v) + alloc.vec_string_free(extra) + return 0 +} diff --git a/tests/programs/wc_file.cap b/tests/programs/wc_file.cap index 1b53783..35aadec 100644 --- a/tests/programs/wc_file.cap +++ b/tests/programs/wc_file.cap @@ -7,14 +7,14 @@ use sys::buffer fn count_text(c: Console, alloc: Alloc, s: string) -> i32 { let buf = s.bytes() let bytes = buf.len() - let words_vec = s.split_whitespace() + let words_vec = s.split_whitespace(alloc) let words = words_vec.len() alloc.vec_string_free(words_vec) let i: i32 = 0 let lines: i32 = 0 while (i < bytes) { let b = buf.at(i) - if (b == 10u8) { + if (b == '\n') { lines = lines + 1 } i = i + 1 @@ -40,10 +40,10 @@ pub fn main(rc: RootCap) -> i32 { let code = match args.at(1) { Ok(path) => { + let alloc = rc.mint_alloc_default() let rfs = rc.mint_readfs("./") - match rfs.read_to_string(path) { + match rfs.read_to_string(alloc, path) { Ok(s) => { - let alloc = rc.mint_alloc_default() count_text(c, alloc, s) } Err(e) => { diff --git a/tests/programs/wc_stdin.cap b/tests/programs/wc_stdin.cap index ab6c382..d2e491f 100644 --- a/tests/programs/wc_stdin.cap +++ b/tests/programs/wc_stdin.cap @@ -5,19 +5,19 @@ use sys::system pub fn main(rc: RootCap) -> i32 { let c = rc.mint_console() let stdin = rc.mint_stdin() - let code = match stdin.read_to_string() { + let alloc = rc.mint_alloc_default() + let code = match stdin.read_to_string(alloc) { Ok(s) => { let buf = s.bytes() let bytes = buf.len() - let words_vec = s.split_whitespace() + let words_vec = s.split_whitespace(alloc) let words = words_vec.len() - let alloc = rc.mint_alloc_default() alloc.vec_string_free(words_vec) let i: i32 = 0 let lines: i32 = 0 while (i < bytes) { let b = buf.at(i) - if (b == 10u8) { + if (b == '\n') { lines = lines + 1 } i = i + 1 diff --git a/tools/gen_code_browser.py b/tools/gen_code_browser.py new file mode 100644 index 0000000..1a27a2d --- /dev/null +++ b/tools/gen_code_browser.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +""" +Generate a simple static code browser for Capable .cap files. + +Usage: + python3 tools/gen_code_browser.py + python3 tools/gen_code_browser.py --out docs/code-browser +""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path + + +SKIP_DIRS = { + ".git", + "target", + ".hermit", + "vscode/node_modules", + "docs/code-browser", +} + +INCLUDE_EXTS = {".cap"} +INCLUDE_ROOTS = [Path("stdlib/sys")] + + +def should_skip_dir(path: Path) -> bool: + rel = str(path).replace("\\", "/") + for skip in SKIP_DIRS: + if rel == skip or rel.startswith(skip + "/"): + return True + return False + + +def collect_files(root: Path) -> list[Path]: + files: list[Path] = [] + for dirpath, dirnames, filenames in os.walk(root): + rel_dir = Path(dirpath).relative_to(root) + if should_skip_dir(rel_dir): + dirnames[:] = [] + continue + if rel_dir != Path("."): + rel_str = str(rel_dir).replace("\\", "/") + under_root = any(rel_str.startswith(str(base)) for base in INCLUDE_ROOTS) + is_prefix = any(str(base).startswith(rel_str) for base in INCLUDE_ROOTS) + if not (under_root or is_prefix): + dirnames[:] = [] + continue + dirnames[:] = [ + d for d in dirnames if not should_skip_dir(rel_dir / d) + ] + for name in filenames: + path = Path(dirpath) / name + rel = path.relative_to(root) + if path.suffix in INCLUDE_EXTS: + files.append(rel) + return sorted(files) + + +def count_braces(line: str) -> tuple[int, int]: + return line.count("{"), line.count("}") + + +def parse_cap_file(path: Path) -> dict: + text = path.read_text(encoding="utf-8") + lines = text.splitlines() + module_name = path.stem + module_docs: list[str] = [] + doc_buf: list[str] = [] + items: list[dict] = [] + current_impl: str | None = None + impl_depth: int | None = None + brace_depth = 0 + + def flush_docs() -> list[str]: + nonlocal doc_buf + docs = doc_buf + doc_buf = [] + return docs + + for raw in lines: + line = raw.strip() + if line.startswith("///"): + doc_buf.append(line[3:].lstrip()) + continue + + if line.startswith("package ") and doc_buf and not module_docs: + module_docs = flush_docs() + if line.startswith("module "): + parts = line.split() + if len(parts) >= 2: + module_name = parts[1] + if doc_buf and not module_docs: + module_docs = flush_docs() + + if line.startswith("impl "): + parts = line.split() + if len(parts) >= 2: + current_impl = parts[1] + impl_depth = brace_depth + flush_docs() + + if "fn " in line and line.startswith(("pub fn", "fn")): + docs = flush_docs() + items.append( + { + "kind": "method" if current_impl else "fn", + "impl": current_impl, + "sig": line.rstrip("{").strip(), + "docs": docs, + } + ) + elif "struct " in line and line.startswith( + ( + "pub struct", + "struct", + "pub copy struct", + "pub copy opaque struct", + "pub copy capability struct", + "pub linear capability struct", + "pub capability struct", + ) + ): + docs = flush_docs() + items.append( + { + "kind": "struct", + "sig": line.rstrip("{").strip(), + "docs": docs, + } + ) + elif "enum " in line and line.startswith(("pub enum", "enum")): + docs = flush_docs() + items.append( + { + "kind": "enum", + "sig": line.rstrip("{").strip(), + "docs": docs, + } + ) + + opens, closes = count_braces(line) + brace_depth += opens - closes + if current_impl is not None and impl_depth is not None: + if brace_depth <= impl_depth: + current_impl = None + impl_depth = None + + if doc_buf and not module_docs: + module_docs = flush_docs() + + return { + "path": str(path).replace("\\", "/"), + "module": module_name, + "docs": module_docs, + "items": items, + "source": text, + } + + +def render_index(modules: list[dict]) -> str: + data = json.dumps(modules) + return f""" + + + + Capable Docs + + + +
+
Capable Docs
+
+ +
+
Select a module…
+
+
+
+ + + +""" + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--out", default="docs/code-browser", help="output directory") + args = parser.parse_args() + + repo_root = Path(__file__).resolve().parents[1] + out_root = (repo_root / args.out).resolve() + out_root.mkdir(parents=True, exist_ok=True) + + files = collect_files(repo_root) + modules = [parse_cap_file(repo_root / rel) for rel in files] + index_html = render_index(modules) + (out_root / "index.html").write_text(index_html, encoding="utf-8") + + print(f"Wrote {out_root / 'index.html'}") + + +if __name__ == "__main__": + main()