Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions capc/src/codegen/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,19 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap<String, FnInfo> {
ret: AbiType::Handle,
};
// Net.
let net_listen = FnSig {
params: vec![AbiType::Handle, AbiType::String, AbiType::I32],
ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
};
let net_listen_abi = FnSig {
params: vec![
AbiType::Handle,
AbiType::String,
AbiType::I32,
AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
],
ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
};
let net_connect = FnSig {
params: vec![AbiType::Handle, AbiType::String, AbiType::I32],
ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
Expand Down Expand Up @@ -205,10 +218,25 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap<String, FnInfo> {
],
ret: AbiType::ResultOut(Box::new(AbiType::Unit), Box::new(AbiType::I32)),
};
let net_accept = FnSig {
params: vec![AbiType::Handle],
ret: AbiType::Result(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
};
let net_accept_abi = FnSig {
params: vec![
AbiType::Handle,
AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
],
ret: AbiType::ResultOut(Box::new(AbiType::Handle), Box::new(AbiType::I32)),
};
let net_close = FnSig {
params: vec![AbiType::Handle],
ret: AbiType::Unit,
};
let net_listener_close = FnSig {
params: vec![AbiType::Handle],
ret: AbiType::Unit,
};
let args_at = FnSig {
params: vec![AbiType::Handle, AbiType::I32],
ret: AbiType::Result(Box::new(AbiType::String), Box::new(AbiType::I32)),
Expand Down Expand Up @@ -649,6 +677,16 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap<String, FnInfo> {
},
);
// === Net ===
map.insert(
"sys.net.Net__listen".to_string(),
FnInfo {
sig: net_listen,
abi_sig: Some(net_listen_abi),
symbol: "capable_rt_net_listen".to_string(),
runtime_symbol: None,
is_runtime: true,
},
);
map.insert(
"sys.net.Net__connect".to_string(),
FnInfo {
Expand All @@ -659,6 +697,26 @@ pub fn register_runtime_intrinsics(ptr_ty: Type) -> HashMap<String, FnInfo> {
is_runtime: true,
},
);
map.insert(
"sys.net.TcpListener__accept".to_string(),
FnInfo {
sig: net_accept,
abi_sig: Some(net_accept_abi),
symbol: "capable_rt_net_accept".to_string(),
runtime_symbol: None,
is_runtime: true,
},
);
map.insert(
"sys.net.TcpListener__close".to_string(),
FnInfo {
sig: net_listener_close,
abi_sig: None,
symbol: "capable_rt_net_listener_close".to_string(),
runtime_symbol: None,
is_runtime: true,
},
);
map.insert(
"sys.net.TcpConn__read_to_string".to_string(),
FnInfo {
Expand Down
182 changes: 182 additions & 0 deletions examples/http_server/http_server.cap
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// WIP! This is a sample serving to improve the language.

package safe
module http_server
use sys::console
use sys::fs
use sys::net
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(_) => {
}
}
}
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(_) => {
""
}
}
}

fn sanitize_segment(parts: VecString, i: i32, acc: string, seg: string) -> Result[string, unit] {
if (seg.len() == 0) {
return sanitize_parts(parts, i + 1, acc)
}
if (seg.eq(".")) {
return sanitize_parts(parts, i + 1, acc)
}
if (seg.eq("..")) {
return Err(())
}
if (acc.len() == 0) {
return sanitize_parts(parts, i + 1, seg)
}
let next = fs::join(acc, seg)
return sanitize_parts(parts, i + 1, next)
}

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)
return match (seg_res) {
Ok(seg) => {
sanitize_segment(parts, i, acc, seg)
}
Err(_) => {
Err(())
}
}
}

fn sanitize_path(raw_path: string) -> Result[string, unit] {
let parts = raw_path.split(47u8)
let res = sanitize_parts(parts, 0, "")
match (res) {
Ok(path) => {
if (path.len() == 0) {
return Ok("index.html")
}
return Ok(path)
}
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) {
Ok(method) => {
if (!method.eq("GET")) {
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(())
}
}
}

fn parse_request_path(req: string) -> Result[string, unit] {
let lines = req.lines()
let line_res = lines.get(0)
return match (line_res) {
Ok(line) => {
parse_request_line(line)
}
Err(_) => {
Err(())
}
}
}

fn respond_ok(conn: &TcpConn, body: string) -> Result[unit, NetErr] {
conn.write("HTTP/1.0 200 OK\r\nContent-Type: text/plain\r\n\r\n")?
conn.write(body)?
return Ok(())
}

fn respond_not_found(conn: &TcpConn) -> Result[unit, NetErr] {
conn.write("HTTP/1.0 404 Not Found\r\nContent-Type: text/plain\r\n\r\nnot found\n")?
return Ok(())
}

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(())
}

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 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)?
}
}
conn.close()
return Ok(())
}

