summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-23 00:50:18 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-23 00:50:18 +0000
commit8cb7089b2eb09d40d9497cea40d088d94676a8c6 (patch)
treed4a2a33fe35807abc0cdeeb0be93edcbe75a4996
parent3a4a72a38675893c3a1854d05c72b957a6bd9364 (diff)
More docs, check 'docs/std'.
-rw-r--r--README.md22
-rw-r--r--docs/std/README.md7
-rw-r--r--docs/std/fs.md90
-rw-r--r--docs/std/map.md91
-rw-r--r--docs/std/option.md51
-rw-r--r--docs/std/path.md58
-rw-r--r--docs/std/result.md55
-rw-r--r--docs/std/string.md88
-rw-r--r--docs/std/vec.md1
-rw-r--r--std/map.zc67
-rw-r--r--std/string.zc106
-rw-r--r--tests/std/test_map_iter.zc32
-rw-r--r--tests/std/test_string_split.zc57
-rw-r--r--tests/std/test_string_utf8.zc56
14 files changed, 781 insertions, 0 deletions
diff --git a/README.md b/README.md
index aa773f4..29a00d9 100644
--- a/README.md
+++ b/README.md
@@ -85,6 +85,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official
- [Volatile](#volatile)
- [Named Constraints](#named-constraints)
- [15. Build Directives](#15-build-directives)
+- [Standard Library](#standard-library)
- [Tooling](#tooling)
- [Language Server (LSP)](#language-server-lsp)
- [REPL](#repl)
@@ -821,6 +822,27 @@ fn main() { ... }
---
+## Standard Library
+
+Zen C includes a standard library (`std`) covering essential functionality.
+
+[Browse the Standard Library Documentation](docs/std/README.md)
+
+### Key Modules
+
+| Module | Description | Docs |
+| :--- | :--- | :--- |
+| **`std/vec.zc`** | Growable dynamic array `Vec<T>`. | [Docs](docs/std/vec.md) |
+| **`std/string.zc`** | Heap-allocated `String` type with UTF-8 support. | [Docs](docs/std/string.md) |
+| **`std/map.zc`** | Generic Hash Map `Map<V>`. | [Docs](docs/std/map.md) |
+| **`std/fs.zc`** | File system operations. | [Docs](docs/std/fs.md) |
+| **`std/option.zc`** | Optional values (`Some`/`None`). | [Docs](docs/std/option.md) |
+| **`std/result.zc`** | Error handling (`Ok`/`Err`). | [Docs](docs/std/result.md) |
+| **`std/path.zc`** | Cross-platform path manipulation. | [Docs](docs/std/path.md) |
+| **`std/env.zc`** | Process environment variables. | [Docs](docs/std/env.md) |
+
+---
+
## Tooling
Zen C provides a built-in Language Server and REPL to enhance the development experience.
diff --git a/docs/std/README.md b/docs/std/README.md
index 8202192..988440d 100644
--- a/docs/std/README.md
+++ b/docs/std/README.md
@@ -1,3 +1,10 @@
# Standard Library
+- [Env (Environment)](./env.md) - Process environment variables.
+- [File System (FS)](./fs.md) - File I/O and directory operations.
+- [Map](./map.md) - Hash map implementation.
+- [Option](./option.md) - Optional values (Some/None).
+- [Path](./path.md) - File path manipulation.
+- [Result](./result.md) - Error handling (Ok/Err).
+- [String](./string.md) - Growable, heap-allocated string type.
- [Vector (Vec)](./vec.md) - A growable dynamic array.
diff --git a/docs/std/fs.md b/docs/std/fs.md
new file mode 100644
index 0000000..8aea0f5
--- /dev/null
+++ b/docs/std/fs.md
@@ -0,0 +1,90 @@
+# Standard Library: File System (`std/fs.zc`)
+
+The `fs` module provides functionality for interacting with the file system, including file I/O and directory operations.
+
+## Usage
+
+```zc
+import "std/fs.zc"
+
+fn main() {
+ // Reading a file
+ var res = File::open("example.txt", "r");
+ if (res.is_ok()) {
+ var file = res.unwrap();
+ var content = file.read_to_string();
+ if (content.is_ok()) {
+ println "{content.unwrap()}";
+ }
+ file.close();
+ }
+
+ // Static utilities
+ if (File::exists("data")) {
+ println "Data directory exists";
+ }
+}
+```
+
+## Structs
+
+### File
+
+Represents an open file handle.
+
+```zc
+struct File {
+ handle: void*;
+}
+```
+
+### Metadata
+
+File or directory metadata.
+
+```zc
+struct Metadata {
+ size: U64;
+ is_dir: bool;
+ is_file: bool;
+}
+```
+
+### DirEntry
+
+Represents an entry in a directory.
+
+```zc
+struct DirEntry {
+ name: String;
+ is_dir: bool;
+}
+```
+
+## File Methods
+
+### Open / Close
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **open** | `File::open(path: char*, mode: char*) -> Result<File>` | Opens a file with the specified mode (e.g., "r", "w", "rb"). Returns a `Result`. |
+| **close** | `close(self)` | Closes the file handle. |
+
+### Read / Write
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **read_to_string** | `read_to_string(self) -> Result<String>` | Reads the entire file content into a String. |
+| **read_all** | `File::read_all(path: char*) -> Result<String>` | Static utility to open, read, and close a file in one go. |
+| **write_string** | `write_string(self, content: char*) -> Result<bool>` | Writes a string to the file. |
+
+## Static Utilities
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **exists** | `File::exists(path: char*) -> bool` | Checks if a path exists. |
+| **metadata** | `File::metadata(path: char*) -> Result<Metadata>` | Retrieves metadata for a path. |
+| **create_dir** | `File::create_dir(path: char*) -> Result<bool>` | Creates a new directory. |
+| **remove_file** | `File::remove_file(path: char*) -> Result<bool>` | Deletes a file. |
+| **remove_dir** | `File::remove_dir(path: char*) -> Result<bool>` | Deletes a directory. |
+| **read_dir** | `File::read_dir(path: char*) -> Result<Vec<DirEntry>>` | Reads the contents of a directory. Returns a vector of `DirEntry`. |
diff --git a/docs/std/map.md b/docs/std/map.md
new file mode 100644
index 0000000..f8bd76a
--- /dev/null
+++ b/docs/std/map.md
@@ -0,0 +1,91 @@
+# Standard Library: Map (`std/map.zc`)
+
+`Map<V>` is a generic hash map implementation mapping string keys to values of type `V`.
+
+## Usage
+
+```zc
+import "std/map.zc"
+
+fn main() {
+ var m = Map<int>::new();
+
+ m.put("one", 1);
+ m.put("two", 2);
+
+ if (m.contains("one")) {
+ var val = m.get("one");
+ println "{val.unwrap()}";
+ }
+
+ m.remove("two");
+} // m is freed automatically (but values are NOT freed automatically if they are pointers)
+```
+
+## Structure
+
+```zc
+struct Map<V> {
+ keys: char**;
+ vals: V*;
+ // ... internal fields
+}
+```
+
+## Methods
+
+### Construction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **new** | `Map<V>::new() -> Map<V>` | Creates a new, empty map. |
+
+### Iteration
+
+You can iterate over the map's key-value pairs using a `for` loop.
+
+```zc
+var m = Map<int>::new();
+m.put("a", 1);
+
+for entry in m {
+ println "Key: {entry.key}, Val: {entry.val}";
+}
+```
+
+The iterator yields a `MapEntry<V>` struct:
+```zc
+struct MapEntry<V> {
+ key: char*;
+ val: V;
+}
+```
+
+### Modification
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **put** | `put(self, key: char*, val: V)` | Inserts or updates a key-value pair. |
+| **remove** | `remove(self, key: char*)` | Removes a key and its value from the map. |
+| **free** | `free(self)` | Frees the map's internal storage. **Note**: This does not free the values if they are pointers/objects. |
+
+### Access
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **get** | `get(self, key: char*) -> Option<V>` | Retrieves the value associated with the key. |
+| **contains** | `contains(self, key: char*) -> bool` | Returns true if the key exists. |
+| **length** | `length(self) -> usize` | Returns the number of items in the map. |
+| **is_empty** | `is_empty(self) -> bool` | Returns true if the map is empty. |
+| **capacity** | `capacity(self) -> usize` | Returns the current capacity of the map. |
+
+
+### Iteration Helpers
+
+You can use index-based access if needed:
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **is_slot_occupied** | `is_slot_occupied(self, idx: usize) -> bool` | Checks if a raw slot index is occupied. |
+| **key_at** | `key_at(self, idx: usize) -> char*` | Gets key at raw slot index. |
+| **val_at** | `val_at(self, idx: usize) -> V` | Gets value at raw slot index. |
diff --git a/docs/std/option.md b/docs/std/option.md
new file mode 100644
index 0000000..a048b00
--- /dev/null
+++ b/docs/std/option.md
@@ -0,0 +1,51 @@
+# Standard Library: Option (`std/option.zc`)
+
+`Option<T>` represents an optional value: every `Option` is either `Some` and contains a value, or `None`, and does not.
+
+## Usage
+
+```zc
+import "std/option.zc"
+
+fn main() {
+ var val = Option<int>::Some(10);
+
+ if (val.is_some()) {
+ println "{val.unwrap()}";
+ }
+
+ var nothing = Option<int>::None();
+ println "{nothing.unwrap_or(0)}"; // Prints 0
+}
+```
+
+## Structure
+
+```zc
+struct Option<T> {
+ is_some: bool;
+ val: T;
+}
+```
+
+## Methods
+
+### Construction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **Some** | `Option<T>::Some(v: T) -> Option<T>` | Creates a `Some` option containing `v`. |
+| **None** | `Option<T>::None() -> Option<T>` | Creates a `None` option. |
+
+### Query / Extraction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **is_some** | `is_some(self) -> bool` | Returns `true` if the option is a `Some` value. |
+| **is_none** | `is_none(self) -> bool` | Returns `true` if the option is a `None` value. |
+| **unwrap** | `unwrap(self) -> T` | Returns the contained value. Panics if `None`. |
+| **unwrap_ref** | `unwrap_ref(self) -> T*` | Returns a pointer to the contained value. Panics if `None`. |
+| **unwrap_or** | `unwrap_or(self, def: T) -> T` | Returns the contained value or `def` if `None`. |
+| **expect** | `expect(self, msg: char*) -> T` | Returns the contained value or panics with `msg` if `None`. |
+| **or_else** | `or_else(self, other: Option<T>) -> Option<T>` | Returns the option if it contains a value, otherwise returns `other`. |
+| **forget** | `forget(self)` | Zeroes out memory without destructors (if applicable). |
diff --git a/docs/std/path.md b/docs/std/path.md
new file mode 100644
index 0000000..0be30fd
--- /dev/null
+++ b/docs/std/path.md
@@ -0,0 +1,58 @@
+# Standard Library: Path (`std/path.zc`)
+
+`Path` provides cross-platform file path manipulation utilities. It wraps a `String` and handles separators (slash vs backslash) intelligently, though currently biased towards Unix defaults pending full Windows support integration.
+
+## Usage
+
+```zc
+import "std/path.zc"
+
+fn main() {
+ var p = Path::new("/home/user");
+ var full_path = p.join("docs/file.txt");
+
+ println "Full path: {full_path.c_str()}";
+
+ if (full_path.extension().is_some()) {
+ println "Extension: {full_path.extension().unwrap()}";
+ }
+}
+```
+
+## Structure
+
+```zc
+struct Path {
+ str: String;
+}
+```
+
+## Methods
+
+### Construction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **new** | `Path::new(s: char*) -> Path` | Creates a new Path from a C string. |
+| **from_string** | `Path::from_string(s: String) -> Path` | Creates a Path taking ownership of a String. |
+
+### Manipulation
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **join** | `join(self, other: char*) -> Path` | Joins the current path with `other` using the correct separator. Returns a new Path. |
+| **clone** | `clone(self) -> Path` | Creates a deep copy of the Path. |
+
+### Parsing
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **extension** | `extension(self) -> Option<String>` | Returns the file extension (without the dot), or `None` if no extension. |
+| **file_name** | `file_name(self) -> Option<String>` | Returns the last component of the path. |
+| **parent** | `parent(self) -> Option<Path>` | Returns the path without its final component. |
+
+### Access
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **c_str** | `c_str(self) -> char*` | Returns a pointer to the underlying C string. |
diff --git a/docs/std/result.md b/docs/std/result.md
new file mode 100644
index 0000000..1f9390e
--- /dev/null
+++ b/docs/std/result.md
@@ -0,0 +1,55 @@
+# Standard Library: Result (`std/result.zc`)
+
+`Result<T>` is the standard type for error handling. It represents either success (`Ok`) containing a value, or failure (`Err`) containing a string error message.
+
+## Usage
+
+```zc
+import "std/result.zc"
+
+fn divide(a: int, b: int) -> Result<int> {
+ if (b == 0) {
+ return Result<int>::Err("Division by zero");
+ }
+ return Result<int>::Ok(a / b);
+}
+
+fn main() {
+ var res = divide(10, 2);
+ if (res.is_ok()) {
+ println "Result: {res.unwrap()}";
+ } else {
+ println "Error: {res.err}";
+ }
+}
+```
+
+## Structure
+
+```zc
+struct Result<T> {
+ is_ok: bool;
+ val: T;
+ err: char*;
+}
+```
+
+## Methods
+
+### Construction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **Ok** | `Result<T>::Ok(v: T) -> Result<T>` | Creates a success result containing `v`. |
+| **Err** | `Result<T>::Err(e: char*) -> Result<T>` | Creates an error result with message `e`. |
+
+### Query / Extraction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **is_ok** | `is_ok(self) -> bool` | Returns `true` if the result is `Ok`. |
+| **is_err** | `is_err(self) -> bool` | Returns `true` if the result is `Err`. |
+| **unwrap** | `unwrap(self) -> T` | Returns the contained value. Panics if `Err`. |
+| **unwrap_ref** | `unwrap_ref(self) -> T*` | Returns a pointer to the contained value. Panics if `Err`. |
+| **expect** | `expect(self, msg: char*) -> T` | Returns the contained value or panics with `msg` if `Err`. |
+| **forget** | `forget(self)` | Zeroes out memory without destructors. |
diff --git a/docs/std/string.md b/docs/std/string.md
new file mode 100644
index 0000000..b83ee80
--- /dev/null
+++ b/docs/std/string.md
@@ -0,0 +1,88 @@
+# Standard Library: String (`std/string.zc`)
+
+`String` is a growable, heap-allocated string type. It wraps a `Vec<char>` and ensures null-termination for C compatibility.
+
+## Usage
+
+```zc
+import "std/string.zc"
+
+fn main() {
+ var s = String::from("Hello");
+ s.append(String::from(" World"));
+
+ println "{s}"; // Prints "Hello World"
+
+ if (s.starts_with("Hello")) {
+ // ...
+ }
+} // s is freed automatically
+```
+
+## Structure
+
+```zc
+struct String {
+ vec: Vec<char>;
+}
+```
+
+## Methods
+
+### Construction
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **new** | `String::new(s: char*) -> String` | Creates a new String from a C string primitive. |
+| **from** | `String::from(s: char*) -> String` | Alias for `new`. |
+
+### Modification
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **append** | `append(self, other: String*)` | Appends another string to this one. |
+| **add** | `add(self, other: String*) -> String` | Concatenates this string and another into a new String. |
+
+### Access & Query
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **c_str** | `c_str(self) -> char*` | Returns the underlying C string pointer. |
+| **length** | `length(self) -> usize` | Returns the length of the string (excluding null terminator). |
+| **is_empty** | `is_empty(self) -> bool` | Returns true if length is 0. |
+| **starts_with** | `starts_with(self, prefix: char*) -> bool` | Checks if the string starts with the given prefix. |
+| **ends_with** | `ends_with(self, suffix: char*) -> bool` | Checks if the string ends with the given suffix. |
+| **contains** | `contains(self, target: char) -> bool` | Checks if the string contains the given character. |
+| **find** | `find(self, target: char) -> Option<usize>` | Returns the index of the first occurrence of `target`. |
+| **substring** | `substring(self, start: usize, len: usize) -> String` | Returns a new String containing the specified substring. |
+
+### UTF-8 Support
+
+These methods handle UTF-8 character boundaries correctly, contrasting with the byte-oriented methods above.
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **utf8_len** | `utf8_len(self) -> usize` | Returns the number of Unicode code points (characters). |
+| **utf8_at** | `utf8_at(self, idx: usize) -> String` | Returns the character at the specified *character index* as a new String. |
+| **utf8_substr** | `utf8_substr(self, start_idx: usize, num_chars: usize) -> String` | Returns a substring based on character indices and character count. |
+
+
+### Transformations
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **split** | `split(self, delim: char) -> Vec<String>` | Splits the string into a vector of substrings separated by `delim`. |
+
+### Comparison
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **eq** | `eq(self, other: String*) -> bool` | Returns true if the strings are equal content-wise. |
+
+### Memory Management
+
+| Method | Signature | Description |
+| :--- | :--- | :--- |
+| **free** | `free(self)` | Frees the string memory. |
+| **destroy** | `destroy(self)` | Alias for `free`. |
+| **forget** | `forget(self)` | Prevents automatic freeing (useful for transferring ownership). |
diff --git a/docs/std/vec.md b/docs/std/vec.md
index 3ccdf7e..1b48361 100644
--- a/docs/std/vec.md
+++ b/docs/std/vec.md
@@ -75,6 +75,7 @@ struct Vec<T> {
| **is_empty** | `is_empty(self) -> bool` | Returns `true` if the vector contains no elements. |
| **contains** | `contains(self, item: T) -> bool` | Returns `true` if vector contains an element equal to `item` (byte-wise). |
| **clone** | `clone(self) -> Vec<T>` | Returns a new vector with a deep copy of the data. |
+| **eq** | `eq(self, other: Vec<T>) -> bool` | Returns `true` if two vectors are equal byte-wise. |
### Iteration
diff --git a/std/map.zc b/std/map.zc
index a7bae69..ca4766f 100644
--- a/std/map.zc
+++ b/std/map.zc
@@ -23,6 +23,62 @@ struct Map<V> {
cap: usize;
}
+struct MapEntry<V> {
+ key: char*;
+ val: V;
+}
+
+struct MapIter<V> {
+ keys: char**;
+ vals: V*;
+ occupied: bool*;
+ deleted: bool*;
+ cap: usize;
+ idx: usize;
+}
+
+struct MapIterResult<V> {
+ entry: MapEntry<V>;
+ has_val: bool;
+}
+
+impl MapIterResult<V> {
+ fn is_none(self) -> bool {
+ return !self.has_val;
+ }
+
+ fn unwrap(self) -> MapEntry<V> {
+ if (!self.has_val) {
+ !"Panic: Map iterator unwrap on None";
+ exit(1);
+ }
+ return self.entry;
+ }
+}
+
+impl MapIter<V> {
+ fn next(self) -> MapIterResult<V> {
+ while self.idx < self.cap {
+ var i = self.idx;
+ self.idx = self.idx + 1;
+
+ if (self.occupied[i] && !self.deleted[i]) {
+ var entry = MapEntry<V> {
+ key: self.keys[i],
+ val: self.vals[i]
+ };
+ return MapIterResult<V> {
+ entry: entry,
+ has_val: true
+ };
+ }
+ }
+ // Empty entry for None
+ var empty = MapEntry<V> { key: 0, val: self.vals[0] }; // Should be 0-init if possible
+ return MapIterResult<V> { entry: empty, has_val: false };
+ }
+}
+
impl Map<V> {
fn new() -> Map<V> {
return Map<V> { keys: 0, vals: 0, occupied: 0, deleted: 0, len: 0, cap: 0 };
@@ -180,4 +236,15 @@ impl Map<V> {
fn val_at(self, idx: usize) -> V {
return self.vals[idx];
}
+
+ fn iterator(self) -> MapIter<V> {
+ return MapIter<V> {
+ keys: self.keys,
+ vals: self.vals,
+ occupied: self.occupied,
+ deleted: self.deleted,
+ cap: self.cap,
+ idx: 0
+ };
+ }
}
diff --git a/std/string.zc b/std/string.zc
index 02ae6b9..3f96053 100644
--- a/std/string.zc
+++ b/std/string.zc
@@ -139,4 +139,110 @@ impl String {
fn free(self) {
self.vec.free();
}
+
+ fn _utf8_seq_len(first_byte: char) -> usize {
+ var b = (int)first_byte;
+ if ((b & 0x80) == 0) { return 1; }
+ if ((b & 0xE0) == 0xC0) { return 2; }
+ if ((b & 0xF0) == 0xE0) { return 3; }
+ if ((b & 0xF8) == 0xF0) { return 4; }
+ return 1; // Fallback
+ }
+
+ fn utf8_len(self) -> usize {
+ var count: usize = 0;
+ var i: usize = 0;
+ var len = self.length();
+ while i < len {
+ var c = self.vec.get(i);
+ i = i + String::_utf8_seq_len(c);
+ count = count + 1;
+ }
+ return count;
+ }
+
+ fn utf8_at(self, idx: usize) -> String {
+ var count: usize = 0;
+ var i: usize = 0;
+ var len = self.length();
+ while i < len {
+ var c = self.vec.get(i);
+ var seq = String::_utf8_seq_len(c);
+
+ if (count == idx) {
+ return self.substring(i, seq);
+ }
+
+ i = i + seq;
+ count = count + 1;
+ }
+ return String::new("");
+ }
+
+ fn utf8_substr(self, start_idx: usize, num_chars: usize) -> String {
+ if (num_chars == 0) { return String::new(""); }
+
+ var byte_start: usize = 0;
+ var byte_len: usize = 0;
+
+ var count: usize = 0;
+ var i: usize = 0;
+ var len = self.length();
+ var found_start = false;
+
+ while i < len {
+ // Check if we reached the start char
+ if (!found_start && count == start_idx) {
+ byte_start = i;
+ found_start = true;
+ // Reset count to track chars collected
+ count = 0;
+ } else if (!found_start) {
+ // Still seeking start
+ var c = self.vec.get(i);
+ i = i + String::_utf8_seq_len(c);
+ count = count + 1;
+ continue;
+ }
+
+ // If we are here, we are collecting chars
+ if (count < num_chars) {
+ var c = self.vec.get(i);
+ var seq = String::_utf8_seq_len(c);
+ byte_len = byte_len + seq;
+ i = i + seq;
+ count = count + 1;
+ } else {
+ break;
+ }
+ }
+
+ if (!found_start) { return String::new(""); }
+
+ return self.substring(byte_start, byte_len);
+ }
+ fn split(self, delim: char) -> Vec<String> {
+ var parts = Vec<String>::new();
+ var len = self.length();
+ if (len == 0) { return parts; }
+
+ var start: usize = 0;
+ var i: usize = 0;
+
+ while i < len {
+ if (self.vec.get(i) == delim) {
+ // Found delimiter
+ parts.push(self.substring(start, i - start));
+ start = i + 1;
+ }
+ i = i + 1;
+ }
+
+ // Push last segment
+ if (start <= len) {
+ parts.push(self.substring(start, len - start));
+ }
+
+ return parts;
+ }
} \ No newline at end of file
diff --git a/tests/std/test_map_iter.zc b/tests/std/test_map_iter.zc
new file mode 100644
index 0000000..efce356
--- /dev/null
+++ b/tests/std/test_map_iter.zc
@@ -0,0 +1,32 @@
+import "std/map.zc"
+import "std/string.zc"
+
+test "map_iterator" {
+ var m = Map<int>::new();
+
+ m.put("one", 1);
+ m.put("two", 2);
+ m.put("three", 3);
+
+ var count = 0;
+ var sum = 0;
+
+ for entry in m {
+ count = count + 1;
+ sum = sum + entry.val;
+
+ println "Key: {entry.key}, Val: {entry.val}";
+ }
+
+ if (count != 3) {
+ println "Map iterator count failed: {count}";
+ exit(1);
+ }
+
+ if (sum != 6) {
+ println "Map iterator sum failed: {sum}";
+ exit(1);
+ }
+
+ m.free();
+}
diff --git a/tests/std/test_string_split.zc b/tests/std/test_string_split.zc
new file mode 100644
index 0000000..496dbd1
--- /dev/null
+++ b/tests/std/test_string_split.zc
@@ -0,0 +1,57 @@
+import "std/string.zc"
+
+test "string_split_basic" {
+ var s = String::from("a,b,c");
+ var parts = s.split(',');
+
+ if (parts.length() != 3) {
+ println "Split length fail: {parts.length()}";
+ exit(1);
+ }
+
+ var p0 = parts.get(0);
+ var t0 = String::from("a");
+ if (!p0.eq(&t0)) exit(1);
+
+ var p1 = parts.get(1);
+ var t1 = String::from("b");
+ if (!p1.eq(&t1)) exit(1);
+
+ var p2 = parts.get(2);
+ var t2 = String::from("c");
+ if (!p2.eq(&t2)) exit(1);
+
+ for p in &parts {
+ p.free();
+ }
+ parts.free();
+}
+
+test "string_split_edge" {
+ var s = String::from("hello");
+ var parts = s.split(' '); // Not found
+
+ if (parts.length() != 1) {
+ println "Split edge 1 failed";
+ exit(1);
+ }
+
+ var p0 = parts.get(0);
+ if (!p0.eq(&s)) exit(1);
+
+ p0.free();
+ parts.free();
+
+ var s2 = String::from("a,,b");
+ var parts2 = s2.split(',');
+ if (parts2.length() != 3) {
+ println "Split edge 2 failed: {parts2.length()}";
+ exit(1);
+ }
+
+ var empty = parts2.get(1);
+ if (!empty.is_empty()) exit(1);
+
+ for p in &parts2 { p.free(); }
+ parts2.free();
+}
diff --git a/tests/std/test_string_utf8.zc b/tests/std/test_string_utf8.zc
new file mode 100644
index 0000000..a4d2095
--- /dev/null
+++ b/tests/std/test_string_utf8.zc
@@ -0,0 +1,56 @@
+import "std/string.zc"
+
+test "utf8_basic_ascii" {
+ var s = String::from("Hello");
+
+ if (s.utf8_len() != 5) {
+ println "ASCII Len failed";
+ exit(1);
+ }
+
+ var h = s.utf8_at(0);
+ var t1 = String::from("H");
+ if (!h.eq(&t1)) {
+ println "ASCII At failed";
+ exit(1);
+ }
+
+ var sub = s.utf8_substr(1, 4);
+ var t2 = String::from("ello");
+ if (!sub.eq(&t2)) {
+ println "ASCII Substr failed";
+ exit(1);
+ }
+}
+
+test "utf8_multibyte" {
+ // "Hey 🌎!" - "Hey " (4) + 🌎 (1) + "!" (1) = 6 chars
+ // Bytes: H(1) e(1) y(1) (1) 🌎(4) !(1) = 9 bytes?
+ // 🌎 is approx F0 9F 8C 8D (4 bytes)
+
+ var s = String::from("Hey 🌎!");
+
+ if (s.length() != 9) {
+ println "Byte length unexpected: {s.length()}";
+ exit(1);
+ }
+
+ if (s.utf8_len() != 6) {
+ println "UTF8 Len failed. Got {s.utf8_len()}, expected 6";
+ exit(1);
+ }
+
+ var globe = s.utf8_at(4);
+ var t3 = String::from("🌎");
+ if (!globe.eq(&t3)) {
+ println "UTF8 At failed";
+ exit(1);
+ }
+
+ var sub = s.utf8_substr(4, 2); // "🌎!"
+ var t4 = String::from("🌎!");
+ if (!sub.eq(&t4)) {
+ println "UTF8 Substr failed";
+ exit(1);
+ }
+} \ No newline at end of file