From 1866bdc8428b8a49aadd6e1a5ca6ed98ee2646c3 Mon Sep 17 00:00:00 2001 From: LunaStev Date: Sun, 23 Aug 2026 18:15:23 +0900 Subject: [PATCH] Complete the Wave module import system Signed-off-by: LunaStev --- README.md | 35 + examples/doom.wave | 92 +- examples/observability_gateway.wave | 219 ++- front/lexer/src/ident.rs | 5 + front/lexer/src/scan.rs | 9 +- front/lexer/src/token.rs | 2 + front/parser/src/ast.rs | 23 +- front/parser/src/expr/primary.rs | 21 +- front/parser/src/generics.rs | 27 +- front/parser/src/import.rs | 151 +- front/parser/src/parser/control.rs | 2 + front/parser/src/parser/decl.rs | 31 +- front/parser/src/parser/functions.rs | 5 +- front/parser/src/parser/items.rs | 87 +- front/parser/src/parser/parse.rs | 106 +- front/parser/src/parser/types.rs | 31 +- front/parser/src/verification.rs | 1 + front/parser/tests/parse_var_and_generics.rs | 86 ++ llvm/src/codegen/ir.rs | 2 +- llvm/src/codegen/types.rs | 1 + llvm/src/importgen.rs | 4 +- llvm/src/statement/variable.rs | 1 + src/lib.rs | 1 + src/module_resolver.rs | 1402 ++++++++++++++++++ src/runner.rs | 123 +- std/POLICY.md | 3 + std/buffer/alloc.wave | 63 +- std/buffer/read.wave | 13 +- std/buffer/types.wave | 4 +- std/buffer/write.wave | 33 +- std/bytes/endian.wave | 30 +- std/env/consts.wave | 10 +- std/env/cwd.wave | 45 +- std/env/environ.wave | 33 +- std/env/parse.wave | 8 +- std/fs/consts.wave | 6 +- std/fs/file.wave | 97 +- std/io/consts.wave | 14 +- std/io/fd.wave | 73 +- std/libc/netinet.wave | 2 +- std/libc/poll.wave | 6 +- std/libc/socket.wave | 24 +- std/libc/time.wave | 6 +- std/math/bits.wave | 40 +- std/math/float.wave | 26 +- std/math/int.wave | 30 +- std/math/num.wave | 6 +- std/math/trig.wave | 32 +- std/mem/alloc.wave | 75 +- std/mem/consts.wave | 16 +- std/mem/cstr.wave | 12 +- std/mem/ops.wave | 45 +- std/net/address.wave | 57 +- std/net/poll.wave | 54 +- std/net/socket_base.wave | 114 +- std/net/socketopt.wave | 84 +- std/net/tcp.wave | 183 ++- std/net/udp.wave | 121 +- std/path/analyze.wave | 16 +- std/path/consts.wave | 2 +- std/path/copy.wave | 22 +- std/path/core.wave | 6 +- std/process/consts.wave | 8 +- std/process/core.wave | 31 +- std/process/spawn.wave | 57 +- std/process/wait.wave | 35 +- std/string/ascii.wave | 16 +- std/string/cmp.wave | 8 +- std/string/find.wave | 14 +- std/string/hash.wave | 4 +- std/string/len.wave | 4 +- std/string/trim.wave | 6 +- std/sys/env.wave | 9 +- std/sys/fs.wave | 78 +- std/sys/linux/env.wave | 41 +- std/sys/linux/fs.wave | 82 +- std/sys/linux/memory.wave | 28 +- std/sys/linux/process.wave | 24 +- std/sys/linux/socket.wave | 74 +- std/sys/linux/syscall.wave | 14 +- std/sys/linux/time.wave | 16 +- std/sys/linux/tty.wave | 70 +- std/sys/macos/env.wave | 4 +- std/sys/macos/fs.wave | 83 +- std/sys/macos/memory.wave | 29 +- std/sys/macos/process.wave | 25 +- std/sys/macos/socket.wave | 89 +- std/sys/macos/syscall.wave | 16 +- std/sys/macos/time.wave | 17 +- std/sys/macos/tty.wave | 67 +- std/sys/memory.wave | 24 +- std/sys/process.wave | 20 +- std/sys/socket.wave | 70 +- std/sys/time.wave | 12 +- std/sys/tty.wave | 52 +- std/time/clock.wave | 14 +- std/time/consts.wave | 10 +- std/time/diff.wave | 10 +- std/time/sleep.wave | 12 +- tests/cases/test103.wave | 44 +- tests/cases/test104.wave | 49 +- tests/cases/test105.wave | 90 +- tests/cases/test106.wave | 56 +- tests/cases/test28/main.wave | 4 +- tests/cases/test28/math.wave | 10 +- tests/cases/test84.wave | 133 +- tests/codegen_regressions.rs | 163 +- 107 files changed, 4671 insertions(+), 929 deletions(-) create mode 100644 src/module_resolver.rs diff --git a/README.md b/README.md index 8ab401a6..ea137a33 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,41 @@ Save this as `main.wave`, then run it directly: wavec run main.wave ``` +## Modules and Vex packages + +Wave keeps each imported file in its own module namespace. A bare import names +a Vex dependency, a qualified package path names a source module, and `./` +explicitly names a file relative to the importing module: + +```wave +import("add"); +import("add::math"); +import("./helpers" as helpers); +import("add")::{sum, Point}; + +fun main() { + var qualified = add::sum(1, 2); + var selected = sum(1, 2); + var local = helpers::triple(3); + var point = Point(); +} +``` + +A dependency named `add` resolves to its canonical `src/lib.wave` entry; +`add::math` resolves to `src/math.wave`. Only declarations marked `pub` can be +selected or accessed through another module: + +```wave +fun internal_sum(a: i32, b: i32) -> i32 { return a + b; } +pub fun sum(a: i32, b: i32) -> i32 { return internal_sum(a, b); } +pub struct Point {} +``` + +`pub` controls Wave module visibility and is independent from `export(c)`, +which controls the C ABI boundary. `main` is always a private entry point, so +`pub fun main()` is rejected. A module can deliberately forward public API with +`pub import("module")::{symbol};`. + ## Install Linux and macOS: diff --git a/examples/doom.wave b/examples/doom.wave index 196be566..9a49814d 100644 --- a/examples/doom.wave +++ b/examples/doom.wave @@ -1,8 +1,90 @@ -import("std::sys::linux::fs"); -import("std::sys::linux::tty"); -import("std::time::clock"); -import("std::time::sleep"); -import("std::math::trig"); +import("std::sys::linux::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; +import("std::sys::linux::tty")::{ + TTY_SYS_IOCTL, + TTY_SYS_FCNTL, + TTY_TCGETS, + TTY_TCSETS, + TTY_TCSETSW, + TTY_TCSETSF, + TTY_TCSANOW, + TTY_TCSADRAIN, + TTY_TCSAFLUSH, + TTY_F_GETFL, + TTY_F_SETFL, + TTY_O_NONBLOCK, + TTY_ICANON, + TTY_ECHO, + TTY_VTIME_IDX, + TTY_VMIN_IDX, + Termios, + TtyRawState, + tty_getattr, + tty_setattr, + tty_getfl, + tty_setfl, + tty_enable_raw_nonblock, + tty_restore, +}; +import("std::time::clock")::{ + time_now_realtime, + time_now_monotonic, + time_now_realtime_ns, + time_now_monotonic_ns, + TimeSpec, + nanosleep, + clock_gettime, +}; +import("std::time::sleep")::{ + time_sleep_ns, + time_sleep_us, + time_sleep_ms, +}; +import("std::math::trig")::{ + MATH_PI_F64, + MATH_TWO_PI_F64, + abs_f64, + wrap_angle_pi_f64, + sin_f64, + cos_f64, + sqrt_f64, +}; const STDIN_FILENO: i32 = 0; const STDOUT_FILENO: i32 = 1; diff --git a/examples/observability_gateway.wave b/examples/observability_gateway.wave index 54c517a9..e2e66165 100644 --- a/examples/observability_gateway.wave +++ b/examples/observability_gateway.wave @@ -1,22 +1,203 @@ -import("std::env::cwd"); -import("std::env::environ"); -import("std::path::copy"); -import("std::string::len"); -import("std::string::trim"); -import("std::string::ascii"); -import("std::string::hash"); -import("std::math::int"); -import("std::math::bits"); -import("std::math::float"); -import("std::math::num"); -import("std::time::clock"); -import("std::time::diff"); -import("std::time::sleep"); -import("std::buffer::alloc"); -import("std::buffer::write"); -import("std::buffer::read"); -import("std::mem::ops"); -import("std::net::tcp"); +import("std::env::cwd")::{ + env_getcwd, + env_chdir, + env_access, +}; +import("std::env::environ")::{ + EnvResult, + env_result_ok, + env_result_err, + env_unwrap_or, + env_get, + env_exists, + env_get_i64, + env_get_i32, + env_get_i32_default, + env_get_i64_default, +}; +import("std::path::copy")::{ + path_join2, + path_basename_copy, + path_dirname_copy, +}; +import("std::path::analyze")::{ + path_ext_start, + path_has_ext, +}; +import("std::path::core")::{ + path_is_abs, +}; +import("std::string::len")::{ + len, + is_empty, +}; +import("std::string::trim")::{ + trim_left_index, + trim_right_index, + trim_range, +}; +import("std::string::ascii")::{ + is_digit, + is_lower, + is_upper, + is_alpha, + is_alnum, + is_space, + to_lower, + to_upper, +}; +import("std::string::hash")::{ + djb2_32, + fnv1a_64, +}; +import("std::math::int")::{ + num_abs, + num_min, + num_max, + num_clamp, + ptr_swap, + abs, + min, + max, + clamp, + sign, + is_even, + is_odd, + div_ceil_pos, + div_floor_pos, + swap_i32, +}; +import("std::math::bits")::{ + is_pow2, + align_down, + align_up, + low_bit, + popcount, + ctz32, + bit_length, + ilog2_floor, + ilog2_ceil, + is_pow2_i64, + align_down_i64, + align_up_i64, + low_bit_i64, + popcount64, + ctz64, + bit_length64, + ilog2_floor64, + ilog2_ceil64, + bswap32, + bswap64, +}; +import("std::math::float")::{ + abs_f32, + min_f32, + max_f32, + clamp_f32, +}; +import("std::math::num")::{ + gcd, + lcm, + pow_i32, +}; +import("std::time::clock")::{ + time_now_realtime, + time_now_monotonic, + time_now_realtime_ns, + time_now_monotonic_ns, + TimeSpec, + nanosleep, + clock_gettime, +}; +import("std::time::diff")::{ + time_diff_ns, + time_diff_ms, +}; +import("std::time::sleep")::{ + time_sleep_ns, + time_sleep_us, + time_sleep_ms, +}; +import("std::buffer::alloc")::{ + buffer_new, + buffer_new_default, + buffer_free, + buffer_clear, + buffer_reserve, + tbuffer_new, + tbuffer_free, + tbuffer_clear, + tbuffer_reserve, + Buffer, + TypedBuffer, +}; +import("std::buffer::write")::{ + buffer_push, + buffer_append, + buffer_append_str, + buffer_set, + tbuffer_push, + tbuffer_set, +}; +import("std::buffer::read")::{ + buffer_at, + tbuffer_ptr, + tbuffer_len, + tbuffer_at, +}; +import("std::mem::ops")::{ + mem_set, + mem_zero, + mem_copy, + mem_move, + mem_cmp, + mem_eq, + mem_find_byte, + mem_swap, + mem_copy_items, + mem_set_items, + mem_move_items, + mem_zero_items, + mem_copy_checked, + mem_move_checked, + mem_set_checked, + mem_zero_checked, +}; +import("std::net::tcp")::{ + TcpAddr, + TcpListener, + TcpStream, + _tcp_to_net_addr, + _tcp_from_net_addr, + htons, + htonl, + tcp_addr, + tcp_addr_any, + tcp_addr_loopback, + tcp_set_reuseaddr, + tcp_bind, + tcp_bind_with_backlog, + tcp_bind_addr, + tcp_accept, + tcp_accept_addr, + tcp_close_listener, + tcp_connect, + tcp_try_connect, + tcp_read, + tcp_write, + tcp_write_all, + tcp_read_exact, + tcp_write_str, + tcp_stream_set_nonblock, + tcp_stream_get_nonblock, + tcp_listener_set_nonblock, + tcp_listener_get_nonblock, + tcp_from_fd, + tcp_listener_from_fd, + tcp_stream_valid, + tcp_listener_valid, + tcp_close, +}; const DEFAULT_PORT: i32 = 18080; const ALERT_THRESHOLD: i32 = 3; diff --git a/front/lexer/src/ident.rs b/front/lexer/src/ident.rs index 798ec699..302865c2 100644 --- a/front/lexer/src/ident.rs +++ b/front/lexer/src/ident.rs @@ -56,6 +56,11 @@ impl<'a> Lexer<'a> { lexeme: "export".to_string(), line: self.line, }, + "pub" => Token { + token_type: TokenType::Pub, + lexeme: "pub".to_string(), + line: self.line, + }, "type" => Token { token_type: TokenType::Type, lexeme: "type".to_string(), diff --git a/front/lexer/src/scan.rs b/front/lexer/src/scan.rs index dcdb9164..930b3e1f 100644 --- a/front/lexer/src/scan.rs +++ b/front/lexer/src/scan.rs @@ -146,11 +146,18 @@ impl<'a> Lexer<'a> { }) } ':' => { + if self.match_next(':') { + return Ok(Token { + token_type: TokenType::DoubleColon, + lexeme: "::".to_string(), + line: self.line, + }); + } return Ok(Token { token_type: TokenType::Colon, lexeme: ":".to_string(), line: self.line, - }) + }); } '<' => { if self.match_next('<') { diff --git a/front/lexer/src/token.rs b/front/lexer/src/token.rs index 8566a0d9..ffec5f88 100644 --- a/front/lexer/src/token.rs +++ b/front/lexer/src/token.rs @@ -89,6 +89,7 @@ pub enum TokenType { Fun, Extern, Export, + Pub, Type, Enum, Static, @@ -168,6 +169,7 @@ pub enum TokenType { Dot, // . SemiColon, // ; Colon, // : + DoubleColon, // :: Lchevr, // < LchevrEq, // <= Rchevr, // > diff --git a/front/parser/src/ast.rs b/front/parser/src/ast.rs index 445a666f..6215ae20 100644 --- a/front/parser/src/ast.rs +++ b/front/parser/src/ast.rs @@ -28,6 +28,7 @@ pub enum Value { #[derive(Debug, Clone, PartialEq, Eq)] pub enum WaveType { + Infer, Int(u16), Uint(u16), Float(u16), @@ -55,10 +56,18 @@ pub enum ASTNode { Enum(EnumNode), } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Visibility { + #[default] + Private, + Public, +} + #[derive(Debug, Clone)] pub struct TypeAliasNode { pub name: String, pub target: WaveType, + pub visibility: Visibility, } #[derive(Debug, Clone)] @@ -66,6 +75,7 @@ pub struct EnumNode { pub name: String, pub repr_type: WaveType, pub variants: Vec, + pub visibility: Visibility, } #[derive(Debug, Clone)] @@ -82,6 +92,7 @@ pub struct FunctionNode { pub return_type: Option, pub body: Vec, pub export: Option, + pub visibility: Visibility, } #[derive(Debug, Clone)] @@ -96,6 +107,7 @@ pub struct StructNode { pub generic_params: Vec, pub fields: Vec<(String, WaveType)>, pub methods: Vec, + pub visibility: Visibility, } #[derive(Debug, Clone)] @@ -304,7 +316,7 @@ pub enum StatementNode { value: Expression, arms: Vec, }, - Import(String), + Import(ImportNode), Assign { variable: String, value: Expression, @@ -321,6 +333,14 @@ pub enum StatementNode { Expression(Expression), } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ImportNode { + pub path: String, + pub alias: Option, + pub selections: Vec, + pub visibility: Visibility, +} + #[derive(Debug, Clone, Copy, PartialEq)] pub enum Mutability { Static, @@ -334,6 +354,7 @@ pub struct VariableNode { pub type_name: WaveType, pub initial_value: Option, pub mutability: Mutability, + pub visibility: Visibility, } #[derive(Clone)] diff --git a/front/parser/src/expr/primary.rs b/front/parser/src/expr/primary.rs index ea572aad..8f373d1d 100644 --- a/front/parser/src/expr/primary.rs +++ b/front/parser/src/expr/primary.rs @@ -88,9 +88,28 @@ where Some(Expression::Null) } TokenType::Identifier(name) => { - let name = name.clone(); + let mut name = name.clone(); tokens.next(); + while matches!( + tokens.peek().map(|token| &token.token_type), + Some(TokenType::DoubleColon) + ) { + tokens.next(); + let segment = match tokens.next() { + Some(Token { + token_type: TokenType::Identifier(segment), + .. + }) => segment, + _ => { + println!("Error: Expected identifier after '::'"); + return None; + } + }; + name.push_str("::"); + name.push_str(segment); + } + let expr = if let Some(peeked_token) = tokens.peek() { match &peeked_token.token_type { TokenType::Lchevr if peek_is_generic_call(tokens) => { diff --git a/front/parser/src/generics.rs b/front/parser/src/generics.rs index 4bc77ee8..5572c0c7 100644 --- a/front/parser/src/generics.rs +++ b/front/parser/src/generics.rs @@ -129,21 +129,28 @@ pub fn monomorphize_generics(ast: Vec) -> Result, String> &mut env, )?)); } - ASTNode::TypeAlias(TypeAliasNode { name, target }) => { + ASTNode::TypeAlias(TypeAliasNode { + name, + target, + visibility, + }) => { out.push(ASTNode::TypeAlias(TypeAliasNode { name, target: rewrite_wave_type(&target, &empty_subst, &mut env)?, + visibility, })); } ASTNode::Enum(EnumNode { name, repr_type, variants, + visibility, }) => { out.push(ASTNode::Enum(EnumNode { name, repr_type: rewrite_wave_type(&repr_type, &empty_subst, &mut env)?, variants, + visibility, })); } ASTNode::Statement(stmt) => { @@ -316,20 +323,25 @@ fn rewrite_node( ASTNode::Struct(s) => Ok(ASTNode::Struct(rewrite_struct(s, subst, env)?)), ASTNode::ExternFunction(e) => Ok(ASTNode::ExternFunction(rewrite_extern(e, subst, env)?)), ASTNode::ProtoImpl(p) => Ok(ASTNode::ProtoImpl(rewrite_proto(p, subst, env)?)), - ASTNode::TypeAlias(TypeAliasNode { name, target }) => { - Ok(ASTNode::TypeAlias(TypeAliasNode { - name, - target: rewrite_wave_type(&target, subst, env)?, - })) - } + ASTNode::TypeAlias(TypeAliasNode { + name, + target, + visibility, + }) => Ok(ASTNode::TypeAlias(TypeAliasNode { + name, + target: rewrite_wave_type(&target, subst, env)?, + visibility, + })), ASTNode::Enum(EnumNode { name, repr_type, variants, + visibility, }) => Ok(ASTNode::Enum(EnumNode { name, repr_type: rewrite_wave_type(&repr_type, subst, env)?, variants, + visibility, })), ASTNode::Program(p) => Ok(ASTNode::Program(p)), } @@ -864,6 +876,7 @@ fn mangle_instance_name(base: &str, args: &[WaveType]) -> String { fn mangle_type(ty: &WaveType) -> String { match ty { + WaveType::Infer => "infer".to_string(), WaveType::Int(n) => format!("i{}", n), WaveType::Uint(n) => format!("u{}", n), WaveType::Float(n) => format!("f{}", n), diff --git a/front/parser/src/import.rs b/front/parser/src/import.rs index dd153bd2..329459f1 100644 --- a/front/parser/src/import.rs +++ b/front/parser/src/import.rs @@ -136,7 +136,8 @@ fn is_supported_target_item_start(line: &str) -> bool { let trimmed = line.trim_start(); for kw in [ - "import", "extern", "export", "fun", "struct", "enum", "const", "static", "type", "proto", + "import", "extern", "export", "pub", "fun", "struct", "enum", "const", "static", "type", + "proto", ] { if let Some(rest) = trimmed.strip_prefix(kw) { if has_ident_boundary(rest) { @@ -374,14 +375,37 @@ pub fn local_import_unit_with_config( return std_import_unit(path, already_imported, config); } - if path.contains("::") { + if !path.starts_with("./") { return external_import_unit(path, already_imported, config); } - let target_file_name = if path.ends_with(".wave") { - path.to_string() + if path.contains('\\') || path.split('/').any(|segment| segment == "..") { + return Err(WaveError::new( + WaveErrorKind::SyntaxError("Invalid local import path".to_string()), + format!("local import '{}' escapes its module directory", path), + path, + 0, + 0, + ) + .with_help("local imports must start with `./` and may not contain `..` or backslashes")); + } + + let local_path = path.strip_prefix("./").unwrap_or(path); + if local_path.is_empty() || Path::new(local_path).is_absolute() { + return Err(WaveError::new( + WaveErrorKind::SyntaxError("Invalid local import path".to_string()), + format!("invalid local import '{}'", path), + path, + 0, + 0, + ) + .with_help("use a relative module path such as import(\"./helpers\")")); + } + + let target_file_name = if local_path.ends_with(".wave") { + local_path.to_string() } else { - format!("{}.wave", path) + format!("{}.wave", local_path) }; let found_path = base_dir.join(&target_file_name); @@ -395,7 +419,44 @@ pub fn local_import_unit_with_config( )); } - parse_wave_file(&found_path, &target_file_name, already_imported, config) + let canonical_base = base_dir.canonicalize().map_err(|error| { + WaveError::new( + WaveErrorKind::SyntaxError("Canonicalization failed".to_string()), + format!("failed to canonicalize module directory: {}", error), + path, + 0, + 0, + ) + })?; + let canonical_target = found_path.canonicalize().map_err(|error| { + WaveError::new( + WaveErrorKind::SyntaxError("Canonicalization failed".to_string()), + format!("failed to canonicalize local import '{}': {}", path, error), + path, + 0, + 0, + ) + })?; + if !canonical_target.starts_with(&canonical_base) { + return Err(WaveError::new( + WaveErrorKind::SyntaxError("Local import escapes module directory".to_string()), + format!( + "local import '{}' resolves outside its module directory", + path + ), + path, + 0, + 0, + ) + .with_help("remove symlink or path traversal components from the local import")); + } + + parse_wave_file( + &canonical_target, + &target_file_name, + already_imported, + config, + ) } pub fn local_import( @@ -450,21 +511,29 @@ fn external_import_unit( let package = parts.next().unwrap_or("").trim(); let module_parts: Vec<&str> = parts.collect(); - if package.is_empty() - || module_parts.is_empty() - || module_parts.iter().any(|s| s.trim().is_empty()) - { + let valid_segment = |segment: &str| { + !segment.is_empty() + && segment + .chars() + .next() + .is_some_and(|ch| ch.is_ascii_alphabetic() || ch == '_') + && segment + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || ch == '_') + }; + + if !valid_segment(package) || module_parts.iter().any(|segment| !valid_segment(segment)) { return Err(WaveError::new( WaveErrorKind::SyntaxError("Invalid external import path".to_string()), format!( - "invalid external import '{}': expected `package::module::path`", + "invalid external import '{}': package and module names must be identifiers", path ), path, 0, 0, ) - .with_help("use at least two segments, for example: import(\"math::vector::ops\")")); + .with_help("use `package` or `package::module`; local files must use `./path`")); } let package_root = match resolve_external_package_root(package, config) { @@ -481,7 +550,7 @@ fn external_import_unit( 0, ) .with_help("provide dependency paths with `--dep-root ` or `--dep =`") - .with_suggestion("example: wavec run main.wave --dep-root .vex/dep") + .with_suggestion("example: wavec run main.wave --dep-root .vex/deps") .with_suggestion(format!( "example: wavec run main.wave --dep {}=/abs/path/to/{}", package, package @@ -535,21 +604,59 @@ fn external_import_unit( .with_help("pass a valid directory path via `--dep =`")); } - let module_rel = module_parts.join("/"); - let module_file = if module_rel.ends_with(".wave") { - module_rel + let candidates = if module_parts.is_empty() { + vec![ + package_root.join("src/lib.wave"), + package_root.join("lib.wave"), + ] } else { - format!("{}.wave", module_rel) + let module_file = format!("{}.wave", module_parts.join("/")); + vec![ + package_root.join("src").join(&module_file), + package_root.join(&module_file), + ] }; - let candidates = [ - package_root.join(&module_file), - package_root.join("src").join(&module_file), - ]; + let canonical_package_root = package_root.canonicalize().map_err(|error| { + WaveError::new( + WaveErrorKind::SyntaxError("Canonicalization failed".to_string()), + format!( + "failed to canonicalize dependency package '{}': {}", + package, error + ), + path, + 0, + 0, + ) + })?; for candidate in &candidates { if candidate.exists() && candidate.is_file() { - return parse_wave_file(candidate, path, already_imported, config); + let canonical_candidate = candidate.canonicalize().map_err(|error| { + WaveError::new( + WaveErrorKind::SyntaxError("Canonicalization failed".to_string()), + format!("failed to canonicalize import '{}': {}", path, error), + path, + 0, + 0, + ) + })?; + if !canonical_candidate.starts_with(&canonical_package_root) { + return Err(WaveError::new( + WaveErrorKind::SyntaxError( + "Package import escapes dependency root".to_string(), + ), + format!( + "package import '{}' resolves outside package '{}'", + path, package + ), + path, + 0, + 0, + ) + .with_help("remove symlinks that point outside the dependency package")); + } + return parse_wave_file(&canonical_candidate, path, already_imported, config); } } diff --git a/front/parser/src/parser/control.rs b/front/parser/src/parser/control.rs index 25b310ce..94b0abf6 100644 --- a/front/parser/src/parser/control.rs +++ b/front/parser/src/parser/control.rs @@ -18,6 +18,7 @@ use crate::ast::{ ASTNode, Expression, MatchArm, MatchPattern, Mutability, StatementNode, VariableNode, + Visibility, }; use crate::expr::parse_expression; use crate::parser::stmt::parse_block; @@ -224,6 +225,7 @@ fn parse_typed_for_initializer( type_name, initial_value, mutability, + visibility: Visibility::Private, })) } diff --git a/front/parser/src/parser/decl.rs b/front/parser/src/parser/decl.rs index 82af793c..2f779cd2 100644 --- a/front/parser/src/parser/decl.rs +++ b/front/parser/src/parser/decl.rs @@ -18,7 +18,7 @@ use crate::ast::{ ASTNode, EnumNode, EnumVariantNode, Expression, ExternFunctionNode, Mutability, TypeAliasNode, - VariableNode, WaveType, + VariableNode, Visibility, WaveType, }; use crate::expr::parse_expression; use crate::parser::types::{parse_type, token_type_to_wave_type}; @@ -192,6 +192,7 @@ pub fn parse_const_decl(tokens: &mut Peekable>) -> Option>) -> Option { }; skip_ws(tokens); + if matches!( + tokens.peek().map(|token| &token.token_type), + Some(TokenType::Equal) + ) { + tokens.next(); + let initial_value = parse_expression(tokens)?; + if tokens.peek().map(|token| &token.token_type) != Some(&TokenType::SemiColon) { + println!("Expected ';' after inferred variable initializer"); + return None; + } + tokens.next(); + return Some(ASTNode::Variable(VariableNode { + name, + type_name: WaveType::Infer, + initial_value: Some(initial_value), + mutability, + visibility: Visibility::Private, + })); + } + if !matches!(tokens.next().map(|t| &t.token_type), Some(TokenType::Colon)) { println!("Expected ':' after identifier"); return None; @@ -319,6 +340,7 @@ pub fn parse_var(tokens: &mut Peekable>) -> Option { type_name: wave_type, initial_value, mutability, + visibility: Visibility::Private, })) } @@ -761,7 +783,11 @@ pub fn parse_type_alias(tokens: &mut Peekable>) -> Option Option { @@ -908,5 +934,6 @@ pub fn parse_enum(tokens: &mut Peekable>) -> Option { name, repr_type, variants, + visibility: Visibility::Private, })) } diff --git a/front/parser/src/parser/functions.rs b/front/parser/src/parser/functions.rs index 58865727..8c65bdf4 100644 --- a/front/parser/src/parser/functions.rs +++ b/front/parser/src/parser/functions.rs @@ -16,7 +16,9 @@ //! within generic parameter lists are rejected here; program-wide symbol and //! body type checks remain the semantic verifier's responsibility. -use crate::ast::{ASTNode, ExportAttribute, FunctionNode, ParameterNode, StatementNode, Value}; +use crate::ast::{ + ASTNode, ExportAttribute, FunctionNode, ParameterNode, StatementNode, Value, Visibility, +}; use crate::expr::parse_expression; use crate::parser::asm::*; use crate::parser::control::*; @@ -266,6 +268,7 @@ pub fn parse_function_with_export( body, return_type, export, + visibility: Visibility::Private, })) } diff --git a/front/parser/src/parser/items.rs b/front/parser/src/parser/items.rs index 5d711bc8..f01635a6 100644 --- a/front/parser/src/parser/items.rs +++ b/front/parser/src/parser/items.rs @@ -16,7 +16,9 @@ //! delimiter or semicolon. Method bodies reuse the function parser so parameter, //! generic, and return-type grammar stays consistent across item kinds. -use crate::ast::{ASTNode, ProtoImplNode, StatementNode, StructNode, WaveType}; +use crate::ast::{ + ASTNode, ImportNode, ProtoImplNode, StatementNode, StructNode, Visibility, WaveType, +}; use crate::parser::functions::{parse_function, parse_generic_param_names}; use crate::types::parse_type_from_stream; use lexer::token::TokenType; @@ -56,19 +58,93 @@ pub fn parse_import(tokens: &mut Peekable>) -> Option { } }; + let alias = if tokens.peek()?.token_type == TokenType::As { + tokens.next(); + match tokens.next() { + Some(Token { + token_type: TokenType::Identifier(name), + .. + }) => Some(name.clone()), + other => { + println!("Error: Expected import alias after 'as', found {:?}", other); + return None; + } + } + } else { + None + }; + if tokens.peek()?.token_type != TokenType::Rparen { println!("Error: Expected ')' after 'import' condition"); return None; } tokens.next(); - if tokens.peek()?.token_type != TokenType::SemiColon { - println!("Error: Expected ';' after 'import' condition"); + let mut selections = Vec::new(); + if tokens.peek()?.token_type == TokenType::DoubleColon { + if alias.is_some() { + println!("Error: Import aliases cannot be combined with selective imports"); + return None; + } + tokens.next(); + if tokens.next()?.token_type != TokenType::Lbrace { + println!("Error: Expected '{{' after '::' in selective import"); + return None; + } + loop { + match tokens.next() { + Some(Token { + token_type: TokenType::Identifier(name), + .. + }) => selections.push(name.clone()), + Some(Token { + token_type: TokenType::Rbrace, + .. + }) if !selections.is_empty() => break, + other => { + println!( + "Error: Expected symbol name in selective import, found {:?}", + other + ); + return None; + } + } + match tokens.next() { + Some(Token { + token_type: TokenType::Comma, + .. + }) => { + if tokens.peek()?.token_type == TokenType::Rbrace { + tokens.next(); + break; + } + } + Some(Token { + token_type: TokenType::Rbrace, + .. + }) => break, + other => { + println!( + "Error: Expected ',' or '}}' in selective import, found {:?}", + other + ); + return None; + } + } + } + } + + if tokens.next()?.token_type != TokenType::SemiColon { + println!("Error: Expected ';' after import declaration"); return None; } - tokens.next(); - Some(ASTNode::Statement(StatementNode::Import(import_path))) + Some(ASTNode::Statement(StatementNode::Import(ImportNode { + path: import_path, + alias, + selections, + visibility: Visibility::Private, + }))) } pub fn parse_proto(tokens: &mut Peekable>) -> Option { @@ -308,5 +384,6 @@ pub fn parse_struct(tokens: &mut Peekable>) -> Option { generic_params, fields, methods, + visibility: Visibility::Private, })) } diff --git a/front/parser/src/parser/parse.rs b/front/parser/src/parser/parse.rs index e56e823e..c6687dd9 100644 --- a/front/parser/src/parser/parse.rs +++ b/front/parser/src/parser/parse.rs @@ -16,7 +16,7 @@ //! generic specialization, and semantic type validation are later phases and //! must not be silently performed while consuming syntax. -use crate::ast::ASTNode; +use crate::ast::{ASTNode, Visibility}; use crate::parser::decl::*; use crate::parser::functions::{parse_export, parse_function}; use crate::parser::items::*; @@ -221,6 +221,108 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { .with_help("imports must use parentheses and end with ';'")); } } + TokenType::Pub => { + let anchor = (*token).clone(); + iter.next(); + while matches!( + iter.peek().map(|token| &token.token_type), + Some(TokenType::Whitespace | TokenType::Newline) + ) { + iter.next(); + } + + let declaration = match iter.peek().map(|token| &token.token_type) { + Some(TokenType::Import) => { + iter.next(); + parse_import(&mut iter) + } + Some(TokenType::Export) => { + iter.next(); + parse_export(&mut iter).and_then(|mut declarations| { + if declarations.len() == 1 { + declarations.pop() + } else { + None + } + }) + } + Some(TokenType::Fun) => parse_function(&mut iter), + Some(TokenType::Struct) => { + iter.next(); + parse_struct(&mut iter) + } + Some(TokenType::Type) => { + iter.next(); + parse_type_alias(&mut iter) + } + Some(TokenType::Enum) => { + iter.next(); + parse_enum(&mut iter) + } + Some(TokenType::Const) => { + iter.next(); + parse_const(&mut iter) + } + Some(TokenType::Static) => { + iter.next(); + parse_static(&mut iter) + } + _ => None, + }; + + let mut declaration = declaration.ok_or_else(|| { + ParseError::syntax_at( + Some(&anchor), + "`pub` must precede an importable declaration", + ) + .with_context("public declaration") + .with_expected_many([ + "pub fun", + "pub import", + "pub export(c) fun", + "pub struct", + "pub enum", + "pub type", + "pub const", + "pub static", + ]) + .with_found_token(iter.peek().copied()) + .with_help("`pub` controls Wave module visibility; it is not an ABI export") + })?; + + match &mut declaration { + ASTNode::Statement(crate::ast::StatementNode::Import(import)) => { + if import.selections.is_empty() { + return Err(ParseError::syntax_at( + Some(&anchor), + "public imports must select symbols to re-export", + ) + .with_context("public import") + .with_expected("pub import(\"module\")::{symbol};") + .with_help("select the public symbols that this module re-exports")); + } + import.visibility = Visibility::Public; + } + ASTNode::Function(function) => { + if function.name == "main" { + return Err(ParseError::syntax_at( + Some(&anchor), + "entry function `main` cannot be public", + ) + .with_context("public declaration") + .with_expected("fun main() { ... }") + .with_help("remove `pub`; `main` is a private program entry point")); + } + function.visibility = Visibility::Public; + } + ASTNode::Struct(structure) => structure.visibility = Visibility::Public, + ASTNode::TypeAlias(alias) => alias.visibility = Visibility::Public, + ASTNode::Enum(enumeration) => enumeration.visibility = Visibility::Public, + ASTNode::Variable(variable) => variable.visibility = Visibility::Public, + _ => unreachable!("public parser only constructs importable declarations"), + } + nodes.push(declaration); + } TokenType::Extern => { let anchor = (*token).clone(); iter.next(); @@ -381,7 +483,7 @@ pub fn parse_syntax_only(tokens: &[Token]) -> Result, ParseError> { ParseError::syntax_at(Some(token), "unexpected token at top level") .with_context("top-level items") .with_expected_many([ - "import", "extern", "const", "static", "type", "enum", "struct", + "import", "extern", "pub", "const", "static", "type", "enum", "struct", "proto", "fun", "export", ]) .with_found_token(Some(token)) diff --git a/front/parser/src/parser/types.rs b/front/parser/src/parser/types.rs index b402ee65..4a18eb5f 100644 --- a/front/parser/src/parser/types.rs +++ b/front/parser/src/parser/types.rs @@ -186,12 +186,14 @@ pub fn parse_type(type_str: &str) -> Option { return Some(TokenType::TypeString); } - if type_str - .chars() - .next() - .map_or(false, |c| c.is_alphabetic() || c == '_') - && type_str.chars().all(|c| c.is_alphanumeric() || c == '_') - { + if type_str.split("::").all(|segment| { + !segment.is_empty() + && segment + .chars() + .next() + .is_some_and(|c| c.is_alphabetic() || c == '_') + && segment.chars().all(|c| c.is_alphanumeric() || c == '_') + }) { return Some(TokenType::TypeCustom(type_str.to_string())); } @@ -276,6 +278,21 @@ where let type_token = tokens.next()?; if let TokenType::Identifier(name) = &type_token.token_type { + let mut name = name.clone(); + while matches!( + tokens.peek().map(|token| &token.token_type), + Some(TokenType::DoubleColon) + ) { + tokens.next(); + match tokens.next().map(|token| &token.token_type) { + Some(TokenType::Identifier(segment)) => { + name.push_str("::"); + name.push_str(segment); + } + _ => return None, + } + } + while matches!( tokens.peek().map(|t| &t.token_type), Some(TokenType::Whitespace | TokenType::Newline) @@ -296,7 +313,7 @@ where return token_type_to_wave_type(&parsed_tt); } - let parsed_tt = parse_type(name)?; + let parsed_tt = parse_type(&name)?; return token_type_to_wave_type(&parsed_tt); } diff --git a/front/parser/src/verification.rs b/front/parser/src/verification.rs index 0a9b70ba..d71defca 100644 --- a/front/parser/src/verification.rs +++ b/front/parser/src/verification.rs @@ -2539,6 +2539,7 @@ fn display_expression_type(ty: &ExpressionType) -> String { fn display_wave_type(ty: &WaveType) -> String { match ty { + WaveType::Infer => "inferred type".to_string(), WaveType::Int(bits) => format!("i{}", bits), WaveType::Uint(bits) => format!("u{}", bits), WaveType::Float(bits) => format!("f{}", bits), diff --git a/front/parser/tests/parse_var_and_generics.rs b/front/parser/tests/parse_var_and_generics.rs index 9d374641..097d13ae 100644 --- a/front/parser/tests/parse_var_and_generics.rs +++ b/front/parser/tests/parse_var_and_generics.rs @@ -1,6 +1,7 @@ //! Regression coverage for variable declarations and generic type syntax. use lexer::Lexer; +use parser::ast::{ASTNode, Expression, StatementNode, Visibility, WaveType}; use parser::parse_syntax_only; fn parse_ok(src: &str) { @@ -22,6 +23,12 @@ fn parse_ok(src: &str) { } } +fn parse_nodes(src: &str) -> Vec { + let mut lexer = Lexer::new(src); + let tokens = lexer.tokenize().expect("lex should succeed"); + parse_syntax_only(&tokens).expect("parse should succeed") +} + #[test] fn parses_var_in_function_body() { parse_ok( @@ -50,3 +57,82 @@ fun make_pair(a: A, b: B) -> Pair { "#, ); } + +#[test] +fn parses_public_declarations_and_import_forms() { + let nodes = parse_nodes( + r#" +import("add"); +import("add::math"); +import("./helpers" as helpers); +import("add")::{sum, Point,}; + +pub fun sum(a: i32, b: i32) -> i32 { return a + b; } +pub struct Point {} +fun main() { + var a = add::sum(1, 2); + var b = sum(1, 2); + var p = Point(); +} +"#, + ); + + let imports = nodes + .iter() + .filter_map(|node| match node { + ASTNode::Statement(StatementNode::Import(import)) => Some(import), + _ => None, + }) + .collect::>(); + assert_eq!(imports.len(), 4); + assert_eq!(imports[0].path, "add"); + assert_eq!(imports[1].path, "add::math"); + assert_eq!(imports[2].alias.as_deref(), Some("helpers")); + assert_eq!(imports[3].selections, ["sum", "Point"]); + + assert!(matches!( + &nodes[4], + ASTNode::Function(function) if function.visibility == Visibility::Public + )); + assert!(matches!( + &nodes[5], + ASTNode::Struct(structure) if structure.visibility == Visibility::Public + )); + + let ASTNode::Function(main) = &nodes[6] else { + panic!("expected main function"); + }; + let ASTNode::Variable(first) = &main.body[0] else { + panic!("expected first variable"); + }; + assert_eq!(first.type_name, WaveType::Infer); + assert!(matches!( + first.initial_value.as_ref(), + Some(Expression::FunctionCall { name, .. }) if name == "add::sum" + )); +} + +#[test] +fn rejects_public_main() { + let mut lexer = Lexer::new("pub fun main() {}\n"); + let tokens = lexer.tokenize().expect("lex should succeed"); + let error = parse_syntax_only(&tokens).expect_err("pub main must fail"); + assert_eq!(error.message(), "entry function `main` cannot be public"); +} + +#[test] +fn public_visibility_is_independent_from_c_abi_export() { + let nodes = parse_nodes( + "pub export(c) fun shared_value() -> i32 { return 1; }\nexport(c) fun abi_only() -> i32 { return 2; }\n", + ); + let ASTNode::Function(shared) = &nodes[0] else { + panic!("expected shared export"); + }; + let ASTNode::Function(abi_only) = &nodes[1] else { + panic!("expected ABI-only export"); + }; + assert_eq!(shared.visibility, Visibility::Public); + assert!(shared.export.is_some()); + assert_eq!(abi_only.visibility, Visibility::Private); + assert!(abi_only.export.is_some()); +} diff --git a/llvm/src/codegen/ir.rs b/llvm/src/codegen/ir.rs index c3aca5c9..8c9084b3 100644 --- a/llvm/src/codegen/ir.rs +++ b/llvm/src/codegen/ir.rs @@ -1101,7 +1101,7 @@ fn collect_named_types(nodes: &[ASTNode]) -> HashMap { let mut m = HashMap::new(); for n in nodes { match n { - ASTNode::TypeAlias(TypeAliasNode { name, target }) => { + ASTNode::TypeAlias(TypeAliasNode { name, target, .. }) => { m.insert(name.clone(), target.clone()); } ASTNode::Enum(EnumNode { diff --git a/llvm/src/codegen/types.rs b/llvm/src/codegen/types.rs index 77bc9dcf..82edf4bf 100644 --- a/llvm/src/codegen/types.rs +++ b/llvm/src/codegen/types.rs @@ -56,6 +56,7 @@ pub fn wave_type_to_llvm_type<'ctx>( flavor: TypeFlavor, ) -> BasicTypeEnum<'ctx> { match wave_type { + WaveType::Infer => panic!("inferred Wave type reached LLVM lowering"), WaveType::Int(bits) | WaveType::Uint(bits) => context .custom_width_int_type(*bits as u32) .as_basic_type_enum(), diff --git a/llvm/src/importgen.rs b/llvm/src/importgen.rs index f37532e9..e46f35ec 100644 --- a/llvm/src/importgen.rs +++ b/llvm/src/importgen.rs @@ -32,8 +32,8 @@ fn expand_imports_recursive( for node in ast { match node { - ASTNode::Statement(StatementNode::Import(path)) => { - let imported = local_import_unit(&path, already, current_file_dir)?; + ASTNode::Statement(StatementNode::Import(import)) => { + let imported = local_import_unit(&import.path, already, current_file_dir)?; let next_dir = imported.abs_path.parent().unwrap_or(current_file_dir); diff --git a/llvm/src/statement/variable.rs b/llvm/src/statement/variable.rs index 1f100477..6ead082c 100644 --- a/llvm/src/statement/variable.rs +++ b/llvm/src/statement/variable.rs @@ -158,6 +158,7 @@ pub(super) fn gen_variable_ir<'ctx>( type_name, initial_value, mutability, + .. } = var_node; if matches!(initial_value, Some(Expression::Null)) && !matches!(type_name, WaveType::Pointer(_)) diff --git a/src/lib.rs b/src/lib.rs index 61e65e57..e48e4d8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,7 @@ pub mod cli; pub mod errors; pub mod flags; pub mod link_validation; +pub mod module_resolver; pub mod runner; pub mod std; pub mod version; diff --git a/src/module_resolver.rs b/src/module_resolver.rs new file mode 100644 index 00000000..8855d59a --- /dev/null +++ b/src/module_resolver.rs @@ -0,0 +1,1402 @@ +//! Import graph construction, module visibility, and namespace lowering. +//! +//! Wave's backend still consumes one concrete AST. This pass preserves module +//! boundaries while resolving imports, then gives every imported declaration a +//! collision-free internal name before the existing semantic and LLVM phases. + +use ::error::{WaveError, WaveErrorKind}; +use ::parser::ast::*; +use ::parser::import::{local_import_unit_with_config, ImportConfig}; +use std::collections::{HashMap, HashSet}; +use std::path::{Path, PathBuf}; + +#[derive(Debug)] +pub struct ModuleSource { + pub path: PathBuf, + pub source: String, +} + +#[derive(Debug)] +pub struct ResolvedModuleGraph { + pub ast: Vec, + pub origins: Vec, + pub sources: Vec, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SymbolKind { + Function, + Struct, + Type, + Value, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +struct ModuleSymbol { + lowered: String, + visibility: Visibility, + kind: SymbolKind, +} + +#[derive(Debug, Clone, Default, PartialEq, Eq)] +struct ModuleInterface { + symbols: HashMap, +} + +#[derive(Default)] +struct NameContext { + own: HashMap, + selected: HashMap, + namespaces: HashMap, +} + +struct Resolver<'a> { + config: &'a ImportConfig, + interfaces: HashMap, + visiting: Vec, + source_indices: HashMap, + ast: Vec, + origins: Vec, + sources: Vec, +} + +pub fn resolve_import_graph( + entry_path: &Path, + entry_source: &str, + ast: Vec, + config: &ImportConfig, +) -> Result { + let entry_key = canonical_key(entry_path); + let mut resolver = Resolver { + config, + interfaces: HashMap::new(), + visiting: Vec::new(), + source_indices: HashMap::from([(entry_key.clone(), 0)]), + ast: Vec::new(), + origins: Vec::new(), + sources: vec![ModuleSource { + path: entry_path.to_path_buf(), + source: entry_source.to_string(), + }], + }; + + resolver.resolve_module(entry_key.clone(), ast, 0, true)?; + infer_variable_types(&mut resolver.ast).map_err(|message| { + module_error( + &entry_key, + "Type inference failed", + message, + "add an explicit `: type` annotation to the variable", + ) + })?; + Ok(ResolvedModuleGraph { + ast: resolver.ast, + origins: resolver.origins, + sources: resolver.sources, + }) +} + +#[derive(Clone)] +struct CallableType { + generic_params: Vec, + return_type: WaveType, +} + +#[derive(Default)] +struct InferenceTypes { + functions: HashMap, + methods: HashMap<(String, String), CallableType>, + fields: HashMap>, + globals: HashMap, +} + +fn infer_variable_types(ast: &mut [ASTNode]) -> Result<(), String> { + let mut types = InferenceTypes::default(); + for node in ast.iter() { + match node { + ASTNode::Function(function) => { + types.functions.insert( + function.name.clone(), + CallableType { + generic_params: function.generic_params.clone(), + return_type: function.return_type.clone().unwrap_or(WaveType::Void), + }, + ); + } + ASTNode::ExternFunction(function) => { + types.functions.insert( + function.name.clone(), + CallableType { + generic_params: Vec::new(), + return_type: function.return_type.clone(), + }, + ); + } + ASTNode::Struct(structure) => { + types.fields.insert( + structure.name.clone(), + structure.fields.iter().cloned().collect(), + ); + for method in &structure.methods { + types.methods.insert( + (structure.name.clone(), method.name.clone()), + CallableType { + generic_params: method.generic_params.clone(), + return_type: method.return_type.clone().unwrap_or(WaveType::Void), + }, + ); + } + } + ASTNode::Variable(variable) => { + types + .globals + .insert(variable.name.clone(), variable.type_name.clone()); + } + _ => {} + } + } + + for node in ast.iter_mut() { + if let ASTNode::Variable(variable) = node { + if variable.type_name == WaveType::Infer { + let initializer = variable.initial_value.as_ref().ok_or_else(|| { + format!( + "cannot infer type of global variable '{}' without an initializer", + variable.name + ) + })?; + variable.type_name = infer_expression_type(initializer, &types, &HashMap::new())?; + types + .globals + .insert(variable.name.clone(), variable.type_name.clone()); + } + } + } + + for node in ast.iter_mut() { + match node { + ASTNode::Function(function) => infer_function_body(function, &types)?, + ASTNode::Struct(structure) => { + for method in &mut structure.methods { + infer_function_body(method, &types)?; + } + } + ASTNode::ProtoImpl(implementation) => { + for method in &mut implementation.methods { + infer_function_body(method, &types)?; + } + } + _ => {} + } + } + Ok(()) +} + +fn infer_function_body(function: &mut FunctionNode, types: &InferenceTypes) -> Result<(), String> { + let mut locals = function + .parameters + .iter() + .map(|parameter| (parameter.name.clone(), parameter.param_type.clone())) + .collect::>(); + infer_block(&mut function.body, types, &mut locals) +} + +fn infer_block( + nodes: &mut [ASTNode], + types: &InferenceTypes, + locals: &mut HashMap, +) -> Result<(), String> { + for node in nodes { + match node { + ASTNode::Variable(variable) => { + if variable.type_name == WaveType::Infer { + let initializer = variable.initial_value.as_ref().ok_or_else(|| { + format!( + "cannot infer type of variable '{}' without an initializer", + variable.name + ) + })?; + variable.type_name = infer_expression_type(initializer, types, locals) + .map_err(|reason| { + format!( + "cannot infer type of variable '{}': {}", + variable.name, reason + ) + })?; + } + locals.insert(variable.name.clone(), variable.type_name.clone()); + } + ASTNode::Statement(statement) => infer_statement(statement, types, locals)?, + _ => {} + } + } + Ok(()) +} + +fn infer_statement( + statement: &mut StatementNode, + types: &InferenceTypes, + locals: &mut HashMap, +) -> Result<(), String> { + match statement { + StatementNode::If { + body, + else_if_blocks, + else_block, + .. + } => { + let mut scope = locals.clone(); + infer_block(body, types, &mut scope)?; + if let Some(blocks) = else_if_blocks { + for (_, body) in blocks.iter_mut() { + let mut scope = locals.clone(); + infer_block(body, types, &mut scope)?; + } + } + if let Some(body) = else_block { + let mut scope = locals.clone(); + infer_block(body, types, &mut scope)?; + } + } + StatementNode::For { + initialization, + body, + .. + } => { + let mut scope = locals.clone(); + infer_block( + std::slice::from_mut(initialization.as_mut()), + types, + &mut scope, + )?; + infer_block(body, types, &mut scope)?; + } + StatementNode::While { body, .. } => { + let mut scope = locals.clone(); + infer_block(body, types, &mut scope)?; + } + StatementNode::Match { arms, .. } => { + for arm in arms { + let mut scope = locals.clone(); + infer_block(&mut arm.body, types, &mut scope)?; + } + } + _ => {} + } + Ok(()) +} + +fn infer_expression_type( + expression: &Expression, + types: &InferenceTypes, + locals: &HashMap, +) -> Result { + match expression { + Expression::Literal(Literal::Int(_)) => Ok(WaveType::Int(32)), + Expression::Literal(Literal::Float(_)) => Ok(WaveType::Float(64)), + Expression::Literal(Literal::String(_)) => Ok(WaveType::String), + Expression::Literal(Literal::Bool(_)) => Ok(WaveType::Bool), + Expression::Literal(Literal::Char(_)) => Ok(WaveType::Char), + Expression::Literal(Literal::Byte(_)) => Ok(WaveType::Byte), + Expression::Null => Err("`null` needs an explicit pointer type".to_string()), + Expression::Variable(name) => locals + .get(name) + .or_else(|| types.globals.get(name)) + .cloned() + .ok_or_else(|| format!("unknown value '{}'", name)), + Expression::StructLiteral { name, .. } => Ok(WaveType::Struct(name.clone())), + Expression::FunctionCall { + name, type_args, .. + } => { + let callable = types + .functions + .get(name) + .ok_or_else(|| format!("unknown function '{}'", name))?; + Ok(substitute_callable_return(callable, type_args)) + } + Expression::MethodCall { object, name, .. } => { + let object_type = infer_expression_type(object, types, locals)?; + let WaveType::Struct(struct_name) = object_type else { + return Err(format!("method '{}' receiver type is not a struct", name)); + }; + let callable = types + .methods + .get(&(struct_name.clone(), name.clone())) + .ok_or_else(|| format!("unknown method '{}.{}'", struct_name, name))?; + Ok(callable.return_type.clone()) + } + Expression::Deref(inner) => match infer_expression_type(inner, types, locals)? { + WaveType::Pointer(inner) => Ok(*inner), + other => Err(format!("cannot dereference {:?}", other)), + }, + Expression::AddressOf(inner) => Ok(WaveType::Pointer(Box::new(infer_expression_type( + inner, types, locals, + )?))), + Expression::BinaryExpression { + left, + operator, + right, + } => { + if matches!( + operator, + Operator::GreaterEqual + | Operator::LessEqual + | Operator::Greater + | Operator::Less + | Operator::Equal + | Operator::NotEqual + | Operator::LogicalAnd + | Operator::LogicalOr + | Operator::LogicalNot + | Operator::Not + ) { + return Ok(WaveType::Bool); + } + let left_literal = expression_literal_kind(left); + let right_literal = expression_literal_kind(right); + let left = infer_expression_type(left, types, locals)?; + let right = infer_expression_type(right, types, locals)?; + if left == right { + Ok(left) + } else if matches!(left_literal, Some(false)) && is_integer_type(&right) { + Ok(right) + } else if matches!(right_literal, Some(false)) && is_integer_type(&left) { + Ok(left) + } else if matches!(left_literal, Some(true)) && matches!(&right, WaveType::Float(_)) { + Ok(right) + } else if matches!(right_literal, Some(true)) && matches!(&left, WaveType::Float(_)) { + Ok(left) + } else { + Err(format!( + "binary operands have different types {:?} and {:?}", + left, right + )) + } + } + Expression::IndexAccess { target, .. } => { + match infer_expression_type(target, types, locals)? { + WaveType::Array(inner, _) | WaveType::Pointer(inner) => Ok(*inner), + WaveType::String => Ok(WaveType::Byte), + other => Err(format!("cannot index {:?}", other)), + } + } + Expression::ArrayLiteral(values) => { + let first = values + .first() + .ok_or_else(|| "empty array literal needs an explicit type".to_string())?; + let element = infer_expression_type(first, types, locals)?; + Ok(WaveType::Array(Box::new(element), values.len() as u32)) + } + Expression::Grouped(inner) => infer_expression_type(inner, types, locals), + Expression::AssignOperation { target, .. } + | Expression::Assignment { target, .. } + | Expression::IncDec { target, .. } => infer_expression_type(target, types, locals), + Expression::FieldAccess { object, field } => { + let WaveType::Struct(struct_name) = infer_expression_type(object, types, locals)? + else { + return Err(format!("field '{}' receiver is not a struct", field)); + }; + types + .fields + .get(&struct_name) + .and_then(|fields| fields.get(field)) + .cloned() + .ok_or_else(|| format!("unknown field '{}.{}'", struct_name, field)) + } + Expression::Unary { operator, expr } => { + if matches!(operator, Operator::Not | Operator::LogicalNot) { + Ok(WaveType::Bool) + } else { + infer_expression_type(expr, types, locals) + } + } + Expression::Cast { target_type, .. } => Ok(target_type.clone()), + Expression::AsmBlock { .. } => { + Err("inline assembly result needs an explicit type".to_string()) + } + } +} + +fn expression_literal_kind(expression: &Expression) -> Option { + match expression { + Expression::Literal(Literal::Int(_)) => Some(false), + Expression::Literal(Literal::Float(_)) => Some(true), + Expression::Grouped(inner) => expression_literal_kind(inner), + _ => None, + } +} + +fn is_integer_type(ty: &WaveType) -> bool { + matches!( + ty, + WaveType::Int(_) | WaveType::Uint(_) | WaveType::Byte | WaveType::Char + ) +} + +fn substitute_callable_return(callable: &CallableType, type_args: &[WaveType]) -> WaveType { + let substitutions = callable + .generic_params + .iter() + .cloned() + .zip(type_args.iter().cloned()) + .collect::>(); + substitute_type(&callable.return_type, &substitutions) +} + +fn substitute_type(ty: &WaveType, substitutions: &HashMap) -> WaveType { + match ty { + WaveType::Struct(name) => substitutions + .get(name) + .cloned() + .unwrap_or_else(|| ty.clone()), + WaveType::Pointer(inner) => { + WaveType::Pointer(Box::new(substitute_type(inner, substitutions))) + } + WaveType::Array(inner, size) => { + WaveType::Array(Box::new(substitute_type(inner, substitutions)), *size) + } + _ => ty.clone(), + } +} + +/// Removes the internal module hash prefix from names shown to Wave users. +pub fn demangle_module_names(text: &str) -> String { + let mut out = String::with_capacity(text.len()); + let mut rest = text; + const PREFIX: &str = "__wave_"; + while let Some(index) = rest.find(PREFIX) { + out.push_str(&rest[..index]); + let candidate = &rest[index + PREFIX.len()..]; + if candidate.len() >= 17 + && candidate.as_bytes()[..16] + .iter() + .all(|byte| byte.is_ascii_hexdigit()) + && candidate.as_bytes()[16] == b'_' + { + rest = &candidate[17..]; + } else { + out.push_str(PREFIX); + rest = candidate; + } + } + out.push_str(rest); + out +} + +impl Resolver<'_> { + fn resolve_module( + &mut self, + key: PathBuf, + ast: Vec, + origin: usize, + is_entry: bool, + ) -> Result { + if let Some(interface) = self.interfaces.get(&key) { + return Ok(interface.clone()); + } + if let Some(cycle_start) = self.visiting.iter().position(|path| path == &key) { + let mut chain = self.visiting[cycle_start..] + .iter() + .map(|path| path.display().to_string()) + .collect::>(); + chain.push(key.display().to_string()); + return Err(module_error( + &key, + "Import cycle", + format!("import cycle detected: {}", chain.join(" -> ")), + "remove one dependency edge or move shared declarations into a third module", + )); + } + + self.visiting.push(key.clone()); + let mut interface = collect_symbols(&key, &ast, is_entry)?; + let mut names = NameContext { + own: interface.symbols.clone(), + ..NameContext::default() + }; + + for node in &ast { + let ASTNode::Statement(StatementNode::Import(import)) = node else { + continue; + }; + + let base_dir = key.parent().unwrap_or(Path::new(".")); + let mut loaded = HashSet::new(); + let unit = + local_import_unit_with_config(&import.path, &mut loaded, base_dir, self.config)?; + let child_key = canonical_key(&unit.abs_path); + let child_origin = if let Some(index) = self.source_indices.get(&child_key) { + *index + } else { + let index = self.sources.len(); + self.sources.push(ModuleSource { + path: unit.abs_path.clone(), + source: unit.source, + }); + self.source_indices.insert(child_key.clone(), index); + index + }; + let child = self.resolve_module(child_key, unit.ast, child_origin, false)?; + bind_import(&key, import, child.clone(), &mut names)?; + if import.visibility == Visibility::Public { + for selected in &import.selections { + let symbol = child + .symbols + .get(selected) + .expect("bind_import validated public selections") + .clone(); + if interface.symbols.insert(selected.clone(), symbol).is_some() { + return Err(module_error( + &key, + "Duplicate public symbol", + format!("re-exported symbol '{}' conflicts in this module", selected), + "remove one re-export or rename the local declaration", + )); + } + } + } + } + + let mut lowered = Vec::new(); + for node in ast { + if matches!(node, ASTNode::Statement(StatementNode::Import(_))) { + continue; + } + lowered.push(rewrite_top_level(node, &names, &key, is_entry)?); + } + + self.visiting.pop(); + self.interfaces.insert(key, interface.clone()); + self.origins + .extend(std::iter::repeat_n(origin, lowered.len())); + self.ast.extend(lowered); + Ok(interface) + } +} + +fn canonical_key(path: &Path) -> PathBuf { + path.canonicalize().unwrap_or_else(|_| path.to_path_buf()) +} + +fn module_error( + path: &Path, + title: &str, + message: impl Into, + help: impl Into, +) -> WaveError { + WaveError::new( + WaveErrorKind::InvalidStatement(title.to_string()), + message, + path.display().to_string(), + 1, + 1, + ) + .with_code("E3001") + .with_context("module resolution") + .with_help(help) +} + +fn internal_name(path: &Path, name: &str, is_entry: bool) -> String { + if is_entry { + return name.to_string(); + } + let mut hash = 0xcbf29ce484222325_u64; + for byte in path.to_string_lossy().as_bytes() { + hash ^= u64::from(*byte); + hash = hash.wrapping_mul(0x100000001b3); + } + format!("__wave_{hash:016x}_{name}") +} + +fn insert_symbol( + path: &Path, + symbols: &mut HashMap, + source_name: &str, + visibility: Visibility, + kind: SymbolKind, + is_entry: bool, +) -> Result<(), WaveError> { + if symbols.contains_key(source_name) { + // The semantic validator owns duplicate-declaration diagnostics because + // it can point at the repeated source declaration. Both declarations + // receive the same lowered name, so flattening cannot hide the error. + return Ok(()); + } + symbols.insert( + source_name.to_string(), + ModuleSymbol { + lowered: internal_name(path, source_name, is_entry), + visibility, + kind, + }, + ); + Ok(()) +} + +fn collect_symbols( + path: &Path, + ast: &[ASTNode], + is_entry: bool, +) -> Result { + let mut symbols = HashMap::new(); + for node in ast { + match node { + ASTNode::Function(function) => { + if !is_entry && function.name == "main" { + return Err(module_error( + path, + "Invalid module entry point", + "function 'main' may only be declared in the entry module", + "remove `main` from the library module or rename it", + )); + } + insert_symbol( + path, + &mut symbols, + &function.name, + function.visibility, + SymbolKind::Function, + is_entry, + )?; + } + ASTNode::ExternFunction(function) => insert_symbol( + path, + &mut symbols, + &function.name, + Visibility::Private, + SymbolKind::Function, + is_entry, + )?, + ASTNode::Struct(structure) => insert_symbol( + path, + &mut symbols, + &structure.name, + structure.visibility, + SymbolKind::Struct, + is_entry, + )?, + ASTNode::TypeAlias(alias) => insert_symbol( + path, + &mut symbols, + &alias.name, + alias.visibility, + SymbolKind::Type, + is_entry, + )?, + ASTNode::Enum(enumeration) => { + insert_symbol( + path, + &mut symbols, + &enumeration.name, + enumeration.visibility, + SymbolKind::Type, + is_entry, + )?; + for variant in &enumeration.variants { + insert_symbol( + path, + &mut symbols, + &variant.name, + Visibility::Private, + SymbolKind::Value, + is_entry, + )?; + if enumeration.visibility == Visibility::Public { + symbols.insert( + format!("{}::{}", enumeration.name, variant.name), + ModuleSymbol { + lowered: internal_name(path, &variant.name, is_entry), + visibility: Visibility::Public, + kind: SymbolKind::Value, + }, + ); + } + } + } + ASTNode::Variable(variable) + if matches!(variable.mutability, Mutability::Const | Mutability::Static) => + { + insert_symbol( + path, + &mut symbols, + &variable.name, + variable.visibility, + SymbolKind::Value, + is_entry, + )?; + } + _ => {} + } + } + Ok(ModuleInterface { symbols }) +} + +fn default_namespace(path: &str) -> Option { + if path.starts_with("./") { + return Path::new(path) + .file_stem() + .and_then(|name| name.to_str()) + .map(str::to_string); + } + Some(path.to_string()) +} + +fn bind_import( + current_path: &Path, + import: &ImportNode, + interface: ModuleInterface, + names: &mut NameContext, +) -> Result<(), WaveError> { + if !import.selections.is_empty() { + for selected in &import.selections { + let Some(symbol) = interface.symbols.get(selected) else { + return Err(module_error( + current_path, + "Unknown imported symbol", + format!("module '{}' has no symbol '{}'", import.path, selected), + "check the declaration name and the selected import list", + )); + }; + if symbol.visibility != Visibility::Public { + return Err(module_error( + current_path, + "Private imported symbol", + format!( + "symbol '{}' is private in module '{}'", + selected, import.path + ), + "mark the declaration `pub` or remove it from the import list", + )); + } + if names.own.contains_key(selected) { + return Err(module_error( + current_path, + "Ambiguous imported symbol", + format!("imported symbol '{}' conflicts in this module", selected), + "rename the local declaration or use a qualified module import", + )); + } + if let Some(existing) = names.selected.get(selected) { + if existing == symbol { + continue; + } + return Err(module_error( + current_path, + "Ambiguous imported symbol", + format!("imported symbol '{}' conflicts in this module", selected), + "use a qualified module import to disambiguate the declarations", + )); + } + names.selected.insert(selected.clone(), symbol.clone()); + } + return Ok(()); + } + + let namespace = import + .alias + .clone() + .or_else(|| default_namespace(&import.path)) + .ok_or_else(|| { + module_error( + current_path, + "Invalid import namespace", + format!("cannot derive a namespace from import '{}'", import.path), + "provide an explicit alias with `as name`", + ) + })?; + + if let Some(existing) = names.namespaces.get(&namespace) { + if existing == &interface { + return Ok(()); + } + return Err(module_error( + current_path, + "Duplicate import namespace", + format!("namespace '{}' is bound to multiple modules", namespace), + "use a distinct `as` alias for one import", + )); + } + names.namespaces.insert(namespace, interface); + Ok(()) +} + +fn resolve_name( + name: &str, + names: &NameContext, + path: &Path, +) -> Result, WaveError> { + if name.contains("::") { + if let Some(symbol) = names.own.get(name).or_else(|| names.selected.get(name)) { + return Ok(Some(symbol.clone())); + } + let mut best: Option<(&str, &ModuleInterface)> = None; + for (namespace, interface) in &names.namespaces { + if name + .strip_prefix(namespace) + .is_some_and(|rest| rest.starts_with("::")) + && best.is_none_or(|(current, _)| namespace.len() > current.len()) + { + best = Some((namespace, interface)); + } + } + let Some((namespace, interface)) = best else { + return Err(module_error( + path, + "Unknown import namespace", + format!("qualified name '{}' uses an unknown module namespace", name), + "import the module first or check its alias", + )); + }; + let symbol_name = &name[namespace.len() + 2..]; + let Some(symbol) = interface.symbols.get(symbol_name) else { + return Err(module_error( + path, + "Unknown imported symbol", + format!("module '{}' has no symbol '{}'", namespace, symbol_name), + "check the public declaration name", + )); + }; + if symbol.visibility != Visibility::Public { + return Err(module_error( + path, + "Private imported symbol", + format!( + "symbol '{}' is private in module '{}'", + symbol_name, namespace + ), + "only `pub` declarations are accessible outside their module", + )); + } + return Ok(Some(symbol.clone())); + } + + Ok(names + .own + .get(name) + .or_else(|| names.selected.get(name)) + .cloned()) +} + +fn rewrite_type(ty: WaveType, names: &NameContext, path: &Path) -> Result { + match ty { + WaveType::Pointer(inner) => Ok(WaveType::Pointer(Box::new(rewrite_type( + *inner, names, path, + )?))), + WaveType::Array(inner, size) => Ok(WaveType::Array( + Box::new(rewrite_type(*inner, names, path)?), + size, + )), + WaveType::Struct(name) => Ok(WaveType::Struct(rewrite_type_name(&name, names, path)?)), + other => Ok(other), + } +} + +fn rewrite_type_name(name: &str, names: &NameContext, path: &Path) -> Result { + if let Some((base, arguments)) = split_type_application(name) { + let rewritten_base = rewrite_type_name(base, names, path)?; + let rewritten_arguments = split_type_arguments(arguments) + .into_iter() + .map(|argument| rewrite_type_name(argument.trim(), names, path)) + .collect::, _>>()?; + return Ok(format!( + "{}<{}>", + rewritten_base, + rewritten_arguments.join(",") + )); + } + + match resolve_name(name, names, path)? { + Some(symbol) if matches!(symbol.kind, SymbolKind::Struct | SymbolKind::Type) => { + Ok(symbol.lowered) + } + Some(_) => Err(module_error( + path, + "Expected imported type", + format!("symbol '{}' is not a type", name), + "use a public struct, enum, or type alias in this position", + )), + None => Ok(name.to_string()), + } +} + +fn split_type_application(name: &str) -> Option<(&str, &str)> { + let open = name.find('<')?; + if !name.ends_with('>') || open == 0 { + return None; + } + Some((&name[..open], &name[open + 1..name.len() - 1])) +} + +fn split_type_arguments(arguments: &str) -> Vec<&str> { + let mut out = Vec::new(); + let mut depth = 0_i32; + let mut start = 0; + for (index, ch) in arguments.char_indices() { + match ch { + '<' => depth += 1, + '>' => depth -= 1, + ',' if depth == 0 => { + out.push(&arguments[start..index]); + start = index + 1; + } + _ => {} + } + } + out.push(&arguments[start..]); + out +} + +fn rewrite_top_level( + node: ASTNode, + names: &NameContext, + path: &Path, + is_entry: bool, +) -> Result { + match node { + ASTNode::Function(mut function) => { + let original = function.name.clone(); + function.name = names.own[&original].lowered.clone(); + if !is_entry && original == "main" { + function.name = internal_name(path, "main", false); + } + if !is_entry { + if let Some(export) = &mut function.export { + if export.symbol.is_none() { + export.symbol = Some(original); + } + } + } + rewrite_function(&mut function, names, path)?; + Ok(ASTNode::Function(function)) + } + ASTNode::ExternFunction(mut function) => { + function.name = names.own[&function.name].lowered.clone(); + function.params = function + .params + .into_iter() + .map(|(name, ty)| Ok((name, rewrite_type(ty, names, path)?))) + .collect::>()?; + function.return_type = rewrite_type(function.return_type, names, path)?; + Ok(ASTNode::ExternFunction(function)) + } + ASTNode::Struct(mut structure) => { + structure.name = names.own[&structure.name].lowered.clone(); + structure.fields = structure + .fields + .into_iter() + .map(|(name, ty)| Ok((name, rewrite_type(ty, names, path)?))) + .collect::>()?; + for method in &mut structure.methods { + rewrite_function(method, names, path)?; + } + Ok(ASTNode::Struct(structure)) + } + ASTNode::TypeAlias(mut alias) => { + alias.name = names.own[&alias.name].lowered.clone(); + alias.target = rewrite_type(alias.target, names, path)?; + Ok(ASTNode::TypeAlias(alias)) + } + ASTNode::Enum(mut enumeration) => { + enumeration.name = names.own[&enumeration.name].lowered.clone(); + enumeration.repr_type = rewrite_type(enumeration.repr_type, names, path)?; + for variant in &mut enumeration.variants { + variant.name = names.own[&variant.name].lowered.clone(); + } + Ok(ASTNode::Enum(enumeration)) + } + ASTNode::Variable(mut variable) => { + variable.name = names.own[&variable.name].lowered.clone(); + variable.type_name = rewrite_type(variable.type_name, names, path)?; + if let Some(value) = variable.initial_value.take() { + variable.initial_value = + Some(rewrite_expression(value, names, path, &HashSet::new())?); + } + Ok(ASTNode::Variable(variable)) + } + ASTNode::ProtoImpl(mut implementation) => { + implementation.target = resolve_name(&implementation.target, names, path)? + .map_or(implementation.target, |symbol| symbol.lowered); + for method in &mut implementation.methods { + rewrite_function(method, names, path)?; + } + Ok(ASTNode::ProtoImpl(implementation)) + } + ASTNode::Statement(statement) => Ok(ASTNode::Statement(rewrite_statement( + statement, + names, + path, + &mut HashSet::new(), + )?)), + ASTNode::Expression(expression) => Ok(ASTNode::Expression(rewrite_expression( + expression, + names, + path, + &HashSet::new(), + )?)), + other => Ok(other), + } +} + +fn rewrite_function( + function: &mut FunctionNode, + names: &NameContext, + path: &Path, +) -> Result<(), WaveError> { + for parameter in &mut function.parameters { + parameter.param_type = rewrite_type(parameter.param_type.clone(), names, path)?; + } + function.return_type = function + .return_type + .take() + .map(|ty| rewrite_type(ty, names, path)) + .transpose()?; + let mut locals = function + .parameters + .iter() + .map(|parameter| parameter.name.clone()) + .collect::>(); + function.body = rewrite_block(std::mem::take(&mut function.body), names, path, &mut locals)?; + Ok(()) +} + +fn rewrite_block( + nodes: Vec, + names: &NameContext, + path: &Path, + locals: &mut HashSet, +) -> Result, WaveError> { + let mut out = Vec::with_capacity(nodes.len()); + for node in nodes { + match node { + ASTNode::Variable(mut variable) => { + variable.type_name = rewrite_type(variable.type_name, names, path)?; + if let Some(value) = variable.initial_value.take() { + variable.initial_value = Some(rewrite_expression(value, names, path, locals)?); + } + locals.insert(variable.name.clone()); + out.push(ASTNode::Variable(variable)); + } + ASTNode::Statement(statement) => out.push(ASTNode::Statement(rewrite_statement( + statement, names, path, locals, + )?)), + ASTNode::Expression(expression) => out.push(ASTNode::Expression(rewrite_expression( + expression, names, path, locals, + )?)), + other => out.push(other), + } + } + Ok(out) +} + +fn rewrite_statement( + statement: StatementNode, + names: &NameContext, + path: &Path, + locals: &mut HashSet, +) -> Result { + Ok(match statement { + StatementNode::PrintFormat { format, args } => StatementNode::PrintFormat { + format, + args: rewrite_expressions(args, names, path, locals)?, + }, + StatementNode::PrintlnFormat { format, args } => StatementNode::PrintlnFormat { + format, + args: rewrite_expressions(args, names, path, locals)?, + }, + StatementNode::Input { format, args } => StatementNode::Input { + format, + args: rewrite_expressions(args, names, path, locals)?, + }, + StatementNode::If { + condition, + body, + else_if_blocks, + else_block, + } => { + let condition = rewrite_expression(condition, names, path, locals)?; + let mut body_scope = locals.clone(); + let body = rewrite_block(body, names, path, &mut body_scope)?; + let else_if_blocks = else_if_blocks + .map(|blocks| { + blocks + .into_iter() + .map(|(condition, body)| { + let condition = rewrite_expression(condition, names, path, locals)?; + let mut scope = locals.clone(); + Ok((condition, rewrite_block(body, names, path, &mut scope)?)) + }) + .collect::, WaveError>>() + .map(Box::new) + }) + .transpose()?; + let else_block = else_block + .map(|body| { + let mut scope = locals.clone(); + rewrite_block(*body, names, path, &mut scope).map(Box::new) + }) + .transpose()?; + StatementNode::If { + condition, + body, + else_if_blocks, + else_block, + } + } + StatementNode::For { + initialization, + condition, + increment, + body, + } => { + let mut scope = locals.clone(); + let initialization = rewrite_block(vec![*initialization], names, path, &mut scope)? + .into_iter() + .next() + .expect("for initializer is preserved"); + let condition = rewrite_expression(condition, names, path, &scope)?; + let increment = rewrite_expression(increment, names, path, &scope)?; + let body = rewrite_block(body, names, path, &mut scope)?; + StatementNode::For { + initialization: Box::new(initialization), + condition, + increment, + body, + } + } + StatementNode::While { condition, body } => { + let condition = rewrite_expression(condition, names, path, locals)?; + let mut scope = locals.clone(); + let body = rewrite_block(body, names, path, &mut scope)?; + StatementNode::While { condition, body } + } + StatementNode::Match { value, arms } => StatementNode::Match { + value: rewrite_expression(value, names, path, locals)?, + arms: arms + .into_iter() + .map(|mut arm| { + if let MatchPattern::Ident(name) = &mut arm.pattern { + if !locals.contains(name) { + if let Some(symbol) = resolve_name(name, names, path)? { + *name = symbol.lowered; + } + } + } + let mut scope = locals.clone(); + arm.body = rewrite_block(arm.body, names, path, &mut scope)?; + Ok(arm) + }) + .collect::>()?, + }, + StatementNode::Assign { variable, value } => StatementNode::Assign { + variable: if locals.contains(&variable) { + variable + } else { + resolve_name(&variable, names, path)?.map_or(variable, |symbol| symbol.lowered) + }, + value: rewrite_expression(value, names, path, locals)?, + }, + StatementNode::AsmBlock { + instructions, + inputs, + outputs, + clobbers, + } => StatementNode::AsmBlock { + instructions, + inputs: inputs + .into_iter() + .map(|(constraint, expression)| { + Ok(( + constraint, + rewrite_expression(expression, names, path, locals)?, + )) + }) + .collect::>()?, + outputs: outputs + .into_iter() + .map(|(constraint, expression)| { + Ok(( + constraint, + rewrite_expression(expression, names, path, locals)?, + )) + }) + .collect::>()?, + clobbers, + }, + StatementNode::Return(value) => StatementNode::Return( + value + .map(|expression| rewrite_expression(expression, names, path, locals)) + .transpose()?, + ), + StatementNode::Expression(expression) => { + StatementNode::Expression(rewrite_expression(expression, names, path, locals)?) + } + other => other, + }) +} + +fn rewrite_expressions( + expressions: Vec, + names: &NameContext, + path: &Path, + locals: &HashSet, +) -> Result, WaveError> { + expressions + .into_iter() + .map(|expression| rewrite_expression(expression, names, path, locals)) + .collect() +} + +fn rewrite_expression( + expression: Expression, + names: &NameContext, + path: &Path, + locals: &HashSet, +) -> Result { + Ok(match expression { + Expression::StructLiteral { name, fields } => Expression::StructLiteral { + name: resolve_name(&name, names, path)?.map_or(name, |symbol| symbol.lowered), + fields: fields + .into_iter() + .map(|(name, value)| Ok((name, rewrite_expression(value, names, path, locals)?))) + .collect::>()?, + }, + Expression::FunctionCall { + name, + type_args, + args, + } => { + let symbol = resolve_name(&name, names, path)?; + let type_args = type_args + .into_iter() + .map(|ty| rewrite_type(ty, names, path)) + .collect::, _>>()?; + let args = rewrite_expressions(args, names, path, locals)?; + match symbol { + Some(symbol) if symbol.kind == SymbolKind::Struct && type_args.is_empty() => { + if !args.is_empty() { + return Err(module_error( + path, + "Invalid struct constructor", + format!("struct '{}' must be initialized with named fields", name), + "use `Type { field: value }`; `Type()` is only valid for empty structs", + )); + } + Expression::StructLiteral { + name: symbol.lowered, + fields: Vec::new(), + } + } + Some(symbol) if symbol.kind == SymbolKind::Function => Expression::FunctionCall { + name: symbol.lowered, + type_args, + args, + }, + Some(_) => { + return Err(module_error( + path, + "Symbol is not callable", + format!("symbol '{}' cannot be called", name), + "call a function or construct an empty struct", + )) + } + None => Expression::FunctionCall { + name, + type_args, + args, + }, + } + } + Expression::MethodCall { object, name, args } => Expression::MethodCall { + object: Box::new(rewrite_expression(*object, names, path, locals)?), + name, + args: rewrite_expressions(args, names, path, locals)?, + }, + Expression::Variable(name) => Expression::Variable(if locals.contains(&name) { + name + } else { + resolve_name(&name, names, path)?.map_or(name, |symbol| symbol.lowered) + }), + Expression::Deref(inner) => { + Expression::Deref(Box::new(rewrite_expression(*inner, names, path, locals)?)) + } + Expression::AddressOf(inner) => { + Expression::AddressOf(Box::new(rewrite_expression(*inner, names, path, locals)?)) + } + Expression::BinaryExpression { + left, + operator, + right, + } => Expression::BinaryExpression { + left: Box::new(rewrite_expression(*left, names, path, locals)?), + operator, + right: Box::new(rewrite_expression(*right, names, path, locals)?), + }, + Expression::IndexAccess { target, index } => Expression::IndexAccess { + target: Box::new(rewrite_expression(*target, names, path, locals)?), + index: Box::new(rewrite_expression(*index, names, path, locals)?), + }, + Expression::ArrayLiteral(values) => { + Expression::ArrayLiteral(rewrite_expressions(values, names, path, locals)?) + } + Expression::Grouped(inner) => { + Expression::Grouped(Box::new(rewrite_expression(*inner, names, path, locals)?)) + } + Expression::AssignOperation { + target, + operator, + value, + } => Expression::AssignOperation { + target: Box::new(rewrite_expression(*target, names, path, locals)?), + operator, + value: Box::new(rewrite_expression(*value, names, path, locals)?), + }, + Expression::Assignment { target, value } => Expression::Assignment { + target: Box::new(rewrite_expression(*target, names, path, locals)?), + value: Box::new(rewrite_expression(*value, names, path, locals)?), + }, + Expression::AsmBlock { + instructions, + inputs, + outputs, + clobbers, + } => Expression::AsmBlock { + instructions, + inputs: inputs + .into_iter() + .map(|(constraint, expression)| { + Ok(( + constraint, + rewrite_expression(expression, names, path, locals)?, + )) + }) + .collect::>()?, + outputs: outputs + .into_iter() + .map(|(constraint, expression)| { + Ok(( + constraint, + rewrite_expression(expression, names, path, locals)?, + )) + }) + .collect::>()?, + clobbers, + }, + Expression::FieldAccess { object, field } => Expression::FieldAccess { + object: Box::new(rewrite_expression(*object, names, path, locals)?), + field, + }, + Expression::Unary { operator, expr } => Expression::Unary { + operator, + expr: Box::new(rewrite_expression(*expr, names, path, locals)?), + }, + Expression::Cast { expr, target_type } => Expression::Cast { + expr: Box::new(rewrite_expression(*expr, names, path, locals)?), + target_type: rewrite_type(target_type, names, path)?, + }, + Expression::IncDec { kind, target } => Expression::IncDec { + kind, + target: Box::new(rewrite_expression(*target, names, path, locals)?), + }, + other => other, + }) +} diff --git a/src/runner.rs b/src/runner.rs index 28e32c4d..e7c8750f 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -18,6 +18,7 @@ //! AST reaches LLVM. Backend panics are caught here and translated into Wave //! diagnostics; lower layers should not duplicate that presentation policy. +use crate::module_resolver::{demangle_module_names, resolve_import_graph}; use crate::{DebugFlags, DepFlags, LinkFlags, LlvmFlags}; use ::error::*; use ::parser::ast::*; @@ -137,9 +138,16 @@ fn validate_wave_ast_or_exit(file_path: &Path, source: &str, ast: &[ASTNode]) { } fn validate_expanded_ast_or_exit(expanded: &ExpandedWaveAst) { - let Err(diagnostic) = validate_program_detailed(&expanded.ast) else { + let Err(mut diagnostic) = validate_program_detailed(&expanded.ast) else { return; }; + diagnostic.message = demangle_module_names(&diagnostic.message); + diagnostic.label = demangle_module_names(&diagnostic.label); + diagnostic.help = demangle_module_names(&diagnostic.help); + diagnostic.note = diagnostic.note.map(|note| demangle_module_names(¬e)); + if let Some(primary) = &mut diagnostic.primary { + primary.text = demangle_module_names(&primary.text); + } let origin = expanded .origins .get(diagnostic.top_level_index) @@ -269,13 +277,25 @@ fn semantic_node_scope( ) -> Option<(usize, usize)> { let node = node?; let needle = match node { - ASTNode::Function(function) => format!("fun {}(", function.name), - ASTNode::ExternFunction(function) => format!("fun {}(", function.name), - ASTNode::Struct(structure) => format!("struct {}", structure.name), - ASTNode::ProtoImpl(implementation) => format!("proto {}", implementation.target), - ASTNode::TypeAlias(alias) => format!("type {}", alias.name), - ASTNode::Enum(enumeration) => format!("enum {}", enumeration.name), - ASTNode::Variable(variable) => variable.name.clone(), + ASTNode::Function(function) => { + format!("fun {}(", demangle_module_names(&function.name)) + } + ASTNode::ExternFunction(function) => { + format!("fun {}(", demangle_module_names(&function.name)) + } + ASTNode::Struct(structure) => { + format!("struct {}", demangle_module_names(&structure.name)) + } + ASTNode::ProtoImpl(implementation) => { + format!("proto {}", demangle_module_names(&implementation.target)) + } + ASTNode::TypeAlias(alias) => { + format!("type {}", demangle_module_names(&alias.name)) + } + ASTNode::Enum(enumeration) => { + format!("enum {}", demangle_module_names(&enumeration.name)) + } + ASTNode::Variable(variable) => demangle_module_names(&variable.name), ASTNode::Statement(_) | ASTNode::Expression(_) | ASTNode::Program(_) => { return Some((0, source.len())); } @@ -801,83 +821,18 @@ fn expand_imports_for_codegen( ast: Vec, import_config: &ImportConfig, ) -> Result { - fn expand_from_dir( - base_dir: &Path, - ast: Vec, - origin: usize, - out: &mut Vec, - origins: &mut Vec, - sources: &mut Vec, - already: &mut HashSet, - import_config: &ImportConfig, - ) -> Result<(), WaveError> { - for node in ast { - match node { - ASTNode::Statement(StatementNode::Import(module)) => { - let unit = - local_import_unit_with_config(&module, already, base_dir, import_config)?; - - if unit.ast.is_empty() { - continue; - } - - let next_dir = unit.abs_path.parent().unwrap_or(base_dir); - let imported_origin = sources.len(); - sources.push(SemanticSourceUnit { - path: unit.abs_path.clone(), - source: unit.source, - }); - expand_from_dir( - next_dir, - unit.ast, - imported_origin, - out, - origins, - sources, - already, - import_config, - )?; - } - - other => { - out.push(other); - origins.push(origin); - } - } - } - - Ok(()) - } - - let mut already = HashSet::new(); - - if let Ok(abs) = entry_path.canonicalize() { - if let Some(s) = abs.to_str() { - already.insert(s.to_string()); - } - } - - let base_dir = entry_path.parent().unwrap_or(Path::new(".")); - let mut out = Vec::new(); - let mut origins = Vec::new(); - let mut sources = vec![SemanticSourceUnit { - path: entry_path.to_path_buf(), - source: entry_source.to_string(), - }]; - expand_from_dir( - base_dir, - ast, - 0, - &mut out, - &mut origins, - &mut sources, - &mut already, - import_config, - )?; + let graph = resolve_import_graph(entry_path, entry_source, ast, import_config)?; Ok(ExpandedWaveAst { - ast: out, - origins, - sources, + ast: graph.ast, + origins: graph.origins, + sources: graph + .sources + .into_iter() + .map(|source| SemanticSourceUnit { + path: source.path, + source: source.source, + }) + .collect(), }) } diff --git a/std/POLICY.md b/std/POLICY.md index 8059bd1a..21b1d6c2 100644 --- a/std/POLICY.md +++ b/std/POLICY.md @@ -13,6 +13,9 @@ This document defines non-negotiable rules for the Wave standard library (`std/` - `var` is the only local variable declaration syntax. - The retired `let` and `let mut` declaration forms are forbidden. - Use top-level `const` declarations for immutable constants. +- Every symbol used by another module must be explicitly marked `pub`. +- Imports must select their unqualified symbols; dispatcher modules may use + `pub import("module")::{symbol};` for deliberate re-exports. ## 3) Runtime Contract Rules diff --git a/std/buffer/alloc.wave b/std/buffer/alloc.wave index d66c3505..1e830492 100644 --- a/std/buffer/alloc.wave +++ b/std/buffer/alloc.wave @@ -16,11 +16,48 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::mem::alloc"); -import("std::mem::ops"); -import("std::buffer::types"); - -fun buffer_new(capacity: i64) -> Buffer { +import("std::mem::alloc")::{ + mem_alloc, + mem_alloc_zeroed, + mem_free, + mem_realloc, + mem_alloc_items, + mem_alloc_items_zeroed, + mem_realloc_items, + mem_free_items, + mem_page_size, + mem_pages_for_size, + mem_size_align_page, + mem_alloc_pages, + mem_free_pages, + mem_is_aligned, + mem_alloc_aligned, + mem_free_aligned, +}; +import("std::mem::ops")::{ + mem_set, + mem_zero, + mem_copy, + mem_move, + mem_cmp, + mem_eq, + mem_find_byte, + mem_swap, + mem_copy_items, + mem_set_items, + mem_move_items, + mem_zero_items, + mem_copy_checked, + mem_move_checked, + mem_set_checked, + mem_zero_checked, +}; +pub import("std::buffer::types")::{ + Buffer, + TypedBuffer, +}; + +pub fun buffer_new(capacity: i64) -> Buffer { var cap: i64 = capacity; if (cap <= 0) { cap = 64; @@ -42,11 +79,11 @@ fun buffer_new(capacity: i64) -> Buffer { }; } -fun buffer_new_default() -> Buffer { +pub fun buffer_new_default() -> Buffer { return buffer_new(64); } -fun buffer_free(buf: ptr) -> i64 { +pub fun buffer_free(buf: ptr) -> i64 { var ret: i64 = 0; if (deref buf.cap > 0) { @@ -59,11 +96,11 @@ fun buffer_free(buf: ptr) -> i64 { return ret; } -fun buffer_clear(buf: ptr) { +pub fun buffer_clear(buf: ptr) { deref buf.len = 0; } -fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { +pub fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { if (required_cap <= deref buf.cap) { return 0; } @@ -96,7 +133,7 @@ fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { return 0; } -fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { +pub fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { if (elem_size <= 0) { var empty: TypedBuffer; empty.data = null; @@ -131,7 +168,7 @@ fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { return created; } -fun tbuffer_free(buf: ptr>) -> i64 { +pub fun tbuffer_free(buf: ptr>) -> i64 { var ret: i64 = 0; if (deref buf.cap_bytes > 0) { @@ -145,11 +182,11 @@ fun tbuffer_free(buf: ptr>) -> i64 { return ret; } -fun tbuffer_clear(buf: ptr>) { +pub fun tbuffer_clear(buf: ptr>) { deref buf.len = 0; } -fun tbuffer_reserve(buf: ptr>, required_len: i64) -> i64 { +pub fun tbuffer_reserve(buf: ptr>, required_len: i64) -> i64 { if (deref buf.elem_size <= 0) { return -1; } diff --git a/std/buffer/read.wave b/std/buffer/read.wave index f0f31a2d..9b8eef81 100644 --- a/std/buffer/read.wave +++ b/std/buffer/read.wave @@ -16,9 +16,12 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::buffer::types"); +import("std::buffer::types")::{ + Buffer, + TypedBuffer, +}; -fun buffer_at(buf: Buffer, index: i64) -> u8 { +pub fun buffer_at(buf: Buffer, index: i64) -> u8 { if (index < 0 || index >= buf.len) { return 0; } @@ -26,15 +29,15 @@ fun buffer_at(buf: Buffer, index: i64) -> u8 { return buf.data[index]; } -fun tbuffer_ptr(buf: TypedBuffer) -> ptr { +pub fun tbuffer_ptr(buf: TypedBuffer) -> ptr { return buf.data as ptr; } -fun tbuffer_len(buf: TypedBuffer) -> i64 { +pub fun tbuffer_len(buf: TypedBuffer) -> i64 { return buf.len; } -fun tbuffer_at(buf: TypedBuffer, index: i64, out_value: ptr) -> bool { +pub fun tbuffer_at(buf: TypedBuffer, index: i64, out_value: ptr) -> bool { if (index < 0 || index >= buf.len) { return false; } diff --git a/std/buffer/types.wave b/std/buffer/types.wave index 5a159c32..0caae39b 100644 --- a/std/buffer/types.wave +++ b/std/buffer/types.wave @@ -16,13 +16,13 @@ // // SPDX-License-Identifier: Apache-2.0 -struct Buffer { +pub struct Buffer { data: ptr; len: i64; cap: i64; } -struct TypedBuffer { +pub struct TypedBuffer { data: ptr; len: i64; cap_bytes: i64; diff --git a/std/buffer/write.wave b/std/buffer/write.wave index 71e4a624..854e5495 100644 --- a/std/buffer/write.wave +++ b/std/buffer/write.wave @@ -16,10 +16,25 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::buffer::types"); -import("std::buffer::alloc"); - -fun buffer_push(buf: ptr, value: u8) -> i64 { +import("std::buffer::types")::{ + Buffer, + TypedBuffer, +}; +import("std::buffer::alloc")::{ + buffer_new, + buffer_new_default, + buffer_free, + buffer_clear, + buffer_reserve, + tbuffer_new, + tbuffer_free, + tbuffer_clear, + tbuffer_reserve, + Buffer, + TypedBuffer, +}; + +pub fun buffer_push(buf: ptr, value: u8) -> i64 { var needed: i64 = deref buf.len + 1; var ret: i64 = buffer_reserve(buf, needed); @@ -34,7 +49,7 @@ fun buffer_push(buf: ptr, value: u8) -> i64 { return 0; } -fun buffer_append(buf: ptr, src: ptr, size: i64) -> i64 { +pub fun buffer_append(buf: ptr, src: ptr, size: i64) -> i64 { if (size <= 0) { return 0; } @@ -61,7 +76,7 @@ fun buffer_append(buf: ptr, src: ptr, size: i64) -> i64 { return 0; } -fun buffer_append_str(buf: ptr, s: str) -> i64 { +pub fun buffer_append_str(buf: ptr, s: str) -> i64 { var i: i64 = 0; while (s[i] != 0) { @@ -75,7 +90,7 @@ fun buffer_append_str(buf: ptr, s: str) -> i64 { return 0; } -fun buffer_set(buf: ptr, index: i64, value: u8) -> bool { +pub fun buffer_set(buf: ptr, index: i64, value: u8) -> bool { if (index < 0 || index >= deref buf.len) { return false; } @@ -85,7 +100,7 @@ fun buffer_set(buf: ptr, index: i64, value: u8) -> bool { return true; } -fun tbuffer_push(buf: ptr>, value: T) -> i64 { +pub fun tbuffer_push(buf: ptr>, value: T) -> i64 { var needed_len: i64 = deref buf.len + 1; var ret: i64 = tbuffer_reserve(buf, needed_len); @@ -102,7 +117,7 @@ fun tbuffer_push(buf: ptr>, value: T) -> i64 { return 0; } -fun tbuffer_set(buf: ptr>, index: i64, value: T) -> bool { +pub fun tbuffer_set(buf: ptr>, index: i64, value: T) -> bool { if (index < 0 || index >= deref buf.len) { return false; } diff --git a/std/bytes/endian.wave b/std/bytes/endian.wave index bb0598fe..d18bdacb 100644 --- a/std/bytes/endian.wave +++ b/std/bytes/endian.wave @@ -16,19 +16,19 @@ // // SPDX-License-Identifier: Apache-2.0 -fun bytes_swap16(x: i16) -> i16 { +pub fun bytes_swap16(x: i16) -> i16 { var v: i32 = x as i32; return (((v & 255) << 8) | ((v >> 8) & 255)) as i16; } -fun bytes_swap32(x: i32) -> i32 { +pub fun bytes_swap32(x: i32) -> i32 { return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24); } -fun bytes_swap64(x: i64) -> i64 { +pub fun bytes_swap64(x: i64) -> i64 { return ((x & 0x00000000000000FF) << 56) | ((x & 0x000000000000FF00) << 40) | ((x & 0x0000000000FF0000) << 24) @@ -39,31 +39,31 @@ fun bytes_swap64(x: i64) -> i64 { | ((x & 0xFF00000000000000) >> 56); } -fun bytes_load_be_i16(src: ptr) -> i16 { +pub fun bytes_load_be_i16(src: ptr) -> i16 { var b0: i16 = src[0] as i16; var b1: i16 = src[1] as i16; return (b0 << 8) | b1; } -fun bytes_load_le_i16(src: ptr) -> i16 { +pub fun bytes_load_le_i16(src: ptr) -> i16 { var b0: i16 = src[0] as i16; var b1: i16 = src[1] as i16; return b0 | (b1 << 8); } -fun bytes_store_be_i16(dst: ptr, value: i16) { +pub fun bytes_store_be_i16(dst: ptr, value: i16) { var v: i32 = value as i32; deref dst[0] = ((v >> 8) & 255) as u8; deref dst[1] = (v & 255) as u8; } -fun bytes_store_le_i16(dst: ptr, value: i16) { +pub fun bytes_store_le_i16(dst: ptr, value: i16) { var v: i32 = value as i32; deref dst[0] = (v & 255) as u8; deref dst[1] = ((v >> 8) & 255) as u8; } -fun bytes_load_be_i32(src: ptr) -> i32 { +pub fun bytes_load_be_i32(src: ptr) -> i32 { var b0: i32 = src[0] as i32; var b1: i32 = src[1] as i32; var b2: i32 = src[2] as i32; @@ -71,7 +71,7 @@ fun bytes_load_be_i32(src: ptr) -> i32 { return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; } -fun bytes_load_le_i32(src: ptr) -> i32 { +pub fun bytes_load_le_i32(src: ptr) -> i32 { var b0: i32 = src[0] as i32; var b1: i32 = src[1] as i32; var b2: i32 = src[2] as i32; @@ -79,21 +79,21 @@ fun bytes_load_le_i32(src: ptr) -> i32 { return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); } -fun bytes_store_be_i32(dst: ptr, value: i32) { +pub fun bytes_store_be_i32(dst: ptr, value: i32) { deref dst[0] = ((value >> 24) & 255) as u8; deref dst[1] = ((value >> 16) & 255) as u8; deref dst[2] = ((value >> 8) & 255) as u8; deref dst[3] = (value & 255) as u8; } -fun bytes_store_le_i32(dst: ptr, value: i32) { +pub fun bytes_store_le_i32(dst: ptr, value: i32) { deref dst[0] = (value & 255) as u8; deref dst[1] = ((value >> 8) & 255) as u8; deref dst[2] = ((value >> 16) & 255) as u8; deref dst[3] = ((value >> 24) & 255) as u8; } -fun bytes_load_be_i64(src: ptr) -> i64 { +pub fun bytes_load_be_i64(src: ptr) -> i64 { var b0: i64 = src[0] as i64; var b1: i64 = src[1] as i64; var b2: i64 = src[2] as i64; @@ -113,7 +113,7 @@ fun bytes_load_be_i64(src: ptr) -> i64 { | b7; } -fun bytes_load_le_i64(src: ptr) -> i64 { +pub fun bytes_load_le_i64(src: ptr) -> i64 { var b0: i64 = src[0] as i64; var b1: i64 = src[1] as i64; var b2: i64 = src[2] as i64; @@ -133,7 +133,7 @@ fun bytes_load_le_i64(src: ptr) -> i64 { | (b7 << 56); } -fun bytes_store_be_i64(dst: ptr, value: i64) { +pub fun bytes_store_be_i64(dst: ptr, value: i64) { deref dst[0] = ((value >> 56) & 255) as u8; deref dst[1] = ((value >> 48) & 255) as u8; deref dst[2] = ((value >> 40) & 255) as u8; @@ -144,7 +144,7 @@ fun bytes_store_be_i64(dst: ptr, value: i64) { deref dst[7] = (value & 255) as u8; } -fun bytes_store_le_i64(dst: ptr, value: i64) { +pub fun bytes_store_le_i64(dst: ptr, value: i64) { deref dst[0] = (value & 255) as u8; deref dst[1] = ((value >> 8) & 255) as u8; deref dst[2] = ((value >> 16) & 255) as u8; diff --git a/std/env/consts.wave b/std/env/consts.wave index 07ba8885..ac35507d 100644 --- a/std/env/consts.wave +++ b/std/env/consts.wave @@ -16,9 +16,9 @@ // // SPDX-License-Identifier: Apache-2.0 -const ENV_ERR_NOT_FOUND: i64 = -2; -const ENV_ERR_NO_SPACE: i64 = -3; -const ENV_ERR_INVALID_KEY: i64 = -4; +pub const ENV_ERR_NOT_FOUND: i64 = -2; +pub const ENV_ERR_NO_SPACE: i64 = -3; +pub const ENV_ERR_INVALID_KEY: i64 = -4; -const ENV_O_RDONLY: i32 = 0; -const ENV_SCAN_CAP: i64 = 32768; +pub const ENV_O_RDONLY: i32 = 0; +pub const ENV_SCAN_CAP: i64 = 32768; diff --git a/std/env/cwd.wave b/std/env/cwd.wave index 85f5a635..8c23619c 100644 --- a/std/env/cwd.wave +++ b/std/env/cwd.wave @@ -16,9 +16,46 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::fs"); +import("std::sys::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; -fun env_getcwd(dst: ptr, cap: i64) -> i64 { +pub fun env_getcwd(dst: ptr, cap: i64) -> i64 { var r: i64 = getcwd(dst, cap); if (r <= 0) { return -1; @@ -32,10 +69,10 @@ fun env_getcwd(dst: ptr, cap: i64) -> i64 { return n; } -fun env_chdir(path: str) -> i64 { +pub fun env_chdir(path: str) -> i64 { return chdir(path); } -fun env_access(path: str, mode: i32) -> i64 { +pub fun env_access(path: str, mode: i32) -> i64 { return access(path, mode); } diff --git a/std/env/environ.wave b/std/env/environ.wave index b0c7c7f5..0bbfa0bf 100644 --- a/std/env/environ.wave +++ b/std/env/environ.wave @@ -16,29 +16,36 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::env"); -import("std::env::parse"); - -struct EnvResult { +import("std::sys::env")::{ + env_read, +}; +import("std::env::parse")::{ + _env_key_len, + _env_match_key, + _env_copy_value, + _env_parse_i64, +}; + +pub struct EnvResult { ok: bool; value: T; } -fun env_result_ok(value: T) -> EnvResult { +pub fun env_result_ok(value: T) -> EnvResult { var result: EnvResult; result.ok = true; result.value = value; return result; } -fun env_result_err(fallback: T) -> EnvResult { +pub fun env_result_err(fallback: T) -> EnvResult { var result: EnvResult; result.ok = false; result.value = fallback; return result; } -fun env_unwrap_or(result: EnvResult, default_value: T) -> T { +pub fun env_unwrap_or(result: EnvResult, default_value: T) -> T { if (result.ok) { return result.value; } @@ -46,7 +53,7 @@ fun env_unwrap_or(result: EnvResult, default_value: T) -> T { return default_value; } -fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { +pub fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { var key_len: i64 = _env_key_len(name); if (key_len <= 0) { @@ -100,7 +107,7 @@ fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { return -2; } -fun env_exists(name: str) -> bool { +pub fun env_exists(name: str) -> bool { var tmp: array; var r: i64 = env_get(name, &tmp[0], 2); @@ -111,7 +118,7 @@ fun env_exists(name: str) -> bool { return false; } -fun env_get_i64(name: str) -> EnvResult { +pub fun env_get_i64(name: str) -> EnvResult { var raw: array; var n: i64 = env_get(name, &raw[0], 64); @@ -127,7 +134,7 @@ fun env_get_i64(name: str) -> EnvResult { return env_result_ok(parsed); } -fun env_get_i32(name: str) -> EnvResult { +pub fun env_get_i32(name: str) -> EnvResult { var raw: array; var n: i64 = env_get(name, &raw[0], 64); @@ -147,10 +154,10 @@ fun env_get_i32(name: str) -> EnvResult { return env_result_ok(parsed_i64 as i32); } -fun env_get_i32_default(name: str, default_value: i32) -> i32 { +pub fun env_get_i32_default(name: str, default_value: i32) -> i32 { return env_unwrap_or(env_get_i32(name), default_value); } -fun env_get_i64_default(name: str, default_value: i64) -> i64 { +pub fun env_get_i64_default(name: str, default_value: i64) -> i64 { return env_unwrap_or(env_get_i64(name), default_value); } diff --git a/std/env/parse.wave b/std/env/parse.wave index 8bd2683a..064731b8 100644 --- a/std/env/parse.wave +++ b/std/env/parse.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun _env_key_len(name: str) -> i64 { +pub fun _env_key_len(name: str) -> i64 { var n: i64 = 0; while (name[n] != 0) { @@ -29,7 +29,7 @@ fun _env_key_len(name: str) -> i64 { return n; } -fun _env_match_key(blob: ptr, start: i64, name: str, key_len: i64) -> bool { +pub fun _env_match_key(blob: ptr, start: i64, name: str, key_len: i64) -> bool { var blob_idx: i64 = start; var name_idx: i64 = 0; @@ -44,7 +44,7 @@ fun _env_match_key(blob: ptr, start: i64, name: str, key_len: i64) -> bool { return true; } -fun _env_copy_value( +pub fun _env_copy_value( blob: ptr, begin: i64, end: i64, @@ -69,7 +69,7 @@ fun _env_copy_value( return value_len; } -fun _env_parse_i64(raw: ptr, out_value: ptr) -> bool { +pub fun _env_parse_i64(raw: ptr, out_value: ptr) -> bool { var i: i64 = 0; var sign: i64 = 1; diff --git a/std/fs/consts.wave b/std/fs/consts.wave index c51484f2..3bfe47db 100644 --- a/std/fs/consts.wave +++ b/std/fs/consts.wave @@ -17,6 +17,6 @@ // SPDX-License-Identifier: Apache-2.0 // unix-style file modes -const FS_MODE_USER_RW: i32 = 384; // 0600 -const FS_MODE_FILE_DEFAULT: i32 = 420; // 0644 -const FS_MODE_DIR_DEFAULT: i32 = 493; // 0755 +pub const FS_MODE_USER_RW: i32 = 384; // 0600 +pub const FS_MODE_FILE_DEFAULT: i32 = 420; // 0644 +pub const FS_MODE_DIR_DEFAULT: i32 = 493; // 0755 diff --git a/std/fs/file.wave b/std/fs/file.wave index 14b09ca7..0d11a010 100644 --- a/std/fs/file.wave +++ b/std/fs/file.wave @@ -16,11 +16,68 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::io::consts"); -import("std::io::fd"); -import("std::sys::fs"); - -fun fs_exists(path: str) -> bool { +import("std::io::consts")::{ + IO_STDIN_FD, + IO_STDOUT_FD, + IO_STDERR_FD, + IO_ERR_INVALID, + IO_ERR_EOF, + IO_ERR_NO_SPACE, + IO_ERR_INTR, +}; +import("std::io::fd")::{ + io_close, + io_seek, + io_read, + io_write, + io_write_all, + io_read_exact, + io_read_at_most, + io_copy, + io_pipe, + io_dup, + io_dup2, +}; +import("std::sys::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; + +pub fun fs_exists(path: str) -> bool { if (access(path, FS_F_OK) == 0) { return true; } @@ -28,31 +85,31 @@ fun fs_exists(path: str) -> bool { return false; } -fun fs_open(path: str, flags: i32, mode: i32) -> i64 { +pub fun fs_open(path: str, flags: i32, mode: i32) -> i64 { return open(path, flags, mode); } -fun fs_open_read(path: str) -> i64 { +pub fun fs_open_read(path: str) -> i64 { return fs_open(path, FS_O_RDONLY, 0); } -fun fs_open_write(path: str, mode: i32) -> i64 { +pub fun fs_open_write(path: str, mode: i32) -> i64 { return fs_open(path, FS_O_WRONLY | FS_O_CREAT | FS_O_TRUNC, mode); } -fun fs_open_append(path: str, mode: i32) -> i64 { +pub fun fs_open_append(path: str, mode: i32) -> i64 { return fs_open(path, FS_O_WRONLY | FS_O_CREAT | FS_O_APPEND, mode); } -fun fs_open_rw(path: str, mode: i32) -> i64 { +pub fun fs_open_rw(path: str, mode: i32) -> i64 { return fs_open(path, FS_O_RDWR | FS_O_CREAT, mode); } -fun fs_close(fd: i64) -> i64 { +pub fun fs_close(fd: i64) -> i64 { return io_close(fd); } -fun fs_file_size(path: str) -> i64 { +pub fun fs_file_size(path: str) -> i64 { var fd: i64 = fs_open_read(path); if (fd < 0) { return fd; @@ -72,7 +129,7 @@ fun fs_file_size(path: str) -> i64 { return sz; } -fun fs_file_size_fd(fd: i64) -> i64 { +pub fun fs_file_size_fd(fd: i64) -> i64 { var cur: i64 = lseek(fd, 0, FS_SEEK_CUR); if (cur < 0) { return cur; @@ -92,19 +149,19 @@ fun fs_file_size_fd(fd: i64) -> i64 { return end; } -fun fs_remove(path: str) -> i64 { +pub fun fs_remove(path: str) -> i64 { return unlink(path); } -fun fs_mkdir(path: str, mode: i32) -> i64 { +pub fun fs_mkdir(path: str, mode: i32) -> i64 { return mkdir(path, mode); } -fun fs_rmdir(path: str) -> i64 { +pub fun fs_rmdir(path: str) -> i64 { return rmdir(path); } -fun fs_read_all(path: str, dst: ptr, dst_cap: i64) -> i64 { +pub fun fs_read_all(path: str, dst: ptr, dst_cap: i64) -> i64 { if (dst_cap < 0) { return IO_ERR_INVALID; } @@ -139,7 +196,7 @@ fun fs_read_all(path: str, dst: ptr, dst_cap: i64) -> i64 { return n; } -fun fs_write_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { +pub fun fs_write_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { if (len < 0) { return IO_ERR_INVALID; } @@ -163,7 +220,7 @@ fun fs_write_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { return n; } -fun fs_append_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { +pub fun fs_append_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { if (len < 0) { return IO_ERR_INVALID; } @@ -187,7 +244,7 @@ fun fs_append_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { return n; } -fun fs_copy( +pub fun fs_copy( src_path: str, dst_path: str, scratch: ptr, diff --git a/std/io/consts.wave b/std/io/consts.wave index 6bd6e20f..a5bee98a 100644 --- a/std/io/consts.wave +++ b/std/io/consts.wave @@ -16,15 +16,15 @@ // // SPDX-License-Identifier: Apache-2.0 -const IO_STDIN_FD: i64 = 0; -const IO_STDOUT_FD: i64 = 1; -const IO_STDERR_FD: i64 = 2; +pub const IO_STDIN_FD: i64 = 0; +pub const IO_STDOUT_FD: i64 = 1; +pub const IO_STDERR_FD: i64 = 2; // syscall-returned errors are negative -errno values. // reserve a separate range for std-level contract errors. -const IO_ERR_INVALID: i64 = -4096; -const IO_ERR_EOF: i64 = -4097; -const IO_ERR_NO_SPACE: i64 = -4098; +pub const IO_ERR_INVALID: i64 = -4096; +pub const IO_ERR_EOF: i64 = -4097; +pub const IO_ERR_NO_SPACE: i64 = -4098; // interrupted system call (-EINTR) -const IO_ERR_INTR: i64 = -4; +pub const IO_ERR_INTR: i64 = -4; diff --git a/std/io/fd.wave b/std/io/fd.wave index 6a6adcb2..a0aa0b19 100644 --- a/std/io/fd.wave +++ b/std/io/fd.wave @@ -16,18 +16,63 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::io::consts"); -import("std::sys::fs"); - -fun io_close(fd: i64) -> i64 { +import("std::io::consts")::{ + IO_STDIN_FD, + IO_STDOUT_FD, + IO_STDERR_FD, + IO_ERR_INVALID, + IO_ERR_EOF, + IO_ERR_NO_SPACE, + IO_ERR_INTR, +}; +import("std::sys::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; + +pub fun io_close(fd: i64) -> i64 { return close(fd); } -fun io_seek(fd: i64, offset: i64, whence: i32) -> i64 { +pub fun io_seek(fd: i64, offset: i64, whence: i32) -> i64 { return lseek(fd, offset, whence); } -fun io_read(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun io_read(fd: i64, buf: ptr, len: i64) -> i64 { if (len < 0) { return IO_ERR_INVALID; } @@ -43,7 +88,7 @@ fun io_read(fd: i64, buf: ptr, len: i64) -> i64 { return 0; } -fun io_write(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun io_write(fd: i64, buf: ptr, len: i64) -> i64 { if (len < 0) { return IO_ERR_INVALID; } @@ -59,7 +104,7 @@ fun io_write(fd: i64, buf: ptr, len: i64) -> i64 { return 0; } -fun io_write_all(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun io_write_all(fd: i64, buf: ptr, len: i64) -> i64 { if (len < 0) { return IO_ERR_INVALID; } @@ -85,7 +130,7 @@ fun io_write_all(fd: i64, buf: ptr, len: i64) -> i64 { return written; } -fun io_read_exact(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun io_read_exact(fd: i64, buf: ptr, len: i64) -> i64 { if (len < 0) { return IO_ERR_INVALID; } @@ -111,7 +156,7 @@ fun io_read_exact(fd: i64, buf: ptr, len: i64) -> i64 { return total; } -fun io_read_at_most(fd: i64, dst: ptr, dst_cap: i64) -> i64 { +pub fun io_read_at_most(fd: i64, dst: ptr, dst_cap: i64) -> i64 { if (dst_cap < 0) { return IO_ERR_INVALID; } @@ -137,7 +182,7 @@ fun io_read_at_most(fd: i64, dst: ptr, dst_cap: i64) -> i64 { return total; } -fun io_copy(src_fd: i64, dst_fd: i64, scratch: ptr, scratch_cap: i64) -> i64 { +pub fun io_copy(src_fd: i64, dst_fd: i64, scratch: ptr, scratch_cap: i64) -> i64 { if (scratch_cap <= 0) { return IO_ERR_INVALID; } @@ -163,14 +208,14 @@ fun io_copy(src_fd: i64, dst_fd: i64, scratch: ptr, scratch_cap: i64) -> i64 return total; } -fun io_pipe(out_fds: ptr) -> i64 { +pub fun io_pipe(out_fds: ptr) -> i64 { return pipe(out_fds); } -fun io_dup(fd: i64) -> i64 { +pub fun io_dup(fd: i64) -> i64 { return dup(fd); } -fun io_dup2(oldfd: i64, newfd: i64) -> i64 { +pub fun io_dup2(oldfd: i64, newfd: i64) -> i64 { return dup2(oldfd, newfd); } diff --git a/std/libc/netinet.wave b/std/libc/netinet.wave index 7da3cee8..7398f6e0 100644 --- a/std/libc/netinet.wave +++ b/std/libc/netinet.wave @@ -17,7 +17,7 @@ // SPDX-License-Identifier: Apache-2.0 // sockaddr_in (IPv4) -struct SockAddrIn { +pub struct SockAddrIn { family: i16; // AF_INET port: i16; // network byte order addr: i32; // in_addr diff --git a/std/libc/poll.wave b/std/libc/poll.wave index 9e1cfb3c..1606417b 100644 --- a/std/libc/poll.wave +++ b/std/libc/poll.wave @@ -17,11 +17,11 @@ // SPDX-License-Identifier: Apache-2.0 // poll events -const POLLIN: i16 = 0x001; -const POLLOUT: i16 = 0x004; +pub const POLLIN: i16 = 0x001; +pub const POLLOUT: i16 = 0x004; // pollfd -struct PollFd { +pub struct PollFd { fd: i32; events: i16; revents: i16; diff --git a/std/libc/socket.wave b/std/libc/socket.wave index eb607ad0..9e9d7a10 100644 --- a/std/libc/socket.wave +++ b/std/libc/socket.wave @@ -17,26 +17,26 @@ // SPDX-License-Identifier: Apache-2.0 // address families -const AF_INET: i32 = 2; -const AF_INET6: i32 = 10; +pub const AF_INET: i32 = 2; +pub const AF_INET6: i32 = 10; // socket types -const SOCK_STREAM: i32 = 1; -const SOCK_DGRAM: i32 = 2; +pub const SOCK_STREAM: i32 = 1; +pub const SOCK_DGRAM: i32 = 2; // protocols -const IPPROTO_IP: i32 = 0; -const IPPROTO_TCP: i32 = 6; -const IPPROTO_UDP: i32 = 17; +pub const IPPROTO_IP: i32 = 0; +pub const IPPROTO_TCP: i32 = 6; +pub const IPPROTO_UDP: i32 = 17; // shutdown how -const SHUT_RD: i32 = 0; -const SHUT_WR: i32 = 1; -const SHUT_RDWR: i32 = 2; +pub const SHUT_RD: i32 = 0; +pub const SHUT_WR: i32 = 1; +pub const SHUT_RDWR: i32 = 2; // socket options -const SOL_SOCKET: i32 = 1; -const SO_REUSEADDR: i32 = 2; +pub const SOL_SOCKET: i32 = 1; +pub const SO_REUSEADDR: i32 = 2; // ====================== diff --git a/std/libc/time.wave b/std/libc/time.wave index 9ba9dbf1..d7df878b 100644 --- a/std/libc/time.wave +++ b/std/libc/time.wave @@ -16,13 +16,13 @@ // // SPDX-License-Identifier: Apache-2.0 -struct TimeSpec { +pub struct TimeSpec { sec: i64; nsec: i64; } -const CLOCK_REALTIME: i32 = 0; -const CLOCK_MONOTONIC: i32 = 1; +pub const CLOCK_REALTIME: i32 = 0; +pub const CLOCK_MONOTONIC: i32 = 1; extern(c) { fun nanosleep(ptr, ptr) -> i32; diff --git a/std/math/bits.wave b/std/math/bits.wave index 0956d685..f8dadaad 100644 --- a/std/math/bits.wave +++ b/std/math/bits.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun is_pow2(x: i32) -> bool { +pub fun is_pow2(x: i32) -> bool { if (x <= 0) { return false; } @@ -28,7 +28,7 @@ fun is_pow2(x: i32) -> bool { return false; } -fun align_down(x: i32, align: i32) -> i32 { +pub fun align_down(x: i32, align: i32) -> i32 { if (align <= 0) { return x; } @@ -36,7 +36,7 @@ fun align_down(x: i32, align: i32) -> i32 { return x & ~(align - 1); } -fun align_up(x: i32, align: i32) -> i32 { +pub fun align_up(x: i32, align: i32) -> i32 { if (align <= 0) { return x; } @@ -44,11 +44,11 @@ fun align_up(x: i32, align: i32) -> i32 { return (x + (align - 1)) & ~(align - 1); } -fun low_bit(x: i32) -> i32 { +pub fun low_bit(x: i32) -> i32 { return x & (-x); } -fun popcount(x0: i32) -> i32 { +pub fun popcount(x0: i32) -> i32 { var x: i32 = x0; var c: i32 = 0; @@ -60,7 +60,7 @@ fun popcount(x0: i32) -> i32 { return c; } -fun ctz32(x: i32) -> i32 { +pub fun ctz32(x: i32) -> i32 { if (x == 0) { return 32; } @@ -69,7 +69,7 @@ fun ctz32(x: i32) -> i32 { return popcount(lb - 1); } -fun bit_length(x0: i32) -> i32 { +pub fun bit_length(x0: i32) -> i32 { if (x0 <= 0) { return 0; } @@ -85,7 +85,7 @@ fun bit_length(x0: i32) -> i32 { return n; } -fun ilog2_floor(x: i32) -> i32 { +pub fun ilog2_floor(x: i32) -> i32 { if (x <= 0) { return -1; } @@ -93,7 +93,7 @@ fun ilog2_floor(x: i32) -> i32 { return bit_length(x) - 1; } -fun ilog2_ceil(x: i32) -> i32 { +pub fun ilog2_ceil(x: i32) -> i32 { if (x <= 1) { return 0; } @@ -101,7 +101,7 @@ fun ilog2_ceil(x: i32) -> i32 { return ilog2_floor(x - 1) + 1; } -fun is_pow2_i64(x: i64) -> bool { +pub fun is_pow2_i64(x: i64) -> bool { if (x <= 0) { return false; } @@ -113,7 +113,7 @@ fun is_pow2_i64(x: i64) -> bool { return false; } -fun align_down_i64(x: i64, align: i64) -> i64 { +pub fun align_down_i64(x: i64, align: i64) -> i64 { if (align <= 0) { return x; } @@ -121,7 +121,7 @@ fun align_down_i64(x: i64, align: i64) -> i64 { return x & ~(align - 1); } -fun align_up_i64(x: i64, align: i64) -> i64 { +pub fun align_up_i64(x: i64, align: i64) -> i64 { if (align <= 0) { return x; } @@ -129,11 +129,11 @@ fun align_up_i64(x: i64, align: i64) -> i64 { return (x + (align - 1)) & ~(align - 1); } -fun low_bit_i64(x: i64) -> i64 { +pub fun low_bit_i64(x: i64) -> i64 { return x & (-x); } -fun popcount64(x0: i64) -> i32 { +pub fun popcount64(x0: i64) -> i32 { var x: i64 = x0; var c: i32 = 0; @@ -145,7 +145,7 @@ fun popcount64(x0: i64) -> i32 { return c; } -fun ctz64(x: i64) -> i32 { +pub fun ctz64(x: i64) -> i32 { if (x == 0) { return 64; } @@ -154,7 +154,7 @@ fun ctz64(x: i64) -> i32 { return popcount64(lb - 1); } -fun bit_length64(x0: i64) -> i32 { +pub fun bit_length64(x0: i64) -> i32 { if (x0 <= 0) { return 0; } @@ -170,7 +170,7 @@ fun bit_length64(x0: i64) -> i32 { return n; } -fun ilog2_floor64(x: i64) -> i32 { +pub fun ilog2_floor64(x: i64) -> i32 { if (x <= 0) { return -1; } @@ -178,7 +178,7 @@ fun ilog2_floor64(x: i64) -> i32 { return bit_length64(x) - 1; } -fun ilog2_ceil64(x: i64) -> i32 { +pub fun ilog2_ceil64(x: i64) -> i32 { if (x <= 1) { return 0; } @@ -186,14 +186,14 @@ fun ilog2_ceil64(x: i64) -> i32 { return ilog2_floor64(x - 1) + 1; } -fun bswap32(x: i32) -> i32 { +pub fun bswap32(x: i32) -> i32 { return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24); } -fun bswap64(x: i64) -> i64 { +pub fun bswap64(x: i64) -> i64 { return ((x & 0x00000000000000FF) << 56) | ((x & 0x000000000000FF00) << 40) | ((x & 0x0000000000FF0000) << 24) diff --git a/std/math/float.wave b/std/math/float.wave index c4a864f1..14e21060 100644 --- a/std/math/float.wave +++ b/std/math/float.wave @@ -16,20 +16,36 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::math::int"); +import("std::math::int")::{ + num_abs, + num_min, + num_max, + num_clamp, + ptr_swap, + abs, + min, + max, + clamp, + sign, + is_even, + is_odd, + div_ceil_pos, + div_floor_pos, + swap_i32, +}; -fun abs_f32(x: f32) -> f32 { +pub fun abs_f32(x: f32) -> f32 { return num_abs(x, 0.0); } -fun min_f32(a: f32, b: f32) -> f32 { +pub fun min_f32(a: f32, b: f32) -> f32 { return num_min(a, b); } -fun max_f32(a: f32, b: f32) -> f32 { +pub fun max_f32(a: f32, b: f32) -> f32 { return num_max(a, b); } -fun clamp_f32(x: f32, lo: f32, hi: f32) -> f32 { +pub fun clamp_f32(x: f32, lo: f32, hi: f32) -> f32 { return num_clamp(x, lo, hi); } diff --git a/std/math/int.wave b/std/math/int.wave index 19591a0f..6ed2de18 100644 --- a/std/math/int.wave +++ b/std/math/int.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun num_abs(x: T, zero: T) -> T { +pub fun num_abs(x: T, zero: T) -> T { if (x < zero) { return -x; } @@ -24,7 +24,7 @@ fun num_abs(x: T, zero: T) -> T { return x; } -fun num_min(a: T, b: T) -> T { +pub fun num_min(a: T, b: T) -> T { if (a < b) { return a; } @@ -32,7 +32,7 @@ fun num_min(a: T, b: T) -> T { return b; } -fun num_max(a: T, b: T) -> T { +pub fun num_max(a: T, b: T) -> T { if (a > b) { return a; } @@ -40,7 +40,7 @@ fun num_max(a: T, b: T) -> T { return b; } -fun num_clamp(x: T, lo: T, hi: T) -> T { +pub fun num_clamp(x: T, lo: T, hi: T) -> T { if (x < lo) { return lo; } @@ -52,30 +52,30 @@ fun num_clamp(x: T, lo: T, hi: T) -> T { return x; } -fun ptr_swap(a: ptr, b: ptr) { +pub fun ptr_swap(a: ptr, b: ptr) { var t: T = deref a; deref a = deref b; deref b = t; } -fun abs(x: i32) -> i32 { +pub fun abs(x: i32) -> i32 { return num_abs(x, 0); } -fun min(a: i32, b: i32) -> i32 { +pub fun min(a: i32, b: i32) -> i32 { return num_min(a, b); } -fun max(a: i32, b: i32) -> i32 { +pub fun max(a: i32, b: i32) -> i32 { return num_max(a, b); } -fun clamp(x: i32, lo: i32, hi: i32) -> i32 { +pub fun clamp(x: i32, lo: i32, hi: i32) -> i32 { return num_clamp(x, lo, hi); } -fun sign(x: i32) -> i32 { +pub fun sign(x: i32) -> i32 { if (x < 0) { return -1; } @@ -87,7 +87,7 @@ fun sign(x: i32) -> i32 { return 0; } -fun is_even(x: i32) -> bool { +pub fun is_even(x: i32) -> bool { if ((x % 2) == 0) { return true; } @@ -95,7 +95,7 @@ fun is_even(x: i32) -> bool { return false; } -fun is_odd(x: i32) -> bool { +pub fun is_odd(x: i32) -> bool { if ((x % 2) != 0) { return true; } @@ -103,7 +103,7 @@ fun is_odd(x: i32) -> bool { return false; } -fun div_ceil_pos(a: i32, b: i32) -> i32 { +pub fun div_ceil_pos(a: i32, b: i32) -> i32 { if (b == 0) { return 0; } @@ -115,7 +115,7 @@ fun div_ceil_pos(a: i32, b: i32) -> i32 { return (a + (b - 1)) / b; } -fun div_floor_pos(a: i32, b: i32) -> i32 { +pub fun div_floor_pos(a: i32, b: i32) -> i32 { if (b == 0) { return 0; } @@ -127,6 +127,6 @@ fun div_floor_pos(a: i32, b: i32) -> i32 { return a / b; } -fun swap_i32(a: ptr, b: ptr) { +pub fun swap_i32(a: ptr, b: ptr) { ptr_swap(a, b); } diff --git a/std/math/num.wave b/std/math/num.wave index 4fa725b7..b9ec8464 100644 --- a/std/math/num.wave +++ b/std/math/num.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun gcd(a0: i32, b0: i32) -> i32 { +pub fun gcd(a0: i32, b0: i32) -> i32 { var a: i32 = a0; var b: i32 = b0; @@ -37,7 +37,7 @@ fun gcd(a0: i32, b0: i32) -> i32 { return a; } -fun lcm(a: i32, b: i32) -> i32 { +pub fun lcm(a: i32, b: i32) -> i32 { if (a == 0 || b == 0) { return 0; } @@ -53,7 +53,7 @@ fun lcm(a: i32, b: i32) -> i32 { return r; } -fun pow_i32(base0: i32, exp0: i32) -> i32 { +pub fun pow_i32(base0: i32, exp0: i32) -> i32 { var base: i32 = base0; var exp: i32 = exp0; var result: i32 = 1; diff --git a/std/math/trig.wave b/std/math/trig.wave index 37efaa93..030e1619 100644 --- a/std/math/trig.wave +++ b/std/math/trig.wave @@ -16,16 +16,32 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::math::int"); +import("std::math::int")::{ + num_abs, + num_min, + num_max, + num_clamp, + ptr_swap, + abs, + min, + max, + clamp, + sign, + is_even, + is_odd, + div_ceil_pos, + div_floor_pos, + swap_i32, +}; -const MATH_PI_F64: f64 = 3.141592653589793; -const MATH_TWO_PI_F64: f64 = 6.283185307179586; +pub const MATH_PI_F64: f64 = 3.141592653589793; +pub const MATH_TWO_PI_F64: f64 = 6.283185307179586; -fun abs_f64(x: f64) -> f64 { +pub fun abs_f64(x: f64) -> f64 { return num_abs(x, 0.0); } -fun wrap_angle_pi_f64(x: f64) -> f64 { +pub fun wrap_angle_pi_f64(x: f64) -> f64 { var k: i64 = (x / MATH_TWO_PI_F64) as i64; var y: f64 = x - (k as f64) * MATH_TWO_PI_F64; @@ -40,7 +56,7 @@ fun wrap_angle_pi_f64(x: f64) -> f64 { return y; } -fun sin_f64(x0: f64) -> f64 { +pub fun sin_f64(x0: f64) -> f64 { var x: f64 = wrap_angle_pi_f64(x0); var x2: f64 = x * x; var x3: f64 = x * x2; @@ -55,7 +71,7 @@ fun sin_f64(x0: f64) -> f64 { + (x9 / 362880.0); } -fun cos_f64(x0: f64) -> f64 { +pub fun cos_f64(x0: f64) -> f64 { var x: f64 = wrap_angle_pi_f64(x0); var x2: f64 = x * x; var x4: f64 = x2 * x2; @@ -69,7 +85,7 @@ fun cos_f64(x0: f64) -> f64 { + (x8 / 40320.0); } -fun sqrt_f64(x: f64) -> f64 { +pub fun sqrt_f64(x: f64) -> f64 { if (x <= 0.0) { return 0.0; } diff --git a/std/mem/alloc.wave b/std/mem/alloc.wave index fbe7979d..58149bff 100644 --- a/std/mem/alloc.wave +++ b/std/mem/alloc.wave @@ -16,15 +16,50 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::memory"); -import("std::mem::consts"); -import("std::mem::ops"); - -fun mem_alloc(size: i64) -> ptr { +import("std::sys::memory")::{ + PROT_READ, + PROT_WRITE, + MAP_PRIVATE, + mmap, + munmap, + brk, + sys_alloc, + sys_free, +}; +import("std::mem::consts")::{ + MEM_PROT_READ, + MEM_PROT_WRITE, + MEM_PROT_EXEC, + MEM_MAP_PRIVATE, + MEM_MAP_ANON, + MEM_PAGE_SIZE, + MEM_ERR_INVALID, + MEM_ERR_NO_SPACE, +}; +import("std::mem::ops")::{ + mem_set, + mem_zero, + mem_copy, + mem_move, + mem_cmp, + mem_eq, + mem_find_byte, + mem_swap, + mem_copy_items, + mem_set_items, + mem_move_items, + mem_zero_items, + mem_copy_checked, + mem_move_checked, + mem_set_checked, + mem_zero_checked, +}; + +pub fun mem_alloc(size: i64) -> ptr { return sys_alloc(size); } -fun mem_alloc_zeroed(size: i64) -> ptr { +pub fun mem_alloc_zeroed(size: i64) -> ptr { var p: ptr = mem_alloc(size); if (p == null) { return null; @@ -38,11 +73,11 @@ fun mem_alloc_zeroed(size: i64) -> ptr { return p; } -fun mem_free(p: ptr, size: i64) -> i64 { +pub fun mem_free(p: ptr, size: i64) -> i64 { return sys_free(p, size); } -fun mem_realloc(old_ptr: ptr, old_size: i64, new_size: i64) -> ptr { +pub fun mem_realloc(old_ptr: ptr, old_size: i64, new_size: i64) -> ptr { if (new_size <= 0) { if (old_ptr != null && old_size > 0) { mem_free(old_ptr, old_size); @@ -69,7 +104,7 @@ fun mem_realloc(old_ptr: ptr, old_size: i64, new_size: i64) -> ptr { return new_ptr; } -fun mem_alloc_items(count: i64, elem_size: i64) -> ptr { +pub fun mem_alloc_items(count: i64, elem_size: i64) -> ptr { if (count <= 0 || elem_size <= 0) { return null; } @@ -77,7 +112,7 @@ fun mem_alloc_items(count: i64, elem_size: i64) -> ptr { return mem_alloc(count * elem_size) as ptr; } -fun mem_alloc_items_zeroed(count: i64, elem_size: i64) -> ptr { +pub fun mem_alloc_items_zeroed(count: i64, elem_size: i64) -> ptr { if (count <= 0 || elem_size <= 0) { return null; } @@ -85,7 +120,7 @@ fun mem_alloc_items_zeroed(count: i64, elem_size: i64) -> ptr { return mem_alloc_zeroed(count * elem_size) as ptr; } -fun mem_realloc_items( +pub fun mem_realloc_items( old_ptr: ptr, old_count: i64, new_count: i64, @@ -108,7 +143,7 @@ fun mem_realloc_items( return mem_realloc(old_ptr as ptr, old_size, new_size) as ptr; } -fun mem_free_items(p: ptr, count: i64, elem_size: i64) -> i64 { +pub fun mem_free_items(p: ptr, count: i64, elem_size: i64) -> i64 { if (p == null) { return 0; } @@ -120,11 +155,11 @@ fun mem_free_items(p: ptr, count: i64, elem_size: i64) -> i64 { return mem_free(p as ptr, count * elem_size); } -fun mem_page_size() -> i64 { +pub fun mem_page_size() -> i64 { return MEM_PAGE_SIZE; } -fun mem_pages_for_size(size: i64) -> i64 { +pub fun mem_pages_for_size(size: i64) -> i64 { if (size <= 0) { return 0; } @@ -132,11 +167,11 @@ fun mem_pages_for_size(size: i64) -> i64 { return (size + (MEM_PAGE_SIZE - 1)) / MEM_PAGE_SIZE; } -fun mem_size_align_page(size: i64) -> i64 { +pub fun mem_size_align_page(size: i64) -> i64 { return mem_pages_for_size(size) * MEM_PAGE_SIZE; } -fun mem_alloc_pages(page_count: i64) -> ptr { +pub fun mem_alloc_pages(page_count: i64) -> ptr { if (page_count <= 0) { return null; } @@ -144,7 +179,7 @@ fun mem_alloc_pages(page_count: i64) -> ptr { return mem_alloc(page_count * MEM_PAGE_SIZE); } -fun mem_free_pages(p: ptr, page_count: i64) -> i64 { +pub fun mem_free_pages(p: ptr, page_count: i64) -> i64 { if (p == null || page_count <= 0) { return 0; } @@ -152,7 +187,7 @@ fun mem_free_pages(p: ptr, page_count: i64) -> i64 { return mem_free(p, page_count * MEM_PAGE_SIZE); } -fun mem_is_aligned(p: ptr, align: i64) -> bool { +pub fun mem_is_aligned(p: ptr, align: i64) -> bool { if (p == null || align <= 0) { return false; } @@ -168,7 +203,7 @@ fun mem_is_aligned(p: ptr, align: i64) -> bool { return false; } -fun mem_alloc_aligned(size: i64, align: i64) -> ptr { +pub fun mem_alloc_aligned(size: i64, align: i64) -> ptr { if (size <= 0 || align <= 0) { return null; } @@ -201,7 +236,7 @@ fun mem_alloc_aligned(size: i64, align: i64) -> ptr { return aligned_addr as ptr; } -fun mem_free_aligned(p: ptr) -> i64 { +pub fun mem_free_aligned(p: ptr) -> i64 { if (p == null) { return 0; } diff --git a/std/mem/consts.wave b/std/mem/consts.wave index 5aa18127..45376857 100644 --- a/std/mem/consts.wave +++ b/std/mem/consts.wave @@ -16,14 +16,14 @@ // // SPDX-License-Identifier: Apache-2.0 -const MEM_PROT_READ: i64 = 1; -const MEM_PROT_WRITE: i64 = 2; -const MEM_PROT_EXEC: i64 = 4; +pub const MEM_PROT_READ: i64 = 1; +pub const MEM_PROT_WRITE: i64 = 2; +pub const MEM_PROT_EXEC: i64 = 4; -const MEM_MAP_PRIVATE: i64 = 2; -const MEM_MAP_ANON: i64 = 32; +pub const MEM_MAP_PRIVATE: i64 = 2; +pub const MEM_MAP_ANON: i64 = 32; -const MEM_PAGE_SIZE: i64 = 4096; +pub const MEM_PAGE_SIZE: i64 = 4096; -const MEM_ERR_INVALID: i64 = -4096; -const MEM_ERR_NO_SPACE: i64 = -4097; +pub const MEM_ERR_INVALID: i64 = -4096; +pub const MEM_ERR_NO_SPACE: i64 = -4097; diff --git a/std/mem/cstr.wave b/std/mem/cstr.wave index b3a77026..de556c0a 100644 --- a/std/mem/cstr.wave +++ b/std/mem/cstr.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun mem_len_cstr(s: str) -> i64 { +pub fun mem_len_cstr(s: str) -> i64 { var i: i64 = 0; while (s[i] != 0) { @@ -26,7 +26,7 @@ fun mem_len_cstr(s: str) -> i64 { return i; } -fun mem_copy_cstr(dst: ptr, s: str) -> i64 { +pub fun mem_copy_cstr(dst: ptr, s: str) -> i64 { var i: i64 = 0; while (s[i] != 0) { @@ -38,7 +38,7 @@ fun mem_copy_cstr(dst: ptr, s: str) -> i64 { return i; } -fun mem_copy_cstr_n(dst: ptr, dst_cap: i64, s: str) -> i64 { +pub fun mem_copy_cstr_n(dst: ptr, dst_cap: i64, s: str) -> i64 { if (dst_cap <= 0) { return -1; } @@ -55,7 +55,7 @@ fun mem_copy_cstr_n(dst: ptr, dst_cap: i64, s: str) -> i64 { return i; } -fun mem_eq_cstr(a: str, b: str) -> bool { +pub fun mem_eq_cstr(a: str, b: str) -> bool { var i: i64 = 0; while (true) { if (a[i] != b[i]) { @@ -70,7 +70,7 @@ fun mem_eq_cstr(a: str, b: str) -> bool { return true; } -fun mem_starts_with_cstr(s: str, prefix: str) -> bool { +pub fun mem_starts_with_cstr(s: str, prefix: str) -> bool { var i: i64 = 0; while (prefix[i] != 0) { if (s[i] != prefix[i]) { @@ -81,7 +81,7 @@ fun mem_starts_with_cstr(s: str, prefix: str) -> bool { return true; } -fun mem_find_cstr_char(s: str, c: u8) -> i64 { +pub fun mem_find_cstr_char(s: str, c: u8) -> i64 { var i: i64 = 0; while (s[i] != 0) { if (s[i] == c) { diff --git a/std/mem/ops.wave b/std/mem/ops.wave index 42983cba..8a8ca736 100644 --- a/std/mem/ops.wave +++ b/std/mem/ops.wave @@ -16,9 +16,18 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::mem::consts"); - -fun mem_set(dst: ptr, value: u8, size: i64) { +import("std::mem::consts")::{ + MEM_PROT_READ, + MEM_PROT_WRITE, + MEM_PROT_EXEC, + MEM_MAP_PRIVATE, + MEM_MAP_ANON, + MEM_PAGE_SIZE, + MEM_ERR_INVALID, + MEM_ERR_NO_SPACE, +}; + +pub fun mem_set(dst: ptr, value: u8, size: i64) { var i: i64 = 0; while (i < size) { deref dst[i] = value; @@ -26,11 +35,11 @@ fun mem_set(dst: ptr, value: u8, size: i64) { } } -fun mem_zero(dst: ptr, size: i64) { +pub fun mem_zero(dst: ptr, size: i64) { mem_set(dst, 0, size); } -fun mem_copy(dst: ptr, src: ptr, size: i64) { +pub fun mem_copy(dst: ptr, src: ptr, size: i64) { var i: i64 = 0; while (i < size) { deref dst[i] = src[i]; @@ -38,7 +47,7 @@ fun mem_copy(dst: ptr, src: ptr, size: i64) { } } -fun mem_move(dst: ptr, src: ptr, size: i64) { +pub fun mem_move(dst: ptr, src: ptr, size: i64) { if (size <= 0) { return; } @@ -63,7 +72,7 @@ fun mem_move(dst: ptr, src: ptr, size: i64) { } } -fun mem_cmp(a: ptr, b: ptr, size: i64) -> i32 { +pub fun mem_cmp(a: ptr, b: ptr, size: i64) -> i32 { var i: i64 = 0; while (i < size) { @@ -84,7 +93,7 @@ fun mem_cmp(a: ptr, b: ptr, size: i64) -> i32 { return 0; } -fun mem_eq(a: ptr, b: ptr, size: i64) -> bool { +pub fun mem_eq(a: ptr, b: ptr, size: i64) -> bool { if (mem_cmp(a, b, size) == 0) { return true; } @@ -92,7 +101,7 @@ fun mem_eq(a: ptr, b: ptr, size: i64) -> bool { return false; } -fun mem_find_byte(src: ptr, size: i64, value: u8) -> i64 { +pub fun mem_find_byte(src: ptr, size: i64, value: u8) -> i64 { var i: i64 = 0; while (i < size) { if (src[i] == value) { @@ -103,13 +112,13 @@ fun mem_find_byte(src: ptr, size: i64, value: u8) -> i64 { return -1; } -fun mem_swap(a: ptr, b: ptr) { +pub fun mem_swap(a: ptr, b: ptr) { var tmp: T = deref a; deref a = deref b; deref b = tmp; } -fun mem_copy_items(dst: ptr, src: ptr, count: i64, elem_size: i64) { +pub fun mem_copy_items(dst: ptr, src: ptr, count: i64, elem_size: i64) { if (count <= 0 || elem_size <= 0) { return; } @@ -117,7 +126,7 @@ fun mem_copy_items(dst: ptr, src: ptr, count: i64, elem_size: i64) { mem_copy(dst as ptr, src as ptr, count * elem_size); } -fun mem_set_items(dst: ptr, value: T, count: i64, elem_size: i64) { +pub fun mem_set_items(dst: ptr, value: T, count: i64, elem_size: i64) { if (count <= 0 || elem_size <= 0) { return; } @@ -129,7 +138,7 @@ fun mem_set_items(dst: ptr, value: T, count: i64, elem_size: i64) { } } -fun mem_move_items(dst: ptr, src: ptr, count: i64, elem_size: i64) { +pub fun mem_move_items(dst: ptr, src: ptr, count: i64, elem_size: i64) { if (count <= 0 || elem_size <= 0) { return; } @@ -137,7 +146,7 @@ fun mem_move_items(dst: ptr, src: ptr, count: i64, elem_size: i64) { mem_move(dst as ptr, src as ptr, count * elem_size); } -fun mem_zero_items(dst: ptr, count: i64, elem_size: i64) { +pub fun mem_zero_items(dst: ptr, count: i64, elem_size: i64) { if (count <= 0 || elem_size <= 0) { return; } @@ -145,7 +154,7 @@ fun mem_zero_items(dst: ptr, count: i64, elem_size: i64) { mem_zero(dst as ptr, count * elem_size); } -fun mem_copy_checked(dst: ptr, dst_cap: i64, src: ptr, src_len: i64) -> i64 { +pub fun mem_copy_checked(dst: ptr, dst_cap: i64, src: ptr, src_len: i64) -> i64 { if (dst_cap < 0 || src_len < 0) { return MEM_ERR_INVALID; } @@ -162,7 +171,7 @@ fun mem_copy_checked(dst: ptr, dst_cap: i64, src: ptr, src_len: i64) -> return src_len; } -fun mem_move_checked(dst: ptr, dst_cap: i64, src: ptr, src_len: i64) -> i64 { +pub fun mem_move_checked(dst: ptr, dst_cap: i64, src: ptr, src_len: i64) -> i64 { if (dst_cap < 0 || src_len < 0) { return MEM_ERR_INVALID; } @@ -179,7 +188,7 @@ fun mem_move_checked(dst: ptr, dst_cap: i64, src: ptr, src_len: i64) -> return src_len; } -fun mem_set_checked(dst: ptr, dst_cap: i64, value: u8, len: i64) -> i64 { +pub fun mem_set_checked(dst: ptr, dst_cap: i64, value: u8, len: i64) -> i64 { if (dst_cap < 0 || len < 0) { return MEM_ERR_INVALID; } @@ -196,6 +205,6 @@ fun mem_set_checked(dst: ptr, dst_cap: i64, value: u8, len: i64) -> i64 { return len; } -fun mem_zero_checked(dst: ptr, dst_cap: i64, len: i64) -> i64 { +pub fun mem_zero_checked(dst: ptr, dst_cap: i64, len: i64) -> i64 { return mem_set_checked(dst, dst_cap, 0, len); } diff --git a/std/net/address.wave b/std/net/address.wave index 1db278f7..39dc7b4a 100644 --- a/std/net/address.wave +++ b/std/net/address.wave @@ -16,55 +16,88 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::socket"); +import("std::sys::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; -struct NetAddrV4 { +pub struct NetAddrV4 { ip: i32; // network byte order port: i16; // network byte order } -struct NetSockAddrIn { +pub struct NetSockAddrIn { family: i16; port: i16; addr: i32; zero: array; } -fun net_htons(x: i16) -> i16 { +pub fun net_htons(x: i16) -> i16 { return ((x & 255) << 8) | ((x >> 8) & 255); } -fun net_ntohs(x: i16) -> i16 { +pub fun net_ntohs(x: i16) -> i16 { return net_htons(x); } -fun net_htonl(x: i32) -> i32 { +pub fun net_htonl(x: i32) -> i32 { return ((x & 0x000000FF) << 24) | ((x & 0x0000FF00) << 8) | ((x & 0x00FF0000) >> 8) | ((x & 0xFF000000) >> 24); } -fun net_ntohl(x: i32) -> i32 { +pub fun net_ntohl(x: i32) -> i32 { return net_htonl(x); } -fun net_addr_v4(ip_host_order: i32, port_host_order: i16) -> NetAddrV4 { +pub fun net_addr_v4(ip_host_order: i32, port_host_order: i16) -> NetAddrV4 { var addr_value: NetAddrV4; addr_value.ip = net_htonl(ip_host_order); addr_value.port = net_htons(port_host_order); return addr_value; } -fun net_addr_any_v4(port_host_order: i16) -> NetAddrV4 { +pub fun net_addr_any_v4(port_host_order: i16) -> NetAddrV4 { return net_addr_v4(0, port_host_order); } -fun net_addr_loopback_v4(port_host_order: i16) -> NetAddrV4 { +pub fun net_addr_loopback_v4(port_host_order: i16) -> NetAddrV4 { return net_addr_v4(0x7F000001, port_host_order); } -fun net_to_sockaddr_v4(addr: NetAddrV4) -> NetSockAddrIn { +pub fun net_to_sockaddr_v4(addr: NetAddrV4) -> NetSockAddrIn { return NetSockAddrIn { family: AF_INET as i16, port: addr.port, @@ -73,7 +106,7 @@ fun net_to_sockaddr_v4(addr: NetAddrV4) -> NetSockAddrIn { }; } -fun net_from_sockaddr_v4(sa: NetSockAddrIn) -> NetAddrV4 { +pub fun net_from_sockaddr_v4(sa: NetSockAddrIn) -> NetAddrV4 { return NetAddrV4 { ip: sa.addr, port: sa.port diff --git a/std/net/poll.wave b/std/net/poll.wave index a6508d58..dfadb4a1 100644 --- a/std/net/poll.wave +++ b/std/net/poll.wave @@ -16,19 +16,55 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::socket"); +import("std::sys::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; +pub import("std::sys::socket")::{ + PollFd, +}; -const NET_POLLIN: i16 = POLLIN; -const NET_POLLOUT: i16 = POLLOUT; -const NET_POLLERR: i16 = POLLERR; -const NET_POLLHUP: i16 = POLLHUP; -const NET_POLLNVAL: i16 = POLLNVAL; +pub const NET_POLLIN: i16 = POLLIN; +pub const NET_POLLOUT: i16 = POLLOUT; +pub const NET_POLLERR: i16 = POLLERR; +pub const NET_POLLHUP: i16 = POLLHUP; +pub const NET_POLLNVAL: i16 = POLLNVAL; -fun net_poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { +pub fun net_poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { return poll(fds, nfds, timeout_ms); } -fun net_wait_readable(fd: i64, timeout_ms: i32) -> i64 { +pub fun net_wait_readable(fd: i64, timeout_ms: i32) -> i64 { var pfd: PollFd = PollFd { fd: fd as i32, events: NET_POLLIN, @@ -51,7 +87,7 @@ fun net_wait_readable(fd: i64, timeout_ms: i32) -> i64 { return 0; } -fun net_wait_writable(fd: i64, timeout_ms: i32) -> i64 { +pub fun net_wait_writable(fd: i64, timeout_ms: i32) -> i64 { var pfd: PollFd = PollFd { fd: fd as i32, events: NET_POLLOUT, diff --git a/std/net/socket_base.wave b/std/net/socket_base.wave index b4cedc2c..0d6e3f86 100644 --- a/std/net/socket_base.wave +++ b/std/net/socket_base.wave @@ -16,18 +16,100 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::socket"); -import("std::sys::fs"); -import("std::net::address"); - -fun net_fd_valid(fd: i64) -> bool { +import("std::sys::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; +import("std::sys::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; +import("std::net::address")::{ + NetAddrV4, + NetSockAddrIn, + net_htons, + net_ntohs, + net_htonl, + net_ntohl, + net_addr_v4, + net_addr_any_v4, + net_addr_loopback_v4, + net_to_sockaddr_v4, + net_from_sockaddr_v4, +}; + +pub fun net_fd_valid(fd: i64) -> bool { if (fd >= 0) { return true; } return false; } -fun net_set_reuseaddr(fd: i64) -> i64 { +pub fun net_set_reuseaddr(fd: i64) -> i64 { var one: i32 = 1; return setsockopt( fd, @@ -38,25 +120,25 @@ fun net_set_reuseaddr(fd: i64) -> i64 { ); } -fun net_socket_tcp_v4() -> i64 { +pub fun net_socket_tcp_v4() -> i64 { return socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); } -fun net_socket_udp_v4() -> i64 { +pub fun net_socket_udp_v4() -> i64 { return socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); } -fun net_bind_v4(fd: i64, addr: NetAddrV4) -> i64 { +pub fun net_bind_v4(fd: i64, addr: NetAddrV4) -> i64 { var sa: NetSockAddrIn = net_to_sockaddr_v4(addr); return bind(fd, &sa, 16); } -fun net_connect_v4(fd: i64, addr: NetAddrV4) -> i64 { +pub fun net_connect_v4(fd: i64, addr: NetAddrV4) -> i64 { var sa: NetSockAddrIn = net_to_sockaddr_v4(addr); return connect(fd, &sa, 16); } -fun net_accept_v4(fd: i64, out_addr: ptr) -> i64 { +pub fun net_accept_v4(fd: i64, out_addr: ptr) -> i64 { var sa: NetSockAddrIn; var salen: i32 = 16; @@ -68,7 +150,7 @@ fun net_accept_v4(fd: i64, out_addr: ptr) -> i64 { return cfd; } -fun net_send_all(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { +pub fun net_send_all(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { if (len <= 0) { return 0; } @@ -87,7 +169,7 @@ fun net_send_all(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return sent_total; } -fun net_recv_exact(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { +pub fun net_recv_exact(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { if (len <= 0) { return 0; } @@ -106,7 +188,7 @@ fun net_recv_exact(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return read_total; } -fun net_sendto_v4( +pub fun net_sendto_v4( fd: i64, addr: NetAddrV4, buf: ptr, @@ -117,7 +199,7 @@ fun net_sendto_v4( return sendto(fd, buf, len, flags, &sa, 16); } -fun net_recvfrom_v4( +pub fun net_recvfrom_v4( fd: i64, buf: ptr, len: i64, @@ -134,7 +216,7 @@ fun net_recvfrom_v4( return n; } -fun net_shutdown_close(fd: i64) { +pub fun net_shutdown_close(fd: i64) { shutdown(fd, SHUT_RDWR); close(fd); } diff --git a/std/net/socketopt.wave b/std/net/socketopt.wave index 11c66545..fa1b9536 100644 --- a/std/net/socketopt.wave +++ b/std/net/socketopt.wave @@ -16,10 +16,80 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::fs"); -import("std::sys::socket"); +import("std::sys::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; +import("std::sys::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; -fun net_set_nonblock(fd: i64, enabled: i32) -> i64 { +pub fun net_set_nonblock(fd: i64, enabled: i32) -> i64 { var flags_raw: i64 = fcntl(fd, FS_F_GETFL, 0); if (flags_raw < 0) { return flags_raw; @@ -35,7 +105,7 @@ fun net_set_nonblock(fd: i64, enabled: i32) -> i64 { return fcntl(fd, FS_F_SETFL, flags as i64); } -fun net_get_nonblock(fd: i64) -> i64 { +pub fun net_get_nonblock(fd: i64) -> i64 { var flags_raw: i64 = fcntl(fd, FS_F_GETFL, 0); if (flags_raw < 0) { return flags_raw; @@ -49,7 +119,7 @@ fun net_get_nonblock(fd: i64) -> i64 { return 0; } -fun net_set_reuseaddr_flag(fd: i64, enabled: i32) -> i64 { +pub fun net_set_reuseaddr_flag(fd: i64, enabled: i32) -> i64 { var value: i32 = 0; if (enabled != 0) { value = 1; @@ -64,7 +134,7 @@ fun net_set_reuseaddr_flag(fd: i64, enabled: i32) -> i64 { ); } -fun net_get_reuseaddr_flag(fd: i64) -> i64 { +pub fun net_get_reuseaddr_flag(fd: i64) -> i64 { var value: i32 = 0; var size: i32 = 4; @@ -86,7 +156,7 @@ fun net_get_reuseaddr_flag(fd: i64) -> i64 { return 0; } -fun net_set_reuseport_flag(fd: i64, enabled: i32) -> i64 { +pub fun net_set_reuseport_flag(fd: i64, enabled: i32) -> i64 { var value: i32 = 0; if (enabled != 0) { value = 1; diff --git a/std/net/tcp.wave b/std/net/tcp.wave index eb5c208d..1ac6460d 100644 --- a/std/net/tcp.wave +++ b/std/net/tcp.wave @@ -16,73 +16,178 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::socket"); -import("std::net::address"); -import("std::net::socket_base"); -import("std::net::socketopt"); -import("std::string::len"); - -struct TcpAddr { +import("std::sys::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; +import("std::sys::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; +import("std::net::address")::{ + NetAddrV4, + NetSockAddrIn, + net_htons, + net_ntohs, + net_htonl, + net_ntohl, + net_addr_v4, + net_addr_any_v4, + net_addr_loopback_v4, + net_to_sockaddr_v4, + net_from_sockaddr_v4, +}; +import("std::net::socket_base")::{ + net_fd_valid, + net_set_reuseaddr, + net_socket_tcp_v4, + net_socket_udp_v4, + net_bind_v4, + net_connect_v4, + net_accept_v4, + net_send_all, + net_recv_exact, + net_sendto_v4, + net_recvfrom_v4, + net_shutdown_close, +}; +import("std::net::socketopt")::{ + net_set_nonblock, + net_get_nonblock, + net_set_reuseaddr_flag, + net_get_reuseaddr_flag, + net_set_reuseport_flag, +}; +import("std::string::len")::{ + len, + is_empty, +}; + +pub struct TcpAddr { ip: i32; // network byte order port: i16; // network byte order } -struct TcpListener { +pub struct TcpListener { fd: i64; } -struct TcpStream { +pub struct TcpStream { fd: i64; } -fun _tcp_to_net_addr(addr: TcpAddr) -> NetAddrV4 { +pub fun _tcp_to_net_addr(addr: TcpAddr) -> NetAddrV4 { var value: NetAddrV4; value.ip = addr.ip; value.port = addr.port; return value; } -fun _tcp_from_net_addr(addr: NetAddrV4) -> TcpAddr { +pub fun _tcp_from_net_addr(addr: NetAddrV4) -> TcpAddr { var value: TcpAddr; value.ip = addr.ip; value.port = addr.port; return value; } -fun htons(x: i16) -> i16 { +pub fun htons(x: i16) -> i16 { return net_htons(x); } -fun htonl(x: i32) -> i32 { +pub fun htonl(x: i32) -> i32 { return net_htonl(x); } -fun tcp_addr(ip_host_order: i32, port_host_order: i16) -> TcpAddr { +pub fun tcp_addr(ip_host_order: i32, port_host_order: i16) -> TcpAddr { return _tcp_from_net_addr(net_addr_v4(ip_host_order, port_host_order)); } -fun tcp_addr_any(port_host_order: i16) -> TcpAddr { +pub fun tcp_addr_any(port_host_order: i16) -> TcpAddr { return _tcp_from_net_addr(net_addr_any_v4(port_host_order)); } -fun tcp_addr_loopback(port_host_order: i16) -> TcpAddr { +pub fun tcp_addr_loopback(port_host_order: i16) -> TcpAddr { return _tcp_from_net_addr(net_addr_loopback_v4(port_host_order)); } -fun tcp_set_reuseaddr(fd: i64) -> i64 { +pub fun tcp_set_reuseaddr(fd: i64) -> i64 { return net_set_reuseaddr(fd); } -fun tcp_bind(port: i16) -> TcpListener { +pub fun tcp_bind(port: i16) -> TcpListener { return tcp_bind_with_backlog(port, 128); } -fun tcp_bind_with_backlog(port: i16, backlog: i32) -> TcpListener { +pub fun tcp_bind_with_backlog(port: i16, backlog: i32) -> TcpListener { var addr: TcpAddr = tcp_addr_any(port); return tcp_bind_addr(addr, backlog); } -fun tcp_bind_addr(addr: TcpAddr, backlog: i32) -> TcpListener { +pub fun tcp_bind_addr(addr: TcpAddr, backlog: i32) -> TcpListener { var fd: i64 = net_socket_tcp_v4(); net_set_reuseaddr(fd); @@ -92,12 +197,12 @@ fun tcp_bind_addr(addr: TcpAddr, backlog: i32) -> TcpListener { return TcpListener { fd: fd }; } -fun tcp_accept(listener: TcpListener) -> TcpStream { +pub fun tcp_accept(listener: TcpListener) -> TcpStream { var cfd: i64 = net_accept_v4(listener.fd, null); return TcpStream { fd: cfd }; } -fun tcp_accept_addr(listener: TcpListener, src: ptr) -> TcpStream { +pub fun tcp_accept_addr(listener: TcpListener, src: ptr) -> TcpStream { var peer: NetAddrV4; var cfd: i64 = net_accept_v4(listener.fd, &peer); if (cfd >= 0) { @@ -106,17 +211,17 @@ fun tcp_accept_addr(listener: TcpListener, src: ptr) -> TcpStream { return TcpStream { fd: cfd }; } -fun tcp_close_listener(listener: TcpListener) { +pub fun tcp_close_listener(listener: TcpListener) { net_shutdown_close(listener.fd); } -fun tcp_connect(addr: TcpAddr) -> TcpStream { +pub fun tcp_connect(addr: TcpAddr) -> TcpStream { var fd: i64 = net_socket_tcp_v4(); net_connect_v4(fd, _tcp_to_net_addr(addr)); return TcpStream { fd: fd }; } -fun tcp_try_connect(addr: TcpAddr) -> i64 { +pub fun tcp_try_connect(addr: TcpAddr) -> i64 { var fd: i64 = net_socket_tcp_v4(); var r: i64 = net_connect_v4(fd, _tcp_to_net_addr(addr)); if (r < 0) { @@ -126,23 +231,23 @@ fun tcp_try_connect(addr: TcpAddr) -> i64 { return fd; } -fun tcp_read(stream: TcpStream, buf: ptr, len: i64) -> i64 { +pub fun tcp_read(stream: TcpStream, buf: ptr, len: i64) -> i64 { return recv(stream.fd, buf, len, 0); } -fun tcp_write(stream: TcpStream, buf: ptr, len: i64) -> i64 { +pub fun tcp_write(stream: TcpStream, buf: ptr, len: i64) -> i64 { return send(stream.fd, buf, len, 0); } -fun tcp_write_all(stream: TcpStream, buf: ptr, len: i64) -> i64 { +pub fun tcp_write_all(stream: TcpStream, buf: ptr, len: i64) -> i64 { return net_send_all(stream.fd, buf, len, 0); } -fun tcp_read_exact(stream: TcpStream, buf: ptr, len: i64) -> i64 { +pub fun tcp_read_exact(stream: TcpStream, buf: ptr, len: i64) -> i64 { return net_recv_exact(stream.fd, buf, len, 0); } -fun tcp_write_str(stream: TcpStream, s: str) -> i64 { +pub fun tcp_write_str(stream: TcpStream, s: str) -> i64 { var n: i64 = len(s) as i64; if (n <= 0) { return 0; @@ -150,38 +255,38 @@ fun tcp_write_str(stream: TcpStream, s: str) -> i64 { return tcp_write_all(stream, s as ptr, n); } -fun tcp_stream_set_nonblock(stream: TcpStream, enabled: i32) -> i64 { +pub fun tcp_stream_set_nonblock(stream: TcpStream, enabled: i32) -> i64 { return net_set_nonblock(stream.fd, enabled); } -fun tcp_stream_get_nonblock(stream: TcpStream) -> i64 { +pub fun tcp_stream_get_nonblock(stream: TcpStream) -> i64 { return net_get_nonblock(stream.fd); } -fun tcp_listener_set_nonblock(listener: TcpListener, enabled: i32) -> i64 { +pub fun tcp_listener_set_nonblock(listener: TcpListener, enabled: i32) -> i64 { return net_set_nonblock(listener.fd, enabled); } -fun tcp_listener_get_nonblock(listener: TcpListener) -> i64 { +pub fun tcp_listener_get_nonblock(listener: TcpListener) -> i64 { return net_get_nonblock(listener.fd); } -fun tcp_from_fd(fd: i64) -> TcpStream { +pub fun tcp_from_fd(fd: i64) -> TcpStream { return TcpStream { fd: fd }; } -fun tcp_listener_from_fd(fd: i64) -> TcpListener { +pub fun tcp_listener_from_fd(fd: i64) -> TcpListener { return TcpListener { fd: fd }; } -fun tcp_stream_valid(stream: TcpStream) -> bool { +pub fun tcp_stream_valid(stream: TcpStream) -> bool { return net_fd_valid(stream.fd); } -fun tcp_listener_valid(listener: TcpListener) -> bool { +pub fun tcp_listener_valid(listener: TcpListener) -> bool { return net_fd_valid(listener.fd); } -fun tcp_close(stream: TcpStream) { +pub fun tcp_close(stream: TcpStream) { net_shutdown_close(stream.fd); } diff --git a/std/net/udp.wave b/std/net/udp.wave index 4e086294..02fd2cf3 100644 --- a/std/net/udp.wave +++ b/std/net/udp.wave @@ -16,75 +16,142 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::socket"); -import("std::net::address"); -import("std::net::socket_base"); -import("std::net::socketopt"); -import("std::string::len"); - -struct UdpAddr { +import("std::sys::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; +import("std::net::address")::{ + NetAddrV4, + NetSockAddrIn, + net_htons, + net_ntohs, + net_htonl, + net_ntohl, + net_addr_v4, + net_addr_any_v4, + net_addr_loopback_v4, + net_to_sockaddr_v4, + net_from_sockaddr_v4, +}; +import("std::net::socket_base")::{ + net_fd_valid, + net_set_reuseaddr, + net_socket_tcp_v4, + net_socket_udp_v4, + net_bind_v4, + net_connect_v4, + net_accept_v4, + net_send_all, + net_recv_exact, + net_sendto_v4, + net_recvfrom_v4, + net_shutdown_close, +}; +import("std::net::socketopt")::{ + net_set_nonblock, + net_get_nonblock, + net_set_reuseaddr_flag, + net_get_reuseaddr_flag, + net_set_reuseport_flag, +}; +import("std::string::len")::{ + len, + is_empty, +}; + +pub struct UdpAddr { ip: i32; // network byte order port: i16; // network byte order } -struct UdpSocket { +pub struct UdpSocket { fd: i64; } -fun _udp_to_net_addr(addr: UdpAddr) -> NetAddrV4 { +pub fun _udp_to_net_addr(addr: UdpAddr) -> NetAddrV4 { var value: NetAddrV4; value.ip = addr.ip; value.port = addr.port; return value; } -fun _udp_from_net_addr(addr: NetAddrV4) -> UdpAddr { +pub fun _udp_from_net_addr(addr: NetAddrV4) -> UdpAddr { var value: UdpAddr; value.ip = addr.ip; value.port = addr.port; return value; } -fun htons(x: i16) -> i16 { +pub fun htons(x: i16) -> i16 { return net_htons(x); } -fun htonl(x: i32) -> i32 { +pub fun htonl(x: i32) -> i32 { return net_htonl(x); } -fun udp_addr(ip_host_order: i32, port_host_order: i16) -> UdpAddr { +pub fun udp_addr(ip_host_order: i32, port_host_order: i16) -> UdpAddr { return _udp_from_net_addr(net_addr_v4(ip_host_order, port_host_order)); } -fun udp_addr_any(port_host_order: i16) -> UdpAddr { +pub fun udp_addr_any(port_host_order: i16) -> UdpAddr { return _udp_from_net_addr(net_addr_any_v4(port_host_order)); } -fun udp_addr_loopback(port_host_order: i16) -> UdpAddr { +pub fun udp_addr_loopback(port_host_order: i16) -> UdpAddr { return _udp_from_net_addr(net_addr_loopback_v4(port_host_order)); } -fun udp_set_reuseaddr(fd: i64) -> i64 { +pub fun udp_set_reuseaddr(fd: i64) -> i64 { return net_set_reuseaddr(fd); } -fun udp_bind(port: i16) -> UdpSocket { +pub fun udp_bind(port: i16) -> UdpSocket { return udp_bind_addr(udp_addr_any(port)); } -fun udp_bind_addr(addr: UdpAddr) -> UdpSocket { +pub fun udp_bind_addr(addr: UdpAddr) -> UdpSocket { var fd: i64 = net_socket_udp_v4(); net_set_reuseaddr(fd); net_bind_v4(fd, _udp_to_net_addr(addr)); return UdpSocket { fd: fd }; } -fun udp_close(sock: UdpSocket) { +pub fun udp_close(sock: UdpSocket) { net_shutdown_close(sock.fd); } -fun udp_send_to( +pub fun udp_send_to( sock: UdpSocket, addr: UdpAddr, buf: ptr, @@ -93,7 +160,7 @@ fun udp_send_to( return net_sendto_v4(sock.fd, _udp_to_net_addr(addr), buf, len, 0); } -fun udp_send_str_to(sock: UdpSocket, addr: UdpAddr, s: str) -> i64 { +pub fun udp_send_str_to(sock: UdpSocket, addr: UdpAddr, s: str) -> i64 { var n: i64 = len(s) as i64; if (n <= 0) { return 0; @@ -101,11 +168,11 @@ fun udp_send_str_to(sock: UdpSocket, addr: UdpAddr, s: str) -> i64 { return udp_send_to(sock, addr, s as ptr, n); } -fun udp_recv(sock: UdpSocket, buf: ptr, len: i64) -> i64 { +pub fun udp_recv(sock: UdpSocket, buf: ptr, len: i64) -> i64 { return recv(sock.fd, buf, len, 0); } -fun udp_recv_from( +pub fun udp_recv_from( sock: UdpSocket, buf: ptr, len: i64, @@ -119,18 +186,18 @@ fun udp_recv_from( return n; } -fun udp_from_fd(fd: i64) -> UdpSocket { +pub fun udp_from_fd(fd: i64) -> UdpSocket { return UdpSocket { fd: fd }; } -fun udp_socket_valid(sock: UdpSocket) -> bool { +pub fun udp_socket_valid(sock: UdpSocket) -> bool { return net_fd_valid(sock.fd); } -fun udp_set_nonblock(sock: UdpSocket, enabled: i32) -> i64 { +pub fun udp_set_nonblock(sock: UdpSocket, enabled: i32) -> i64 { return net_set_nonblock(sock.fd, enabled); } -fun udp_get_nonblock(sock: UdpSocket) -> i64 { +pub fun udp_get_nonblock(sock: UdpSocket) -> i64 { return net_get_nonblock(sock.fd); } diff --git a/std/path/analyze.wave b/std/path/analyze.wave index 9b6bdea6..16407797 100644 --- a/std/path/analyze.wave +++ b/std/path/analyze.wave @@ -16,9 +16,13 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::path::core"); +import("std::path::core")::{ + path_is_sep, + path_len, + path_is_abs, +}; -fun path_basename_start(path: str) -> i32 { +pub fun path_basename_start(path: str) -> i32 { var i: i32 = 0; var base: i32 = 0; @@ -32,14 +36,14 @@ fun path_basename_start(path: str) -> i32 { return base; } -fun path_basename_len(path: str) -> i32 { +pub fun path_basename_len(path: str) -> i32 { var base: i32 = path_basename_start(path); var end: i32 = path_len(path); return end - base; } -fun path_dirname_len(path: str) -> i32 { +pub fun path_dirname_len(path: str) -> i32 { var base: i32 = path_basename_start(path); if (base <= 0) { @@ -59,7 +63,7 @@ fun path_dirname_len(path: str) -> i32 { return i + 1; } -fun path_ext_start(path: str) -> i32 { +pub fun path_ext_start(path: str) -> i32 { var base: i32 = path_basename_start(path); var i: i32 = base; var dot: i32 = -1; @@ -78,7 +82,7 @@ fun path_ext_start(path: str) -> i32 { return dot; } -fun path_has_ext(path: str) -> bool { +pub fun path_has_ext(path: str) -> bool { if (path_ext_start(path) >= 0) { return true; } diff --git a/std/path/consts.wave b/std/path/consts.wave index 3e82e548..6ef0799b 100644 --- a/std/path/consts.wave +++ b/std/path/consts.wave @@ -16,4 +16,4 @@ // // SPDX-License-Identifier: Apache-2.0 -const PATH_ERR_NO_SPACE: i32 = -1; +pub const PATH_ERR_NO_SPACE: i32 = -1; diff --git a/std/path/copy.wave b/std/path/copy.wave index de5c3c9f..331453ce 100644 --- a/std/path/copy.wave +++ b/std/path/copy.wave @@ -16,10 +16,20 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::path::core"); -import("std::path::analyze"); - -fun path_join2(dst: ptr, dst_cap: i32, left: str, right: str) -> i32 { +import("std::path::core")::{ + path_is_sep, + path_len, + path_is_abs, +}; +import("std::path::analyze")::{ + path_basename_start, + path_basename_len, + path_dirname_len, + path_ext_start, + path_has_ext, +}; + +pub fun path_join2(dst: ptr, dst_cap: i32, left: str, right: str) -> i32 { var ll: i32 = path_len(left); var rl: i32 = path_len(right); @@ -64,7 +74,7 @@ fun path_join2(dst: ptr, dst_cap: i32, left: str, right: str) -> i32 { return total; } -fun path_basename_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { +pub fun path_basename_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { var start: i32 = path_basename_start(path); var n: i32 = path_basename_len(path); @@ -84,7 +94,7 @@ fun path_basename_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { return n; } -fun path_dirname_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { +pub fun path_dirname_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { var n: i32 = path_dirname_len(path); if (n + 1 > dst_cap) { diff --git a/std/path/core.wave b/std/path/core.wave index b3d7cbd9..373277c6 100644 --- a/std/path/core.wave +++ b/std/path/core.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun path_is_sep(c: u8) -> bool { +pub fun path_is_sep(c: u8) -> bool { if (c == 47) { return true; } @@ -28,7 +28,7 @@ fun path_is_sep(c: u8) -> bool { return false; } -fun path_len(path: str) -> i32 { +pub fun path_len(path: str) -> i32 { var i: i32 = 0; while (path[i] != 0) { @@ -38,7 +38,7 @@ fun path_len(path: str) -> i32 { return i; } -fun path_is_abs(path: str) -> bool { +pub fun path_is_abs(path: str) -> bool { if (path[0] == 47) { return true; } diff --git a/std/process/consts.wave b/std/process/consts.wave index ae1282bd..29f87359 100644 --- a/std/process/consts.wave +++ b/std/process/consts.wave @@ -16,9 +16,9 @@ // // SPDX-License-Identifier: Apache-2.0 -const PROC_ERR_INTR: i64 = -4; +pub const PROC_ERR_INTR: i64 = -4; -const PROC_WAIT_NOHANG: i32 = 1; +pub const PROC_WAIT_NOHANG: i32 = 1; -const PROC_EXIT_EXEC_FAIL: i32 = 127; -const PROC_EXIT_DUP_FAIL: i32 = 126; +pub const PROC_EXIT_EXEC_FAIL: i32 = 127; +pub const PROC_EXIT_DUP_FAIL: i32 = 126; diff --git a/std/process/core.wave b/std/process/core.wave index 167903b1..a35d34c3 100644 --- a/std/process/core.wave +++ b/std/process/core.wave @@ -16,30 +16,43 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::process::consts"); -import("std::sys::process"); +import("std::process::consts")::{ + PROC_ERR_INTR, + PROC_WAIT_NOHANG, + PROC_EXIT_EXEC_FAIL, + PROC_EXIT_DUP_FAIL, +}; +import("std::sys::process")::{ + exit, + getpid, + getppid, + fork, + execve, + waitpid, + kill, +}; -fun proc_exit(code: i32) -> ! { +pub fun proc_exit(code: i32) -> ! { exit(code); } -fun proc_getpid() -> i64 { +pub fun proc_getpid() -> i64 { return getpid(); } -fun proc_getppid() -> i64 { +pub fun proc_getppid() -> i64 { return getppid(); } -fun proc_fork() -> i64 { +pub fun proc_fork() -> i64 { return fork(); } -fun proc_execve(path: str, argv: ptr>, envp: ptr>) -> i64 { +pub fun proc_execve(path: str, argv: ptr>, envp: ptr>) -> i64 { return execve(path, argv, envp); } -fun proc_waitpid_raw(pid: i64, status: ptr, options: i32) -> i64 { +pub fun proc_waitpid_raw(pid: i64, status: ptr, options: i32) -> i64 { while (true) { var r: i64 = waitpid(pid, status, options); if (r == PROC_ERR_INTR) { @@ -51,6 +64,6 @@ fun proc_waitpid_raw(pid: i64, status: ptr, options: i32) -> i64 { return -1; } -fun proc_kill(pid: i64, sig: i32) -> i64 { +pub fun proc_kill(pid: i64, sig: i32) -> i64 { return kill(pid, sig); } diff --git a/std/process/spawn.wave b/std/process/spawn.wave index e1a4fe51..49037bd7 100644 --- a/std/process/spawn.wave +++ b/std/process/spawn.wave @@ -16,24 +16,57 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::io::consts"); -import("std::io::fd"); -import("std::process::consts"); -import("std::process::core"); - -struct ProcPipeResult { +import("std::io::consts")::{ + IO_STDIN_FD, + IO_STDOUT_FD, + IO_STDERR_FD, + IO_ERR_INVALID, + IO_ERR_EOF, + IO_ERR_NO_SPACE, + IO_ERR_INTR, +}; +import("std::io::fd")::{ + io_close, + io_seek, + io_read, + io_write, + io_write_all, + io_read_exact, + io_read_at_most, + io_copy, + io_pipe, + io_dup, + io_dup2, +}; +import("std::process::consts")::{ + PROC_ERR_INTR, + PROC_WAIT_NOHANG, + PROC_EXIT_EXEC_FAIL, + PROC_EXIT_DUP_FAIL, +}; +import("std::process::core")::{ + proc_exit, + proc_getpid, + proc_getppid, + proc_fork, + proc_execve, + proc_waitpid_raw, + proc_kill, +}; + +pub struct ProcPipeResult { status: i64; read_fd: i64; write_fd: i64; } -struct ProcSpawnStdoutResult { +pub struct ProcSpawnStdoutResult { status: i64; pid: i64; read_fd: i64; } -fun _proc_dup_child_fd(src_fd: i64, dst_fd: i64) -> i64 { +pub fun _proc_dup_child_fd(src_fd: i64, dst_fd: i64) -> i64 { if (src_fd < 0) { return 0; } @@ -51,7 +84,7 @@ fun _proc_dup_child_fd(src_fd: i64, dst_fd: i64) -> i64 { return 0; } -fun proc_spawn_exec_raw( +pub fun proc_spawn_exec_raw( path: str, argv: ptr>, envp: ptr>, @@ -88,11 +121,11 @@ fun proc_spawn_exec_raw( return pid; } -fun proc_spawn(path: str, argv: ptr>, envp: ptr>) -> i64 { +pub fun proc_spawn(path: str, argv: ptr>, envp: ptr>) -> i64 { return proc_spawn_exec_raw(path, argv, envp, -1, -1, -1); } -fun proc_make_pipe() -> ProcPipeResult { +pub fun proc_make_pipe() -> ProcPipeResult { var fds: array; var r: i64 = io_pipe(&fds[0]); if (r < 0) { @@ -110,7 +143,7 @@ fun proc_make_pipe() -> ProcPipeResult { }; } -fun proc_spawn_capture_stdout( +pub fun proc_spawn_capture_stdout( path: str, argv: ptr>, envp: ptr> diff --git a/std/process/wait.wave b/std/process/wait.wave index f506a1f9..31a0b471 100644 --- a/std/process/wait.wave +++ b/std/process/wait.wave @@ -16,18 +16,31 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::process::consts"); -import("std::process::core"); +import("std::process::consts")::{ + PROC_ERR_INTR, + PROC_WAIT_NOHANG, + PROC_EXIT_EXEC_FAIL, + PROC_EXIT_DUP_FAIL, +}; +import("std::process::core")::{ + proc_exit, + proc_getpid, + proc_getppid, + proc_fork, + proc_execve, + proc_waitpid_raw, + proc_kill, +}; -fun proc_wait(pid: i64, out_status: ptr) -> i64 { +pub fun proc_wait(pid: i64, out_status: ptr) -> i64 { return proc_waitpid_raw(pid, out_status, 0); } -fun proc_wait_nohang(pid: i64, out_status: ptr) -> i64 { +pub fun proc_wait_nohang(pid: i64, out_status: ptr) -> i64 { return proc_waitpid_raw(pid, out_status, PROC_WAIT_NOHANG); } -fun proc_status_exited(status: i32) -> bool { +pub fun proc_status_exited(status: i32) -> bool { if ((status & 127) == 0) { return true; } @@ -35,11 +48,11 @@ fun proc_status_exited(status: i32) -> bool { return false; } -fun proc_status_exit_code(status: i32) -> i32 { +pub fun proc_status_exit_code(status: i32) -> i32 { return (status >> 8) & 255; } -fun proc_status_signaled(status: i32) -> bool { +pub fun proc_status_signaled(status: i32) -> bool { var sig: i32 = status & 127; if (sig != 0 && sig != 127) { return true; @@ -48,11 +61,11 @@ fun proc_status_signaled(status: i32) -> bool { return false; } -fun proc_status_term_signal(status: i32) -> i32 { +pub fun proc_status_term_signal(status: i32) -> i32 { return status & 127; } -fun proc_status_stopped(status: i32) -> bool { +pub fun proc_status_stopped(status: i32) -> bool { if ((status & 255) == 127) { return true; } @@ -60,11 +73,11 @@ fun proc_status_stopped(status: i32) -> bool { return false; } -fun proc_status_stop_signal(status: i32) -> i32 { +pub fun proc_status_stop_signal(status: i32) -> i32 { return (status >> 8) & 255; } -fun proc_status_continued(status: i32) -> bool { +pub fun proc_status_continued(status: i32) -> bool { if (status == 65535) { return true; } diff --git a/std/string/ascii.wave b/std/string/ascii.wave index 6222a285..79bba315 100644 --- a/std/string/ascii.wave +++ b/std/string/ascii.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun is_digit(c: u8) -> bool { +pub fun is_digit(c: u8) -> bool { if (c >= 48 && c <= 57) { return true; } @@ -24,7 +24,7 @@ fun is_digit(c: u8) -> bool { return false; } -fun is_lower(c: u8) -> bool { +pub fun is_lower(c: u8) -> bool { if (c >= 97 && c <= 122) { return true; } @@ -32,7 +32,7 @@ fun is_lower(c: u8) -> bool { return false; } -fun is_upper(c: u8) -> bool { +pub fun is_upper(c: u8) -> bool { if (c >= 65 && c <= 90) { return true; } @@ -40,7 +40,7 @@ fun is_upper(c: u8) -> bool { return false; } -fun is_alpha(c: u8) -> bool { +pub fun is_alpha(c: u8) -> bool { if (is_lower(c)) { return true; } @@ -52,7 +52,7 @@ fun is_alpha(c: u8) -> bool { return false; } -fun is_alnum(c: u8) -> bool { +pub fun is_alnum(c: u8) -> bool { if (is_alpha(c)) { return true; } @@ -64,7 +64,7 @@ fun is_alnum(c: u8) -> bool { return false; } -fun is_space(c: u8) -> bool { +pub fun is_space(c: u8) -> bool { if (c == 32) { return true; } @@ -84,7 +84,7 @@ fun is_space(c: u8) -> bool { return false; } -fun to_lower(c: u8) -> u8 { +pub fun to_lower(c: u8) -> u8 { if (is_upper(c)) { return c + 32; } @@ -92,7 +92,7 @@ fun to_lower(c: u8) -> u8 { return c; } -fun to_upper(c: u8) -> u8 { +pub fun to_upper(c: u8) -> u8 { if (is_lower(c)) { return c - 32; } diff --git a/std/string/cmp.wave b/std/string/cmp.wave index 0552d439..89b51371 100644 --- a/std/string/cmp.wave +++ b/std/string/cmp.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun eq(a: str, b: str) -> bool { +pub fun eq(a: str, b: str) -> bool { var i: i32 = 0; while (true) { @@ -37,7 +37,7 @@ fun eq(a: str, b: str) -> bool { return true; } -fun cmp(a: str, b: str) -> i32 { +pub fun cmp(a: str, b: str) -> i32 { var i: i32 = 0; while (true) { @@ -62,7 +62,7 @@ fun cmp(a: str, b: str) -> i32 { return 0; } -fun starts_with(s: str, prefix: str) -> bool { +pub fun starts_with(s: str, prefix: str) -> bool { var i: i32 = 0; while (prefix[i] != 0) { @@ -76,7 +76,7 @@ fun starts_with(s: str, prefix: str) -> bool { return true; } -fun ends_with(s: str, suffix: str) -> bool { +pub fun ends_with(s: str, suffix: str) -> bool { var sl: i32 = 0; while (s[sl] != 0) { sl += 1; diff --git a/std/string/find.wave b/std/string/find.wave index 6ca85e09..f57b6a39 100644 --- a/std/string/find.wave +++ b/std/string/find.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun find_char(s: str, c: u8) -> i32 { +pub fun find_char(s: str, c: u8) -> i32 { var i: i32 = 0; while (s[i] != 0) { @@ -30,7 +30,7 @@ fun find_char(s: str, c: u8) -> i32 { return -1; } -fun rfind_char(s: str, c: u8) -> i32 { +pub fun rfind_char(s: str, c: u8) -> i32 { var last: i32 = -1; var i: i32 = 0; @@ -45,7 +45,7 @@ fun rfind_char(s: str, c: u8) -> i32 { return last; } -fun contains_char(s: str, c: u8) -> bool { +pub fun contains_char(s: str, c: u8) -> bool { if (find_char(s, c) != -1) { return true; } @@ -53,7 +53,7 @@ fun contains_char(s: str, c: u8) -> bool { return false; } -fun count_char(s: str, c: u8) -> i32 { +pub fun count_char(s: str, c: u8) -> i32 { var i: i32 = 0; var count: i32 = 0; @@ -68,7 +68,7 @@ fun count_char(s: str, c: u8) -> i32 { return count; } -fun find(s: str, needle: str) -> i32 { +pub fun find(s: str, needle: str) -> i32 { if (needle[0] == 0) { return 0; } @@ -112,7 +112,7 @@ fun find(s: str, needle: str) -> i32 { return -1; } -fun contains(s: str, needle: str) -> bool { +pub fun contains(s: str, needle: str) -> bool { if (find(s, needle) != -1) { return true; } @@ -120,7 +120,7 @@ fun contains(s: str, needle: str) -> bool { return false; } -fun count(s: str, needle: str) -> i32 { +pub fun count(s: str, needle: str) -> i32 { if (needle[0] == 0) { return 0; } diff --git a/std/string/hash.wave b/std/string/hash.wave index 247b8ed4..cddeb73b 100644 --- a/std/string/hash.wave +++ b/std/string/hash.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun djb2_32(s: str) -> i32 { +pub fun djb2_32(s: str) -> i32 { var h: i32 = 5381; var i: i32 = 0; @@ -29,7 +29,7 @@ fun djb2_32(s: str) -> i32 { return h; } -fun fnv1a_64(s: str) -> i64 { +pub fun fnv1a_64(s: str) -> i64 { var h: i64 = 1469598103934665603; var i: i32 = 0; diff --git a/std/string/len.wave b/std/string/len.wave index bc8de005..61d1ac7e 100644 --- a/std/string/len.wave +++ b/std/string/len.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun len(s: str) -> i32 { +pub fun len(s: str) -> i32 { var i: i32 = 0; while (s[i] != 0) { i += 1; @@ -24,7 +24,7 @@ fun len(s: str) -> i32 { return i; } -fun is_empty(s: str) -> bool { +pub fun is_empty(s: str) -> bool { if (s[0] == 0) { return true; } diff --git a/std/string/trim.wave b/std/string/trim.wave index 10d26ea4..a157f0b1 100644 --- a/std/string/trim.wave +++ b/std/string/trim.wave @@ -16,7 +16,7 @@ // // SPDX-License-Identifier: Apache-2.0 -fun trim_left_index(s: str) -> i32 { +pub fun trim_left_index(s: str) -> i32 { var i: i32 = 0; while (s[i] != 0) { @@ -32,7 +32,7 @@ fun trim_left_index(s: str) -> i32 { return i; } -fun trim_right_index(s: str) -> i32 { +pub fun trim_right_index(s: str) -> i32 { var sl: i32 = 0; while (s[sl] != 0) { @@ -58,7 +58,7 @@ fun trim_right_index(s: str) -> i32 { return 0; } -fun trim_range(s: str, out_start: ptr, out_end: ptr) { +pub fun trim_range(s: str, out_start: ptr, out_end: ptr) { var start: i32 = trim_left_index(s); var end: i32 = trim_right_index(s); diff --git a/std/sys/env.wave b/std/sys/env.wave index fb01c5ba..b8e4032c 100644 --- a/std/sys/env.wave +++ b/std/sys/env.wave @@ -21,7 +21,12 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::env"); +pub import("std::sys::linux::env")::{ + env_read, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::env"); +pub import("std::sys::macos::env")::{ + ERR_NOT_IMPLEMENTED, + env_read, +}; diff --git a/std/sys/fs.wave b/std/sys/fs.wave index e4610ccc..9c9328ad 100644 --- a/std/sys/fs.wave +++ b/std/sys/fs.wave @@ -25,7 +25,81 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::fs"); +pub import("std::sys::linux::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::fs"); +pub import("std::sys::macos::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_NONBLOCK, + FS_O_APPEND, + FS_O_CREAT, + FS_O_TRUNC, + FS_O_EXCL, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + Stat, + open, + close, + dup, + pipe, + dup2, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + stat, + fstat, +}; diff --git a/std/sys/linux/env.wave b/std/sys/linux/env.wave index dcee8c19..9d8cd73e 100644 --- a/std/sys/linux/env.wave +++ b/std/sys/linux/env.wave @@ -16,9 +16,46 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::linux::fs"); +import("std::sys::linux::fs")::{ + FS_O_RDONLY, + FS_O_WRONLY, + FS_O_RDWR, + FS_O_CREAT, + FS_O_EXCL, + FS_O_TRUNC, + FS_O_APPEND, + FS_O_NONBLOCK, + FS_F_OK, + FS_X_OK, + FS_W_OK, + FS_R_OK, + FS_SEEK_SET, + FS_SEEK_CUR, + FS_SEEK_END, + FS_F_GETFL, + FS_F_SETFL, + open, + close, + dup, + dup2, + pipe, + fsync, + fcntl, + read, + write, + getcwd, + chdir, + access, + lseek, + unlink, + mkdir, + rmdir, + Stat, + stat, + fstat, +}; -fun env_read(buf: ptr, cap: i64) -> i64 { +pub fun env_read(buf: ptr, cap: i64) -> i64 { if (cap <= 0) { return -22; } diff --git a/std/sys/linux/fs.wave b/std/sys/linux/fs.wave index 7b919980..07685809 100644 --- a/std/sys/linux/fs.wave +++ b/std/sys/linux/fs.wave @@ -27,68 +27,76 @@ // Negative values indicate -errno. // ======================================================= -import("std::sys::linux::syscall"); +import("std::sys::linux::syscall")::{ + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; // open flags -const FS_O_RDONLY: i32 = 0; -const FS_O_WRONLY: i32 = 1; -const FS_O_RDWR: i32 = 2; -const FS_O_CREAT: i32 = 64; -const FS_O_EXCL: i32 = 128; -const FS_O_TRUNC: i32 = 512; -const FS_O_APPEND: i32 = 1024; -const FS_O_NONBLOCK: i32 = 2048; +pub const FS_O_RDONLY: i32 = 0; +pub const FS_O_WRONLY: i32 = 1; +pub const FS_O_RDWR: i32 = 2; +pub const FS_O_CREAT: i32 = 64; +pub const FS_O_EXCL: i32 = 128; +pub const FS_O_TRUNC: i32 = 512; +pub const FS_O_APPEND: i32 = 1024; +pub const FS_O_NONBLOCK: i32 = 2048; // access modes -const FS_F_OK: i32 = 0; -const FS_X_OK: i32 = 1; -const FS_W_OK: i32 = 2; -const FS_R_OK: i32 = 4; +pub const FS_F_OK: i32 = 0; +pub const FS_X_OK: i32 = 1; +pub const FS_W_OK: i32 = 2; +pub const FS_R_OK: i32 = 4; // seek modes -const FS_SEEK_SET: i32 = 0; -const FS_SEEK_CUR: i32 = 1; -const FS_SEEK_END: i32 = 2; +pub const FS_SEEK_SET: i32 = 0; +pub const FS_SEEK_CUR: i32 = 1; +pub const FS_SEEK_END: i32 = 2; // fcntl modes -const FS_F_GETFL: i32 = 3; -const FS_F_SETFL: i32 = 4; +pub const FS_F_GETFL: i32 = 3; +pub const FS_F_SETFL: i32 = 4; // ----------------------- // open / close // ----------------------- -fun open(path: str, flags: i32, mode: i32) -> i64 { +pub fun open(path: str, flags: i32, mode: i32) -> i64 { // syscall: open (2) return syscall3(2, path as i64, flags as i64, mode as i64); } -fun close(fd: i64) -> i64 { +pub fun close(fd: i64) -> i64 { // syscall: close (3) return syscall1(3, fd); } -fun dup(fd: i64) -> i64 { +pub fun dup(fd: i64) -> i64 { // syscall: dup (32) return syscall1(32, fd); } -fun dup2(oldfd: i64, newfd: i64) -> i64 { +pub fun dup2(oldfd: i64, newfd: i64) -> i64 { // syscall: dup2 (33) return syscall2(33, oldfd, newfd); } -fun pipe(fds: ptr) -> i64 { +pub fun pipe(fds: ptr) -> i64 { // syscall: pipe (22) return syscall1(22, fds as i64); } -fun fsync(fd: i64) -> i64 { +pub fun fsync(fd: i64) -> i64 { // syscall: fsync (74) return syscall1(74, fd); } -fun fcntl(fd: i64, cmd: i32, arg: i64) -> i64 { +pub fun fcntl(fd: i64, cmd: i32, arg: i64) -> i64 { // syscall: fcntl (72) return syscall3(72, fd, cmd as i64, arg); } @@ -98,27 +106,27 @@ fun fcntl(fd: i64, cmd: i32, arg: i64) -> i64 { // read / write // ----------------------- -fun read(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun read(fd: i64, buf: ptr, len: i64) -> i64 { // syscall: read (0) return syscall3(0, fd, buf as i64, len); } -fun write(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun write(fd: i64, buf: ptr, len: i64) -> i64 { // syscall: write (1) return syscall3(1, fd, buf as i64, len); } -fun getcwd(buf: ptr, size: i64) -> i64 { +pub fun getcwd(buf: ptr, size: i64) -> i64 { // syscall: getcwd (79) return syscall2(79, buf as i64, size); } -fun chdir(path: str) -> i64 { +pub fun chdir(path: str) -> i64 { // syscall: chdir (80) return syscall1(80, path as i64); } -fun access(path: str, mode: i32) -> i64 { +pub fun access(path: str, mode: i32) -> i64 { // syscall: access (21) return syscall2(21, path as i64, mode as i64); } @@ -128,7 +136,7 @@ fun access(path: str, mode: i32) -> i64 { // seek // ----------------------- -fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { +pub fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { // syscall: lseek (8) return syscall3(8, fd, offset, whence as i64); } @@ -138,17 +146,17 @@ fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { // file operations // ----------------------- -fun unlink(path: str) -> i64 { +pub fun unlink(path: str) -> i64 { // syscall: unlink (87) return syscall1(87, path as i64); } -fun mkdir(path: str, mode: i32) -> i64 { +pub fun mkdir(path: str, mode: i32) -> i64 { // syscall: mkdir (83) return syscall2(83, path as i64, mode as i64); } -fun rmdir(path: str) -> i64 { +pub fun rmdir(path: str) -> i64 { // syscall: rmdir (84) return syscall1(84, path as i64); } @@ -158,7 +166,7 @@ fun rmdir(path: str) -> i64 { // metadata // ----------------------- -struct Stat { +pub struct Stat { dev: i64; ino: i64; nlink: i64; @@ -175,12 +183,12 @@ struct Stat { ctime: i64; } -fun stat(path: str, st: ptr) -> i64 { +pub fun stat(path: str, st: ptr) -> i64 { // syscall: stat (4) return syscall2(4, path as i64, st as i64); } -fun fstat(fd: i64, st: ptr) -> i64 { +pub fun fstat(fd: i64, st: ptr) -> i64 { // syscall: fstat (5) return syscall2(5, fd, st as i64); } diff --git a/std/sys/linux/memory.wave b/std/sys/linux/memory.wave index 71fb5f68..230f6f2e 100644 --- a/std/sys/linux/memory.wave +++ b/std/sys/linux/memory.wave @@ -25,19 +25,27 @@ // Negative values indicate -errno. // ======================================================= -import("std::sys::linux::syscall"); +import("std::sys::linux::syscall")::{ + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; -const PROT_READ: i32 = 1; -const PROT_WRITE: i32 = 2; -const MAP_PRIVATE: i32 = 2; -const MAP_ANONYMOUS: i32 = 32; +pub const PROT_READ: i32 = 1; +pub const PROT_WRITE: i32 = 2; +pub const MAP_PRIVATE: i32 = 2; +pub const MAP_ANONYMOUS: i32 = 32; // ----------------------- // mmap / munmap // ----------------------- // 3b1b9d23-75fc-4c50-87a4-11865458c2ba -fun mmap( +pub fun mmap( addr: ptr, length: i64, prot: i32, @@ -57,7 +65,7 @@ fun mmap( ) as ptr; } -fun munmap(addr: ptr, length: i64) -> i64 { +pub fun munmap(addr: ptr, length: i64) -> i64 { // syscall: munmap (11) return syscall2(11, addr as i64, length); } @@ -67,12 +75,12 @@ fun munmap(addr: ptr, length: i64) -> i64 { // brk // ----------------------- -fun brk(addr: ptr) -> ptr { +pub fun brk(addr: ptr) -> ptr { // syscall: brk (12) return syscall1(12, addr as i64) as ptr; } -fun sys_alloc(size: i64) -> ptr { +pub fun sys_alloc(size: i64) -> ptr { if (size <= 0) { return null; } @@ -93,7 +101,7 @@ fun sys_alloc(size: i64) -> ptr { return p as ptr; } -fun sys_free(p: ptr, size: i64) -> i64 { +pub fun sys_free(p: ptr, size: i64) -> i64 { if (p == null || size <= 0) { return 0; } diff --git a/std/sys/linux/process.wave b/std/sys/linux/process.wave index 8e5ba6cd..ef47d7a7 100644 --- a/std/sys/linux/process.wave +++ b/std/sys/linux/process.wave @@ -25,25 +25,33 @@ // Negative values indicate -errno. // ======================================================= -import("std::sys::linux::syscall"); +import("std::sys::linux::syscall")::{ + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; // ----------------------- // process lifecycle // ----------------------- -fun exit(code: i32) -> ! { +pub fun exit(code: i32) -> ! { // syscall: exit (60) syscall1(60, code as i64); while (true) { } } -fun getpid() -> i64 { +pub fun getpid() -> i64 { // syscall: getpid (39) return syscall0(39); } -fun getppid() -> i64 { +pub fun getppid() -> i64 { // syscall: getppid (110) return syscall0(110); } @@ -53,12 +61,12 @@ fun getppid() -> i64 { // process creation // ----------------------- -fun fork() -> i64 { +pub fun fork() -> i64 { // syscall: fork (57) return syscall0(57); } -fun execve( +pub fun execve( path: str, argv: ptr>, envp: ptr> @@ -72,7 +80,7 @@ fun execve( // waiting // ----------------------- -fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { +pub fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { // syscall: wait4 (61) // waitpid is implemented via wait4 return syscall4(61, pid, status as i64, options as i64, 0); @@ -83,7 +91,7 @@ fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { // signals // ----------------------- -fun kill(pid: i64, sig: i32) -> i64 { +pub fun kill(pid: i64, sig: i32) -> i64 { // syscall: kill (62) return syscall2(62, pid, sig as i64); } diff --git a/std/sys/linux/socket.wave b/std/sys/linux/socket.wave index 3f028062..da4aad6d 100644 --- a/std/sys/linux/socket.wave +++ b/std/sys/linux/socket.wave @@ -25,7 +25,15 @@ // No protocol abstraction (TCP/UDP) here. // ======================================================= -import("std::sys::linux::syscall"); +import("std::sys::linux::syscall")::{ + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; // ----------------------- @@ -33,36 +41,36 @@ import("std::sys::linux::syscall"); // ----------------------- // address families -const AF_INET: i32 = 2; -const AF_INET6: i32 = 10; +pub const AF_INET: i32 = 2; +pub const AF_INET6: i32 = 10; // socket types -const SOCK_STREAM: i32 = 1; -const SOCK_DGRAM: i32 = 2; +pub const SOCK_STREAM: i32 = 1; +pub const SOCK_DGRAM: i32 = 2; // protocol -const IPPROTO_IP: i32 = 0; -const IPPROTO_TCP: i32 = 6; -const IPPROTO_UDP: i32 = 17; +pub const IPPROTO_IP: i32 = 0; +pub const IPPROTO_TCP: i32 = 6; +pub const IPPROTO_UDP: i32 = 17; // shutdown how -const SHUT_RD: i32 = 0; -const SHUT_WR: i32 = 1; -const SHUT_RDWR: i32 = 2; +pub const SHUT_RD: i32 = 0; +pub const SHUT_WR: i32 = 1; +pub const SHUT_RDWR: i32 = 2; // socket options -const SOL_SOCKET: i32 = 1; -const SO_REUSEADDR: i32 = 2; -const SO_REUSEPORT: i32 = 15; +pub const SOL_SOCKET: i32 = 1; +pub const SO_REUSEADDR: i32 = 2; +pub const SO_REUSEPORT: i32 = 15; // poll events -const POLLIN: i16 = 0x001; -const POLLOUT: i16 = 0x004; -const POLLERR: i16 = 0x008; -const POLLHUP: i16 = 0x010; -const POLLNVAL: i16 = 0x020; +pub const POLLIN: i16 = 0x001; +pub const POLLOUT: i16 = 0x004; +pub const POLLERR: i16 = 0x008; +pub const POLLHUP: i16 = 0x010; +pub const POLLNVAL: i16 = 0x020; -struct PollFd { +pub struct PollFd { fd: i32; events: i16; revents: i16; @@ -73,37 +81,37 @@ struct PollFd { // basic socket syscalls // ----------------------- -fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { +pub fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { // syscall: socket (41) return syscall3(41, domain as i64, ty as i64, protocol as i64); } -fun bind(fd: i64, addr: ptr, len: i32) -> i64 { +pub fun bind(fd: i64, addr: ptr, len: i32) -> i64 { // syscall: bind (49) return syscall3(49, fd, addr as i64, len as i64); } -fun listen(fd: i64, backlog: i32) -> i64 { +pub fun listen(fd: i64, backlog: i32) -> i64 { // syscall: listen (50) return syscall2(50, fd, backlog as i64); } -fun accept(fd: i64, addr: ptr, len: ptr) -> i64 { +pub fun accept(fd: i64, addr: ptr, len: ptr) -> i64 { // syscall: accept (43) return syscall3(43, fd, addr as i64, len as i64); } -fun connect(fd: i64, addr: ptr, len: i32) -> i64 { +pub fun connect(fd: i64, addr: ptr, len: i32) -> i64 { // syscall: connect (42) return syscall3(42, fd, addr as i64, len as i64); } -fun shutdown(fd: i64, how: i32) -> i64 { +pub fun shutdown(fd: i64, how: i32) -> i64 { // syscall: shutdown (48) return syscall2(48, fd, how as i64); } -fun setsockopt( +pub fun setsockopt( fd: i64, level: i32, optname: i32, @@ -121,7 +129,7 @@ fun setsockopt( ); } -fun getsockopt( +pub fun getsockopt( fd: i64, level: i32, optname: i32, @@ -139,7 +147,7 @@ fun getsockopt( ); } -fun poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { +pub fun poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { // syscall: poll (7) return syscall3(7, fds as i64, nfds, timeout_ms as i64); } @@ -149,15 +157,15 @@ fun poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { // send / recv // ----------------------- -fun send(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { +pub fun send(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return sendto(fd, buf, len, flags, null, 0); } -fun recv(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { +pub fun recv(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return recvfrom(fd, buf, len, flags, null, null); } -fun sendto( +pub fun sendto( fd: i64, buf: ptr, len: i64, @@ -177,7 +185,7 @@ fun sendto( ); } -fun recvfrom( +pub fun recvfrom( fd: i64, buf: ptr, len: i64, diff --git a/std/sys/linux/syscall.wave b/std/sys/linux/syscall.wave index c0ad0da6..cadf5fc2 100644 --- a/std/sys/linux/syscall.wave +++ b/std/sys/linux/syscall.wave @@ -32,7 +32,7 @@ // ----------------------- // syscall with 0 args // ----------------------- -fun syscall0(id: i64) -> i64 { +pub fun syscall0(id: i64) -> i64 { var ret: i64; asm { "syscall" @@ -46,7 +46,7 @@ fun syscall0(id: i64) -> i64 { // ----------------------- // syscall with 1 arg // ----------------------- -fun syscall1(id: i64, a1: i64) -> i64 { +pub fun syscall1(id: i64, a1: i64) -> i64 { var ret: i64; asm { "syscall" @@ -61,7 +61,7 @@ fun syscall1(id: i64, a1: i64) -> i64 { // ----------------------- // syscall with 2 args // ----------------------- -fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { +pub fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { var ret: i64; asm { "syscall" @@ -77,7 +77,7 @@ fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { // ----------------------- // syscall with 3 args // ----------------------- -fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { +pub fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { var ret: i64; asm { "syscall" @@ -94,7 +94,7 @@ fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { // ----------------------- // syscall with 4 args // ----------------------- -fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { +pub fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { var ret: i64; asm { "syscall" @@ -112,7 +112,7 @@ fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { // ----------------------- // syscall with 5 args // ----------------------- -fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { +pub fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { var ret: i64; asm { "syscall" @@ -131,7 +131,7 @@ fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { // ----------------------- // syscall with 6 args // ----------------------- -fun syscall6( +pub fun syscall6( id: i64, a1: i64, a2: i64, diff --git a/std/sys/linux/time.wave b/std/sys/linux/time.wave index f17dc46f..c04ba3d8 100644 --- a/std/sys/linux/time.wave +++ b/std/sys/linux/time.wave @@ -25,14 +25,22 @@ // Negative values indicate -errno. // ======================================================= -import("std::sys::linux::syscall"); +import("std::sys::linux::syscall")::{ + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; // ----------------------- // timespec // ----------------------- -struct TimeSpec { +pub struct TimeSpec { sec: i64; nsec: i64; } @@ -42,7 +50,7 @@ struct TimeSpec { // nanosleep // ----------------------- -fun nanosleep(req: ptr, rem: ptr) -> i64 { +pub fun nanosleep(req: ptr, rem: ptr) -> i64 { // syscall: nanosleep (35) return syscall2(35, req as i64, rem as i64); } @@ -52,7 +60,7 @@ fun nanosleep(req: ptr, rem: ptr) -> i64 { // clock_gettime // ----------------------- -fun clock_gettime(clock_id: i32, tp: ptr) -> i64 { +pub fun clock_gettime(clock_id: i32, tp: ptr) -> i64 { // syscall: clock_gettime (228) return syscall2(228, clock_id as i64, tp as i64); } diff --git a/std/sys/linux/tty.wave b/std/sys/linux/tty.wave index 06fb49d2..2c63135e 100644 --- a/std/sys/linux/tty.wave +++ b/std/sys/linux/tty.wave @@ -24,30 +24,38 @@ // No libc dependency. // ======================================================= -import("std::sys::linux::syscall"); - -const TTY_SYS_IOCTL: i64 = 16; -const TTY_SYS_FCNTL: i64 = 72; - -const TTY_TCGETS: i64 = 0x5401; -const TTY_TCSETS: i64 = 0x5402; -const TTY_TCSETSW: i64 = 0x5403; -const TTY_TCSETSF: i64 = 0x5404; - -const TTY_TCSANOW: i32 = 0; -const TTY_TCSADRAIN: i32 = 1; -const TTY_TCSAFLUSH: i32 = 2; - -const TTY_F_GETFL: i32 = 3; -const TTY_F_SETFL: i32 = 4; -const TTY_O_NONBLOCK: i32 = 2048; - -const TTY_ICANON: u32 = 2; -const TTY_ECHO: u32 = 8; -const TTY_VTIME_IDX: i32 = 5; -const TTY_VMIN_IDX: i32 = 6; - -struct Termios { +import("std::sys::linux::syscall")::{ + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; + +pub const TTY_SYS_IOCTL: i64 = 16; +pub const TTY_SYS_FCNTL: i64 = 72; + +pub const TTY_TCGETS: i64 = 0x5401; +pub const TTY_TCSETS: i64 = 0x5402; +pub const TTY_TCSETSW: i64 = 0x5403; +pub const TTY_TCSETSF: i64 = 0x5404; + +pub const TTY_TCSANOW: i32 = 0; +pub const TTY_TCSADRAIN: i32 = 1; +pub const TTY_TCSAFLUSH: i32 = 2; + +pub const TTY_F_GETFL: i32 = 3; +pub const TTY_F_SETFL: i32 = 4; +pub const TTY_O_NONBLOCK: i32 = 2048; + +pub const TTY_ICANON: u32 = 2; +pub const TTY_ECHO: u32 = 8; +pub const TTY_VTIME_IDX: i32 = 5; +pub const TTY_VMIN_IDX: i32 = 6; + +pub struct Termios { c_iflag: u32; c_oflag: u32; c_cflag: u32; @@ -58,17 +66,17 @@ struct Termios { c_ospeed: u32; } -struct TtyRawState { +pub struct TtyRawState { term: Termios; flags: i32; active: i32; } -fun tty_getattr(fd: i32, t: ptr) -> i64 { +pub fun tty_getattr(fd: i32, t: ptr) -> i64 { return syscall3(TTY_SYS_IOCTL, fd as i64, TTY_TCGETS, t as i64); } -fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { +pub fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { var req: i64 = TTY_TCSETS; if (action == TTY_TCSADRAIN) { @@ -80,15 +88,15 @@ fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { return syscall3(TTY_SYS_IOCTL, fd as i64, req, t as i64); } -fun tty_getfl(fd: i32) -> i64 { +pub fun tty_getfl(fd: i32) -> i64 { return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_GETFL as i64, 0); } -fun tty_setfl(fd: i32, flags: i32) -> i64 { +pub fun tty_setfl(fd: i32, flags: i32) -> i64 { return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_SETFL as i64, flags as i64); } -fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { +pub fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { deref st.active = 0; var orig: Termios; @@ -123,7 +131,7 @@ fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { return 0; } -fun tty_restore(fd: i32, st: ptr) { +pub fun tty_restore(fd: i32, st: ptr) { if (deref st.active == 0) { return; } diff --git a/std/sys/macos/env.wave b/std/sys/macos/env.wave index 0d02880e..6943b1eb 100644 --- a/std/sys/macos/env.wave +++ b/std/sys/macos/env.wave @@ -17,9 +17,9 @@ // SPDX-License-Identifier: Apache-2.0 // ENOSYS-style return for not implemented path. -const ERR_NOT_IMPLEMENTED: i64 = -38; +pub const ERR_NOT_IMPLEMENTED: i64 = -38; -fun env_read(buf: ptr, cap: i64) -> i64 { +pub fun env_read(buf: ptr, cap: i64) -> i64 { if (buf == null || cap <= 0) { return -22; } diff --git a/std/sys/macos/fs.wave b/std/sys/macos/fs.wave index f3fd3b15..de7e3c99 100644 --- a/std/sys/macos/fs.wave +++ b/std/sys/macos/fs.wave @@ -20,34 +20,43 @@ // macOS filesystem syscalls // ======================================================= -import("std::sys::macos::syscall"); +import("std::sys::macos::syscall")::{ + MACOS_SYSCALL_BASE, + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; // open flags -const FS_O_RDONLY: i32 = 0; -const FS_O_WRONLY: i32 = 1; -const FS_O_RDWR: i32 = 2; -const FS_O_NONBLOCK: i32 = 4; -const FS_O_APPEND: i32 = 8; -const FS_O_CREAT: i32 = 512; -const FS_O_TRUNC: i32 = 1024; -const FS_O_EXCL: i32 = 2048; +pub const FS_O_RDONLY: i32 = 0; +pub const FS_O_WRONLY: i32 = 1; +pub const FS_O_RDWR: i32 = 2; +pub const FS_O_NONBLOCK: i32 = 4; +pub const FS_O_APPEND: i32 = 8; +pub const FS_O_CREAT: i32 = 512; +pub const FS_O_TRUNC: i32 = 1024; +pub const FS_O_EXCL: i32 = 2048; // access modes -const FS_F_OK: i32 = 0; -const FS_X_OK: i32 = 1; -const FS_W_OK: i32 = 2; -const FS_R_OK: i32 = 4; +pub const FS_F_OK: i32 = 0; +pub const FS_X_OK: i32 = 1; +pub const FS_W_OK: i32 = 2; +pub const FS_R_OK: i32 = 4; // seek modes -const FS_SEEK_SET: i32 = 0; -const FS_SEEK_CUR: i32 = 1; -const FS_SEEK_END: i32 = 2; +pub const FS_SEEK_SET: i32 = 0; +pub const FS_SEEK_CUR: i32 = 1; +pub const FS_SEEK_END: i32 = 2; // fcntl modes -const FS_F_GETFL: i32 = 3; -const FS_F_SETFL: i32 = 4; +pub const FS_F_GETFL: i32 = 3; +pub const FS_F_SETFL: i32 = 4; -struct Stat { +pub struct Stat { dev: i64; ino: i64; nlink: i64; @@ -64,74 +73,74 @@ struct Stat { ctime: i64; } -fun open(path: str, flags: i32, mode: i32) -> i64 { +pub fun open(path: str, flags: i32, mode: i32) -> i64 { return syscall3(5, path as i64, flags as i64, mode as i64); } -fun close(fd: i64) -> i64 { +pub fun close(fd: i64) -> i64 { return syscall1(6, fd); } -fun dup(fd: i64) -> i64 { +pub fun dup(fd: i64) -> i64 { return syscall1(41, fd); } -fun pipe(fds: ptr) -> i64 { +pub fun pipe(fds: ptr) -> i64 { return syscall1(42, fds as i64); } -fun dup2(oldfd: i64, newfd: i64) -> i64 { +pub fun dup2(oldfd: i64, newfd: i64) -> i64 { return syscall2(90, oldfd, newfd); } -fun fsync(fd: i64) -> i64 { +pub fun fsync(fd: i64) -> i64 { return syscall1(95, fd); } -fun fcntl(fd: i64, cmd: i32, arg: i64) -> i64 { +pub fun fcntl(fd: i64, cmd: i32, arg: i64) -> i64 { return syscall3(92, fd, cmd as i64, arg); } -fun read(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun read(fd: i64, buf: ptr, len: i64) -> i64 { return syscall3(3, fd, buf as i64, len); } -fun write(fd: i64, buf: ptr, len: i64) -> i64 { +pub fun write(fd: i64, buf: ptr, len: i64) -> i64 { return syscall3(4, fd, buf as i64, len); } -fun getcwd(buf: ptr, size: i64) -> i64 { +pub fun getcwd(buf: ptr, size: i64) -> i64 { return syscall2(326, buf as i64, size); } -fun chdir(path: str) -> i64 { +pub fun chdir(path: str) -> i64 { return syscall1(12, path as i64); } -fun access(path: str, mode: i32) -> i64 { +pub fun access(path: str, mode: i32) -> i64 { return syscall2(33, path as i64, mode as i64); } -fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { +pub fun lseek(fd: i64, offset: i64, whence: i32) -> i64 { return syscall3(199, fd, offset, whence as i64); } -fun unlink(path: str) -> i64 { +pub fun unlink(path: str) -> i64 { return syscall1(10, path as i64); } -fun mkdir(path: str, mode: i32) -> i64 { +pub fun mkdir(path: str, mode: i32) -> i64 { return syscall2(136, path as i64, mode as i64); } -fun rmdir(path: str) -> i64 { +pub fun rmdir(path: str) -> i64 { return syscall1(137, path as i64); } -fun stat(path: str, st: ptr) -> i64 { +pub fun stat(path: str, st: ptr) -> i64 { return syscall2(338, path as i64, st as i64); } -fun fstat(fd: i64, st: ptr) -> i64 { +pub fun fstat(fd: i64, st: ptr) -> i64 { return syscall2(339, fd, st as i64); } diff --git a/std/sys/macos/memory.wave b/std/sys/macos/memory.wave index 8bd49851..0cb832f9 100644 --- a/std/sys/macos/memory.wave +++ b/std/sys/macos/memory.wave @@ -20,14 +20,23 @@ // macOS memory syscalls // ======================================================= -import("std::sys::macos::syscall"); +import("std::sys::macos::syscall")::{ + MACOS_SYSCALL_BASE, + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; -const PROT_READ: i32 = 1; -const PROT_WRITE: i32 = 2; -const MAP_PRIVATE: i32 = 2; -const MAP_ANON: i32 = 0x1000; +pub const PROT_READ: i32 = 1; +pub const PROT_WRITE: i32 = 2; +pub const MAP_PRIVATE: i32 = 2; +pub const MAP_ANON: i32 = 0x1000; -fun mmap( +pub fun mmap( addr: ptr, length: i64, prot: i32, @@ -46,15 +55,15 @@ fun mmap( ) as ptr; } -fun munmap(addr: ptr, length: i64) -> i64 { +pub fun munmap(addr: ptr, length: i64) -> i64 { return syscall2(73, addr as i64, length); } -fun brk(addr: ptr) -> ptr { +pub fun brk(addr: ptr) -> ptr { return syscall1(17, addr as i64) as ptr; } -fun sys_alloc(size: i64) -> ptr { +pub fun sys_alloc(size: i64) -> ptr { if (size <= 0) { return null; } @@ -75,7 +84,7 @@ fun sys_alloc(size: i64) -> ptr { return p as ptr; } -fun sys_free(p: ptr, size: i64) -> i64 { +pub fun sys_free(p: ptr, size: i64) -> i64 { if (p == null || size <= 0) { return 0; } diff --git a/std/sys/macos/process.wave b/std/sys/macos/process.wave index 6e214c1f..fbc7a987 100644 --- a/std/sys/macos/process.wave +++ b/std/sys/macos/process.wave @@ -20,26 +20,35 @@ // macOS process syscalls // ======================================================= -import("std::sys::macos::syscall"); +import("std::sys::macos::syscall")::{ + MACOS_SYSCALL_BASE, + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; -fun exit(code: i32) -> ! { +pub fun exit(code: i32) -> ! { syscall1(1, code as i64); while (true) { } } -fun getpid() -> i64 { +pub fun getpid() -> i64 { return syscall0(20); } -fun getppid() -> i64 { +pub fun getppid() -> i64 { return syscall0(39); } -fun fork() -> i64 { +pub fun fork() -> i64 { return syscall0(2); } -fun execve( +pub fun execve( path: str, argv: ptr>, envp: ptr> @@ -47,10 +56,10 @@ fun execve( return syscall3(59, path as i64, argv as i64, envp as i64); } -fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { +pub fun waitpid(pid: i64, status: ptr, options: i32) -> i64 { return syscall4(7, pid, status as i64, options as i64, 0); } -fun kill(pid: i64, sig: i32) -> i64 { +pub fun kill(pid: i64, sig: i32) -> i64 { return syscall2(37, pid, sig as i64); } diff --git a/std/sys/macos/socket.wave b/std/sys/macos/socket.wave index d30fff40..0ea5db4c 100644 --- a/std/sys/macos/socket.wave +++ b/std/sys/macos/socket.wave @@ -23,63 +23,72 @@ // Raw socket layer for macOS built on syscall.wave. // ======================================================= -import("std::sys::macos::syscall"); - -const AF_INET: i32 = 2; -const AF_INET6: i32 = 30; - -const SOCK_STREAM: i32 = 1; -const SOCK_DGRAM: i32 = 2; - -const IPPROTO_IP: i32 = 0; -const IPPROTO_TCP: i32 = 6; -const IPPROTO_UDP: i32 = 17; - -const SHUT_RD: i32 = 0; -const SHUT_WR: i32 = 1; -const SHUT_RDWR: i32 = 2; - -const SOL_SOCKET: i32 = 0xFFFF; -const SO_REUSEADDR: i32 = 0x0004; -const SO_REUSEPORT: i32 = 0x0200; - -const POLLIN: i16 = 0x001; -const POLLOUT: i16 = 0x004; -const POLLERR: i16 = 0x008; -const POLLHUP: i16 = 0x010; -const POLLNVAL: i16 = 0x020; - -struct PollFd { +import("std::sys::macos::syscall")::{ + MACOS_SYSCALL_BASE, + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; + +pub const AF_INET: i32 = 2; +pub const AF_INET6: i32 = 30; + +pub const SOCK_STREAM: i32 = 1; +pub const SOCK_DGRAM: i32 = 2; + +pub const IPPROTO_IP: i32 = 0; +pub const IPPROTO_TCP: i32 = 6; +pub const IPPROTO_UDP: i32 = 17; + +pub const SHUT_RD: i32 = 0; +pub const SHUT_WR: i32 = 1; +pub const SHUT_RDWR: i32 = 2; + +pub const SOL_SOCKET: i32 = 0xFFFF; +pub const SO_REUSEADDR: i32 = 0x0004; +pub const SO_REUSEPORT: i32 = 0x0200; + +pub const POLLIN: i16 = 0x001; +pub const POLLOUT: i16 = 0x004; +pub const POLLERR: i16 = 0x008; +pub const POLLHUP: i16 = 0x010; +pub const POLLNVAL: i16 = 0x020; + +pub struct PollFd { fd: i32; events: i16; revents: i16; } -fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { +pub fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { return syscall3(97, domain as i64, ty as i64, protocol as i64); } -fun bind(fd: i64, addr: ptr, len: i32) -> i64 { +pub fun bind(fd: i64, addr: ptr, len: i32) -> i64 { return syscall3(104, fd, addr as i64, len as i64); } -fun listen(fd: i64, backlog: i32) -> i64 { +pub fun listen(fd: i64, backlog: i32) -> i64 { return syscall2(106, fd, backlog as i64); } -fun accept(fd: i64, addr: ptr, len: ptr) -> i64 { +pub fun accept(fd: i64, addr: ptr, len: ptr) -> i64 { return syscall3(30, fd, addr as i64, len as i64); } -fun connect(fd: i64, addr: ptr, len: i32) -> i64 { +pub fun connect(fd: i64, addr: ptr, len: i32) -> i64 { return syscall3(98, fd, addr as i64, len as i64); } -fun shutdown(fd: i64, how: i32) -> i64 { +pub fun shutdown(fd: i64, how: i32) -> i64 { return syscall2(134, fd, how as i64); } -fun setsockopt( +pub fun setsockopt( fd: i64, level: i32, optname: i32, @@ -96,7 +105,7 @@ fun setsockopt( ); } -fun getsockopt( +pub fun getsockopt( fd: i64, level: i32, optname: i32, @@ -113,19 +122,19 @@ fun getsockopt( ); } -fun poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { +pub fun poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { return syscall3(230, fds as i64, nfds, timeout_ms as i64); } -fun send(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { +pub fun send(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return sendto(fd, buf, len, flags, null, 0); } -fun recv(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { +pub fun recv(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return recvfrom(fd, buf, len, flags, null, null); } -fun sendto( +pub fun sendto( fd: i64, buf: ptr, len: i64, @@ -144,7 +153,7 @@ fun sendto( ); } -fun recvfrom( +pub fun recvfrom( fd: i64, buf: ptr, len: i64, diff --git a/std/sys/macos/syscall.wave b/std/sys/macos/syscall.wave index 5a7ac387..faa4d184 100644 --- a/std/sys/macos/syscall.wave +++ b/std/sys/macos/syscall.wave @@ -25,9 +25,9 @@ // negative return value = -errno // ======================================================= -const MACOS_SYSCALL_BASE: i64 = 0x2000000; +pub const MACOS_SYSCALL_BASE: i64 = 0x2000000; -fun syscall0(id: i64) -> i64 { +pub fun syscall0(id: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { @@ -38,7 +38,7 @@ fun syscall0(id: i64) -> i64 { return ret; } -fun syscall1(id: i64, a1: i64) -> i64 { +pub fun syscall1(id: i64, a1: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { @@ -50,7 +50,7 @@ fun syscall1(id: i64, a1: i64) -> i64 { return ret; } -fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { +pub fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { @@ -63,7 +63,7 @@ fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { return ret; } -fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { +pub fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { @@ -77,7 +77,7 @@ fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { return ret; } -fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { +pub fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { @@ -92,7 +92,7 @@ fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { return ret; } -fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { +pub fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { @@ -108,7 +108,7 @@ fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { return ret; } -fun syscall6(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, a6: i64) -> i64 { +pub fun syscall6(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64, a6: i64) -> i64 { var ret: i64; var sysno: i64 = MACOS_SYSCALL_BASE + id; asm { diff --git a/std/sys/macos/time.wave b/std/sys/macos/time.wave index 39c9ee4c..e971cdfc 100644 --- a/std/sys/macos/time.wave +++ b/std/sys/macos/time.wave @@ -20,17 +20,26 @@ // macOS time syscalls // ======================================================= -import("std::sys::macos::syscall"); +import("std::sys::macos::syscall")::{ + MACOS_SYSCALL_BASE, + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; -struct TimeSpec { +pub struct TimeSpec { sec: i64; nsec: i64; } -fun nanosleep(req: ptr, rem: ptr) -> i64 { +pub fun nanosleep(req: ptr, rem: ptr) -> i64 { return syscall2(240, req as i64, rem as i64); } -fun clock_gettime(clock_id: i32, tp: ptr) -> i64 { +pub fun clock_gettime(clock_id: i32, tp: ptr) -> i64 { return syscall2(116, clock_id as i64, tp as i64); } diff --git a/std/sys/macos/tty.wave b/std/sys/macos/tty.wave index 39582b20..05549e60 100644 --- a/std/sys/macos/tty.wave +++ b/std/sys/macos/tty.wave @@ -20,28 +20,37 @@ // macOS tty helpers // ======================================================= -import("std::sys::macos::syscall"); - -const TTY_SYS_IOCTL: i64 = 54; -const TTY_SYS_FCNTL: i64 = 92; - -const TTY_TCGETS: i64 = 0x40487413; -const TTY_TCSETS: i64 = 0x80487414; -const TTY_TCSETSW: i64 = 0x80487415; -const TTY_TCSETSF: i64 = 0x80487416; - -const TTY_TCSANOW: i32 = 0; -const TTY_TCSADRAIN: i32 = 1; -const TTY_TCSAFLUSH: i32 = 2; - -const TTY_F_GETFL: i32 = 3; -const TTY_F_SETFL: i32 = 4; -const TTY_O_NONBLOCK: i32 = 4; - -const TTY_ICANON: u32 = 0x00000100; -const TTY_ECHO: u32 = 0x00000008; - -struct Termios { +import("std::sys::macos::syscall")::{ + MACOS_SYSCALL_BASE, + syscall0, + syscall1, + syscall2, + syscall3, + syscall4, + syscall5, + syscall6, +}; + +pub const TTY_SYS_IOCTL: i64 = 54; +pub const TTY_SYS_FCNTL: i64 = 92; + +pub const TTY_TCGETS: i64 = 0x40487413; +pub const TTY_TCSETS: i64 = 0x80487414; +pub const TTY_TCSETSW: i64 = 0x80487415; +pub const TTY_TCSETSF: i64 = 0x80487416; + +pub const TTY_TCSANOW: i32 = 0; +pub const TTY_TCSADRAIN: i32 = 1; +pub const TTY_TCSAFLUSH: i32 = 2; + +pub const TTY_F_GETFL: i32 = 3; +pub const TTY_F_SETFL: i32 = 4; +pub const TTY_O_NONBLOCK: i32 = 4; + +pub const TTY_ICANON: u32 = 0x00000100; +pub const TTY_ECHO: u32 = 0x00000008; + +pub struct Termios { c_iflag: u32; c_oflag: u32; c_cflag: u32; @@ -52,17 +61,17 @@ struct Termios { c_ospeed: u32; } -struct TtyRawState { +pub struct TtyRawState { term: Termios; flags: i32; active: i32; } -fun tty_getattr(fd: i32, t: ptr) -> i64 { +pub fun tty_getattr(fd: i32, t: ptr) -> i64 { return syscall3(TTY_SYS_IOCTL, fd as i64, TTY_TCGETS, t as i64); } -fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { +pub fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { var req: i64 = TTY_TCSETS; if (action == TTY_TCSADRAIN) { @@ -74,15 +83,15 @@ fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { return syscall3(TTY_SYS_IOCTL, fd as i64, req, t as i64); } -fun tty_getfl(fd: i32) -> i64 { +pub fun tty_getfl(fd: i32) -> i64 { return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_GETFL as i64, 0); } -fun tty_setfl(fd: i32, flags: i32) -> i64 { +pub fun tty_setfl(fd: i32, flags: i32) -> i64 { return syscall3(TTY_SYS_FCNTL, fd as i64, TTY_F_SETFL as i64, flags as i64); } -fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { +pub fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { deref st.active = 0; var vmin_idx: i32 = 16; var vtime_idx: i32 = 17; @@ -119,7 +128,7 @@ fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { return 0; } -fun tty_restore(fd: i32, st: ptr) { +pub fun tty_restore(fd: i32, st: ptr) { if (deref st.active == 0) { return; } diff --git a/std/sys/memory.wave b/std/sys/memory.wave index 4911c443..c29d54bb 100644 --- a/std/sys/memory.wave +++ b/std/sys/memory.wave @@ -21,7 +21,27 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::memory"); +pub import("std::sys::linux::memory")::{ + PROT_READ, + PROT_WRITE, + MAP_PRIVATE, + MAP_ANONYMOUS, + mmap, + munmap, + brk, + sys_alloc, + sys_free, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::memory"); +pub import("std::sys::macos::memory")::{ + PROT_READ, + PROT_WRITE, + MAP_PRIVATE, + MAP_ANON, + mmap, + munmap, + brk, + sys_alloc, + sys_free, +}; diff --git a/std/sys/process.wave b/std/sys/process.wave index a748f8d6..800ffaee 100644 --- a/std/sys/process.wave +++ b/std/sys/process.wave @@ -21,7 +21,23 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::process"); +pub import("std::sys::linux::process")::{ + exit, + getpid, + getppid, + fork, + execve, + waitpid, + kill, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::process"); +pub import("std::sys::macos::process")::{ + exit, + getpid, + getppid, + fork, + execve, + waitpid, + kill, +}; diff --git a/std/sys/socket.wave b/std/sys/socket.wave index 3eefffc5..1015b17c 100644 --- a/std/sys/socket.wave +++ b/std/sys/socket.wave @@ -25,7 +25,73 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::socket"); +pub import("std::sys::linux::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::socket"); +pub import("std::sys::macos::socket")::{ + AF_INET, + AF_INET6, + SOCK_STREAM, + SOCK_DGRAM, + IPPROTO_IP, + IPPROTO_TCP, + IPPROTO_UDP, + SHUT_RD, + SHUT_WR, + SHUT_RDWR, + SOL_SOCKET, + SO_REUSEADDR, + SO_REUSEPORT, + POLLIN, + POLLOUT, + POLLERR, + POLLHUP, + POLLNVAL, + PollFd, + socket, + bind, + listen, + accept, + connect, + shutdown, + setsockopt, + getsockopt, + poll, + send, + recv, + sendto, + recvfrom, +}; diff --git a/std/sys/time.wave b/std/sys/time.wave index 3714a3bc..5754b240 100644 --- a/std/sys/time.wave +++ b/std/sys/time.wave @@ -21,7 +21,15 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::time"); +pub import("std::sys::linux::time")::{ + TimeSpec, + nanosleep, + clock_gettime, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::time"); +pub import("std::sys::macos::time")::{ + TimeSpec, + nanosleep, + clock_gettime, +}; diff --git a/std/sys/tty.wave b/std/sys/tty.wave index 5a893a81..98a30d6b 100644 --- a/std/sys/tty.wave +++ b/std/sys/tty.wave @@ -21,7 +21,55 @@ // ======================================================= #[target(os="linux")] -import("std::sys::linux::tty"); +pub import("std::sys::linux::tty")::{ + TTY_SYS_IOCTL, + TTY_SYS_FCNTL, + TTY_TCGETS, + TTY_TCSETS, + TTY_TCSETSW, + TTY_TCSETSF, + TTY_TCSANOW, + TTY_TCSADRAIN, + TTY_TCSAFLUSH, + TTY_F_GETFL, + TTY_F_SETFL, + TTY_O_NONBLOCK, + TTY_ICANON, + TTY_ECHO, + TTY_VTIME_IDX, + TTY_VMIN_IDX, + Termios, + TtyRawState, + tty_getattr, + tty_setattr, + tty_getfl, + tty_setfl, + tty_enable_raw_nonblock, + tty_restore, +}; #[target(os="macos", arch="x86_64")] -import("std::sys::macos::tty"); +pub import("std::sys::macos::tty")::{ + TTY_SYS_IOCTL, + TTY_SYS_FCNTL, + TTY_TCGETS, + TTY_TCSETS, + TTY_TCSETSW, + TTY_TCSETSF, + TTY_TCSANOW, + TTY_TCSADRAIN, + TTY_TCSAFLUSH, + TTY_F_GETFL, + TTY_F_SETFL, + TTY_O_NONBLOCK, + TTY_ICANON, + TTY_ECHO, + Termios, + TtyRawState, + tty_getattr, + tty_setattr, + tty_getfl, + tty_setfl, + tty_enable_raw_nonblock, + tty_restore, +}; diff --git a/std/time/clock.wave b/std/time/clock.wave index dd3e0b07..26afe0dd 100644 --- a/std/time/clock.wave +++ b/std/time/clock.wave @@ -16,17 +16,21 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::time"); +pub import("std::sys::time")::{ + TimeSpec, + nanosleep, + clock_gettime, +}; -fun time_now_realtime(tp: ptr) -> i64 { +pub fun time_now_realtime(tp: ptr) -> i64 { return clock_gettime(0, tp); } -fun time_now_monotonic(tp: ptr) -> i64 { +pub fun time_now_monotonic(tp: ptr) -> i64 { return clock_gettime(1, tp); } -fun time_now_realtime_ns() -> i64 { +pub fun time_now_realtime_ns() -> i64 { var ts: TimeSpec; var r: i64 = time_now_realtime(&ts); @@ -37,7 +41,7 @@ fun time_now_realtime_ns() -> i64 { return (ts.sec * 1000000000) + ts.nsec; } -fun time_now_monotonic_ns() -> i64 { +pub fun time_now_monotonic_ns() -> i64 { var ts: TimeSpec; var r: i64 = time_now_monotonic(&ts); diff --git a/std/time/consts.wave b/std/time/consts.wave index 621cd499..c8ca6b7b 100644 --- a/std/time/consts.wave +++ b/std/time/consts.wave @@ -16,9 +16,9 @@ // // SPDX-License-Identifier: Apache-2.0 -const TIME_CLOCK_REALTIME: i32 = 0; -const TIME_CLOCK_MONOTONIC: i32 = 1; +pub const TIME_CLOCK_REALTIME: i32 = 0; +pub const TIME_CLOCK_MONOTONIC: i32 = 1; -const TIME_NS_PER_US: i64 = 1000; -const TIME_NS_PER_MS: i64 = 1000000; -const TIME_NS_PER_SEC: i64 = 1000000000; +pub const TIME_NS_PER_US: i64 = 1000; +pub const TIME_NS_PER_MS: i64 = 1000000; +pub const TIME_NS_PER_SEC: i64 = 1000000000; diff --git a/std/time/diff.wave b/std/time/diff.wave index a8b4a3e1..38b2b58b 100644 --- a/std/time/diff.wave +++ b/std/time/diff.wave @@ -16,15 +16,19 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::time"); +import("std::sys::time")::{ + TimeSpec, + nanosleep, + clock_gettime, +}; -fun time_diff_ns(start_ts: TimeSpec, end_ts: TimeSpec) -> i64 { +pub fun time_diff_ns(start_ts: TimeSpec, end_ts: TimeSpec) -> i64 { var sec: i64 = end_ts.sec - start_ts.sec; var nsec: i64 = end_ts.nsec - start_ts.nsec; return (sec * 1000000000) + nsec; } -fun time_diff_ms(start_ts: TimeSpec, end_ts: TimeSpec) -> i64 { +pub fun time_diff_ms(start_ts: TimeSpec, end_ts: TimeSpec) -> i64 { return time_diff_ns(start_ts, end_ts) / 1000000; } diff --git a/std/time/sleep.wave b/std/time/sleep.wave index 87e802ae..f93c58d9 100644 --- a/std/time/sleep.wave +++ b/std/time/sleep.wave @@ -16,9 +16,13 @@ // // SPDX-License-Identifier: Apache-2.0 -import("std::sys::time"); +import("std::sys::time")::{ + TimeSpec, + nanosleep, + clock_gettime, +}; -fun time_sleep_ns(ns: i64) -> i64 { +pub fun time_sleep_ns(ns: i64) -> i64 { if (ns <= 0) { return 0; } @@ -33,7 +37,7 @@ fun time_sleep_ns(ns: i64) -> i64 { return nanosleep(&req, &rem); } -fun time_sleep_us(us: i64) -> i64 { +pub fun time_sleep_us(us: i64) -> i64 { if (us <= 0) { return 0; } @@ -41,7 +45,7 @@ fun time_sleep_us(us: i64) -> i64 { return time_sleep_ns(us * 1000); } -fun time_sleep_ms(ms: i64) -> i64 { +pub fun time_sleep_ms(ms: i64) -> i64 { if (ms <= 0) { return 0; } diff --git a/tests/cases/test103.wave b/tests/cases/test103.wave index b59c5242..f042509f 100644 --- a/tests/cases/test103.wave +++ b/tests/cases/test103.wave @@ -1,9 +1,45 @@ // wave-test: host-os=linux, host-arch=x86_64 -import("std::string::len"); -import("std::fs::consts"); -import("std::fs::file"); -import("std::io::fd"); +import("std::string::len")::{ + len, + is_empty, +}; +import("std::fs::consts")::{ + FS_MODE_USER_RW, + FS_MODE_FILE_DEFAULT, + FS_MODE_DIR_DEFAULT, +}; +import("std::fs::file")::{ + fs_exists, + fs_open, + fs_open_read, + fs_open_write, + fs_open_append, + fs_open_rw, + fs_close, + fs_file_size, + fs_file_size_fd, + fs_remove, + fs_mkdir, + fs_rmdir, + fs_read_all, + fs_write_all, + fs_append_all, + fs_copy, +}; +import("std::io::fd")::{ + io_close, + io_seek, + io_read, + io_write, + io_write_all, + io_read_exact, + io_read_at_most, + io_copy, + io_pipe, + io_dup, + io_dup2, +}; fun check_i64(label: str, got: i64, expected: i64) -> i64 { if (got == expected) { diff --git a/tests/cases/test104.wave b/tests/cases/test104.wave index 1dbf4d2c..c331ad50 100644 --- a/tests/cases/test104.wave +++ b/tests/cases/test104.wave @@ -1,9 +1,50 @@ // wave-test: host-os=linux, host-arch=x86_64 -import("std::io::fd"); -import("std::net::poll"); -import("std::net::socket_base"); -import("std::net::socketopt"); +import("std::io::fd")::{ + io_close, + io_seek, + io_read, + io_write, + io_write_all, + io_read_exact, + io_read_at_most, + io_copy, + io_pipe, + io_dup, + io_dup2, +}; +import("std::net::poll")::{ + NET_POLLIN, + NET_POLLOUT, + NET_POLLERR, + NET_POLLHUP, + NET_POLLNVAL, + net_poll, + net_wait_readable, + net_wait_writable, + PollFd, +}; +import("std::net::socket_base")::{ + net_fd_valid, + net_set_reuseaddr, + net_socket_tcp_v4, + net_socket_udp_v4, + net_bind_v4, + net_connect_v4, + net_accept_v4, + net_send_all, + net_recv_exact, + net_sendto_v4, + net_recvfrom_v4, + net_shutdown_close, +}; +import("std::net::socketopt")::{ + net_set_nonblock, + net_get_nonblock, + net_set_reuseaddr_flag, + net_get_reuseaddr_flag, + net_set_reuseport_flag, +}; fun check_i64(label: str, got: i64, expected: i64) -> i64 { if (got == expected) { diff --git a/tests/cases/test105.wave b/tests/cases/test105.wave index a441f584..ad26ce7a 100644 --- a/tests/cases/test105.wave +++ b/tests/cases/test105.wave @@ -1,10 +1,90 @@ // wave-test: host-os=linux, host-arch=x86_64 -import("std::bytes::endian"); -import("std::math::bits"); -import("std::mem::alloc"); -import("std::mem::consts"); -import("std::mem::ops"); +import("std::bytes::endian")::{ + bytes_swap16, + bytes_swap32, + bytes_swap64, + bytes_load_be_i16, + bytes_load_le_i16, + bytes_store_be_i16, + bytes_store_le_i16, + bytes_load_be_i32, + bytes_load_le_i32, + bytes_store_be_i32, + bytes_store_le_i32, + bytes_load_be_i64, + bytes_load_le_i64, + bytes_store_be_i64, + bytes_store_le_i64, +}; +import("std::math::bits")::{ + is_pow2, + align_down, + align_up, + low_bit, + popcount, + ctz32, + bit_length, + ilog2_floor, + ilog2_ceil, + is_pow2_i64, + align_down_i64, + align_up_i64, + low_bit_i64, + popcount64, + ctz64, + bit_length64, + ilog2_floor64, + ilog2_ceil64, + bswap32, + bswap64, +}; +import("std::mem::alloc")::{ + mem_alloc, + mem_alloc_zeroed, + mem_free, + mem_realloc, + mem_alloc_items, + mem_alloc_items_zeroed, + mem_realloc_items, + mem_free_items, + mem_page_size, + mem_pages_for_size, + mem_size_align_page, + mem_alloc_pages, + mem_free_pages, + mem_is_aligned, + mem_alloc_aligned, + mem_free_aligned, +}; +import("std::mem::consts")::{ + MEM_PROT_READ, + MEM_PROT_WRITE, + MEM_PROT_EXEC, + MEM_MAP_PRIVATE, + MEM_MAP_ANON, + MEM_PAGE_SIZE, + MEM_ERR_INVALID, + MEM_ERR_NO_SPACE, +}; +import("std::mem::ops")::{ + mem_set, + mem_zero, + mem_copy, + mem_move, + mem_cmp, + mem_eq, + mem_find_byte, + mem_swap, + mem_copy_items, + mem_set_items, + mem_move_items, + mem_zero_items, + mem_copy_checked, + mem_move_checked, + mem_set_checked, + mem_zero_checked, +}; fun check_i64(label: str, got: i64, expected: i64) -> i64 { if (got == expected) { diff --git a/tests/cases/test106.wave b/tests/cases/test106.wave index cab95250..36d78360 100644 --- a/tests/cases/test106.wave +++ b/tests/cases/test106.wave @@ -1,10 +1,56 @@ // wave-test: host-os=linux, host-arch=x86_64 -import("std::io::consts"); -import("std::io::fd"); -import("std::process::core"); -import("std::process::spawn"); -import("std::process::wait"); +import("std::io::consts")::{ + IO_STDIN_FD, + IO_STDOUT_FD, + IO_STDERR_FD, + IO_ERR_INVALID, + IO_ERR_EOF, + IO_ERR_NO_SPACE, + IO_ERR_INTR, +}; +import("std::io::fd")::{ + io_close, + io_seek, + io_read, + io_write, + io_write_all, + io_read_exact, + io_read_at_most, + io_copy, + io_pipe, + io_dup, + io_dup2, +}; +import("std::process::core")::{ + proc_exit, + proc_getpid, + proc_getppid, + proc_fork, + proc_execve, + proc_waitpid_raw, + proc_kill, +}; +import("std::process::spawn")::{ + ProcPipeResult, + ProcSpawnStdoutResult, + _proc_dup_child_fd, + proc_spawn_exec_raw, + proc_spawn, + proc_make_pipe, + proc_spawn_capture_stdout, +}; +import("std::process::wait")::{ + proc_wait, + proc_wait_nohang, + proc_status_exited, + proc_status_exit_code, + proc_status_signaled, + proc_status_term_signal, + proc_status_stopped, + proc_status_stop_signal, + proc_status_continued, +}; fun check_i64(label: str, got: i64, expected: i64) -> i64 { if (got == expected) { diff --git a/tests/cases/test28/main.wave b/tests/cases/test28/main.wave index de1a816e..8cc68b53 100644 --- a/tests/cases/test28/main.wave +++ b/tests/cases/test28/main.wave @@ -1,8 +1,8 @@ -import("math"); +import("./math")::{add, sub, mul, div}; fun main() { println("2 + 3 = {}", add(2, 3)); println("2 - 3 = {}", sub(2, 3)); println("2 * 3 = {}", mul(2, 3)); println("2 / 3 = {}", div(2, 3)); -} \ No newline at end of file +} diff --git a/tests/cases/test28/math.wave b/tests/cases/test28/math.wave index adf06c9d..0c785567 100644 --- a/tests/cases/test28/math.wave +++ b/tests/cases/test28/math.wave @@ -1,15 +1,15 @@ -fun add(a: i32, b: i32) -> i32 { +pub fun add(a: i32, b: i32) -> i32 { return a + b; } -fun sub(a: i32, b: i32) -> i32 { +pub fun sub(a: i32, b: i32) -> i32 { return a - b; } -fun mul(a: i32, b: i32) -> i32 { +pub fun mul(a: i32, b: i32) -> i32 { return a * b; } -fun div(a: i32, b: i32) -> i32 { +pub fun div(a: i32, b: i32) -> i32 { return a / b; -} \ No newline at end of file +} diff --git a/tests/cases/test84.wave b/tests/cases/test84.wave index 1d207bb3..ecbb8f24 100644 --- a/tests/cases/test84.wave +++ b/tests/cases/test84.wave @@ -1,22 +1,121 @@ // wave-test: host-os=linux, host-arch=x86_64 -import("std::time::clock"); -import("std::time::diff"); -import("std::time::sleep"); - -import("std::env::cwd"); -import("std::env::environ"); - -import("std::path::core"); -import("std::path::copy"); - -import("std::mem::alloc"); -import("std::mem::ops"); - -import("std::buffer::types"); -import("std::buffer::alloc"); -import("std::buffer::write"); -import("std::buffer::read"); +import("std::time::clock")::{ + time_now_realtime, + time_now_monotonic, + time_now_realtime_ns, + time_now_monotonic_ns, + TimeSpec, + nanosleep, + clock_gettime, +}; +import("std::time::diff")::{ + time_diff_ns, + time_diff_ms, +}; +import("std::time::sleep")::{ + time_sleep_ns, + time_sleep_us, + time_sleep_ms, +}; + +import("std::env::cwd")::{ + env_getcwd, + env_chdir, + env_access, +}; +import("std::env::environ")::{ + EnvResult, + env_result_ok, + env_result_err, + env_unwrap_or, + env_get, + env_exists, + env_get_i64, + env_get_i32, + env_get_i32_default, + env_get_i64_default, +}; + +import("std::path::core")::{ + path_is_sep, + path_len, + path_is_abs, +}; +import("std::path::copy")::{ + path_join2, + path_basename_copy, + path_dirname_copy, +}; + +import("std::mem::alloc")::{ + mem_alloc, + mem_alloc_zeroed, + mem_free, + mem_realloc, + mem_alloc_items, + mem_alloc_items_zeroed, + mem_realloc_items, + mem_free_items, + mem_page_size, + mem_pages_for_size, + mem_size_align_page, + mem_alloc_pages, + mem_free_pages, + mem_is_aligned, + mem_alloc_aligned, + mem_free_aligned, +}; +import("std::mem::ops")::{ + mem_set, + mem_zero, + mem_copy, + mem_move, + mem_cmp, + mem_eq, + mem_find_byte, + mem_swap, + mem_copy_items, + mem_set_items, + mem_move_items, + mem_zero_items, + mem_copy_checked, + mem_move_checked, + mem_set_checked, + mem_zero_checked, +}; + +import("std::buffer::types")::{ + Buffer, + TypedBuffer, +}; +import("std::buffer::alloc")::{ + buffer_new, + buffer_new_default, + buffer_free, + buffer_clear, + buffer_reserve, + tbuffer_new, + tbuffer_free, + tbuffer_clear, + tbuffer_reserve, + Buffer, + TypedBuffer, +}; +import("std::buffer::write")::{ + buffer_push, + buffer_append, + buffer_append_str, + buffer_set, + tbuffer_push, + tbuffer_set, +}; +import("std::buffer::read")::{ + buffer_at, + tbuffer_ptr, + tbuffer_len, + tbuffer_at, +}; fun check_i32(label: str, got: i32, expected: i32) -> i32 { if (got == expected) { diff --git a/tests/codegen_regressions.rs b/tests/codegen_regressions.rs index ff11ecef..14e60f26 100644 --- a/tests/codegen_regressions.rs +++ b/tests/codegen_regressions.rs @@ -216,6 +216,165 @@ fn retired_let_declarations_are_rejected() { } } +#[test] +fn import_graph_supports_packages_aliases_selections_and_visibility() { + let dir = temp_case_dir("module-import-contract"); + let package = dir.join("add"); + fs::create_dir_all(package.join("src")).unwrap(); + fs::write( + package.join("src/lib.wave"), + r#" +fun internal_sum(a: i32, b: i32) -> i32 { return a + b; } +pub fun sum(a: i32, b: i32) -> i32 { return internal_sum(a, b); } +pub struct Point {} +pub import("./extra")::{increment}; +"#, + ) + .unwrap(); + fs::write( + package.join("src/extra.wave"), + "pub fun increment(value: i32) -> i32 { return value + 1; }\n", + ) + .unwrap(); + fs::write( + package.join("src/math.wave"), + "pub fun double(value: i32) -> i32 { return value * 2; }\n", + ) + .unwrap(); + fs::write( + dir.join("helpers.wave"), + "fun scale(value: i32) -> i32 { return value * 3; }\npub fun triple(value: i32) -> i32 { return scale(value); }\n", + ) + .unwrap(); + fs::write( + dir.join("other.wave"), + "fun scale(value: i32) -> i32 { return value * 4; }\npub fun quadruple(value: i32) -> i32 { return scale(value); }\n", + ) + .unwrap(); + + let entry = write_wave( + &dir, + "main.wave", + r#" +import("add"); +import("add::math"); +import("./helpers" as helpers); +import("./other" as other); +import("add")::{sum, Point, increment}; + +fun main() { + var a = add::sum(1, 2); + var b = sum(1, 2); + var c = add::math::double(2); + var d = helpers::triple(3); + var f = other::quadruple(3); + var p = Point(); + var e = increment(4); +} +"#, + ); + let mapping = OsString::from(format!("add={}", package.display())); + run_wavec([ + OsStr::new("check"), + entry.as_os_str(), + OsStr::new("--dep"), + mapping.as_os_str(), + ]); + + let selected_private = write_wave( + &dir, + "selected_private.wave", + "import(\"add\")::{internal_sum};\nfun main() {}\n", + ); + let error = run_wavec_expect_failure([ + OsStr::new("check"), + selected_private.as_os_str(), + OsStr::new("--dep"), + mapping.as_os_str(), + ]); + assert!( + error.contains("symbol 'internal_sum' is private in module 'add'"), + "{}", + error + ); + + let qualified_private = write_wave( + &dir, + "qualified_private.wave", + "import(\"add\");\nfun main() { var n: i32 = add::internal_sum(1, 2); }\n", + ); + let error = run_wavec_expect_failure([ + OsStr::new("check"), + qualified_private.as_os_str(), + OsStr::new("--dep"), + mapping.as_os_str(), + ]); + assert!( + error.contains("symbol 'internal_sum' is private in module 'add'"), + "{}", + error + ); +} + +#[test] +fn import_graph_rejects_cycles_path_escape_and_public_main() { + let dir = temp_case_dir("module-import-errors"); + fs::write( + dir.join("a.wave"), + "import(\"./b\");\npub fun from_a() -> i32 { return 1; }\n", + ) + .unwrap(); + fs::write( + dir.join("b.wave"), + "import(\"./a\");\npub fun from_b() -> i32 { return 2; }\n", + ) + .unwrap(); + let cycle = write_wave(&dir, "cycle.wave", "import(\"./a\");\nfun main() {}\n"); + let error = run_wavec_expect_failure([OsStr::new("check"), cycle.as_os_str()]); + assert!(error.contains("import cycle detected"), "{}", error); + + let escape = write_wave( + &dir, + "escape.wave", + "import(\"./../outside\");\nfun main() {}\n", + ); + let error = run_wavec_expect_failure([OsStr::new("check"), escape.as_os_str()]); + assert!(error.contains("escapes its module directory"), "{}", error); + + let package_escape = write_wave( + &dir, + "package_escape.wave", + "import(\"add::..\");\nfun main() {}\n", + ); + let error = run_wavec_expect_failure([OsStr::new("check"), package_escape.as_os_str()]); + assert!( + error.contains("package and module names must be identifiers"), + "{}", + error + ); + + let public_main = write_wave(&dir, "public_main.wave", "pub fun main() {}\n"); + let error = run_wavec_expect_failure([OsStr::new("check"), public_main.as_os_str()]); + assert!( + error.contains("entry function `main` cannot be public"), + "{}", + error + ); + + fs::write(dir.join("library_main.wave"), "fun main() {}\n").unwrap(); + let imported_main = write_wave( + &dir, + "imported_main.wave", + "import(\"./library_main\");\nfun main() {}\n", + ); + let error = run_wavec_expect_failure([OsStr::new("check"), imported_main.as_os_str()]); + assert!( + error.contains("function 'main' may only be declared in the entry module"), + "{}", + error + ); +} + fn run_link_tests_enabled() -> bool { std::env::var_os("WAVE_RUN_LINK_TESTS").is_some() } @@ -899,7 +1058,7 @@ fun main() { let entry = write_wave( &dir, "import_main.wave", - "import(\"broken\");\n\nfun main() {}\n", + "import(\"./broken\");\n\nfun main() {}\n", ); let output = run_wavec_raw([OsStr::new("check"), entry.as_os_str()]); assert!(!output.status.success()); @@ -917,7 +1076,7 @@ fun main() { let generic_entry = write_wave( &dir, "generic_import_main.wave", - "import(\"generic_broken\");\n\nfun main() {}\n", + "import(\"./generic_broken\");\n\nfun main() {}\n", ); let output = run_wavec_raw([OsStr::new("check"), generic_entry.as_os_str()]); assert!(!output.status.success());