From a756c95eaa39d3d7a496d7e17833a6351781d3aa Mon Sep 17 00:00:00 2001 From: Jordan Mecom Date: Tue, 30 Dec 2025 17:33:47 -0800 Subject: [PATCH 1/4] Fix HTTP server example and add TcpConn::read method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix match expression syntax in http_server.cap (use explicit returns inside match arms instead of return match) - Add TcpConn::read(max_size) method that reads available bytes without waiting for EOF, which is required for HTTP since clients keep connections open - Update HTTP server to use read(4096) instead of read_to_string() 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- capc/src/codegen/intrinsics.rs | 18 +++++++++++++++ examples/http_server/http_server.cap | 14 ++++++------ runtime/src/lib.rs | 33 ++++++++++++++++++++++++++++ stdlib/sys/net.cap | 4 ++++ 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/capc/src/codegen/intrinsics.rs b/capc/src/codegen/intrinsics.rs index f33b5e3..3958155 100644 --- a/capc/src/codegen/intrinsics.rs +++ b/capc/src/codegen/intrinsics.rs @@ -206,6 +206,14 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { params: vec![AbiType::Handle, AbiType::ResultString], ret: AbiType::ResultString, }; + let net_read = FnSig { + params: vec![AbiType::Handle, AbiType::I32], + ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)), + }; + let net_read_abi = FnSig { + params: vec![AbiType::Handle, AbiType::I32, AbiType::ResultString], + ret: AbiType::ResultString, + }; let net_write = FnSig { params: vec![AbiType::Handle, AbiType::String], ret: AbiType::Result(Box::new(AbiType::Unit), Box::new(AbiType::I32)), @@ -727,6 +735,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap { is_runtime: true, }, ); + map.insert( + "sys.net.TcpConn__read".to_string(), + FnInfo { + sig: net_read, + abi_sig: Some(net_read_abi), + symbol: "capable_rt_net_read".to_string(), + runtime_symbol: None, + is_runtime: true, + }, + ); map.insert( "sys.net.TcpConn__write".to_string(), FnInfo { diff --git a/examples/http_server/http_server.cap b/examples/http_server/http_server.cap index 7b876f3..65e8e83 100644 --- a/examples/http_server/http_server.cap +++ b/examples/http_server/http_server.cap @@ -57,12 +57,12 @@ fn sanitize_parts(parts: VecString, i: i32, acc: string) -> Result[string, unit] return Ok(acc) } let seg_res = parts.get(i) - return match (seg_res) { + match (seg_res) { Ok(seg) => { - sanitize_segment(parts, i, acc, seg) + return sanitize_segment(parts, i, acc, seg) } Err(_) => { - Err(()) + return Err(()) } } } @@ -112,12 +112,12 @@ fn parse_request_line(line: string) -> Result[string, unit] { fn parse_request_path(req: string) -> Result[string, unit] { let lines = req.lines() let line_res = lines.get(0) - return match (line_res) { + match (line_res) { Ok(line) => { - parse_request_line(line) + return parse_request_line(line) } Err(_) => { - Err(()) + return Err(()) } } } @@ -141,7 +141,7 @@ fn respond_bad_request(conn: &TcpConn) -> Result[unit, NetErr] { fn serve_once(c: Console, net: Net, readfs: ReadFS) -> Result[unit, NetErr] { let listener = net.listen("127.0.0.1", 8080)? let conn = listener.accept()? - let req = conn.read_to_string()? + let req = conn.read(4096)? let path_res = parse_request_path(req) match (path_res) { Ok(path) => { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d66a5b6..1a6752f 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -848,6 +848,39 @@ pub extern "C" fn capable_rt_net_read_to_string( ) } +#[no_mangle] +pub extern "C" fn capable_rt_net_read( + conn: Handle, + max_size: i32, + out_ptr: *mut *const u8, + out_len: *mut u64, + out_err: *mut i32, +) -> u8 { + let result = with_table(&TCP_CONNS, "tcp conn table", |table| { + let Some(stream) = table.get_mut(&conn) else { + return Err(NetErr::IoError); + }; + let max = max_size.max(0) as usize; + let mut buffer = vec![0u8; max]; + match stream.read(&mut buffer) { + Ok(n) => { + buffer.truncate(n); + match String::from_utf8(buffer) { + Ok(s) => Ok(s), + Err(_) => Err(NetErr::InvalidData), + } + } + Err(err) => Err(map_net_err(err)), + } + }); + write_string_result( + out_ptr, + out_len, + out_err, + result.map_err(|err| err as i32), + ) +} + #[no_mangle] pub extern "C" fn capable_rt_net_write( conn: Handle, diff --git a/stdlib/sys/net.cap b/stdlib/sys/net.cap index 0403b9a..9bbcf76 100644 --- a/stdlib/sys/net.cap +++ b/stdlib/sys/net.cap @@ -36,6 +36,10 @@ impl TcpConn { return Err(NetErr::IoError) } + pub fn read(self: &TcpConn, max_size: i32) -> Result[string, NetErr] { + return Err(NetErr::IoError) + } + pub fn write(self: &TcpConn, data: string) -> Result[unit, NetErr] { return Err(NetErr::IoError) } From d60f4f473cda64370897b9d977bf41db977ea541 Mon Sep 17 00:00:00 2001 From: Jordan Mecom Date: Tue, 30 Dec 2025 17:58:59 -0800 Subject: [PATCH 2/4] Reduce nesting and improve code quality in HTTP server example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Flatten nested matches by inlining expressions - Use single-line match arms for simple cases - Extract handle_request helper to reduce nesting in serve_once - Remove redundant intermediate variables - Use early returns to avoid else branches - Simplify response functions to single return statements Reduces code from 183 to 133 lines while maintaining functionality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- examples/http_server/http_server.cap | 126 +++++++++------------------ 1 file changed, 39 insertions(+), 87 deletions(-) diff --git a/examples/http_server/http_server.cap b/examples/http_server/http_server.cap index 65e8e83..b71dd76 100644 --- a/examples/http_server/http_server.cap +++ b/examples/http_server/http_server.cap @@ -9,29 +9,19 @@ use sys::args use sys::system fn arg_or_default(args: Args, index: i32, default: string) -> string { - if (args.len() > index) { - let res = args.at(index) - match (res) { - Ok(value) => { - return value - } - Err(_) => { - } - } + if (args.len() <= index) { + return default + } + match (args.at(index)) { + Ok(value) => { return value } + Err(_) => { return default } } - return default } fn strip_query(raw_path: string) -> string { - let parts = raw_path.split(63u8) - let res = parts.get(0) - return match (res) { - Ok(path) => { - path - } - Err(_) => { - "" - } + match (raw_path.split(63u8).get(0)) { + Ok(path) => { return path } + Err(_) => { return "" } } } @@ -48,77 +38,51 @@ fn sanitize_segment(parts: VecString, i: i32, acc: string, seg: string) -> Resul if (acc.len() == 0) { return sanitize_parts(parts, i + 1, seg) } - let next = fs::join(acc, seg) - return sanitize_parts(parts, i + 1, next) + return sanitize_parts(parts, i + 1, fs::join(acc, seg)) } fn sanitize_parts(parts: VecString, i: i32, acc: string) -> Result[string, unit] { if (i >= parts.len()) { return Ok(acc) } - let seg_res = parts.get(i) - match (seg_res) { - Ok(seg) => { - return sanitize_segment(parts, i, acc, seg) - } - Err(_) => { - return Err(()) - } + match (parts.get(i)) { + Ok(seg) => { return sanitize_segment(parts, i, acc, seg) } + Err(_) => { return Err(()) } } } fn sanitize_path(raw_path: string) -> Result[string, unit] { - let parts = raw_path.split(47u8) - let res = sanitize_parts(parts, 0, "") - match (res) { + match (sanitize_parts(raw_path.split(47u8), 0, "")) { Ok(path) => { if (path.len() == 0) { return Ok("index.html") } return Ok(path) } - Err(_) => { - return Err(()) - } + Err(_) => { return Err(()) } } } fn parse_request_line(line: string) -> Result[string, unit] { - let trimmed = line.trim() - let parts = trimmed.split(32u8) - let method_res = parts.get(0) - match (method_res) { + let parts = line.trim().split(32u8) + match (parts.get(0)) { Ok(method) => { if (!method.eq("GET")) { return Err(()) } } - Err(_) => { - return Err(()) - } + Err(_) => { return Err(()) } } - let path_res = parts.get(1) - match (path_res) { - Ok(raw_path) => { - let cleaned = strip_query(raw_path) - return sanitize_path(cleaned) - } - Err(_) => { - return Err(()) - } + match (parts.get(1)) { + Ok(raw_path) => { return sanitize_path(strip_query(raw_path)) } + Err(_) => { return Err(()) } } } fn parse_request_path(req: string) -> Result[string, unit] { - let lines = req.lines() - let line_res = lines.get(0) - match (line_res) { - Ok(line) => { - return parse_request_line(line) - } - Err(_) => { - return Err(()) - } + match (req.lines().get(0)) { + Ok(line) => { return parse_request_line(line) } + Err(_) => { return Err(()) } } } @@ -129,35 +93,27 @@ fn respond_ok(conn: &TcpConn, body: string) -> Result[unit, NetErr] { } fn respond_not_found(conn: &TcpConn) -> Result[unit, NetErr] { - conn.write("HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\nnot found\n")? - return Ok(()) + return conn.write("HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\nnot found\n") } fn respond_bad_request(conn: &TcpConn) -> Result[unit, NetErr] { - conn.write("HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nbad request\n")? - return Ok(()) + return conn.write("HTTP/1.0 400 Bad Request\r\nContent-Type: text/plain\r\n\r\nbad request\n") +} + +fn handle_request(conn: &TcpConn, readfs: ReadFS, path: string) -> Result[unit, NetErr] { + match (readfs.read_to_string(path)) { + Ok(body) => { return respond_ok(conn, body) } + Err(_) => { return respond_not_found(conn) } + } } fn serve_once(c: Console, net: Net, readfs: ReadFS) -> Result[unit, NetErr] { let listener = net.listen("127.0.0.1", 8080)? let conn = listener.accept()? let req = conn.read(4096)? - let path_res = parse_request_path(req) - match (path_res) { - Ok(path) => { - let file_res = readfs.read_to_string(path) - match (file_res) { - Ok(body) => { - respond_ok(conn, body)? - } - Err(_) => { - respond_not_found(conn)? - } - } - } - Err(_) => { - respond_bad_request(conn)? - } + match (parse_request_path(req)) { + Ok(path) => { handle_request(conn, readfs, path)? } + Err(_) => { respond_bad_request(conn)? } } conn.close() return Ok(()) @@ -170,13 +126,9 @@ pub fn main(rc: RootCap) -> i32 { let root = arg_or_default(args, 1, ".") let readfs = rc.mint_readfs(root) c.println("listening on 127.0.0.1:8080") - let res = serve_once(c, net, readfs) - match (res) { - Ok(_) => { - } - Err(_) => { - c.println("server error") - } + match (serve_once(c, net, readfs)) { + Ok(_) => {} + Err(_) => { c.println("server error") } } return 0 } From 51464c6b3e3b864b7f3b9292716b591fe7fe1172 Mon Sep 17 00:00:00 2001 From: Jordan Mecom Date: Tue, 30 Dec 2025 18:06:31 -0800 Subject: [PATCH 3/4] Add Result.ok() method to convert Result[T, E] to Result[T, unit] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method discards the error type, allowing Results with different error types to be unified. Useful for propagating errors with ?. Note: Currently has a codegen limitation - cannot be used directly with ? operator due to "unsupported match result value" error. The method works correctly in other contexts. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- capc/src/typeck/check.rs | 13 ++++++++ capc/src/typeck/lower.rs | 69 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/capc/src/typeck/check.rs b/capc/src/typeck/check.rs index ccd62e9..648474a 100644 --- a/capc/src/typeck/check.rs +++ b/capc/src/typeck/check.rs @@ -1243,6 +1243,19 @@ pub(super) fn check_expr( } return record_expr_type(recorder, expr, err_ty.clone()); } + "ok" => { + if !method_call.args.is_empty() { + return Err(TypeError::new( + "ok expects no arguments".to_string(), + method_call.span, + )); + } + let result_ty = Ty::Path( + "Result".to_string(), + vec![ok_ty.clone(), Ty::Builtin(BuiltinType::Unit)], + ); + return record_expr_type(recorder, expr, result_ty); + } _ => {} } } diff --git a/capc/src/typeck/lower.rs b/capc/src/typeck/lower.rs index 7b40847..8a28edf 100644 --- a/capc/src/typeck/lower.rs +++ b/capc/src/typeck/lower.rs @@ -13,8 +13,8 @@ use crate::hir::{ use super::{ build_type_params, check, function_key, lower_type, resolve_enum_variant, resolve_method_target, - resolve_type_name, EnumInfo, FunctionSig, FunctionTypeTables, SpanExt, StdlibIndex, StructInfo, - Ty, TypeTable, UseMap, + resolve_type_name, BuiltinType, EnumInfo, FunctionSig, FunctionTypeTables, SpanExt, StdlibIndex, + StructInfo, Ty, TypeTable, UseMap, }; /// Context for HIR lowering (uses the type checker as source of truth). @@ -735,6 +735,71 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result { + // Convert Result[T, E] to Result[T, unit] + let ok_name = format!("__ok_val_{}", ctx.local_counter); + let ok_local_id = ctx.fresh_local(ok_name.clone(), args[0].clone()); + let ok_pattern = HirPattern::Variant { + variant_name: "Ok".to_string(), + binding: Some(ok_local_id), + }; + let err_pattern = HirPattern::Variant { + variant_name: "Err".to_string(), + binding: None, + }; + // Ok arm: return Ok(x) + let ok_inner = HirExpr::Local(HirLocal { + local_id: ok_local_id, + ty: hir_type_for(args[0].clone(), ctx, method_call.span)?, + span: method_call.span, + }); + let ok_result = HirExpr::EnumVariant(HirEnumVariantExpr { + enum_ty: hir_ty.clone(), + variant_name: "Ok".to_string(), + payload: Some(Box::new(ok_inner)), + span: method_call.span, + }); + let ok_block = HirBlock { + stmts: vec![HirStmt::Expr(HirExprStmt { + expr: ok_result, + span: method_call.span, + })], + }; + // Err arm: return Err(()) + let unit_ty = hir_type_for(Ty::Builtin(BuiltinType::Unit), ctx, method_call.span)?; + let unit_expr = HirExpr::Literal(HirLiteral { + value: Literal::Unit, + ty: unit_ty, + span: method_call.span, + }); + let err_result = HirExpr::EnumVariant(HirEnumVariantExpr { + enum_ty: hir_ty.clone(), + variant_name: "Err".to_string(), + payload: Some(Box::new(unit_expr)), + span: method_call.span, + }); + let err_block = HirBlock { + stmts: vec![HirStmt::Expr(HirExprStmt { + expr: err_result, + span: method_call.span, + })], + }; + return Ok(HirExpr::Match(HirMatch { + expr: Box::new(receiver), + arms: vec![ + HirMatchArm { + pattern: ok_pattern, + body: ok_block, + }, + HirMatchArm { + pattern: err_pattern, + body: err_block, + }, + ], + result_ty: hir_ty, + span: method_call.span, + })); + } _ => {} } } From c84d2866895036f290c5f4a475d8a37d91a197be Mon Sep 17 00:00:00 2001 From: Jordan Mecom Date: Tue, 30 Dec 2025 18:07:22 -0800 Subject: [PATCH 4/4] Revert "Add Result.ok() method to convert Result[T, E] to Result[T, unit]" This reverts commit 51464c6b3e3b864b7f3b9292716b591fe7fe1172. --- capc/src/typeck/check.rs | 13 -------- capc/src/typeck/lower.rs | 69 ++-------------------------------------- 2 files changed, 2 insertions(+), 80 deletions(-) diff --git a/capc/src/typeck/check.rs b/capc/src/typeck/check.rs index 648474a..ccd62e9 100644 --- a/capc/src/typeck/check.rs +++ b/capc/src/typeck/check.rs @@ -1243,19 +1243,6 @@ pub(super) fn check_expr( } return record_expr_type(recorder, expr, err_ty.clone()); } - "ok" => { - if !method_call.args.is_empty() { - return Err(TypeError::new( - "ok expects no arguments".to_string(), - method_call.span, - )); - } - let result_ty = Ty::Path( - "Result".to_string(), - vec![ok_ty.clone(), Ty::Builtin(BuiltinType::Unit)], - ); - return record_expr_type(recorder, expr, result_ty); - } _ => {} } } diff --git a/capc/src/typeck/lower.rs b/capc/src/typeck/lower.rs index 8a28edf..7b40847 100644 --- a/capc/src/typeck/lower.rs +++ b/capc/src/typeck/lower.rs @@ -13,8 +13,8 @@ use crate::hir::{ use super::{ build_type_params, check, function_key, lower_type, resolve_enum_variant, resolve_method_target, - resolve_type_name, BuiltinType, EnumInfo, FunctionSig, FunctionTypeTables, SpanExt, StdlibIndex, - StructInfo, Ty, TypeTable, UseMap, + resolve_type_name, EnumInfo, FunctionSig, FunctionTypeTables, SpanExt, StdlibIndex, StructInfo, + Ty, TypeTable, UseMap, }; /// Context for HIR lowering (uses the type checker as source of truth). @@ -735,71 +735,6 @@ fn lower_expr(expr: &Expr, ctx: &mut LoweringCtx, ret_ty: &Ty) -> Result { - // Convert Result[T, E] to Result[T, unit] - let ok_name = format!("__ok_val_{}", ctx.local_counter); - let ok_local_id = ctx.fresh_local(ok_name.clone(), args[0].clone()); - let ok_pattern = HirPattern::Variant { - variant_name: "Ok".to_string(), - binding: Some(ok_local_id), - }; - let err_pattern = HirPattern::Variant { - variant_name: "Err".to_string(), - binding: None, - }; - // Ok arm: return Ok(x) - let ok_inner = HirExpr::Local(HirLocal { - local_id: ok_local_id, - ty: hir_type_for(args[0].clone(), ctx, method_call.span)?, - span: method_call.span, - }); - let ok_result = HirExpr::EnumVariant(HirEnumVariantExpr { - enum_ty: hir_ty.clone(), - variant_name: "Ok".to_string(), - payload: Some(Box::new(ok_inner)), - span: method_call.span, - }); - let ok_block = HirBlock { - stmts: vec![HirStmt::Expr(HirExprStmt { - expr: ok_result, - span: method_call.span, - })], - }; - // Err arm: return Err(()) - let unit_ty = hir_type_for(Ty::Builtin(BuiltinType::Unit), ctx, method_call.span)?; - let unit_expr = HirExpr::Literal(HirLiteral { - value: Literal::Unit, - ty: unit_ty, - span: method_call.span, - }); - let err_result = HirExpr::EnumVariant(HirEnumVariantExpr { - enum_ty: hir_ty.clone(), - variant_name: "Err".to_string(), - payload: Some(Box::new(unit_expr)), - span: method_call.span, - }); - let err_block = HirBlock { - stmts: vec![HirStmt::Expr(HirExprStmt { - expr: err_result, - span: method_call.span, - })], - }; - return Ok(HirExpr::Match(HirMatch { - expr: Box::new(receiver), - arms: vec![ - HirMatchArm { - pattern: ok_pattern, - body: ok_block, - }, - HirMatchArm { - pattern: err_pattern, - body: err_block, - }, - ], - result_ty: hir_ty, - span: method_call.span, - })); - } _ => {} } }