feat: multi-instance closures, richer stdlib, and Rust-style diagnostics
Introduce fat function pointers (BuxFn {code, env}) so capturing closures
are heap-allocated per value in both bootstrap and selfhost. Expand
Array/Map/Set/String/Test/Result APIs, add proper tuple codegen and
error snippets with multi-char underlines, golden diagnostic tests, and
LSP diagnostics via buxc check.
This commit is contained in:
@@ -58,4 +58,78 @@ func Array_operator_index_set<T>(self: *Array<T>, idx: uint, value: T) {
|
||||
Array_Set<T>(self, idx, value);
|
||||
}
|
||||
|
||||
/* True if the array has no elements */
|
||||
func Array_IsEmpty<T>(self: *Array<T>) -> bool {
|
||||
return self.len == 0;
|
||||
}
|
||||
|
||||
/* Current capacity (not length) */
|
||||
func Array_Cap<T>(self: *Array<T>) -> uint {
|
||||
return self.cap;
|
||||
}
|
||||
|
||||
/* Drop length to zero; keeps allocated capacity */
|
||||
func Array_Clear<T>(self: *Array<T>) {
|
||||
self.len = 0;
|
||||
}
|
||||
|
||||
/* Ensure capacity is at least minCap (does not shrink) */
|
||||
func Array_Reserve<T>(self: *Array<T>, minCap: uint) {
|
||||
if minCap <= self.cap {
|
||||
return;
|
||||
}
|
||||
self.cap = minCap;
|
||||
self.data = bux_realloc(self.data as *void, self.cap * sizeof(T)) as *T;
|
||||
}
|
||||
|
||||
/* First element (panics if empty via bounds check) */
|
||||
func Array_First<T>(self: *Array<T>) -> T {
|
||||
return Array_Get<T>(self, 0);
|
||||
}
|
||||
|
||||
/* Last element (panics if empty via bounds check) */
|
||||
func Array_Last<T>(self: *Array<T>) -> T {
|
||||
return Array_Get<T>(self, self.len - 1);
|
||||
}
|
||||
|
||||
/* Remove and return the last element (panics if empty) */
|
||||
func Array_Pop<T>(self: *Array<T>) -> T {
|
||||
bux_bounds_check(0, self.len);
|
||||
self.len = self.len - 1;
|
||||
return self.data[self.len];
|
||||
}
|
||||
|
||||
/* Linear search: true if value is present (uses ==) */
|
||||
func Array_Contains<T>(self: *Array<T>, value: T) -> bool {
|
||||
var i: uint = 0;
|
||||
while i < self.len {
|
||||
if self.data[i] == value {
|
||||
return true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Index of first equal element, or -1 if not found */
|
||||
func Array_IndexOf<T>(self: *Array<T>, value: T) -> int {
|
||||
var i: uint = 0;
|
||||
while i < self.len {
|
||||
if self.data[i] == value {
|
||||
return i as int;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Append all elements of other onto self */
|
||||
func Array_Extend<T>(self: *Array<T>, other: *Array<T>) {
|
||||
var i: uint = 0;
|
||||
while i < other.len {
|
||||
Array_Push<T>(self, other.data[i]);
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -67,4 +67,44 @@ func Iter_Take<T>(it: *Iter<T>, n: uint) -> Iter<T> {
|
||||
return Iter<T> { data: it.data, len: endPos, pos: it.pos };
|
||||
}
|
||||
|
||||
/* True if any remaining element equals value */
|
||||
func Iter_AnyEq<T>(it: *Iter<T>, value: T) -> bool {
|
||||
var i: uint = it.pos;
|
||||
while i < it.len {
|
||||
if it.data[i] == value {
|
||||
return true;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* True if every remaining element equals value (true if empty) */
|
||||
func Iter_AllEq<T>(it: *Iter<T>, value: T) -> bool {
|
||||
var i: uint = it.pos;
|
||||
while i < it.len {
|
||||
if it.data[i] != value {
|
||||
return false;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Collect remaining elements into a new Array */
|
||||
func Iter_Collect<T>(it: *Iter<T>) -> Array<T> {
|
||||
let remaining: uint = it.len - it.pos;
|
||||
var cap: uint = remaining;
|
||||
if cap == 0 {
|
||||
cap = 1;
|
||||
}
|
||||
var arr: Array<T> = Array_New<T>(cap);
|
||||
var i: uint = it.pos;
|
||||
while i < it.len {
|
||||
Array_Push<T>(&arr, it.data[i]);
|
||||
i = i + 1;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+69
@@ -80,6 +80,41 @@ func Map_Len<K, V>(m: *Map<K, V>) -> uint {
|
||||
return m.len;
|
||||
}
|
||||
|
||||
func Map_IsEmpty<K, V>(m: *Map<K, V>) -> bool {
|
||||
return m.len == 0;
|
||||
}
|
||||
|
||||
/* Remove key if present. Rebuilds the table to keep open-addressing correct. */
|
||||
func Map_Remove<K, V>(m: *Map<K, V>, key: K) -> bool {
|
||||
if !Map_Has<K, V>(m, key) {
|
||||
return false;
|
||||
}
|
||||
var fresh: Map<K, V> = Map_New<K, V>(m.cap);
|
||||
var i: uint = 0;
|
||||
while i < m.cap {
|
||||
if m.entries[i].occupied {
|
||||
if m.entries[i].key != key {
|
||||
Map_Set<K, V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
bux_free(m.entries as *void);
|
||||
m.entries = fresh.entries;
|
||||
m.cap = fresh.cap;
|
||||
m.len = fresh.len;
|
||||
return true;
|
||||
}
|
||||
|
||||
func Map_Clear<K, V>(m: *Map<K, V>) {
|
||||
var i: uint = 0;
|
||||
while i < m.cap {
|
||||
m.entries[i].occupied = false;
|
||||
i = i + 1;
|
||||
}
|
||||
m.len = 0;
|
||||
}
|
||||
|
||||
func Map_Free<K, V>(m: *Map<K, V>) {
|
||||
bux_free(m.entries as *void);
|
||||
m.entries = null as *MapEntry<K, V>;
|
||||
@@ -163,6 +198,40 @@ func StringMap_Len<V>(m: *StringMap<V>) -> uint {
|
||||
return m.len;
|
||||
}
|
||||
|
||||
func StringMap_IsEmpty<V>(m: *StringMap<V>) -> bool {
|
||||
return m.len == 0;
|
||||
}
|
||||
|
||||
func StringMap_Remove<V>(m: *StringMap<V>, key: String) -> bool {
|
||||
if !StringMap_Has<V>(m, key) {
|
||||
return false;
|
||||
}
|
||||
var fresh: StringMap<V> = StringMap_New<V>(m.cap);
|
||||
var i: uint = 0;
|
||||
while i < m.cap {
|
||||
if m.entries[i].occupied {
|
||||
if !String_Eq(m.entries[i].key, key) {
|
||||
StringMap_Set<V>(&fresh, m.entries[i].key, m.entries[i].value);
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
bux_free(m.entries as *void);
|
||||
m.entries = fresh.entries;
|
||||
m.cap = fresh.cap;
|
||||
m.len = fresh.len;
|
||||
return true;
|
||||
}
|
||||
|
||||
func StringMap_Clear<V>(m: *StringMap<V>) {
|
||||
var i: uint = 0;
|
||||
while i < m.cap {
|
||||
m.entries[i].occupied = false;
|
||||
i = i + 1;
|
||||
}
|
||||
m.len = 0;
|
||||
}
|
||||
|
||||
func StringMap_Free<V>(m: *StringMap<V>) {
|
||||
bux_free(m.entries as *void);
|
||||
m.entries = null as *StringMapEntry<V>;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module Std::Option {
|
||||
import Std::Io::{PrintLine};
|
||||
|
||||
extern func bux_exit(code: int);
|
||||
|
||||
enum Option {
|
||||
Some(int),
|
||||
None,
|
||||
@@ -39,4 +41,21 @@ func Option_UnwrapOr(o: Option, fallback: int) -> int {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/* Unwrap Some or panic with a custom message */
|
||||
func Option_Expect(o: Option, msg: String) -> int {
|
||||
if o.tag != Option_Some {
|
||||
PrintLine(msg);
|
||||
bux_exit(1);
|
||||
}
|
||||
return o.data.Some_0;
|
||||
}
|
||||
|
||||
/* If o is Some return it, otherwise return other */
|
||||
func Option_Or(o: Option, other: Option) -> Option {
|
||||
if o.tag == Option_Some {
|
||||
return o;
|
||||
}
|
||||
return other;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ extern func bux_getenv(name: String) -> String;
|
||||
extern func bux_setenv(name: String, value: String) -> int;
|
||||
extern func bux_getcwd() -> String;
|
||||
extern func bux_chdir(path: String) -> int;
|
||||
extern func bux_exit(code: int);
|
||||
|
||||
func Os_ArgsCount() -> int {
|
||||
return bux_argc();
|
||||
@@ -31,4 +32,9 @@ func Os_Chdir(path: String) -> bool {
|
||||
return bux_chdir(path) == 0;
|
||||
}
|
||||
|
||||
/* Terminate the process with the given exit code */
|
||||
func Os_Exit(code: int) {
|
||||
bux_exit(code);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
module Std::Result {
|
||||
import Std::Io::{PrintLine};
|
||||
|
||||
extern func bux_exit(code: int);
|
||||
|
||||
enum Result {
|
||||
Ok(int),
|
||||
Err(String),
|
||||
@@ -41,4 +43,30 @@ func Result_UnwrapOr(r: Result, fallback: int) -> int {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
/* Unwrap Ok or panic with a custom message */
|
||||
func Result_Expect(r: Result, msg: String) -> int {
|
||||
if r.tag != Result_Ok {
|
||||
PrintLine(msg);
|
||||
bux_exit(1);
|
||||
}
|
||||
return r.data.Ok_0;
|
||||
}
|
||||
|
||||
/* Extract Err payload (panics if Ok) */
|
||||
func Result_UnwrapErr(r: Result) -> String {
|
||||
if r.tag != Result_Err {
|
||||
PrintLine("panic: unwrap_err on Ok");
|
||||
return "";
|
||||
}
|
||||
return r.data.Err_0;
|
||||
}
|
||||
|
||||
/* If r is Ok return it, otherwise return other */
|
||||
func Result_Or(r: Result, other: Result) -> Result {
|
||||
if r.tag == Result_Ok {
|
||||
return r;
|
||||
}
|
||||
return other;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+37
@@ -61,6 +61,43 @@ func Set_Len<T>(s: *Set<T>) -> uint {
|
||||
return s.len;
|
||||
}
|
||||
|
||||
func Set_IsEmpty<T>(s: *Set<T>) -> bool {
|
||||
return s.len == 0;
|
||||
}
|
||||
|
||||
/* Remove value if present. Rebuilds the table to keep open-addressing correct. */
|
||||
func Set_Remove<T>(s: *Set<T>, value: T) -> bool {
|
||||
if !Set_Has<T>(s, value) {
|
||||
return false;
|
||||
}
|
||||
var fresh: Set<T> = Set_New<T>(s.cap);
|
||||
var i: uint = 0;
|
||||
while i < s.cap {
|
||||
if s.entries[i].occupied {
|
||||
var entryPtr: *T = &s.entries[i].value;
|
||||
var valuePtr: *T = &value;
|
||||
if bux_mem_eq(entryPtr as *void, valuePtr as *void, sizeof(T)) == 0 {
|
||||
Set_Add<T>(&fresh, s.entries[i].value);
|
||||
}
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
bux_free(s.entries as *void);
|
||||
s.entries = fresh.entries;
|
||||
s.cap = fresh.cap;
|
||||
s.len = fresh.len;
|
||||
return true;
|
||||
}
|
||||
|
||||
func Set_Clear<T>(s: *Set<T>) {
|
||||
var i: uint = 0;
|
||||
while i < s.cap {
|
||||
s.entries[i].occupied = false;
|
||||
i = i + 1;
|
||||
}
|
||||
s.len = 0;
|
||||
}
|
||||
|
||||
func Set_Free<T>(s: *Set<T>) {
|
||||
bux_free(s.entries as *void);
|
||||
s.entries = null as *SetEntry<T>;
|
||||
|
||||
@@ -34,6 +34,10 @@ func String_Len(s: String) -> uint {
|
||||
return bux_strlen(s);
|
||||
}
|
||||
|
||||
func String_IsEmpty(s: String) -> bool {
|
||||
return bux_strlen(s) == 0;
|
||||
}
|
||||
|
||||
func String_IsNull(s: String) -> bool {
|
||||
return bux_str_is_null(s) != 0;
|
||||
}
|
||||
@@ -147,6 +151,39 @@ func StringBuilder_Free(sb: *StringBuilder) {
|
||||
bux_sb_free(sb.handle);
|
||||
}
|
||||
|
||||
/* True if empty or only whitespace (space, tab, CR, LF) */
|
||||
func String_IsBlank(s: String) -> bool {
|
||||
let n: uint = bux_strlen(s);
|
||||
var i: uint = 0;
|
||||
while i < n {
|
||||
let ch: String = bux_str_slice(s, i, 1);
|
||||
if !(String_Eq(ch, " ") || String_Eq(ch, "\t") || String_Eq(ch, "\n") || String_Eq(ch, "\r")) {
|
||||
return false;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Repeat s, count times (count==0 → empty string) */
|
||||
func String_Repeat(s: String, count: uint) -> String {
|
||||
if count == 0 {
|
||||
return "";
|
||||
}
|
||||
if count == 1 {
|
||||
return s;
|
||||
}
|
||||
var sb: StringBuilder = StringBuilder_New();
|
||||
var i: uint = 0;
|
||||
while i < count {
|
||||
StringBuilder_Append(&sb, s);
|
||||
i = i + 1;
|
||||
}
|
||||
let result: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// String split/join
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -194,6 +231,33 @@ func String_Replace(s: String, old: String, new: String) -> String {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Replace every non-overlapping occurrence of old with new.
|
||||
Empty old is a no-op (returns s unchanged). Safe if new contains old. */
|
||||
func String_ReplaceAll(s: String, old: String, new: String) -> String {
|
||||
let oldLen: uint = bux_strlen(old);
|
||||
if oldLen == 0 {
|
||||
return s;
|
||||
}
|
||||
var sb: StringBuilder = StringBuilder_New();
|
||||
var remaining: String = s;
|
||||
while true {
|
||||
let pos: String = bux_strstr(remaining, old);
|
||||
if String_IsNull(pos) {
|
||||
StringBuilder_Append(&sb, remaining);
|
||||
break;
|
||||
}
|
||||
let prefixLen: uint = String_Offset(pos, remaining);
|
||||
let prefix: String = bux_str_slice(remaining, 0, prefixLen);
|
||||
StringBuilder_Append(&sb, prefix);
|
||||
StringBuilder_Append(&sb, new);
|
||||
let remLen: uint = bux_strlen(remaining);
|
||||
remaining = bux_str_slice(remaining, prefixLen + oldLen, remLen - prefixLen - oldLen);
|
||||
}
|
||||
let result: String = StringBuilder_Build(&sb);
|
||||
StringBuilder_Free(&sb);
|
||||
return result;
|
||||
}
|
||||
|
||||
extern func bux_str_to_float(s: String) -> float64;
|
||||
|
||||
func String_ToFloat(s: String) -> float64 {
|
||||
|
||||
+32
-1
@@ -1,5 +1,6 @@
|
||||
module Std::Test {
|
||||
import Std::Io::{PrintLine, PrintInt};
|
||||
import Std::String::{String_Eq};
|
||||
|
||||
extern func bux_exit(code: int);
|
||||
extern func bux_assert(cond: int, file: String, line: int, expr: String);
|
||||
@@ -14,7 +15,7 @@ func Test_Assert(cond: bool) {
|
||||
|
||||
func Test_AssertEqInt(a: int, b: int) {
|
||||
if a != b {
|
||||
PrintLine("ASSERT_EQ FAILED:");
|
||||
PrintLine("ASSERT_EQ_INT FAILED:");
|
||||
PrintInt(a);
|
||||
PrintLine(" != ");
|
||||
PrintInt(b);
|
||||
@@ -22,6 +23,31 @@ func Test_AssertEqInt(a: int, b: int) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AssertNeqInt(a: int, b: int) {
|
||||
if a == b {
|
||||
PrintLine("ASSERT_NEQ_INT FAILED: both are");
|
||||
PrintInt(a);
|
||||
bux_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AssertEqString(a: String, b: String) {
|
||||
if !String_Eq(a, b) {
|
||||
PrintLine("ASSERT_EQ_STRING FAILED:");
|
||||
PrintLine(a);
|
||||
PrintLine(" != ");
|
||||
PrintLine(b);
|
||||
bux_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AssertEqBool(a: bool, b: bool) {
|
||||
if a != b {
|
||||
PrintLine("ASSERT_EQ_BOOL FAILED");
|
||||
bux_exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AssertTrue(cond: bool) {
|
||||
if !cond {
|
||||
PrintLine("ASSERT_TRUE FAILED");
|
||||
@@ -42,4 +68,9 @@ func Test_Fail(msg: String) {
|
||||
bux_exit(1);
|
||||
}
|
||||
|
||||
func Test_Pass(msg: String) {
|
||||
PrintLine("PASS:");
|
||||
PrintLine(msg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user