diff --git a/std/POLICY.md b/std/POLICY.md new file mode 100644 index 00000000..e62549e4 --- /dev/null +++ b/std/POLICY.md @@ -0,0 +1,39 @@ +# Wave STD Policy + +This document defines non-negotiable rules for the Wave standard library (`std/`). + +## 1) Layering Rules + +- `std/libc/**` is the only directory allowed to declare `extern(c)`. +- Outside `std/libc/**`, modules must use Wave code and/or `std/sys/**` raw syscall wrappers. +- High-level modules (`std/io`, `std/fs`, `std/net`, `std/process`, `std/mem`, etc.) must not import `std::libc::*`. + +## 2) Syntax Rules + +- `var` declarations are banned in `std/**`. +- Use `let` for immutable values. +- Use `let mut` for mutable values. + +## 3) Runtime Contract Rules + +- `std/sys/**` should preserve raw syscall style: success `>= 0`, errors as negative `-errno`. +- High-level `std/**` modules may define additional library-level error codes when needed. + +## 4) Compatibility Rules + +- All `std/` code must remain compatible with the `v0.1.8-pre-beta` compiler baseline. +- Avoid patterns known to break older codegen paths (for example, complex index expressions in single brackets). + +## 5) Automated Check + +Run: + +```bash +./tools/check_std_policy.sh +``` + +The checker validates: + +- `extern(c)` appears only under `std/libc/**` +- no `std::libc::*` import outside `std/libc/**` +- no `var` usage in `std/**` diff --git a/std/README.md b/std/README.md index 73252812..48153c7f 100644 --- a/std/README.md +++ b/std/README.md @@ -15,6 +15,10 @@ This is Wave's standard library. This standard library operates independently of - `std::path`: allocation-free path utilities. - `std::mem`: manual memory utilities for non-GC code. - `std::buffer`: growable byte buffer built on `std::mem`. +- `std::io`: fd-level read/write/seek/copy helpers. +- `std::fs`: basic open/read/write/copy/metadata helpers. +- `std::bytes`: endian swap/load/store helpers. +- `std::process`: fork/exec/wait and stdio redirection helpers. ## Layout diff --git a/std/buffer/alloc.wave b/std/buffer/alloc.wave index 42b9705e..3ba9f3e8 100644 --- a/std/buffer/alloc.wave +++ b/std/buffer/alloc.wave @@ -15,12 +15,12 @@ import("std::mem::ops"); import("std::buffer::types"); fun buffer_new(capacity: i64) -> Buffer { - var cap: i64 = capacity; + let mut cap: i64 = capacity; if (cap <= 0) { cap = 64; } - var data: ptr = mem_alloc(cap); + let mut data: ptr = mem_alloc(cap); if (data == null) { return Buffer { data: null, @@ -41,7 +41,7 @@ fun buffer_new_default() -> Buffer { } fun buffer_free(buf: ptr) -> i64 { - var ret: i64 = 0; + let mut ret: i64 = 0; if (deref buf.cap > 0) { ret = mem_free(deref buf.data, deref buf.cap); @@ -62,7 +62,7 @@ fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { return 0; } - var new_cap: i64 = deref buf.cap; + let mut new_cap: i64 = deref buf.cap; if (new_cap <= 0) { new_cap = 64; } @@ -71,7 +71,7 @@ fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { new_cap = new_cap * 2; } - var new_data: ptr = mem_alloc(new_cap); + let mut new_data: ptr = mem_alloc(new_cap); if (new_data == null) { return -1; } @@ -92,7 +92,7 @@ fun buffer_reserve(buf: ptr, required_cap: i64) -> i64 { fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { if (elem_size <= 0) { - var empty: TypedBuffer; + let mut empty: TypedBuffer; empty.data = null; empty.len = 0; empty.cap_bytes = 0; @@ -100,16 +100,16 @@ fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { return empty; } - var cap_elems: i64 = initial_cap; + let mut cap_elems: i64 = initial_cap; if (cap_elems <= 0) { cap_elems = 16; } - var cap_bytes: i64 = cap_elems * elem_size; - var data: ptr = mem_alloc(cap_bytes); + let mut cap_bytes: i64 = cap_elems * elem_size; + let mut data: ptr = mem_alloc(cap_bytes); if (data == null) { - var failed: TypedBuffer; + let mut failed: TypedBuffer; failed.data = null; failed.len = 0; failed.cap_bytes = 0; @@ -117,7 +117,7 @@ fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { return failed; } - var created: TypedBuffer; + let mut created: TypedBuffer; created.data = data; created.len = 0; created.cap_bytes = cap_bytes; @@ -126,7 +126,7 @@ fun tbuffer_new(elem_size: i64, initial_cap: i64) -> TypedBuffer { } fun tbuffer_free(buf: ptr>) -> i64 { - var ret: i64 = 0; + let mut ret: i64 = 0; if (deref buf.cap_bytes > 0) { ret = mem_free(deref buf.data, deref buf.cap_bytes); @@ -152,12 +152,12 @@ fun tbuffer_reserve(buf: ptr>, required_len: i64) -> i64 { return 0; } - var required_bytes: i64 = required_len * deref buf.elem_size; + let mut required_bytes: i64 = required_len * deref buf.elem_size; if (required_bytes <= deref buf.cap_bytes) { return 0; } - var new_cap_bytes: i64 = deref buf.cap_bytes; + let mut new_cap_bytes: i64 = deref buf.cap_bytes; if (new_cap_bytes <= 0) { new_cap_bytes = deref buf.elem_size * 16; } @@ -166,12 +166,12 @@ fun tbuffer_reserve(buf: ptr>, required_len: i64) -> i64 { new_cap_bytes = new_cap_bytes * 2; } - var new_data: ptr = mem_alloc(new_cap_bytes); + let mut new_data: ptr = mem_alloc(new_cap_bytes); if (new_data == null) { return -1; } - var used_bytes: i64 = deref buf.len * deref buf.elem_size; + let mut used_bytes: i64 = deref buf.len * deref buf.elem_size; if (used_bytes > 0) { mem_copy(new_data, deref buf.data, used_bytes); } diff --git a/std/buffer/read.wave b/std/buffer/read.wave index ebd0d14b..cf0f5e98 100644 --- a/std/buffer/read.wave +++ b/std/buffer/read.wave @@ -33,8 +33,8 @@ fun tbuffer_at(buf: TypedBuffer, index: i64, out_value: ptr) -> bool { return false; } - var offset_bytes: i64 = index * buf.elem_size; - var slot: ptr = (buf.data + offset_bytes) as ptr; + let mut offset_bytes: i64 = index * buf.elem_size; + let mut slot: ptr = (buf.data + offset_bytes) as ptr; deref out_value = deref slot; return true; diff --git a/std/buffer/write.wave b/std/buffer/write.wave index 5d3d6196..639c1660 100644 --- a/std/buffer/write.wave +++ b/std/buffer/write.wave @@ -14,14 +14,14 @@ import("std::buffer::types"); import("std::buffer::alloc"); fun buffer_push(buf: ptr, value: u8) -> i64 { - var needed: i64 = deref buf.len + 1; - var ret: i64 = buffer_reserve(buf, needed); + let mut needed: i64 = deref buf.len + 1; + let mut ret: i64 = buffer_reserve(buf, needed); if (ret < 0) { return ret; } - var p: ptr = deref buf.data; + let mut p: ptr = deref buf.data; deref p[deref buf.len] = value; deref buf.len = needed; @@ -33,17 +33,17 @@ fun buffer_append(buf: ptr, src: ptr, size: i64) -> i64 { return 0; } - var base: i64 = deref buf.len; - var needed: i64 = base + size; - var ret: i64 = buffer_reserve(buf, needed); + let mut base: i64 = deref buf.len; + let mut needed: i64 = base + size; + let mut ret: i64 = buffer_reserve(buf, needed); if (ret < 0) { return ret; } - var dst: ptr = deref buf.data; - var src_idx: i64 = 0; - var dst_idx: i64 = base; + let mut dst: ptr = deref buf.data; + let mut src_idx: i64 = 0; + let mut dst_idx: i64 = base; while (src_idx < size) { deref dst[dst_idx] = src[src_idx]; @@ -56,10 +56,10 @@ fun buffer_append(buf: ptr, src: ptr, size: i64) -> i64 { } fun buffer_append_str(buf: ptr, s: str) -> i64 { - var i: i64 = 0; + let mut i: i64 = 0; while (s[i] != 0) { - var ret: i64 = buffer_push(buf, s[i]); + let mut ret: i64 = buffer_push(buf, s[i]); if (ret < 0) { return ret; } @@ -74,21 +74,21 @@ fun buffer_set(buf: ptr, index: i64, value: u8) -> bool { return false; } - var p: ptr = deref buf.data; + let mut p: ptr = deref buf.data; deref p[index] = value; return true; } fun tbuffer_push(buf: ptr>, value: T) -> i64 { - var needed_len: i64 = deref buf.len + 1; - var ret: i64 = tbuffer_reserve(buf, needed_len); + let mut needed_len: i64 = deref buf.len + 1; + let mut ret: i64 = tbuffer_reserve(buf, needed_len); if (ret < 0) { return ret; } - var offset_bytes: i64 = deref buf.len * deref buf.elem_size; - var slot: ptr = (deref buf.data + offset_bytes) as ptr; + let mut offset_bytes: i64 = deref buf.len * deref buf.elem_size; + let mut slot: ptr = (deref buf.data + offset_bytes) as ptr; deref slot = value; deref buf.len = needed_len; @@ -101,8 +101,8 @@ fun tbuffer_set(buf: ptr>, index: i64, value: T) -> bool { return false; } - var offset_bytes: i64 = index * deref buf.elem_size; - var slot: ptr = (deref buf.data + offset_bytes) as ptr; + let mut offset_bytes: i64 = index * deref buf.elem_size; + let mut slot: ptr = (deref buf.data + offset_bytes) as ptr; deref slot = value; return true; diff --git a/std/bytes/endian.wave b/std/bytes/endian.wave new file mode 100644 index 00000000..c8830333 --- /dev/null +++ b/std/bytes/endian.wave @@ -0,0 +1,150 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +fun bytes_swap16(x: i16) -> i16 { + let v: i32 = x as i32; + return (((v & 255) << 8) | ((v >> 8) & 255)) as i16; +} + +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 { + return ((x & 0x00000000000000FF) << 56) + | ((x & 0x000000000000FF00) << 40) + | ((x & 0x0000000000FF0000) << 24) + | ((x & 0x00000000FF000000) << 8) + | ((x & 0x000000FF00000000) >> 8) + | ((x & 0x0000FF0000000000) >> 24) + | ((x & 0x00FF000000000000) >> 40) + | ((x & 0xFF00000000000000) >> 56); +} + +fun bytes_load_be_i16(src: ptr) -> i16 { + let b0: i16 = src[0] as i16; + let b1: i16 = src[1] as i16; + return (b0 << 8) | b1; +} + +fun bytes_load_le_i16(src: ptr) -> i16 { + let b0: i16 = src[0] as i16; + let b1: i16 = src[1] as i16; + return b0 | (b1 << 8); +} + +fun bytes_store_be_i16(dst: ptr, value: i16) { + let 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) { + let 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 { + let b0: i32 = src[0] as i32; + let b1: i32 = src[1] as i32; + let b2: i32 = src[2] as i32; + let b3: i32 = src[3] as i32; + return (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; +} + +fun bytes_load_le_i32(src: ptr) -> i32 { + let b0: i32 = src[0] as i32; + let b1: i32 = src[1] as i32; + let b2: i32 = src[2] as i32; + let b3: i32 = src[3] as i32; + return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); +} + +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) { + 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 { + let b0: i64 = src[0] as i64; + let b1: i64 = src[1] as i64; + let b2: i64 = src[2] as i64; + let b3: i64 = src[3] as i64; + let b4: i64 = src[4] as i64; + let b5: i64 = src[5] as i64; + let b6: i64 = src[6] as i64; + let b7: i64 = src[7] as i64; + + return (b0 << 56) + | (b1 << 48) + | (b2 << 40) + | (b3 << 32) + | (b4 << 24) + | (b5 << 16) + | (b6 << 8) + | b7; +} + +fun bytes_load_le_i64(src: ptr) -> i64 { + let b0: i64 = src[0] as i64; + let b1: i64 = src[1] as i64; + let b2: i64 = src[2] as i64; + let b3: i64 = src[3] as i64; + let b4: i64 = src[4] as i64; + let b5: i64 = src[5] as i64; + let b6: i64 = src[6] as i64; + let b7: i64 = src[7] as i64; + + return b0 + | (b1 << 8) + | (b2 << 16) + | (b3 << 24) + | (b4 << 32) + | (b5 << 40) + | (b6 << 48) + | (b7 << 56); +} + +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; + deref dst[3] = ((value >> 32) & 255) as u8; + deref dst[4] = ((value >> 24) & 255) as u8; + deref dst[5] = ((value >> 16) & 255) as u8; + deref dst[6] = ((value >> 8) & 255) as u8; + deref dst[7] = (value & 255) as u8; +} + +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; + deref dst[3] = ((value >> 24) & 255) as u8; + deref dst[4] = ((value >> 32) & 255) as u8; + deref dst[5] = ((value >> 40) & 255) as u8; + deref dst[6] = ((value >> 48) & 255) as u8; + deref dst[7] = ((value >> 56) & 255) as u8; +} diff --git a/std/env/cwd.wave b/std/env/cwd.wave index dede467c..8f8d560a 100644 --- a/std/env/cwd.wave +++ b/std/env/cwd.wave @@ -13,12 +13,12 @@ import("std::sys::fs"); fun env_getcwd(dst: ptr, cap: i64) -> i64 { - var r: i64 = getcwd(dst, cap); + let mut r: i64 = getcwd(dst, cap); if (r <= 0) { return -1; } - var n: i64 = 0; + let mut n: i64 = 0; while (dst[n] != 0) { n += 1; } diff --git a/std/env/environ.wave b/std/env/environ.wave index dd0180a9..c6d2d4b0 100644 --- a/std/env/environ.wave +++ b/std/env/environ.wave @@ -19,14 +19,14 @@ struct EnvResult { } fun env_result_ok(value: T) -> EnvResult { - var result: EnvResult; + let mut result: EnvResult; result.ok = true; result.value = value; return result; } fun env_result_err(fallback: T) -> EnvResult { - var result: EnvResult; + let mut result: EnvResult; result.ok = false; result.value = fallback; return result; @@ -41,7 +41,7 @@ fun env_unwrap_or(result: EnvResult, default_value: T) -> T { } fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { - var key_len: i64 = _env_key_len(name); + let mut key_len: i64 = _env_key_len(name); if (key_len <= 0) { return -4; @@ -51,17 +51,17 @@ fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { return -3; } - var raw: array; - var n: i64 = env_read(&raw[0], 32768); + let mut raw: array; + let mut n: i64 = env_read(&raw[0], 32768); if (n <= 0) { return -2; } - var i: i64 = 0; + let mut i: i64 = 0; while (i < n) { - var entry_start: i64 = i; - var eq_pos: i64 = -1; + let mut entry_start: i64 = i; + let mut eq_pos: i64 = -1; while (i < n && raw[i] != 0) { if (raw[i] == 61 && eq_pos < 0) { @@ -70,7 +70,7 @@ fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { i += 1; } - var entry_end: i64 = i; + let mut entry_end: i64 = i; if (i < n) { i += 1; @@ -95,8 +95,8 @@ fun env_get(name: str, dst: ptr, dst_cap: i64) -> i64 { } fun env_exists(name: str) -> bool { - var tmp: array; - var r: i64 = env_get(name, &tmp[0], 2); + let mut tmp: array; + let mut r: i64 = env_get(name, &tmp[0], 2); if (r >= 0) { return true; @@ -106,14 +106,14 @@ fun env_exists(name: str) -> bool { } fun env_get_i64(name: str) -> EnvResult { - var raw: array; - var n: i64 = env_get(name, &raw[0], 64); + let mut raw: array; + let mut n: i64 = env_get(name, &raw[0], 64); if (n <= 0) { return env_result_err(0); } - var parsed: i64 = 0; + let mut parsed: i64 = 0; if (!_env_parse_i64(&raw[0], &parsed)) { return env_result_err(0); } @@ -122,14 +122,14 @@ fun env_get_i64(name: str) -> EnvResult { } fun env_get_i32(name: str) -> EnvResult { - var raw: array; - var n: i64 = env_get(name, &raw[0], 64); + let mut raw: array; + let mut n: i64 = env_get(name, &raw[0], 64); if (n <= 0) { return env_result_err(0); } - var parsed_i64: i64 = 0; + let mut parsed_i64: i64 = 0; if (!_env_parse_i64(&raw[0], &parsed_i64)) { return env_result_err(0); } diff --git a/std/env/parse.wave b/std/env/parse.wave index 54fa25ad..e5a18746 100644 --- a/std/env/parse.wave +++ b/std/env/parse.wave @@ -11,7 +11,7 @@ // AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. fun _env_key_len(name: str) -> i64 { - var n: i64 = 0; + let mut n: i64 = 0; while (name[n] != 0) { if (name[n] == 61) { @@ -24,8 +24,8 @@ fun _env_key_len(name: str) -> i64 { } fun _env_match_key(blob: ptr, start: i64, name: str, key_len: i64) -> bool { - var blob_idx: i64 = start; - var name_idx: i64 = 0; + let mut blob_idx: i64 = start; + let mut name_idx: i64 = 0; while (name_idx < key_len) { if (blob[blob_idx] != name[name_idx]) { @@ -45,14 +45,14 @@ fun _env_copy_value( dst: ptr, dst_cap: i64 ) -> i64 { - var value_len: i64 = end - begin; + let mut value_len: i64 = end - begin; if (value_len + 1 > dst_cap) { return -3; } - var src_idx: i64 = begin; - var dst_idx: i64 = 0; + let mut src_idx: i64 = begin; + let mut dst_idx: i64 = 0; while (dst_idx < value_len) { deref dst[dst_idx] = blob[src_idx]; src_idx += 1; @@ -64,8 +64,8 @@ fun _env_copy_value( } fun _env_parse_i64(raw: ptr, out_value: ptr) -> bool { - var i: i64 = 0; - var sign: i64 = 1; + let mut i: i64 = 0; + let mut sign: i64 = 1; if (raw[0] == 45) { sign = -1; @@ -78,14 +78,14 @@ fun _env_parse_i64(raw: ptr, out_value: ptr) -> bool { return false; } - var value: i64 = 0; + let mut value: i64 = 0; while (raw[i] != 0) { - var c: u8 = raw[i]; + let mut c: u8 = raw[i]; if (c < 48 || c > 57) { return false; } - var digit: i64 = c - 48; + let mut digit: i64 = c - 48; value = (value * 10) + digit; i += 1; } diff --git a/std/fs/consts.wave b/std/fs/consts.wave new file mode 100644 index 00000000..2f3c382a --- /dev/null +++ b/std/fs/consts.wave @@ -0,0 +1,16 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +// 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 diff --git a/std/fs/file.wave b/std/fs/file.wave new file mode 100644 index 00000000..88fdd584 --- /dev/null +++ b/std/fs/file.wave @@ -0,0 +1,224 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::io::consts"); +import("std::io::fd"); +import("std::sys::fs"); + +fun fs_exists(path: str) -> bool { + if (access(path, FS_F_OK) == 0) { + return true; + } + + return false; +} + +fun fs_open(path: str, flags: i32, mode: i32) -> i64 { + return open(path, flags, mode); +} + +fun fs_open_read(path: str) -> i64 { + return fs_open(path, FS_O_RDONLY, 0); +} + +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 { + return fs_open(path, FS_O_WRONLY | FS_O_CREAT | FS_O_APPEND, mode); +} + +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 { + return io_close(fd); +} + +fun fs_file_size(path: str) -> i64 { + let fd: i64 = fs_open_read(path); + if (fd < 0) { + return fd; + } + + let sz: i64 = fs_file_size_fd(fd); + let cr: i64 = io_close(fd); + + if (sz < 0) { + return sz; + } + + if (cr < 0) { + return cr; + } + + return sz; +} + +fun fs_file_size_fd(fd: i64) -> i64 { + let cur: i64 = lseek(fd, 0, FS_SEEK_CUR); + if (cur < 0) { + return cur; + } + + let end: i64 = lseek(fd, 0, FS_SEEK_END); + if (end < 0) { + lseek(fd, cur, FS_SEEK_SET); + return end; + } + + let restore: i64 = lseek(fd, cur, FS_SEEK_SET); + if (restore < 0) { + return restore; + } + + return end; +} + +fun fs_remove(path: str) -> i64 { + return unlink(path); +} + +fun fs_mkdir(path: str, mode: i32) -> i64 { + return mkdir(path, mode); +} + +fun fs_rmdir(path: str) -> i64 { + return rmdir(path); +} + +fun fs_read_all(path: str, dst: ptr, dst_cap: i64) -> i64 { + if (dst_cap < 0) { + return IO_ERR_INVALID; + } + + let fd: i64 = fs_open_read(path); + if (fd < 0) { + return fd; + } + + let sz: i64 = fs_file_size_fd(fd); + if (sz < 0) { + io_close(fd); + return sz; + } + + if (sz > dst_cap) { + io_close(fd); + return IO_ERR_NO_SPACE; + } + + let n: i64 = io_read_exact(fd, dst, sz); + let cr: i64 = io_close(fd); + + if (n < 0) { + return n; + } + + if (cr < 0) { + return cr; + } + + return n; +} + +fun fs_write_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { + if (len < 0) { + return IO_ERR_INVALID; + } + + let fd: i64 = fs_open_write(path, mode); + if (fd < 0) { + return fd; + } + + let n: i64 = io_write_all(fd, src, len); + let cr: i64 = io_close(fd); + + if (n < 0) { + return n; + } + + if (cr < 0) { + return cr; + } + + return n; +} + +fun fs_append_all(path: str, src: ptr, len: i64, mode: i32) -> i64 { + if (len < 0) { + return IO_ERR_INVALID; + } + + let fd: i64 = fs_open_append(path, mode); + if (fd < 0) { + return fd; + } + + let n: i64 = io_write_all(fd, src, len); + let cr: i64 = io_close(fd); + + if (n < 0) { + return n; + } + + if (cr < 0) { + return cr; + } + + return n; +} + +fun fs_copy( + src_path: str, + dst_path: str, + scratch: ptr, + scratch_cap: i64, + mode: i32 +) -> i64 { + if (scratch_cap <= 0) { + return IO_ERR_INVALID; + } + + let src_fd: i64 = fs_open_read(src_path); + if (src_fd < 0) { + return src_fd; + } + + let dst_fd: i64 = fs_open_write(dst_path, mode); + if (dst_fd < 0) { + io_close(src_fd); + return dst_fd; + } + + let copied: i64 = io_copy(src_fd, dst_fd, scratch, scratch_cap); + + let src_cr: i64 = io_close(src_fd); + let dst_cr: i64 = io_close(dst_fd); + + if (copied < 0) { + return copied; + } + + if (src_cr < 0) { + return src_cr; + } + + if (dst_cr < 0) { + return dst_cr; + } + + return copied; +} diff --git a/std/io/consts.wave b/std/io/consts.wave new file mode 100644 index 00000000..c02fc5aa --- /dev/null +++ b/std/io/consts.wave @@ -0,0 +1,24 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +const IO_STDIN_FD: i64 = 0; +const IO_STDOUT_FD: i64 = 1; +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; + +// interrupted system call (-EINTR) +const IO_ERR_INTR: i64 = -4; diff --git a/std/io/fd.wave b/std/io/fd.wave new file mode 100644 index 00000000..2cb1db62 --- /dev/null +++ b/std/io/fd.wave @@ -0,0 +1,170 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::io::consts"); +import("std::sys::fs"); + +fun io_close(fd: i64) -> i64 { + return close(fd); +} + +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 { + if (len < 0) { + return IO_ERR_INVALID; + } + + while (true) { + let n: i64 = read(fd, buf, len); + if (n == IO_ERR_INTR) { + continue; + } + return n; + } + + return 0; +} + +fun io_write(fd: i64, buf: ptr, len: i64) -> i64 { + if (len < 0) { + return IO_ERR_INVALID; + } + + while (true) { + let n: i64 = write(fd, buf, len); + if (n == IO_ERR_INTR) { + continue; + } + return n; + } + + return 0; +} + +fun io_write_all(fd: i64, buf: ptr, len: i64) -> i64 { + if (len < 0) { + return IO_ERR_INVALID; + } + + if (len == 0) { + return 0; + } + + let mut written: i64 = 0; + while (written < len) { + let n: i64 = io_write(fd, buf + written, len - written); + if (n < 0) { + return n; + } + + if (n == 0) { + return IO_ERR_EOF; + } + + written += n; + } + + return written; +} + +fun io_read_exact(fd: i64, buf: ptr, len: i64) -> i64 { + if (len < 0) { + return IO_ERR_INVALID; + } + + if (len == 0) { + return 0; + } + + let mut total: i64 = 0; + while (total < len) { + let n: i64 = io_read(fd, buf + total, len - total); + if (n < 0) { + return n; + } + + if (n == 0) { + return IO_ERR_EOF; + } + + total += n; + } + + return total; +} + +fun io_read_at_most(fd: i64, dst: ptr, dst_cap: i64) -> i64 { + if (dst_cap < 0) { + return IO_ERR_INVALID; + } + + if (dst_cap == 0) { + return 0; + } + + let mut total: i64 = 0; + while (total < dst_cap) { + let n: i64 = io_read(fd, dst + total, dst_cap - total); + if (n < 0) { + return n; + } + + if (n == 0) { + return total; + } + + total += n; + } + + return total; +} + +fun io_copy(src_fd: i64, dst_fd: i64, scratch: ptr, scratch_cap: i64) -> i64 { + if (scratch_cap <= 0) { + return IO_ERR_INVALID; + } + + let mut total: i64 = 0; + while (true) { + let n: i64 = io_read(src_fd, scratch, scratch_cap); + if (n < 0) { + return n; + } + + if (n == 0) { + return total; + } + + let wn: i64 = io_write_all(dst_fd, scratch, n); + if (wn < 0) { + return wn; + } + total += wn; + } + + return total; +} + +fun io_pipe(out_fds: ptr) -> i64 { + return pipe(out_fds); +} + +fun io_dup(fd: i64) -> i64 { + return dup(fd); +} + +fun io_dup2(oldfd: i64, newfd: i64) -> i64 { + return dup2(oldfd, newfd); +} diff --git a/std/manifest.json b/std/manifest.json index bcb2cdfd..870a8864 100644 --- a/std/manifest.json +++ b/std/manifest.json @@ -1,5 +1,5 @@ { "name": "std", "format": 1, - "modules": ["string", "math", "sys", "net", "libc", "time", "env", "path", "mem", "buffer"] + "modules": ["string", "math", "sys", "net", "libc", "time", "env", "path", "mem", "buffer", "io", "fs", "bytes", "process"] } diff --git a/std/math/bits.wave b/std/math/bits.wave index e21eb287..9a831d21 100644 --- a/std/math/bits.wave +++ b/std/math/bits.wave @@ -94,3 +94,106 @@ fun ilog2_ceil(x: i32) -> i32 { return ilog2_floor(x - 1) + 1; } + +fun is_pow2_i64(x: i64) -> bool { + if (x <= 0) { + return false; + } + + if ((x & (x - 1)) == 0) { + return true; + } + + return false; +} + +fun align_down_i64(x: i64, align: i64) -> i64 { + if (align <= 0) { + return x; + } + + return x & ~(align - 1); +} + +fun align_up_i64(x: i64, align: i64) -> i64 { + if (align <= 0) { + return x; + } + + return (x + (align - 1)) & ~(align - 1); +} + +fun low_bit_i64(x: i64) -> i64 { + return x & (-x); +} + +fun popcount64(x0: i64) -> i32 { + let mut x: i64 = x0; + let mut c: i32 = 0; + + while (x != 0) { + x = x & (x - 1); + c += 1; + } + + return c; +} + +fun ctz64(x: i64) -> i32 { + if (x == 0) { + return 64; + } + + let lb: i64 = low_bit_i64(x); + return popcount64(lb - 1); +} + +fun bit_length64(x0: i64) -> i32 { + if (x0 <= 0) { + return 0; + } + + let mut x: i64 = x0; + let mut n: i32 = 0; + + while (x > 0) { + x = x >> 1; + n += 1; + } + + return n; +} + +fun ilog2_floor64(x: i64) -> i32 { + if (x <= 0) { + return -1; + } + + return bit_length64(x) - 1; +} + +fun ilog2_ceil64(x: i64) -> i32 { + if (x <= 1) { + return 0; + } + + return ilog2_floor64(x - 1) + 1; +} + +fun bswap32(x: i32) -> i32 { + return ((x & 0x000000FF) << 24) + | ((x & 0x0000FF00) << 8) + | ((x & 0x00FF0000) >> 8) + | ((x & 0xFF000000) >> 24); +} + +fun bswap64(x: i64) -> i64 { + return ((x & 0x00000000000000FF) << 56) + | ((x & 0x000000000000FF00) << 40) + | ((x & 0x0000000000FF0000) << 24) + | ((x & 0x00000000FF000000) << 8) + | ((x & 0x000000FF00000000) >> 8) + | ((x & 0x0000FF0000000000) >> 24) + | ((x & 0x00FF000000000000) >> 40) + | ((x & 0xFF00000000000000) >> 56); +} diff --git a/std/math/trig.wave b/std/math/trig.wave index 187d8132..76b9fcf2 100644 --- a/std/math/trig.wave +++ b/std/math/trig.wave @@ -20,8 +20,8 @@ fun abs_f64(x: f64) -> f64 { } 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; + let mut k: i64 = (x / MATH_TWO_PI_F64) as i64; + let mut y: f64 = x - (k as f64) * MATH_TWO_PI_F64; if (y > MATH_PI_F64) { y -= MATH_TWO_PI_F64; @@ -35,12 +35,12 @@ fun wrap_angle_pi_f64(x: f64) -> f64 { } fun sin_f64(x0: f64) -> f64 { - var x: f64 = wrap_angle_pi_f64(x0); - var x2: f64 = x * x; - var x3: f64 = x * x2; - var x5: f64 = x3 * x2; - var x7: f64 = x5 * x2; - var x9: f64 = x7 * x2; + let mut x: f64 = wrap_angle_pi_f64(x0); + let mut x2: f64 = x * x; + let mut x3: f64 = x * x2; + let mut x5: f64 = x3 * x2; + let mut x7: f64 = x5 * x2; + let mut x9: f64 = x7 * x2; return x - (x3 / 6.0) @@ -50,11 +50,11 @@ fun sin_f64(x0: f64) -> f64 { } fun cos_f64(x0: f64) -> f64 { - var x: f64 = wrap_angle_pi_f64(x0); - var x2: f64 = x * x; - var x4: f64 = x2 * x2; - var x6: f64 = x4 * x2; - var x8: f64 = x6 * x2; + let mut x: f64 = wrap_angle_pi_f64(x0); + let mut x2: f64 = x * x; + let mut x4: f64 = x2 * x2; + let mut x6: f64 = x4 * x2; + let mut x8: f64 = x6 * x2; return 1.0 - (x2 / 2.0) @@ -68,12 +68,12 @@ fun sqrt_f64(x: f64) -> f64 { return 0.0; } - var g: f64 = x; + let mut g: f64 = x; if (g < 1.0) { g = 1.0; } - var i: i32 = 0; + let mut i: i32 = 0; while (i < 16) { g = 0.5 * (g + (x / g)); i += 1; diff --git a/std/mem/alloc.wave b/std/mem/alloc.wave index 1b151b28..1a77cb7f 100644 --- a/std/mem/alloc.wave +++ b/std/mem/alloc.wave @@ -11,6 +11,7 @@ // AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. import("std::sys::memory"); +import("std::mem::consts"); import("std::mem::ops"); fun mem_alloc(size: i64) -> ptr { @@ -18,12 +19,12 @@ fun mem_alloc(size: i64) -> ptr { } fun mem_alloc_zeroed(size: i64) -> ptr { - var p: ptr = mem_alloc(size); + let mut p: ptr = mem_alloc(size); if (p == null) { return null; } - var i: i64 = 0; + let mut i: i64 = 0; while (i < size) { deref p[i] = 0; i += 1; @@ -47,12 +48,12 @@ fun mem_realloc(old_ptr: ptr, old_size: i64, new_size: i64) -> ptr { return mem_alloc(new_size); } - var new_ptr: ptr = mem_alloc(new_size); + let mut new_ptr: ptr = mem_alloc(new_size); if (new_ptr == null) { return null; } - var copy_size: i64 = old_size; + let mut copy_size: i64 = old_size; if (new_size < copy_size) { copy_size = new_size; } @@ -88,12 +89,12 @@ fun mem_realloc_items( return null; } - var old_size: i64 = 0; + let mut old_size: i64 = 0; if (old_count > 0) { old_size = old_count * elem_size; } - var new_size: i64 = 0; + let mut new_size: i64 = 0; if (new_count > 0) { new_size = new_count * elem_size; } @@ -112,3 +113,98 @@ 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 { + return MEM_PAGE_SIZE; +} + +fun mem_pages_for_size(size: i64) -> i64 { + if (size <= 0) { + return 0; + } + + return (size + (MEM_PAGE_SIZE - 1)) / MEM_PAGE_SIZE; +} + +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 { + if (page_count <= 0) { + return null; + } + + return mem_alloc(page_count * MEM_PAGE_SIZE); +} + +fun mem_free_pages(p: ptr, page_count: i64) -> i64 { + if (p == null || page_count <= 0) { + return 0; + } + + return mem_free(p, page_count * MEM_PAGE_SIZE); +} + +fun mem_is_aligned(p: ptr, align: i64) -> bool { + if (p == null || align <= 0) { + return false; + } + + if (((align & (align - 1)) != 0)) { + return false; + } + + if (((p as i64) & (align - 1)) == 0) { + return true; + } + + return false; +} + +fun mem_alloc_aligned(size: i64, align: i64) -> ptr { + if (size <= 0 || align <= 0) { + return null; + } + + if (((align & (align - 1)) != 0)) { + return null; + } + + let mut real_align: i64 = align; + if (real_align < 8) { + real_align = 8; + } + + let meta_size: i64 = 16; + let total_size: i64 = size + real_align + meta_size; + let raw: ptr = mem_alloc(total_size); + if (raw == null) { + return null; + } + + let raw_addr: i64 = raw as i64; + let base_addr: i64 = raw_addr + meta_size; + let aligned_addr: i64 = (base_addr + (real_align - 1)) & ~(real_align - 1); + + let meta_raw: ptr = (aligned_addr - 16) as ptr; + let meta_total: ptr = (aligned_addr - 8) as ptr; + deref meta_raw = raw_addr; + deref meta_total = total_size; + + return aligned_addr as ptr; +} + +fun mem_free_aligned(p: ptr) -> i64 { + if (p == null) { + return 0; + } + + let addr: i64 = p as i64; + let meta_raw: ptr = (addr - 16) as ptr; + let meta_total: ptr = (addr - 8) as ptr; + + let raw_addr: i64 = deref meta_raw; + let total_size: i64 = deref meta_total; + return mem_free(raw_addr as ptr, total_size); +} diff --git a/std/mem/consts.wave b/std/mem/consts.wave index 06911be1..74e74be2 100644 --- a/std/mem/consts.wave +++ b/std/mem/consts.wave @@ -18,3 +18,6 @@ const MEM_MAP_PRIVATE: i64 = 2; const MEM_MAP_ANON: i64 = 32; const MEM_PAGE_SIZE: i64 = 4096; + +const MEM_ERR_INVALID: i64 = -4096; +const MEM_ERR_NO_SPACE: i64 = -4097; diff --git a/std/mem/cstr.wave b/std/mem/cstr.wave index 98479acb..01a4a0ef 100644 --- a/std/mem/cstr.wave +++ b/std/mem/cstr.wave @@ -11,7 +11,7 @@ // AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. fun mem_len_cstr(s: str) -> i64 { - var i: i64 = 0; + let mut i: i64 = 0; while (s[i] != 0) { i += 1; @@ -21,7 +21,7 @@ fun mem_len_cstr(s: str) -> i64 { } fun mem_copy_cstr(dst: ptr, s: str) -> i64 { - var i: i64 = 0; + let mut i: i64 = 0; while (s[i] != 0) { deref dst[i] = s[i]; @@ -37,8 +37,8 @@ fun mem_copy_cstr_n(dst: ptr, dst_cap: i64, s: str) -> i64 { return -1; } - var i: i64 = 0; - var max_copy: i64 = dst_cap - 1; + let mut i: i64 = 0; + let mut max_copy: i64 = dst_cap - 1; while (s[i] != 0 && i < max_copy) { deref dst[i] = s[i]; @@ -50,7 +50,7 @@ fun mem_copy_cstr_n(dst: ptr, dst_cap: i64, s: str) -> i64 { } fun mem_eq_cstr(a: str, b: str) -> bool { - var i: i64 = 0; + let mut i: i64 = 0; while (true) { if (a[i] != b[i]) { return false; @@ -65,7 +65,7 @@ fun mem_eq_cstr(a: str, b: str) -> bool { } fun mem_starts_with_cstr(s: str, prefix: str) -> bool { - var i: i64 = 0; + let mut i: i64 = 0; while (prefix[i] != 0) { if (s[i] != prefix[i]) { return false; @@ -76,7 +76,7 @@ fun mem_starts_with_cstr(s: str, prefix: str) -> bool { } fun mem_find_cstr_char(s: str, c: u8) -> i64 { - var i: i64 = 0; + let mut i: i64 = 0; while (s[i] != 0) { if (s[i] == c) { return i; diff --git a/std/mem/ops.wave b/std/mem/ops.wave index 64ac74d7..6fb8d691 100644 --- a/std/mem/ops.wave +++ b/std/mem/ops.wave @@ -10,8 +10,10 @@ // SPDX-License-Identifier: MPL-2.0 // AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. +import("std::mem::consts"); + fun mem_set(dst: ptr, value: u8, size: i64) { - var i: i64 = 0; + let mut i: i64 = 0; while (i < size) { deref dst[i] = value; i += 1; @@ -23,7 +25,7 @@ fun mem_zero(dst: ptr, size: i64) { } fun mem_copy(dst: ptr, src: ptr, size: i64) { - var i: i64 = 0; + let mut i: i64 = 0; while (i < size) { deref dst[i] = src[i]; i += 1; @@ -40,7 +42,7 @@ fun mem_move(dst: ptr, src: ptr, size: i64) { } if ((dst as i64) < (src as i64)) { - var i: i64 = 0; + let mut i: i64 = 0; while (i < size) { deref dst[i] = src[i]; i += 1; @@ -48,7 +50,7 @@ fun mem_move(dst: ptr, src: ptr, size: i64) { return; } - var i: i64 = size; + let mut i: i64 = size; while (i > 0) { i -= 1; deref dst[i] = src[i]; @@ -56,11 +58,11 @@ fun mem_move(dst: ptr, src: ptr, size: i64) { } fun mem_cmp(a: ptr, b: ptr, size: i64) -> i32 { - var i: i64 = 0; + let mut i: i64 = 0; while (i < size) { - var av: u8 = a[i]; - var bv: u8 = b[i]; + let mut av: u8 = a[i]; + let mut bv: u8 = b[i]; if (av < bv) { return -1; @@ -85,7 +87,7 @@ fun mem_eq(a: ptr, b: ptr, size: i64) -> bool { } fun mem_find_byte(src: ptr, size: i64, value: u8) -> i64 { - var i: i64 = 0; + let mut i: i64 = 0; while (i < size) { if (src[i] == value) { return i; @@ -96,7 +98,7 @@ fun mem_find_byte(src: ptr, size: i64, value: u8) -> i64 { } fun mem_swap(a: ptr, b: ptr) { - var tmp: T = deref a; + let mut tmp: T = deref a; deref a = deref b; deref b = tmp; } @@ -114,7 +116,7 @@ fun mem_set_items(dst: ptr, value: T, count: i64, elem_size: i64) { return; } - var i: i64 = 0; + let mut i: i64 = 0; while (i < count) { deref dst[i] = value; i += 1; @@ -136,3 +138,58 @@ 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 { + if (dst_cap < 0 || src_len < 0) { + return MEM_ERR_INVALID; + } + + if (src_len > dst_cap) { + return MEM_ERR_NO_SPACE; + } + + if (src_len == 0) { + return 0; + } + + mem_copy(dst, src, src_len); + return src_len; +} + +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; + } + + if (src_len > dst_cap) { + return MEM_ERR_NO_SPACE; + } + + if (src_len == 0) { + return 0; + } + + mem_move(dst, src, src_len); + return src_len; +} + +fun mem_set_checked(dst: ptr, dst_cap: i64, value: u8, len: i64) -> i64 { + if (dst_cap < 0 || len < 0) { + return MEM_ERR_INVALID; + } + + if (len > dst_cap) { + return MEM_ERR_NO_SPACE; + } + + if (len == 0) { + return 0; + } + + mem_set(dst, value, len); + return len; +} + +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 611ba14b..acd07ac7 100644 --- a/std/net/address.wave +++ b/std/net/address.wave @@ -44,7 +44,7 @@ fun net_ntohl(x: i32) -> i32 { } fun net_addr_v4(ip_host_order: i32, port_host_order: i16) -> NetAddrV4 { - var addr_value: NetAddrV4; + let mut addr_value: NetAddrV4; addr_value.ip = net_htonl(ip_host_order); addr_value.port = net_htons(port_host_order); return addr_value; diff --git a/std/net/poll.wave b/std/net/poll.wave new file mode 100644 index 00000000..6baf1cdc --- /dev/null +++ b/std/net/poll.wave @@ -0,0 +1,69 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::sys::socket"); + +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; + +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 { + let mut pfd: PollFd = PollFd { + fd: fd as i32, + events: NET_POLLIN, + revents: 0 + }; + + let r: i64 = net_poll(&pfd, 1, timeout_ms); + if (r <= 0) { + return r; + } + + if ((pfd.revents & (NET_POLLERR | NET_POLLHUP | NET_POLLNVAL)) != 0) { + return -1; + } + + if ((pfd.revents & NET_POLLIN) != 0) { + return 1; + } + + return 0; +} + +fun net_wait_writable(fd: i64, timeout_ms: i32) -> i64 { + let mut pfd: PollFd = PollFd { + fd: fd as i32, + events: NET_POLLOUT, + revents: 0 + }; + + let r: i64 = net_poll(&pfd, 1, timeout_ms); + if (r <= 0) { + return r; + } + + if ((pfd.revents & (NET_POLLERR | NET_POLLHUP | NET_POLLNVAL)) != 0) { + return -1; + } + + if ((pfd.revents & NET_POLLOUT) != 0) { + return 1; + } + + return 0; +} diff --git a/std/net/socket_base.wave b/std/net/socket_base.wave index aed59bf0..5147aaa1 100644 --- a/std/net/socket_base.wave +++ b/std/net/socket_base.wave @@ -22,7 +22,7 @@ fun net_fd_valid(fd: i64) -> bool { } fun net_set_reuseaddr(fd: i64) -> i64 { - var one: i32 = 1; + let mut one: i32 = 1; return setsockopt( fd, SOL_SOCKET, @@ -41,20 +41,20 @@ fun net_socket_udp_v4() -> i64 { } fun net_bind_v4(fd: i64, addr: NetAddrV4) -> i64 { - var sa: NetSockAddrIn = net_to_sockaddr_v4(addr); + let mut sa: NetSockAddrIn = net_to_sockaddr_v4(addr); return bind(fd, &sa, 16); } fun net_connect_v4(fd: i64, addr: NetAddrV4) -> i64 { - var sa: NetSockAddrIn = net_to_sockaddr_v4(addr); + let mut sa: NetSockAddrIn = net_to_sockaddr_v4(addr); return connect(fd, &sa, 16); } fun net_accept_v4(fd: i64, out_addr: ptr) -> i64 { - var sa: NetSockAddrIn; - var salen: i32 = 16; + let mut sa: NetSockAddrIn; + let mut salen: i32 = 16; - var cfd: i64 = accept(fd, &sa, &salen); + let mut cfd: i64 = accept(fd, &sa, &salen); if (cfd >= 0 && out_addr != null) { deref out_addr = net_from_sockaddr_v4(sa); } @@ -67,9 +67,9 @@ fun net_send_all(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return 0; } - var sent_total: i64 = 0; + let mut sent_total: i64 = 0; while (sent_total < len) { - var n: i64 = send(fd, buf + sent_total, len - sent_total, flags); + let mut n: i64 = send(fd, buf + sent_total, len - sent_total, flags); if (n <= 0) { if (sent_total == 0) { return n; @@ -86,9 +86,9 @@ fun net_recv_exact(fd: i64, buf: ptr, len: i64, flags: i32) -> i64 { return 0; } - var read_total: i64 = 0; + let mut read_total: i64 = 0; while (read_total < len) { - var n: i64 = recv(fd, buf + read_total, len - read_total, flags); + let mut n: i64 = recv(fd, buf + read_total, len - read_total, flags); if (n <= 0) { if (read_total == 0) { return n; @@ -107,7 +107,7 @@ fun net_sendto_v4( len: i64, flags: i32 ) -> i64 { - var sa: NetSockAddrIn = net_to_sockaddr_v4(addr); + let mut sa: NetSockAddrIn = net_to_sockaddr_v4(addr); return sendto(fd, buf, len, flags, &sa, 16); } @@ -118,10 +118,10 @@ fun net_recvfrom_v4( flags: i32, out_addr: ptr ) -> i64 { - var sa: NetSockAddrIn; - var salen: i32 = 16; + let mut sa: NetSockAddrIn; + let mut salen: i32 = 16; - var n: i64 = recvfrom(fd, buf, len, flags, &sa, &salen); + let mut n: i64 = recvfrom(fd, buf, len, flags, &sa, &salen); if (n >= 0 && out_addr != null) { deref out_addr = net_from_sockaddr_v4(sa); } diff --git a/std/net/socketopt.wave b/std/net/socketopt.wave new file mode 100644 index 00000000..106f360f --- /dev/null +++ b/std/net/socketopt.wave @@ -0,0 +1,96 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::sys::fs"); +import("std::sys::socket"); + +fun net_set_nonblock(fd: i64, enabled: i32) -> i64 { + let flags_raw: i64 = fcntl(fd, FS_F_GETFL, 0); + if (flags_raw < 0) { + return flags_raw; + } + + let mut flags: i32 = flags_raw as i32; + if (enabled != 0) { + flags = flags | FS_O_NONBLOCK; + } else { + flags = flags & ~FS_O_NONBLOCK; + } + + return fcntl(fd, FS_F_SETFL, flags as i64); +} + +fun net_get_nonblock(fd: i64) -> i64 { + let flags_raw: i64 = fcntl(fd, FS_F_GETFL, 0); + if (flags_raw < 0) { + return flags_raw; + } + + let flags: i32 = flags_raw as i32; + if ((flags & FS_O_NONBLOCK) != 0) { + return 1; + } + + return 0; +} + +fun net_set_reuseaddr_flag(fd: i64, enabled: i32) -> i64 { + let mut value: i32 = 0; + if (enabled != 0) { + value = 1; + } + + return setsockopt( + fd, + SOL_SOCKET, + SO_REUSEADDR, + &value as ptr, + 4 + ); +} + +fun net_get_reuseaddr_flag(fd: i64) -> i64 { + let mut value: i32 = 0; + let mut size: i32 = 4; + + let r: i64 = getsockopt( + fd, + SOL_SOCKET, + SO_REUSEADDR, + &value as ptr, + &size + ); + if (r < 0) { + return r; + } + + if (value != 0) { + return 1; + } + + return 0; +} + +fun net_set_reuseport_flag(fd: i64, enabled: i32) -> i64 { + let mut value: i32 = 0; + if (enabled != 0) { + value = 1; + } + + return setsockopt( + fd, + SOL_SOCKET, + SO_REUSEPORT, + &value as ptr, + 4 + ); +} diff --git a/std/net/tcp.wave b/std/net/tcp.wave index 701a9761..e7f9f7b2 100644 --- a/std/net/tcp.wave +++ b/std/net/tcp.wave @@ -13,6 +13,8 @@ import("std::sys::socket"); import("std::net::address"); import("std::net::socket_base"); +import("std::net::socketopt"); +import("std::string::len"); struct TcpAddr { ip: i32; // network byte order @@ -27,23 +29,15 @@ struct TcpStream { fd: i64; } -fun _str_len(s: str) -> i64 { - var n: i64 = 0; - while (s[n] != 0) { - n += 1; - } - return n; -} - fun _tcp_to_net_addr(addr: TcpAddr) -> NetAddrV4 { - var value: NetAddrV4; + let mut value: NetAddrV4; value.ip = addr.ip; value.port = addr.port; return value; } fun _tcp_from_net_addr(addr: NetAddrV4) -> TcpAddr { - var value: TcpAddr; + let mut value: TcpAddr; value.ip = addr.ip; value.port = addr.port; return value; @@ -78,12 +72,12 @@ fun tcp_bind(port: i16) -> TcpListener { } fun tcp_bind_with_backlog(port: i16, backlog: i32) -> TcpListener { - var addr: TcpAddr = tcp_addr_any(port); + let mut addr: TcpAddr = tcp_addr_any(port); return tcp_bind_addr(addr, backlog); } fun tcp_bind_addr(addr: TcpAddr, backlog: i32) -> TcpListener { - var fd: i64 = net_socket_tcp_v4(); + let mut fd: i64 = net_socket_tcp_v4(); net_set_reuseaddr(fd); net_bind_v4(fd, _tcp_to_net_addr(addr)); @@ -93,13 +87,13 @@ fun tcp_bind_addr(addr: TcpAddr, backlog: i32) -> TcpListener { } fun tcp_accept(listener: TcpListener) -> TcpStream { - var cfd: i64 = net_accept_v4(listener.fd, null); + let mut cfd: i64 = net_accept_v4(listener.fd, null); return TcpStream { fd: cfd }; } fun tcp_accept_addr(listener: TcpListener, src: ptr) -> TcpStream { - var peer: NetAddrV4; - var cfd: i64 = net_accept_v4(listener.fd, &peer); + let mut peer: NetAddrV4; + let mut cfd: i64 = net_accept_v4(listener.fd, &peer); if (cfd >= 0) { deref src = _tcp_from_net_addr(peer); } @@ -111,14 +105,14 @@ fun tcp_close_listener(listener: TcpListener) { } fun tcp_connect(addr: TcpAddr) -> TcpStream { - var fd: i64 = net_socket_tcp_v4(); + let mut 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 { - var fd: i64 = net_socket_tcp_v4(); - var r: i64 = net_connect_v4(fd, _tcp_to_net_addr(addr)); + let mut fd: i64 = net_socket_tcp_v4(); + let mut r: i64 = net_connect_v4(fd, _tcp_to_net_addr(addr)); if (r < 0) { close(fd); return r; @@ -143,13 +137,29 @@ fun tcp_read_exact(stream: TcpStream, buf: ptr, len: i64) -> i64 { } fun tcp_write_str(stream: TcpStream, s: str) -> i64 { - var n: i64 = _str_len(s); + let mut n: i64 = len(s) as i64; if (n <= 0) { return 0; } return tcp_write_all(stream, s as ptr, n); } +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 { + return net_get_nonblock(stream.fd); +} + +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 { + return net_get_nonblock(listener.fd); +} + fun tcp_from_fd(fd: i64) -> TcpStream { return TcpStream { fd: fd }; } diff --git a/std/net/udp.wave b/std/net/udp.wave index a1d21312..aa8aebef 100644 --- a/std/net/udp.wave +++ b/std/net/udp.wave @@ -13,6 +13,8 @@ import("std::sys::socket"); import("std::net::address"); import("std::net::socket_base"); +import("std::net::socketopt"); +import("std::string::len"); struct UdpAddr { ip: i32; // network byte order @@ -23,23 +25,15 @@ struct UdpSocket { fd: i64; } -fun _str_len(s: str) -> i64 { - var n: i64 = 0; - while (s[n] != 0) { - n += 1; - } - return n; -} - fun _udp_to_net_addr(addr: UdpAddr) -> NetAddrV4 { - var value: NetAddrV4; + let mut value: NetAddrV4; value.ip = addr.ip; value.port = addr.port; return value; } fun _udp_from_net_addr(addr: NetAddrV4) -> UdpAddr { - var value: UdpAddr; + let mut value: UdpAddr; value.ip = addr.ip; value.port = addr.port; return value; @@ -74,7 +68,7 @@ fun udp_bind(port: i16) -> UdpSocket { } fun udp_bind_addr(addr: UdpAddr) -> UdpSocket { - var fd: i64 = net_socket_udp_v4(); + let mut fd: i64 = net_socket_udp_v4(); net_set_reuseaddr(fd); net_bind_v4(fd, _udp_to_net_addr(addr)); return UdpSocket { fd: fd }; @@ -94,7 +88,7 @@ fun udp_send_to( } fun udp_send_str_to(sock: UdpSocket, addr: UdpAddr, s: str) -> i64 { - var n: i64 = _str_len(s); + let mut n: i64 = len(s) as i64; if (n <= 0) { return 0; } @@ -111,8 +105,8 @@ fun udp_recv_from( len: i64, src: ptr ) -> i64 { - var peer: NetAddrV4; - var n: i64 = net_recvfrom_v4(sock.fd, buf, len, 0, &peer); + let mut peer: NetAddrV4; + let mut n: i64 = net_recvfrom_v4(sock.fd, buf, len, 0, &peer); if (n >= 0) { deref src = _udp_from_net_addr(peer); } @@ -126,3 +120,11 @@ fun udp_from_fd(fd: i64) -> UdpSocket { fun udp_socket_valid(sock: UdpSocket) -> bool { return net_fd_valid(sock.fd); } + +fun udp_set_nonblock(sock: UdpSocket, enabled: i32) -> i64 { + return net_set_nonblock(sock.fd, enabled); +} + +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 7475f411..deb54494 100644 --- a/std/path/analyze.wave +++ b/std/path/analyze.wave @@ -13,8 +13,8 @@ import("std::path::core"); fun path_basename_start(path: str) -> i32 { - var i: i32 = 0; - var base: i32 = 0; + let mut i: i32 = 0; + let mut base: i32 = 0; while (path[i] != 0) { if (path_is_sep(path[i])) { @@ -27,20 +27,20 @@ fun path_basename_start(path: str) -> i32 { } fun path_basename_len(path: str) -> i32 { - var base: i32 = path_basename_start(path); - var end: i32 = path_len(path); + let mut base: i32 = path_basename_start(path); + let mut end: i32 = path_len(path); return end - base; } fun path_dirname_len(path: str) -> i32 { - var base: i32 = path_basename_start(path); + let mut base: i32 = path_basename_start(path); if (base <= 0) { return 0; } - var i: i32 = base - 1; + let mut i: i32 = base - 1; while (i > 0 && path_is_sep(path[i])) { i -= 1; @@ -54,9 +54,9 @@ fun path_dirname_len(path: str) -> i32 { } fun path_ext_start(path: str) -> i32 { - var base: i32 = path_basename_start(path); - var i: i32 = base; - var dot: i32 = -1; + let mut base: i32 = path_basename_start(path); + let mut i: i32 = base; + let mut dot: i32 = -1; while (path[i] != 0) { if (path[i] == 46) { diff --git a/std/path/copy.wave b/std/path/copy.wave index 25545820..75e2883a 100644 --- a/std/path/copy.wave +++ b/std/path/copy.wave @@ -14,28 +14,28 @@ import("std::path::core"); import("std::path::analyze"); 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); + let mut ll: i32 = path_len(left); + let mut rl: i32 = path_len(right); - var need_sep: i32 = 0; + let mut need_sep: i32 = 0; if (ll > 0 && rl > 0) { - var last_left_idx: i32 = ll; + let mut last_left_idx: i32 = ll; last_left_idx -= 1; - var lc: u8 = left[last_left_idx]; - var rc: u8 = right[0]; + let mut lc: u8 = left[last_left_idx]; + let mut rc: u8 = right[0]; if (!path_is_sep(lc) && !path_is_sep(rc)) { need_sep = 1; } } - var total: i32 = ll + rl + need_sep; + let mut total: i32 = ll + rl + need_sep; if (total + 1 > dst_cap) { return -1; } - var k: i32 = 0; + let mut k: i32 = 0; while (k < ll) { deref dst[k] = left[k]; k += 1; @@ -46,8 +46,8 @@ fun path_join2(dst: ptr, dst_cap: i32, left: str, right: str) -> i32 { k += 1; } - var src_idx: i32 = 0; - var dst_idx: i32 = k; + let mut src_idx: i32 = 0; + let mut dst_idx: i32 = k; while (src_idx < rl) { deref dst[dst_idx] = right[src_idx]; src_idx += 1; @@ -59,15 +59,15 @@ fun path_join2(dst: ptr, dst_cap: i32, left: str, right: str) -> i32 { } 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); + let mut start: i32 = path_basename_start(path); + let mut n: i32 = path_basename_len(path); if (n + 1 > dst_cap) { return -1; } - var src_idx: i32 = start; - var dst_idx: i32 = 0; + let mut src_idx: i32 = start; + let mut dst_idx: i32 = 0; while (dst_idx < n) { deref dst[dst_idx] = path[src_idx]; src_idx += 1; @@ -79,7 +79,7 @@ fun path_basename_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { } fun path_dirname_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { - var n: i32 = path_dirname_len(path); + let mut n: i32 = path_dirname_len(path); if (n + 1 > dst_cap) { return -1; @@ -91,7 +91,7 @@ fun path_dirname_copy(dst: ptr, dst_cap: i32, path: str) -> i32 { return 1; } - var i: i32 = 0; + let mut i: i32 = 0; while (i < n) { deref dst[i] = path[i]; i += 1; diff --git a/std/path/core.wave b/std/path/core.wave index 9d70b9b6..d61b933a 100644 --- a/std/path/core.wave +++ b/std/path/core.wave @@ -23,7 +23,7 @@ fun path_is_sep(c: u8) -> bool { } fun path_len(path: str) -> i32 { - var i: i32 = 0; + let mut i: i32 = 0; while (path[i] != 0) { i += 1; diff --git a/std/process/consts.wave b/std/process/consts.wave new file mode 100644 index 00000000..ec498f90 --- /dev/null +++ b/std/process/consts.wave @@ -0,0 +1,18 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +const PROC_ERR_INTR: i64 = -4; + +const PROC_WAIT_NOHANG: i32 = 1; + +const PROC_EXIT_EXEC_FAIL: i32 = 127; +const PROC_EXIT_DUP_FAIL: i32 = 126; diff --git a/std/process/core.wave b/std/process/core.wave new file mode 100644 index 00000000..4056ecfa --- /dev/null +++ b/std/process/core.wave @@ -0,0 +1,50 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::process::consts"); +import("std::sys::process"); + +fun proc_exit(code: i32) -> ! { + exit(code); +} + +fun proc_getpid() -> i64 { + return getpid(); +} + +fun proc_getppid() -> i64 { + return getppid(); +} + +fun proc_fork() -> i64 { + return fork(); +} + +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 { + while (true) { + let r: i64 = waitpid(pid, status, options); + if (r == PROC_ERR_INTR) { + continue; + } + return r; + } + + return -1; +} + +fun proc_kill(pid: i64, sig: i32) -> i64 { + return kill(pid, sig); +} diff --git a/std/process/spawn.wave b/std/process/spawn.wave new file mode 100644 index 00000000..2fb71c75 --- /dev/null +++ b/std/process/spawn.wave @@ -0,0 +1,137 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::io::consts"); +import("std::io::fd"); +import("std::process::consts"); +import("std::process::core"); + +struct ProcPipeResult { + status: i64; + read_fd: i64; + write_fd: i64; +} + +struct ProcSpawnStdoutResult { + status: i64; + pid: i64; + read_fd: i64; +} + +fun _proc_dup_child_fd(src_fd: i64, dst_fd: i64) -> i64 { + if (src_fd < 0) { + return 0; + } + + if (src_fd == dst_fd) { + return 0; + } + + let r: i64 = io_dup2(src_fd, dst_fd); + if (r < 0) { + return r; + } + + io_close(src_fd); + return 0; +} + +fun proc_spawn_exec_raw( + path: str, + argv: ptr>, + envp: ptr>, + stdin_fd: i64, + stdout_fd: i64, + stderr_fd: i64 +) -> i64 { + let pid: i64 = proc_fork(); + if (pid < 0) { + return pid; + } + + if (pid == 0) { + if (_proc_dup_child_fd(stdin_fd, IO_STDIN_FD) < 0) { + proc_exit(PROC_EXIT_DUP_FAIL); + } + + if (_proc_dup_child_fd(stdout_fd, IO_STDOUT_FD) < 0) { + proc_exit(PROC_EXIT_DUP_FAIL); + } + + if (_proc_dup_child_fd(stderr_fd, IO_STDERR_FD) < 0) { + proc_exit(PROC_EXIT_DUP_FAIL); + } + + let er: i64 = proc_execve(path, argv, envp); + if (er < 0) { + proc_exit(PROC_EXIT_EXEC_FAIL); + } + + proc_exit(0); + } + + return pid; +} + +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 { + let mut fds: array; + let r: i64 = io_pipe(&fds[0]); + if (r < 0) { + return ProcPipeResult { + status: r, + read_fd: -1, + write_fd: -1 + }; + } + + return ProcPipeResult { + status: 0, + read_fd: fds[0] as i64, + write_fd: fds[1] as i64 + }; +} + +fun proc_spawn_capture_stdout( + path: str, + argv: ptr>, + envp: ptr> +) -> ProcSpawnStdoutResult { + let pipe_r: ProcPipeResult = proc_make_pipe(); + if (pipe_r.status < 0) { + return ProcSpawnStdoutResult { + status: pipe_r.status, + pid: -1, + read_fd: -1 + }; + } + + let pid: i64 = proc_spawn_exec_raw(path, argv, envp, -1, pipe_r.write_fd, -1); + io_close(pipe_r.write_fd); + if (pid < 0) { + io_close(pipe_r.read_fd); + return ProcSpawnStdoutResult { + status: pid, + pid: -1, + read_fd: -1 + }; + } + + return ProcSpawnStdoutResult { + status: 0, + pid: pid, + read_fd: pipe_r.read_fd + }; +} diff --git a/std/process/wait.wave b/std/process/wait.wave new file mode 100644 index 00000000..9a324292 --- /dev/null +++ b/std/process/wait.wave @@ -0,0 +1,67 @@ +// This file is part of the Wave language project. +// Copyright (c) 2024-2026 Wave Foundation +// Copyright (c) 2024-2026 LunaStev and contributors +// +// This Source Code Form is subject to the terms of the +// Mozilla Public License, v. 2.0. +// If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. +// +// SPDX-License-Identifier: MPL-2.0 +// AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. + +import("std::process::consts"); +import("std::process::core"); + +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 { + return proc_waitpid_raw(pid, out_status, PROC_WAIT_NOHANG); +} + +fun proc_status_exited(status: i32) -> bool { + if ((status & 127) == 0) { + return true; + } + + return false; +} + +fun proc_status_exit_code(status: i32) -> i32 { + return (status >> 8) & 255; +} + +fun proc_status_signaled(status: i32) -> bool { + let sig: i32 = status & 127; + if (sig != 0 && sig != 127) { + return true; + } + + return false; +} + +fun proc_status_term_signal(status: i32) -> i32 { + return status & 127; +} + +fun proc_status_stopped(status: i32) -> bool { + if ((status & 255) == 127) { + return true; + } + + return false; +} + +fun proc_status_stop_signal(status: i32) -> i32 { + return (status >> 8) & 255; +} + +fun proc_status_continued(status: i32) -> bool { + if (status == 65535) { + return true; + } + + return false; +} diff --git a/std/string/cmp.wave b/std/string/cmp.wave index f76b36ea..4eedf9da 100644 --- a/std/string/cmp.wave +++ b/std/string/cmp.wave @@ -87,7 +87,8 @@ fun ends_with(s: str, suffix: str) -> bool { let mut i: i32 = 0; while (i < tl) { - if (s[sl - tl + i] != suffix[i]) { + let idx: i32 = (sl - tl) + i; + if (s[idx] != suffix[i]) { return false; } diff --git a/std/string/find.wave b/std/string/find.wave index 7fdd4fa6..a70c4213 100644 --- a/std/string/find.wave +++ b/std/string/find.wave @@ -87,7 +87,8 @@ fun find(s: str, needle: str) -> i32 { let mut ok: bool = true; while (j < nl) { - if (s[i + j] != needle[j]) { + let idx: i32 = i + j; + if (s[idx] != needle[j]) { ok = false; j = nl; } else { @@ -140,7 +141,8 @@ fun count(s: str, needle: str) -> i32 { let mut ok: bool = true; while (j < nl) { - if (s[i + j] != needle[j]) { + let idx: i32 = i + j; + if (s[idx] != needle[j]) { ok = false; j = nl; } else { diff --git a/std/string/hash.wave b/std/string/hash.wave index 4433a909..cff9bc5d 100644 --- a/std/string/hash.wave +++ b/std/string/hash.wave @@ -11,11 +11,11 @@ // AI TRAINING NOTICE: Prohibited without prior written permission. No use for machine learning or generative AI training, fine-tuning, distillation, embedding, or dataset creation. fun djb2_32(s: str) -> i32 { - var h: i32 = 5381; - var i: i32 = 0; + let mut h: i32 = 5381; + let mut i: i32 = 0; while (s[i] != 0) { - var c: i32 = s[i]; + let mut c: i32 = s[i]; h = ((h << 5) + h) ^ c; i += 1; } @@ -24,11 +24,11 @@ fun djb2_32(s: str) -> i32 { } fun fnv1a_64(s: str) -> i64 { - var h: i64 = 1469598103934665603; - var i: i32 = 0; + let mut h: i64 = 1469598103934665603; + let mut i: i32 = 0; while (s[i] != 0) { - var c: i64 = s[i]; + let mut c: i64 = s[i]; h = h ^ c; h = h * 1099511628211; i += 1; diff --git a/std/sys/linux/env.wave b/std/sys/linux/env.wave index e9596df5..4bf02c79 100644 --- a/std/sys/linux/env.wave +++ b/std/sys/linux/env.wave @@ -17,12 +17,12 @@ fun env_read(buf: ptr, cap: i64) -> i64 { return -22; } - var fd: i64 = open("/proc/self/environ", 0, 0); + let mut fd: i64 = open("/proc/self/environ", 0, 0); if (fd < 0) { return fd; } - var n: i64 = read(fd, buf, cap); + let mut n: i64 = read(fd, buf, cap); close(fd); return n; } diff --git a/std/sys/linux/fs.wave b/std/sys/linux/fs.wave index 78af60e3..37805d53 100644 --- a/std/sys/linux/fs.wave +++ b/std/sys/linux/fs.wave @@ -23,6 +23,30 @@ import("std::sys::linux::syscall"); +// 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; + +// 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; + +// seek modes +const FS_SEEK_SET: i32 = 0; +const FS_SEEK_CUR: i32 = 1; +const FS_SEEK_END: i32 = 2; + +// fcntl modes +const FS_F_GETFL: i32 = 3; +const FS_F_SETFL: i32 = 4; // ----------------------- // open / close @@ -38,6 +62,31 @@ fun close(fd: i64) -> i64 { return syscall1(3, fd); } +fun dup(fd: i64) -> i64 { + // syscall: dup (32) + return syscall1(32, fd); +} + +fun dup2(oldfd: i64, newfd: i64) -> i64 { + // syscall: dup2 (33) + return syscall2(33, oldfd, newfd); +} + +fun pipe(fds: ptr) -> i64 { + // syscall: pipe (22) + return syscall1(22, fds as i64); +} + +fun fsync(fd: i64) -> i64 { + // syscall: fsync (74) + return syscall1(74, fd); +} + +fun fcntl(fd: i64, cmd: i32, arg: i64) -> i64 { + // syscall: fcntl (72) + return syscall3(72, fd, cmd as i64, arg); +} + // ----------------------- // read / write diff --git a/std/sys/linux/memory.wave b/std/sys/linux/memory.wave index a82710e9..8c168523 100644 --- a/std/sys/linux/memory.wave +++ b/std/sys/linux/memory.wave @@ -30,7 +30,7 @@ const MAP_ANONYMOUS: i32 = 32; // ----------------------- // mmap / munmap // ----------------------- - +// 3b1b9d23-75fc-4c50-87a4-11865458c2ba fun mmap( addr: ptr, length: i64, @@ -71,7 +71,7 @@ fun sys_alloc(size: i64) -> ptr { return null; } - var p: ptr = mmap( + let mut p: ptr = mmap( null, size, PROT_READ | PROT_WRITE, diff --git a/std/sys/linux/socket.wave b/std/sys/linux/socket.wave index 90e7c80b..d2ccb197 100644 --- a/std/sys/linux/socket.wave +++ b/std/sys/linux/socket.wave @@ -47,6 +47,20 @@ const SHUT_RDWR: i32 = 2; // socket options const SOL_SOCKET: i32 = 1; const SO_REUSEADDR: i32 = 2; +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; + +struct PollFd { + fd: i32; + events: i16; + revents: i16; +} // ----------------------- @@ -101,6 +115,29 @@ fun setsockopt( ); } +fun getsockopt( + fd: i64, + level: i32, + optname: i32, + optval: ptr, + optlen: ptr +) -> i64 { + // syscall: getsockopt (55) + return syscall5( + 55, + fd, + level as i64, + optname as i64, + optval as i64, + optlen as i64 + ); +} + +fun poll(fds: ptr, nfds: i64, timeout_ms: i32) -> i64 { + // syscall: poll (7) + return syscall3(7, fds as i64, nfds, timeout_ms as i64); +} + // ----------------------- // send / recv diff --git a/std/sys/linux/syscall.wave b/std/sys/linux/syscall.wave index 2ab3706d..c1070e20 100644 --- a/std/sys/linux/syscall.wave +++ b/std/sys/linux/syscall.wave @@ -27,7 +27,7 @@ // syscall with 0 args // ----------------------- fun syscall0(id: i64) -> i64 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id @@ -41,7 +41,7 @@ fun syscall0(id: i64) -> i64 { // syscall with 1 arg // ----------------------- fun syscall1(id: i64, a1: i64) -> i64 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id @@ -56,7 +56,7 @@ fun syscall1(id: i64, a1: i64) -> i64 { // syscall with 2 args // ----------------------- fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id @@ -72,7 +72,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 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id @@ -89,7 +89,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 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id @@ -107,7 +107,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 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id @@ -134,7 +134,7 @@ fun syscall6( a5: i64, a6: i64 ) -> i64 { - var ret: i64; + let mut ret: i64; asm { "syscall" in("rax") id diff --git a/std/sys/linux/tty.wave b/std/sys/linux/tty.wave index d6f5a26e..b46fb58e 100644 --- a/std/sys/linux/tty.wave +++ b/std/sys/linux/tty.wave @@ -63,7 +63,7 @@ fun tty_getattr(fd: i32, t: ptr) -> i64 { } fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { - var req: i64 = TTY_TCSETS; + let mut req: i64 = TTY_TCSETS; if (action == TTY_TCSADRAIN) { req = TTY_TCSETSW; @@ -85,12 +85,12 @@ fun tty_setfl(fd: i32, flags: i32) -> i64 { fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { deref st.active = 0; - var orig: Termios; + let mut orig: Termios; if (tty_getattr(fd, &orig) < 0) { return -1; } - var raw: Termios = orig; + let mut raw: Termios = orig; raw.c_lflag = raw.c_lflag & ~(TTY_ICANON | TTY_ECHO); raw.c_cc[6] = 0; raw.c_cc[5] = 0; @@ -99,13 +99,13 @@ fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { return -1; } - var flags_raw: i64 = tty_getfl(fd); + let mut flags_raw: i64 = tty_getfl(fd); if (flags_raw < 0) { tty_setattr(fd, TTY_TCSANOW, &orig); return -1; } - var old_flags: i32 = flags_raw as i32; + let mut old_flags: i32 = flags_raw as i32; if (tty_setfl(fd, old_flags | TTY_O_NONBLOCK) < 0) { tty_setattr(fd, TTY_TCSANOW, &orig); return -1; @@ -122,7 +122,7 @@ fun tty_restore(fd: i32, st: ptr) { return; } - var orig: Termios = deref st.term; + let mut orig: Termios = deref st.term; tty_setattr(fd, TTY_TCSANOW, &orig); tty_setfl(fd, deref st.flags); } diff --git a/std/sys/macos/fs.wave b/std/sys/macos/fs.wave index 0a7defc8..76707d3d 100644 --- a/std/sys/macos/fs.wave +++ b/std/sys/macos/fs.wave @@ -16,6 +16,31 @@ import("std::sys::macos::syscall"); +// 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; + +// 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; + +// seek modes +const FS_SEEK_SET: i32 = 0; +const FS_SEEK_CUR: i32 = 1; +const FS_SEEK_END: i32 = 2; + +// fcntl modes +const FS_F_GETFL: i32 = 3; +const FS_F_SETFL: i32 = 4; + struct Stat { dev: i64; ino: i64; @@ -41,6 +66,26 @@ fun close(fd: i64) -> i64 { return syscall1(6, fd); } +fun dup(fd: i64) -> i64 { + return syscall1(41, fd); +} + +fun pipe(fds: ptr) -> i64 { + return syscall1(42, fds as i64); +} + +fun dup2(oldfd: i64, newfd: i64) -> i64 { + return syscall2(90, oldfd, newfd); +} + +fun fsync(fd: i64) -> i64 { + return syscall1(95, fd); +} + +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 { return syscall3(3, fd, buf as i64, len); } diff --git a/std/sys/macos/memory.wave b/std/sys/macos/memory.wave index 7b5b8780..8bf5ad1e 100644 --- a/std/sys/macos/memory.wave +++ b/std/sys/macos/memory.wave @@ -53,7 +53,7 @@ fun sys_alloc(size: i64) -> ptr { return null; } - var p: ptr = mmap( + let mut p: ptr = mmap( null, size, PROT_READ | PROT_WRITE, diff --git a/std/sys/macos/socket.wave b/std/sys/macos/socket.wave index 95c68045..c3ce4ce3 100644 --- a/std/sys/macos/socket.wave +++ b/std/sys/macos/socket.wave @@ -35,6 +35,19 @@ 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 { + fd: i32; + events: i16; + revents: i16; +} fun socket(domain: i32, ty: i32, protocol: i32) -> i64 { return syscall3(97, domain as i64, ty as i64, protocol as i64); @@ -77,6 +90,27 @@ fun setsockopt( ); } +fun getsockopt( + fd: i64, + level: i32, + optname: i32, + optval: ptr, + optlen: ptr +) -> i64 { + return syscall5( + 118, + fd, + level as i64, + optname as i64, + optval as i64, + optlen as i64 + ); +} + +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 { return sendto(fd, buf, len, flags, null, 0); } diff --git a/std/sys/macos/syscall.wave b/std/sys/macos/syscall.wave index c2514720..d5c60792 100644 --- a/std/sys/macos/syscall.wave +++ b/std/sys/macos/syscall.wave @@ -22,8 +22,8 @@ const MACOS_SYSCALL_BASE: i64 = 0x2000000; fun syscall0(id: i64) -> i64 { - var ret: i64; - var sysno: i64 = MACOS_SYSCALL_BASE + id; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno @@ -33,8 +33,8 @@ fun syscall0(id: i64) -> i64 { } fun syscall1(id: i64, a1: i64) -> i64 { - var ret: i64; - var sysno: i64 = MACOS_SYSCALL_BASE + id; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno @@ -45,8 +45,8 @@ fun syscall1(id: i64, a1: i64) -> i64 { } fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { - var ret: i64; - var sysno: i64 = MACOS_SYSCALL_BASE + id; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno @@ -58,8 +58,8 @@ fun syscall2(id: i64, a1: i64, a2: i64) -> i64 { } fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { - var ret: i64; - var sysno: i64 = MACOS_SYSCALL_BASE + id; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno @@ -72,8 +72,8 @@ fun syscall3(id: i64, a1: i64, a2: i64, a3: i64) -> i64 { } fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { - var ret: i64; - var sysno: i64 = MACOS_SYSCALL_BASE + id; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno @@ -87,8 +87,8 @@ fun syscall4(id: i64, a1: i64, a2: i64, a3: i64, a4: i64) -> i64 { } 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; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno @@ -103,8 +103,8 @@ fun syscall5(id: i64, a1: i64, a2: i64, a3: i64, a4: i64, a5: i64) -> i64 { } 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; + let mut ret: i64; + let mut sysno: i64 = MACOS_SYSCALL_BASE + id; asm { "syscall" in("rax") sysno diff --git a/std/sys/macos/tty.wave b/std/sys/macos/tty.wave index 17ffe449..9b915330 100644 --- a/std/sys/macos/tty.wave +++ b/std/sys/macos/tty.wave @@ -34,8 +34,6 @@ const TTY_O_NONBLOCK: i32 = 4; const TTY_ICANON: u32 = 0x00000100; const TTY_ECHO: u32 = 0x00000008; -const TTY_VTIME_IDX: i32 = 17; -const TTY_VMIN_IDX: i32 = 16; struct Termios { c_iflag: u32; @@ -59,7 +57,7 @@ fun tty_getattr(fd: i32, t: ptr) -> i64 { } fun tty_setattr(fd: i32, action: i32, t: ptr) -> i64 { - var req: i64 = TTY_TCSETS; + let mut req: i64 = TTY_TCSETS; if (action == TTY_TCSADRAIN) { req = TTY_TCSETSW; @@ -80,28 +78,30 @@ fun tty_setfl(fd: i32, flags: i32) -> i64 { fun tty_enable_raw_nonblock(fd: i32, st: ptr) -> i64 { deref st.active = 0; + let vmin_idx: i32 = 16; + let vtime_idx: i32 = 17; - var orig: Termios; + let mut orig: Termios; if (tty_getattr(fd, &orig) < 0) { return -1; } - var raw: Termios = orig; + let mut raw: Termios = orig; raw.c_lflag = raw.c_lflag & ~(TTY_ICANON | TTY_ECHO); - raw.c_cc[TTY_VMIN_IDX] = 0; - raw.c_cc[TTY_VTIME_IDX] = 0; + raw.c_cc[vmin_idx] = 0; + raw.c_cc[vtime_idx] = 0; if (tty_setattr(fd, TTY_TCSANOW, &raw) < 0) { return -1; } - var flags_raw: i64 = tty_getfl(fd); + let mut flags_raw: i64 = tty_getfl(fd); if (flags_raw < 0) { tty_setattr(fd, TTY_TCSANOW, &orig); return -1; } - var old_flags: i32 = flags_raw as i32; + let mut old_flags: i32 = flags_raw as i32; if (tty_setfl(fd, old_flags | TTY_O_NONBLOCK) < 0) { tty_setattr(fd, TTY_TCSANOW, &orig); return -1; @@ -118,7 +118,7 @@ fun tty_restore(fd: i32, st: ptr) { return; } - var orig: Termios = deref st.term; + let mut orig: Termios = deref st.term; tty_setattr(fd, TTY_TCSANOW, &orig); tty_setfl(fd, deref st.flags); } diff --git a/std/time/clock.wave b/std/time/clock.wave index 9f2394f1..a5c34ced 100644 --- a/std/time/clock.wave +++ b/std/time/clock.wave @@ -21,8 +21,8 @@ fun time_now_monotonic(tp: ptr) -> i64 { } fun time_now_realtime_ns() -> i64 { - var ts: TimeSpec; - var r: i64 = time_now_realtime(&ts); + let mut ts: TimeSpec; + let mut r: i64 = time_now_realtime(&ts); if (r < 0) { return r; @@ -32,8 +32,8 @@ fun time_now_realtime_ns() -> i64 { } fun time_now_monotonic_ns() -> i64 { - var ts: TimeSpec; - var r: i64 = time_now_monotonic(&ts); + let mut ts: TimeSpec; + let mut r: i64 = time_now_monotonic(&ts); if (r < 0) { return r; diff --git a/std/time/diff.wave b/std/time/diff.wave index 7e49914b..a46df6e9 100644 --- a/std/time/diff.wave +++ b/std/time/diff.wave @@ -13,8 +13,8 @@ import("std::sys::time"); 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; + let mut sec: i64 = end_ts.sec - start_ts.sec; + let mut nsec: i64 = end_ts.nsec - start_ts.nsec; return (sec * 1000000000) + nsec; } diff --git a/std/time/sleep.wave b/std/time/sleep.wave index 6f3074f7..190256ee 100644 --- a/std/time/sleep.wave +++ b/std/time/sleep.wave @@ -17,12 +17,12 @@ fun time_sleep_ns(ns: i64) -> i64 { return 0; } - var req: TimeSpec = TimeSpec { + let mut req: TimeSpec = TimeSpec { sec: ns / 1000000000, nsec: ns % 1000000000 }; - var rem: TimeSpec; + let mut rem: TimeSpec; return nanosleep(&req, &rem); } diff --git a/test/test103.wave b/test/test103.wave new file mode 100644 index 00000000..f3d8814c --- /dev/null +++ b/test/test103.wave @@ -0,0 +1,99 @@ +import("std::string::len"); +import("std::fs::consts"); +import("std::fs::file"); +import("std::io::fd"); + +fun check_i64(label: str, got: i64, expected: i64) -> i64 { + if (got == expected) { + println("[OK] {} => {}", label, got); + return 1; + } + + println("[FAIL] {} => got {}, expected {}", label, got, expected); + return 0; +} + +fun main() { + let path: str = "/tmp/wave-std-test103.txt"; + let copy_path: str = "/tmp/wave-std-test103-copy.txt"; + let payload: str = "wave-std-phase1"; + let bang: str = "!"; + + fs_remove(path); + fs_remove(copy_path); + + let payload_len: i64 = len(payload) as i64; + let write_n: i64 = fs_write_all( + path, + payload as ptr, + payload_len, + FS_MODE_FILE_DEFAULT + ); + + let mut passed: i64 = 0; + passed += check_i64("fs_write_all", write_n, payload_len); + + let mut exists: i64 = 0; + if (fs_exists(path)) { + exists = 1; + } + passed += check_i64("fs_exists", exists, 1); + + let size0: i64 = fs_file_size(path); + passed += check_i64("fs_file_size after write", size0, payload_len); + + var read_buf: array; + let read_n: i64 = fs_read_all(path, &read_buf[0], 128); + passed += check_i64("fs_read_all", read_n, payload_len); + + let mut ok_prefix: i64 = 0; + if (read_buf[0] == 119 && read_buf[1] == 97 && read_buf[2] == 118) { + ok_prefix = 1; + } + passed += check_i64("read prefix bytes", ok_prefix, 1); + + let append_n: i64 = fs_append_all( + path, + bang as ptr, + 1, + FS_MODE_FILE_DEFAULT + ); + passed += check_i64("fs_append_all", append_n, 1); + + let size1: i64 = fs_file_size(path); + passed += check_i64("fs_file_size after append", size1, payload_len + 1); + + var scratch: array; + let copied: i64 = fs_copy(path, copy_path, &scratch[0], 8, FS_MODE_FILE_DEFAULT); + passed += check_i64("fs_copy bytes", copied, payload_len + 1); + + let copy_size: i64 = fs_file_size(copy_path); + passed += check_i64("copied file size", copy_size, payload_len + 1); + + var fds: array; + let pr: i64 = io_pipe(&fds[0]); + passed += check_i64("io_pipe", pr, 0); + + if (pr == 0) { + let wn: i64 = io_write_all(fds[1] as i64, "OK" as ptr, 2); + passed += check_i64("pipe write", wn, 2); + io_close(fds[1] as i64); + + var pipe_buf: array; + let rn: i64 = io_read_exact(fds[0] as i64, &pipe_buf[0], 2); + passed += check_i64("pipe read", rn, 2); + + let mut pipe_ok: i64 = 0; + if (pipe_buf[0] == 79 && pipe_buf[1] == 75) { + pipe_ok = 1; + } + passed += check_i64("pipe payload", pipe_ok, 1); + io_close(fds[0] as i64); + } + + fs_remove(path); + fs_remove(copy_path); + + println("passed checks = {}", passed); + println("expected checks = 13"); +} diff --git a/test/test104.wave b/test/test104.wave new file mode 100644 index 00000000..159596e9 --- /dev/null +++ b/test/test104.wave @@ -0,0 +1,97 @@ +import("std::io::fd"); +import("std::net::poll"); +import("std::net::socket_base"); +import("std::net::socketopt"); + +fun check_i64(label: str, got: i64, expected: i64) -> i64 { + if (got == expected) { + println("[OK] {} => {}", label, got); + return 1; + } + + println("[FAIL] {} => got {}, expected {}", label, got, expected); + return 0; +} + +fun main() { + let mut passed: i64 = 0; + let mut expected: i64 = 8; + + let sfd: i64 = net_socket_tcp_v4(); + if (sfd >= 0) { + expected = 14; + passed += check_i64("net_socket_tcp_v4", 1, 1); + + let r1: i64 = net_set_reuseaddr_flag(sfd, 1); + passed += check_i64("net_set_reuseaddr_flag", r1, 0); + + let v1: i64 = net_get_reuseaddr_flag(sfd); + let mut reuse_ok: i64 = 0; + if (v1 >= 1) { + reuse_ok = 1; + } + passed += check_i64("net_get_reuseaddr_flag", reuse_ok, 1); + + let r2: i64 = net_set_nonblock(sfd, 1); + passed += check_i64("net_set_nonblock on socket", r2, 0); + + let v2: i64 = net_get_nonblock(sfd); + passed += check_i64("net_get_nonblock socket=1", v2, 1); + + let r3: i64 = net_set_nonblock(sfd, 0); + passed += check_i64("net_set_nonblock disable", r3, 0); + + let v3: i64 = net_get_nonblock(sfd); + passed += check_i64("net_get_nonblock socket=0", v3, 0); + + io_close(sfd); + } else { + println("[SKIP] socket option checks skipped (fd={})", sfd); + } + + var fds: array; + let pr: i64 = io_pipe(&fds[0]); + passed += check_i64("io_pipe", pr, 0); + + if (pr == 0) { + let read_fd: i64 = fds[0] as i64; + let write_fd: i64 = fds[1] as i64; + + let nb: i64 = net_set_nonblock(read_fd, 1); + passed += check_i64("net_set_nonblock pipe", nb, 0); + + let nbv: i64 = net_get_nonblock(read_fd); + passed += check_i64("net_get_nonblock pipe", nbv, 1); + + var pfd: PollFd = PollFd { + fd: read_fd as i32, + events: NET_POLLIN, + revents: 0 + }; + + let p0: i64 = net_poll(&pfd, 1, 0); + passed += check_i64("net_poll before write", p0, 0); + + let wn: i64 = io_write_all(write_fd, "Q" as ptr, 1); + passed += check_i64("pipe write", wn, 1); + + pfd.revents = 0; + let p1: i64 = net_poll(&pfd, 1, 200); + passed += check_i64("net_poll after write", p1, 1); + + let mut ev_ok: i64 = 0; + if ((pfd.revents & NET_POLLIN) != 0) { + ev_ok = 1; + } + passed += check_i64("poll revents POLLIN", ev_ok, 1); + + let wr: i64 = net_wait_readable(read_fd, 200); + passed += check_i64("net_wait_readable", wr, 1); + + io_close(read_fd); + io_close(write_fd); + } + + println("passed checks = {}", passed); + println("expected checks = {}", expected); +} diff --git a/test/test105.wave b/test/test105.wave new file mode 100644 index 00000000..a3b4e31a --- /dev/null +++ b/test/test105.wave @@ -0,0 +1,104 @@ +import("std::bytes::endian"); +import("std::math::bits"); +import("std::mem::alloc"); +import("std::mem::consts"); +import("std::mem::ops"); + +fun check_i64(label: str, got: i64, expected: i64) -> i64 { + if (got == expected) { + println("[OK] {} => {}", label, got); + return 1; + } + + println("[FAIL] {} => got {}, expected {}", label, got, expected); + return 0; +} + +fun main() { + let mut passed: i64 = 0; + + passed += check_i64("mem_page_size", mem_page_size(), MEM_PAGE_SIZE); + passed += check_i64("mem_pages_for_size(1)", mem_pages_for_size(1), 1); + passed += check_i64( + "mem_pages_for_size(page+1)", + mem_pages_for_size(MEM_PAGE_SIZE + 1), + 2 + ); + passed += check_i64("mem_size_align_page(1)", mem_size_align_page(1), MEM_PAGE_SIZE); + + let p: ptr = mem_alloc_pages(1); + let mut p_ok: i64 = 0; + if (p != null) { + p_ok = 1; + } + passed += check_i64("mem_alloc_pages(1)", p_ok, 1); + passed += check_i64("mem_free_pages(1)", mem_free_pages(p, 1), 0); + + let ap: ptr = mem_alloc_aligned(33, 64); + let mut ap_ok: i64 = 0; + if (ap != null) { + ap_ok = 1; + } + passed += check_i64("mem_alloc_aligned", ap_ok, 1); + + let mut aligned_ok: i64 = 0; + if (mem_is_aligned(ap, 64)) { + aligned_ok = 1; + } + passed += check_i64("mem_is_aligned(64)", aligned_ok, 1); + passed += check_i64("mem_free_aligned", mem_free_aligned(ap), 0); + + var small: array; + var src: array = [10, 20, 30, 40, 50, 60]; + + let c0: i64 = mem_copy_checked(&small[0], 4, &src[0], 6); + passed += check_i64("mem_copy_checked overflow", c0, MEM_ERR_NO_SPACE); + + let c1: i64 = mem_copy_checked(&small[0], 4, &src[0], 4); + passed += check_i64("mem_copy_checked ok", c1, 4); + + let mut copy_ok: i64 = 0; + if (small[0] == 10 && small[1] == 20 && small[2] == 30 && small[3] == 40) { + copy_ok = 1; + } + passed += check_i64("mem_copy_checked bytes", copy_ok, 1); + + let s0: i64 = mem_set_checked(&small[0], 4, 7, 5); + passed += check_i64("mem_set_checked overflow", s0, MEM_ERR_NO_SPACE); + + let z0: i64 = mem_zero_checked(&small[0], 4, 4); + passed += check_i64("mem_zero_checked ok", z0, 4); + + let mut zero_ok: i64 = 0; + if (small[0] == 0 && small[1] == 0 && small[2] == 0 && small[3] == 0) { + zero_ok = 1; + } + passed += check_i64("mem_zero_checked bytes", zero_ok, 1); + + var be32: array; + bytes_store_be_i32(&be32[0], 0x12345678); + passed += check_i64("bytes_load_be_i32", bytes_load_be_i32(&be32[0]) as i64, 0x12345678); + + var le32: array; + bytes_store_le_i32(&le32[0], 0x12345678); + passed += check_i64("bytes_load_le_i32", bytes_load_le_i32(&le32[0]) as i64, 0x12345678); + + var be64: array; + bytes_store_be_i64(&be64[0], 0x0102030405060708); + passed += check_i64("bytes_load_be_i64", bytes_load_be_i64(&be64[0]), 0x0102030405060708); + + let mut pow2_ok: i64 = 0; + if (is_pow2_i64(1024)) { + pow2_ok = 1; + } + passed += check_i64("is_pow2_i64(1024)", pow2_ok, 1); + passed += check_i64("align_up_i64", align_up_i64(4100, 4096), 8192); + passed += check_i64("popcount64(15)", popcount64(15) as i64, 4); + passed += check_i64("ctz64(8)", ctz64(8) as i64, 3); + passed += check_i64("ilog2_floor64(1024)", ilog2_floor64(1024) as i64, 10); + passed += check_i64("ilog2_ceil64(1025)", ilog2_ceil64(1025) as i64, 11); + passed += check_i64("bswap64", bswap64(0x0102030405060708), 0x0807060504030201); + + println("passed checks = {}", passed); + println("expected checks = 25"); +} diff --git a/test/test106.wave b/test/test106.wave new file mode 100644 index 00000000..dd1d2c42 --- /dev/null +++ b/test/test106.wave @@ -0,0 +1,141 @@ +import("std::io::consts"); +import("std::io::fd"); +import("std::process::core"); +import("std::process::spawn"); +import("std::process::wait"); + +fun check_i64(label: str, got: i64, expected: i64) -> i64 { + if (got == expected) { + println("[OK] {} => {}", label, got); + return 1; + } + + println("[FAIL] {} => got {}, expected {}", label, got, expected); + return 0; +} + +fun main() { + let mut passed: i64 = 0; + + let pid0: i64 = proc_getpid(); + let mut ok_pid: i64 = 0; + if (pid0 > 0) { + ok_pid = 1; + } + passed += check_i64("proc_getpid", ok_pid, 1); + + // fork + wait + exit status + let c1: i64 = proc_fork(); + if (c1 == 0) { + proc_exit(23); + } + + if (c1 > 0) { + var st1: i32 = 0; + let w1: i64 = proc_wait(c1, &st1); + passed += check_i64("proc_wait child1", w1, c1); + + let mut exited_ok: i64 = 0; + if (proc_status_exited(st1)) { + exited_ok = 1; + } + passed += check_i64("proc_status_exited", exited_ok, 1); + passed += check_i64("proc_status_exit_code", proc_status_exit_code(st1) as i64, 23); + } else { + passed += check_i64("proc_fork child1", c1, 1); + } + + // pipe + dup2(stdout) + wait + var fds: array; + let pr: i64 = io_pipe(&fds[0]); + passed += check_i64("io_pipe", pr, 0); + + if (pr == 0) { + let c2: i64 = proc_fork(); + if (c2 == 0) { + io_close(fds[0] as i64); + let dr: i64 = io_dup2(fds[1] as i64, IO_STDOUT_FD); + if (dr < 0) { + proc_exit(125); + } + io_close(fds[1] as i64); + io_write_all(IO_STDOUT_FD, "HELLO\n" as ptr, 6); + proc_exit(0); + } + + if (c2 > 0) { + io_close(fds[1] as i64); + + var rbuf: array; + let rn: i64 = io_read_at_most(fds[0] as i64, &rbuf[0], 32); + io_close(fds[0] as i64); + + let mut read_ok: i64 = 0; + if (rn >= 6) { + read_ok = 1; + } + passed += check_i64("pipe child stdout read", read_ok, 1); + + let mut msg_ok: i64 = 0; + if (rbuf[0] == 72 && rbuf[1] == 69 && rbuf[2] == 76 && rbuf[3] == 76 && rbuf[4] == 79) { + msg_ok = 1; + } + passed += check_i64("pipe child stdout bytes", msg_ok, 1); + + var st2: i32 = 0; + let w2: i64 = proc_wait(c2, &st2); + passed += check_i64("proc_wait child2", w2, c2); + passed += check_i64("child2 exit code", proc_status_exit_code(st2) as i64, 0); + } else { + passed += check_i64("proc_fork child2", c2, 1); + io_close(fds[0] as i64); + io_close(fds[1] as i64); + } + } + + // spawn + stdout capture wrapper + var argv: array, 3>; + argv[0] = "/bin/echo" as ptr; + argv[1] = "WAVEPROC" as ptr; + argv[2] = null; + + let sr: ProcSpawnStdoutResult = proc_spawn_capture_stdout( + "/bin/echo", + &argv[0], + null + ); + + if (sr.status == 0 && sr.pid > 0) { + let mut spawn_ok: i64 = 0; + if (sr.read_fd >= 0) { + spawn_ok = 1; + } + passed += check_i64("proc_spawn_capture_stdout", spawn_ok, 1); + + var sbuf: array; + let sn: i64 = io_read_at_most(sr.read_fd, &sbuf[0], 64); + io_close(sr.read_fd); + + let mut sn_ok: i64 = 0; + if (sn >= 9) { + sn_ok = 1; + } + passed += check_i64("spawn stdout read", sn_ok, 1); + + let mut sbytes_ok: i64 = 0; + if (sbuf[0] == 87 && sbuf[1] == 65 && sbuf[2] == 86 && sbuf[3] == 69 && sbuf[4] == 80) { + sbytes_ok = 1; + } + passed += check_i64("spawn stdout bytes", sbytes_ok, 1); + + var st3: i32 = 0; + let w3: i64 = proc_wait(sr.pid, &st3); + passed += check_i64("proc_wait spawn child", w3, sr.pid); + passed += check_i64("spawn child exit code", proc_status_exit_code(st3) as i64, 0); + } else { + println("[SKIP] proc_spawn_capture_stdout unavailable (status={}, pid={})", sr.status, sr.pid); + } + + println("passed checks = {}", passed); + println("expected checks = 14"); +} diff --git a/tools/check_std_policy.sh b/tools/check_std_policy.sh new file mode 100755 index 00000000..23834278 --- /dev/null +++ b/tools/check_std_policy.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$repo_root" + +failed=0 + +echo "[check] std policy validation" + +echo "[check] rule: extern(c) only in std/libc/**" +extern_hits="$(rg -n "extern\\(c\\)" std --glob '*.wave' || true)" +if [[ -n "$extern_hits" ]]; then + non_libc_extern="$(printf '%s\n' "$extern_hits" | rg -v '^std/libc/' || true)" + if [[ -n "$non_libc_extern" ]]; then + echo "[FAIL] extern(c) found outside std/libc:" + printf '%s\n' "$non_libc_extern" + failed=1 + fi +fi + +echo "[check] rule: std/** must not import std::libc::*" +libc_import_hits="$(rg -n 'import\("std::libc::' std --glob '*.wave' || true)" +if [[ -n "$libc_import_hits" ]]; then + non_libc_imports="$(printf '%s\n' "$libc_import_hits" | rg -v '^std/libc/' || true)" + if [[ -n "$non_libc_imports" ]]; then + echo "[FAIL] std::libc import found outside std/libc:" + printf '%s\n' "$non_libc_imports" + failed=1 + fi +fi + +echo "[check] rule: std/** must not use 'var' declarations" +var_hits="$(rg -n "\\bvar\\b" std --glob '*.wave' || true)" +if [[ -n "$var_hits" ]]; then + echo "[FAIL] var declaration found in std:" + printf '%s\n' "$var_hits" + failed=1 +fi + +if [[ "$failed" -ne 0 ]]; then + echo "[result] FAILED" + exit 1 +fi + +echo "[result] OK"