pub fn main(rc: RootCap) -> i32 {
let c = rc.mint_console()
let net = rc.mint_net()
let args = rc.mint_args()
let root = arg_or_default(args, 1, ".")
let readfs = rc.mint_readfs(root)
c.println("listening on 127.0.0.1:8080")
let res = serve_once(c, net, readfs)
match (res) {
Ok(_) => {
}
Err(_) => {
c.println("server error")
}
}
return 0
}
3 changes: 3 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,8 @@ extern-demo-build:
extern-demo-run:
cargo run -p capc -- run --link-search examples/extern_demo --link-lib extern_demo examples/extern_demo/extern_demo.cap

http-server:
cargo run -p capc -- run examples/http_server/http_server.cap

lsp:
cargo run -p caplsp
58 changes: 57 additions & 1 deletion runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use std::ffi::OsStr;
use std::io::{self, Read, Write};
use std::net::TcpStream;
use std::net::{TcpListener, TcpStream};
use std::path::{Component, Path, PathBuf};
use std::sync::{LazyLock, Mutex};

Expand All @@ -28,6 +28,8 @@ static STDIN_CAPS: LazyLock<Mutex<HashMap<Handle, ()>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static NET_CAPS: LazyLock<Mutex<HashMap<Handle, ()>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static TCP_LISTENERS: LazyLock<Mutex<HashMap<Handle, TcpListener>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static TCP_CONNS: LazyLock<Mutex<HashMap<Handle, TcpStream>>> =
LazyLock::new(|| Mutex::new(HashMap::new()));
static SLICES: LazyLock<Mutex<HashMap<Handle, SliceState>>> =
Expand Down Expand Up @@ -772,6 +774,55 @@ pub extern "C" fn capable_rt_net_connect(
}
}

#[no_mangle]
pub extern "C" fn capable_rt_net_listen(
net: Handle,
host_ptr: *const u8,
host_len: usize,
port: i32,
out_ok: *mut Handle,
out_err: *mut i32,
) -> u8 {
if !has_handle(&NET_CAPS, net, "net table") {
return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32));
}
let host = unsafe { read_str(host_ptr, host_len) };
let Some(host) = host else {
return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32));
};
if host.is_empty() || port <= 0 || port > u16::MAX as i32 {
return write_handle_result_code(out_ok, out_err, Err(NetErr::InvalidAddress as i32));
}
match TcpListener::bind((host.as_str(), port as u16)) {
Ok(listener) => {
let handle = new_handle();
insert_handle(&TCP_LISTENERS, handle, listener, "tcp listener table");
write_handle_result_code(out_ok, out_err, Ok(handle))
}
Err(err) => write_handle_result_code(out_ok, out_err, Err(map_net_err(err) as i32)),
}
}

#[no_mangle]
pub extern "C" fn capable_rt_net_accept(
listener: Handle,
out_ok: *mut Handle,
out_err: *mut i32,
) -> u8 {
let listener = take_handle(&TCP_LISTENERS, listener, "tcp listener table");
let Some(listener) = listener else {
return write_handle_result_code(out_ok, out_err, Err(NetErr::IoError as i32));
};
match listener.accept() {
Ok((stream, _addr)) => {
let handle = new_handle();
insert_handle(&TCP_CONNS, handle, stream, "tcp conn table");
write_handle_result_code(out_ok, out_err, Ok(handle))
}
Err(err) => write_handle_result_code(out_ok, out_err, Err(map_net_err(err) as i32)),
}
}

#[no_mangle]
pub extern "C" fn capable_rt_net_read_to_string(
conn: Handle,
Expand Down Expand Up @@ -844,6 +895,11 @@ pub extern "C" fn capable_rt_net_close(conn: Handle) {
take_handle(&TCP_CONNS, conn, "tcp conn table");
}

#[no_mangle]
pub extern "C" fn capable_rt_net_listener_close(listener: Handle) {
take_handle(&TCP_LISTENERS, listener, "tcp listener table");
}

#[no_mangle]
pub extern "C" fn capable_rt_start() -> i32 {
let sys = new_handle();
Expand Down
Loading