summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-29 13:17:30 +0000
committerZuhaitz Méndez Fernández de Aránguiz <zuhaitz@debian>2026-01-29 13:17:30 +0000
commitfc6ff10acb9d00ea1c8c5924869e0efbd38093c5 (patch)
treea4cb5d9d9d0ea2702de42df1e3c2fbe40185e293
parentda9e8758e9d89dc7362be67f8e7573309efe170c (diff)
Objective-C interop + a few improvements
-rw-r--r--README.md64
-rw-r--r--docs/lex.md130
-rw-r--r--docs/std/README.md6
-rw-r--r--docs/std/json.md57
-rw-r--r--docs/std/net.md44
-rw-r--r--docs/std/set.md38
-rw-r--r--docs/std/stack.md38
-rw-r--r--docs/std/thread.md47
-rw-r--r--docs/std/time.md38
-rw-r--r--examples/objc_interop.zc42
-rw-r--r--src/codegen/codegen_decl.c19
-rw-r--r--src/codegen/compat.h47
-rw-r--r--src/main.c8
-rw-r--r--src/utils/utils.c223
-rw-r--r--src/zprep.h1
-rw-r--r--std/fs.zc46
-rw-r--r--std/json.zc7
-rw-r--r--std/map.zc7
-rw-r--r--std/net.zc23
-rw-r--r--std/thread.zc9
-rw-r--r--std/time.zc26
-rw-r--r--tests/features/test_build_directives.zc21
22 files changed, 793 insertions, 148 deletions
diff --git a/README.md b/README.md
index a35e0de..e295eb5 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ Join the discussion, share demos, ask questions, or report bugs in the official
- [Building with Zig](#building-with-zig)
- [C++ Interop](#c-interop)
- [CUDA Interop](#cuda-interop)
+ - [Objective-C Interop](#objective-c-interop)
- [Contributing](#contributing)
- [Attributions](#attributions)
@@ -929,7 +930,7 @@ Decorate functions and structs to modify compiler behavior.
| `@derive(...)` | Struct | Auto-implement traits. Supports `Debug`, `Eq` (Smart Derive), `Copy`, `Clone`. |
| `@<custom>` | Any | Passes generic attributes to C (e.g. `@flatten`, `@alias("name")`). |
-### Custom Attributes
+#### Custom Attributes
Zen C supports a powerful **Custom Attribute** system that allows you to use any GCC/Clang `__attribute__` directly in your code. Any attribute that is not explicitly recognized by the Zen C compiler is treated as a generic attribute and passed through to the generated C code.
@@ -941,7 +942,7 @@ Zen C attributes are mapped directly to C attributes:
- `@name(args)` → `__attribute__((name(args)))`
- `@name("string")` → `__attribute__((name("string")))`
-### Smart Derives
+#### Smart Derives
Zen C provides "Smart Derives" that respect Move Semantics:
@@ -1007,12 +1008,33 @@ Zen C supports special comments at the top of your source file to configure the
| `//> link:` | `-lfoo` or `path/to/lib.a` | Link against a library or object file. |
| `//> lib:` | `path/to/libs` | Add a library search path (`-L`). |
| `//> include:` | `path/to/headers` | Add an include search path (`-I`). |
+| `//> framework:` | `Cocoa` | Link against a macOS framework. |
| `//> cflags:` | `-Wall -O3` | Pass arbitrary flags to the C compiler. |
| `//> define:` | `MACRO` or `KEY=VAL` | Define a preprocessor macro (`-D`). |
| `//> pkg-config:` | `gtk+-3.0` | Run `pkg-config` and append `--cflags` and `--libs`. |
| `//> shell:` | `command` | Execute a shell command during the build. |
| `//> get:` | `http://url/file` | Download a file if specific file does not exist. |
+#### Features
+
+**1. OS Guarding**
+Prefix directives with an OS name to apply them only on specific platforms.
+Supported prefixes: `linux:`, `windows:`, `macos:` (or `darwin:`).
+
+```zc
+//> linux: link: -lm
+//> windows: link: -lws2_32
+//> macos: framework: Cocoa
+```
+
+**2. Environment Variable Expansion**
+Use `${VAR}` syntax to expand environment variables in your directives.
+
+```zc
+//> include: ${HOME}/mylib/include
+//> lib: ${ZC_ROOT}/std
+```
+
#### Examples
```zc
@@ -1072,6 +1094,12 @@ Zen C includes a standard library (`std`) covering essential functionality.
| **`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) |
+| **`std/net.zc`** | TCP networking (Sockets). | [Docs](docs/std/net.md) |
+| **`std/thread.zc`** | Threads and Synchronization. | [Docs](docs/std/thread.md) |
+| **`std/time.zc`** | Time measurement and sleep. | [Docs](docs/std/time.md) |
+| **`std/json.zc`** | JSON parsing and serialization. | [Docs](docs/std/json.md) |
+| **`std/stack.zc`** | LIFO Stack `Stack<T>`. | [Docs](docs/std/stack.md) |
+| **`std/set.zc`** | Generic Hash Set `Set<T>`. | [Docs](docs/std/set.md) |
---
@@ -1302,6 +1330,38 @@ let tid = local_id();
> **Note:** The `--cuda` flag sets `nvcc` as the compiler and implies `--cpp` mode. Requires the NVIDIA CUDA Toolkit.
+### Objective-C Interop
+
+Zen C can compile to Objective-C (`.m`) using the `--objc` flag, allowing you to use Objective-C frameworks (like Cocoa/Foundation) and syntax.
+
+```bash
+# Compile with clang (or gcc/gnustep)
+zc app.zc --objc --cc clang
+```
+
+#### Using Objective-C in Zen C
+
+Use `include` for headers and `raw` blocks for Objective-C syntax (`@interface`, `[...]`, `@""`).
+
+```zc
+//> macos: framework: Foundation
+//> linux: cflags: -fconstant-string-class=NSConstantString -D_NATIVE_OBJC_EXCEPTIONS
+//> linux: link: -lgnustep-base -lobjc
+
+include <Foundation/Foundation.h>
+
+fn main() {
+ raw {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSLog(@"Hello from Objective-C!");
+ [pool drain];
+ }
+ println "Zen C works too!";
+}
+```
+
+> **Note:** Zen C string interpolation works with Objective-C objects (`id`) by calling `debugDescription` or `description`.
+
---
## Contributing
diff --git a/docs/lex.md b/docs/lex.md
new file mode 100644
index 0000000..1cd70fd
--- /dev/null
+++ b/docs/lex.md
@@ -0,0 +1,130 @@
+# Lexical Structure
+
+## Source Text
+
+Zen-C source code is encoded in UTF-8.
+
+## Grammar Notation
+
+The lexical grammar is defined using a notation similar to EBNF.
+- `Rule ::= Production`: Defines a rule.
+- `[ ... ]`: Character class.
+- `*`: Zero or more repetitions.
+- `+`: One or more repetitions.
+- `?`: Zero or one occurrence.
+- `|`: Alternation.
+- `"..."` or `'...'`: Literal string/character.
+- `~`: Negation (e.g., `~[\n]` means any character except newline).
+
+## Whitespace and Comments
+
+Whitespace separates tokens but is otherwise ignored. Comments are treated as whitespace.
+
+```text
+Whitespace ::= [ \t\n\r]+
+Comment ::= LineComment | BlockComment
+
+LineComment ::= "//" ~[\n]*
+BlockComment ::= "/*" (BlockComment | ~("*/"))* "*/"
+```
+
+## Identifiers
+
+Identifiers name entities such as variables, functions, and types.
+
+```text
+Identifier ::= IdentifierStart IdentifierPart*
+IdentifierStart ::= [a-zA-Z_]
+IdentifierPart ::= [a-zA-Z0-9_]
+```
+
+## Literals
+
+### Integer Literals
+
+Integers can be decimal, hexadecimal, or binary.
+
+```text
+IntegerLiteral ::= ( DecimalInt | HexInt | BinaryInt ) IntegerSuffix?
+
+DecimalInt ::= [0-9]+
+HexInt ::= "0x" [0-9a-fA-F]+
+BinaryInt ::= "0b" [01]+
+
+IntegerSuffix ::= "u" | "L" | "u64" | ...
+```
+*Note: The lexer technically consumes any alphanumeric sequence following a number as a suffix.*
+
+### Floating Point Literals
+
+```text
+FloatLiteral ::= [0-9]+ "." [0-9]* FloatSuffix?
+ | [0-9]+ FloatSuffix
+
+FloatSuffix ::= "f"
+```
+
+### String Literals
+
+```text
+StringLiteral ::= '"' StringChar* '"'
+StringChar ::= ~["\\] | EscapeSequence
+EscapeSequence ::= "\\" ( ["\\/bfnrt] | "u" HexDigit{4} )
+```
+
+### F-Strings
+
+```text
+FStringLiteral ::= 'f"' StringChar* '"'
+```
+
+
+### Character Literals
+
+```text
+CharLiteral ::= "'" ( ~['\\] | EscapeSequence ) "'"
+```
+
+## Keywords
+
+```text
+Keyword ::= Declaration | Control | Special | BoolLiteral | NullLiteral | LogicOp
+
+Declaration ::= "let" | "def" | "fn" | "struct" | "enum" | "union" | "alias"
+ | "trait" | "impl" | "use" | "module" | "import" | "opaque"
+
+Control ::= "if" | "else" | "match" | "for" | "while" | "loop"
+ | "return" | "break" | "continue" | "guard" | "unless"
+ | "defer" | "async" | "await" | "try" | "catch" | "goto"
+
+Special ::= "asm" | "assert" | "test" | "sizeof" | "embed" | "comptime"
+ | "autofree" | "volatile" | "launch" | "ref" | "static" | "const"
+
+BoolLiteral ::= "true" | "false"
+NullLiteral ::= "null"
+
+CReserved ::= "auto" | "case" | "char" | "default" | "do" | "double"
+ | "extern" | "float" | "inline" | "int" | "long" | "register"
+ | "restrict" | "short" | "signed" | "switch" | "typedef"
+ | "unsigned" | "void" | "_Atomic" | "_Bool" | "_Complex"
+ | "_Generic" | "_Imaginary" | "_lmaginary" | "_Noreturn"
+ | "_Static_assert" | "_Thread_local"
+
+LogicOp ::= "and" | "or"
+```
+
+## Operators and Punctuation
+
+```text
+Operator ::= "+" | "-" | "*" | "/" | "%"
+ | "&&" | "||" | "!" | "++" | "--"
+ | "&" | "|" | "^" | "~" | "<<" | ">>"
+ | "==" | "!=" | "<" | ">" | "<=" | ">="
+ | "=" | "+=" | "-=" | "*=" | "/=" | "%="
+ | "&=" | "|=" | "^=" | "<<=" | ">>="
+ | ".." | "..=" | "..<" | "..."
+ | "." | "?." | "??" | "??=" | "->" | "=>"
+ | "::" | "|>" | "?"
+ | "(" | ")" | "{" | "}" | "[" | "]"
+ | "," | ":" | ";" | "@"
+```
diff --git a/docs/std/README.md b/docs/std/README.md
index 6125a4e..16ffc74 100644
--- a/docs/std/README.md
+++ b/docs/std/README.md
@@ -3,10 +3,16 @@
- [Env (Environment)](./env.md) - Process environment variables.
- [File System (FS)](./fs.md) - File I/O and directory operations.
- [IO](./io.md) - Standard Input/Output.
+- [JSON](./json.md) - JSON parsing and serialization.
- [Map](./map.md) - Hash map implementation.
+- [Networking (Net)](./net.md) - TCP networking.
- [Option](./option.md) - Optional values (Some/None).
- [Path](./path.md) - File path manipulation.
- [Result](./result.md) - Error handling (Ok/Err).
- [Queue](./queue.md) - FIFO queue (Ring Buffer).
+- [Set](./set.md) - Hash set implementation.
+- [Stack](./stack.md) - LIFO stack.
- [String](./string.md) - Growable, heap-allocated string type.
+- [Thread (Concurrency)](./thread.md) - Multithreading and synchronization.
+- [Time](./time.md) - Time measurement and sleep.
- [Vector (Vec)](./vec.md) - A growable dynamic array.
diff --git a/docs/std/json.md b/docs/std/json.md
new file mode 100644
index 0000000..fba2ad8
--- /dev/null
+++ b/docs/std/json.md
@@ -0,0 +1,57 @@
+# JSON (`std/json.zc`)
+
+The `std/json` module provides a DOM-style JSON parser and builder.
+
+## Usage
+
+```zc
+import "std/json.zc"
+```
+
+## Types
+
+### Struct `JsonValue`
+
+Represents a node in a JSON document.
+
+#### Creation Methods
+
+- **`fn null() -> JsonValue`**, **`fn null_ptr() -> JsonValue*`**
+- **`fn bool(b: bool) -> JsonValue`**, **`fn bool_ptr(b: bool) -> JsonValue*`**
+- **`fn number(n: double) -> JsonValue`**, **`fn number_ptr(n: double) -> JsonValue*`**
+- **`fn string(s: char*) -> JsonValue`**, **`fn string_ptr(s: char*) -> JsonValue*`**
+- **`fn array() -> JsonValue`**, **`fn array_ptr() -> JsonValue*`**
+- **`fn object() -> JsonValue`**, **`fn object_ptr() -> JsonValue*`**
+
+#### Parsing
+
+- **`fn parse(json: char*) -> Result<JsonValue*>`**
+ Parses a JSON string into a heap-allocated `JsonValue` tree.
+
+#### Accessors
+
+- **`fn is_null(self) -> bool`**, **`is_bool`**, **`is_number`**, **`is_string`**, **`is_array`**, **`is_object`**
+ Check the type of the value.
+
+- **`fn as_string(self) -> Option<char*>`**
+ Returns `Some(string)` if the value is a string, `None` otherwise.
+- **`fn as_int(self) -> Option<int>`**
+- **`fn as_float(self) -> Option<double>`**
+- **`fn as_bool(self) -> Option<bool>`**
+
+#### Object/Array Operations
+
+- **`fn push(self, val: JsonValue)`**
+ Appends a value to an array.
+- **`fn set(self, key: char*, val: JsonValue)`**
+ Sets a key-value pair in an object.
+
+- **`fn get(self, key: char*) -> Option<JsonValue*>`**
+ Retrieves a value from an object by key.
+- **`fn at(self, index: usize) -> Option<JsonValue*>`**
+ Retrieves a value from an array by index.
+
+#### Memory Management
+
+- **`fn free(self)`**
+ Recursively frees the JSON value and all its children.
diff --git a/docs/std/net.md b/docs/std/net.md
new file mode 100644
index 0000000..392c901
--- /dev/null
+++ b/docs/std/net.md
@@ -0,0 +1,44 @@
+# Networking (`std/net.zc`)
+
+The `std/net` module provides basic TCP networking capabilities.
+
+## Usage
+
+```zc
+import "std/net.zc"
+```
+
+## Types
+
+### Type `TcpListener`
+
+Represents a TCP socket listening for incoming connections.
+
+#### Methods
+
+- **`fn bind(host: char*, port: int) -> Result<TcpListener>`**
+ Creates a new listener bound to the specified host and port.
+
+- **`fn accept(self) -> Result<TcpStream>`**
+ Blocks waiting for a new connection. Returns a `TcpStream` for the connected client.
+
+- **`fn close(self)`**
+ Closes the listening socket.
+
+### Type `TcpStream`
+
+Represents a TCP connection stream.
+
+#### Methods
+
+- **`fn connect(host: char*, port: int) -> Result<TcpStream>`**
+ Connects to a remote host.
+
+- **`fn read(self, buf: char*, len: usize) -> Result<usize>`**
+ Reads up to `len` bytes into `buf`. Returns the number of bytes read.
+
+- **`fn write(self, buf: char*, len: usize) -> Result<usize>`**
+ Writes `len` bytes from `buf` to the stream. Returns the number of bytes written.
+
+- **`fn close(self)`**
+ Closes the connection.
diff --git a/docs/std/set.md b/docs/std/set.md
new file mode 100644
index 0000000..0d62a66
--- /dev/null
+++ b/docs/std/set.md
@@ -0,0 +1,38 @@
+# Set (`std/set.zc`)
+
+The `std/set` module provides a Generic Hash Set `Set<T>`.
+
+## Usage
+
+```zc
+import "std/set.zc"
+```
+
+## Types
+
+### Struct `Set<T>`
+
+A set of unique elements.
+
+#### Methods
+
+- **`fn new() -> Set<T>`**
+ Creates a new empty set.
+
+- **`fn add(self, val: T) -> bool`**
+ Adds a value to the set. Returns `true` if the value was added, `false` if it was already present.
+
+- **`fn contains(self, val: T) -> bool`**
+ Returns `true` if the set contains the value.
+
+- **`fn remove(self, val: T) -> bool`**
+ Removes a value from the set. Returns `true` if present and removed.
+
+- **`fn length(self) -> usize`**
+ Returns the number of elements in the set.
+
+- **`fn is_empty(self) -> bool`**
+ Returns `true` if the set is empty.
+
+- **`fn clear(self)`**
+ Removes all elements from the set.
diff --git a/docs/std/stack.md b/docs/std/stack.md
new file mode 100644
index 0000000..6e5da84
--- /dev/null
+++ b/docs/std/stack.md
@@ -0,0 +1,38 @@
+# Stack (`std/stack.zc`)
+
+The `std/stack` module provides a LIFO (Last-In, First-Out) stack data structure.
+
+## Usage
+
+```zc
+import "std/stack.zc"
+```
+
+## Types
+
+### Struct `Stack<T>`
+
+A generic stack.
+
+#### Methods
+
+- **`fn new() -> Stack<T>`**
+ Creates a new empty stack.
+
+- **`fn push(self, value: T)`**
+ Pushes a value onto the top of the stack.
+
+- **`fn pop(self) -> Option<T>`**
+ Removes and returns the top element of the stack. Returns `None` if empty.
+
+- **`fn length(self) -> usize`**
+ Returns the number of elements in the stack.
+
+- **`fn is_empty(self) -> bool`**
+ Returns `true` if the stack contains no elements.
+
+- **`fn clear(self)`**
+ Removes all elements from the stack.
+
+- **`fn clone(self) -> Stack<T>`**
+ Creates a deep copy of the stack.
diff --git a/docs/std/thread.md b/docs/std/thread.md
new file mode 100644
index 0000000..6ac7e29
--- /dev/null
+++ b/docs/std/thread.md
@@ -0,0 +1,47 @@
+# Concurrency (`std/thread.zc`)
+
+The `std/thread` module provides primitives for multithreading and synchronization.
+
+## Usage
+
+```zc
+import "std/thread.zc"
+```
+
+## Functions
+
+- **`fn sleep_ms(ms: int)`**
+ Sleeps the current thread for the specified number of milliseconds.
+
+## Types
+
+### Type `Thread`
+
+Represents a handle to a spawned thread.
+
+#### Methods
+
+- **`fn spawn(func: fn()) -> Result<Thread>`**
+ Spawns a new thread executing the provided function.
+ > Note: Currently supports void functions with no arguments.
+
+- **`fn join(self) -> Result<bool>`**
+ Blocks the current thread until the spawned thread finishes.
+
+### Type `Mutex`
+
+A mutual exclusion primitive for protecting shared data.
+
+#### Methods
+
+- **`fn new() -> Mutex`**
+ Creates a new mutex.
+
+- **`fn lock(self)`**
+ Acquires the lock. Blocks if the lock is already held.
+
+- **`fn unlock(self)`**
+ Releases the lock.
+
+- **`fn free(self)`**
+ Destroys the mutex and frees associated resources.
diff --git a/docs/std/time.md b/docs/std/time.md
new file mode 100644
index 0000000..97dd208
--- /dev/null
+++ b/docs/std/time.md
@@ -0,0 +1,38 @@
+# Time (`std/time.zc`)
+
+The `std/time` module provides functionality for measuring time and sleeping.
+
+## Usage
+
+```zc
+import "std/time.zc"
+```
+
+## Structs
+
+### Struct `Duration`
+
+Represents a span of time in milliseconds.
+
+#### Methods
+
+- **`fn from_ms(ms: U64) -> Duration`**
+ Creates a duration from milliseconds.
+
+- **`fn from_secs(s: U64) -> Duration`**
+ Creates a duration from seconds.
+
+### Struct `Time`
+
+Utilities for time manipulation.
+
+#### Methods
+
+- **`fn now() -> U64`**
+ Returns the current system time in milliseconds since the epoch.
+
+- **`fn sleep(d: Duration)`**
+ Sleeps for the specified duration.
+
+- **`fn sleep_ms(ms: U64)`**
+ Sleeps for the specified number of milliseconds.
diff --git a/examples/objc_interop.zc b/examples/objc_interop.zc
new file mode 100644
index 0000000..c33fd0d
--- /dev/null
+++ b/examples/objc_interop.zc
@@ -0,0 +1,42 @@
+
+//> macos: framework: Foundation
+//> linux: cflags: -fconstant-string-class=NSConstantString -D_NATIVE_OBJC_EXCEPTIONS -I/usr/include/x86_64-linux-gnu/GNUstep -std=gnu99
+//> linux: link: -lgnustep-base -lobjc
+
+include <Foundation/Foundation.h>
+
+raw {
+ id make_nsstring(const char* s) {
+ return [NSString stringWithUTF8String:s];
+ }
+
+ void print_description(id obj) {
+ NSLog(@"[ObjC Log] Description: %@", obj);
+ }
+}
+
+fn main() {
+
+ raw {
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ }
+
+ "=> Zen C + Objective-C interop demo.";
+ "=> (Make sure to run with: zc run --objc examples/objc_interop.zc)";
+
+ // Call ObjC helper to create an NSString (returned as id)
+ let s = make_nsstring("Hello from Objective-C!");
+
+ // Pass it back to ObjC
+ print_description(s);
+
+ "Zen C interpolated string: {s}";
+
+ raw {
+ // You can also raw ObjC syntax anywhere.
+ NSArray *arr = [NSArray arrayWithObjects: @"Zen", @"Bit", @"C", nil];
+ NSLog(@"[ObjC Raw] Array: %@", arr);
+
+ [pool drain];
+ }
+}
diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c
index 11cdece..31513ef 100644
--- a/src/codegen/codegen_decl.c
+++ b/src/codegen/codegen_decl.c
@@ -91,14 +91,8 @@ void emit_preamble(ParserContext *ctx, FILE *out)
fputs("static inline const char* _z_bool_str(_Bool b) { return b ? \"true\" : "
"\"false\"; }\n",
out);
- fputs("#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", "
- "signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", "
- "unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", "
- "long: \"%ld\", unsigned long: \"%lu\", long long: \"%lld\", "
- "unsigned long long: \"%llu\", float: \"%f\", double: \"%f\", "
- "char*: \"%s\", void*: \"%p\")\n",
- out);
- fputs("#define _z_arg(x) _Generic((x), _Bool: _z_bool_str(x), default: (x))\n", out);
+ fputs(ZC_C_GENERIC_STR, out);
+ fputs(ZC_C_ARG_GENERIC_STR, out);
}
fputs("typedef size_t usize;\ntypedef char* string;\n", out);
@@ -108,12 +102,11 @@ void emit_preamble(ParserContext *ctx, FILE *out)
fputs("typedef struct { pthread_t thread; void *result; } Async;\n", out);
}
fputs("typedef struct { void *func; void *ctx; } z_closure_T;\n", out);
- fputs("#define U0 void\n#define I8 int8_t\n#define U8 uint8_t\n#define I16 "
- "int16_t\n#define U16 uint16_t\n",
+ fputs("typedef void U0;\ntypedef int8_t I8;\ntypedef uint8_t U8;\ntypedef "
+ "int16_t I16;\ntypedef uint16_t U16;\n",
out);
- fputs("#define I32 int32_t\n#define U32 uint32_t\n#define I64 "
- "int64_t\n#define U64 "
- "uint64_t\n",
+ fputs("typedef int32_t I32;\ntypedef uint32_t U32;\ntypedef int64_t I64;\ntypedef "
+ "uint64_t U64;\n",
out);
fputs("#define F32 float\n#define F64 double\n", out);
diff --git a/src/codegen/compat.h b/src/codegen/compat.h
index 26b0df5..63a5af5 100644
--- a/src/codegen/compat.h
+++ b/src/codegen/compat.h
@@ -53,6 +53,27 @@
"#endif\n" \
"#endif\n"
+/* Generic selection string for C mode */
+#define ZC_C_GENERIC_STR \
+ "#ifdef __OBJC__\n" \
+ "#define _z_objc_map ,id: \"%s\", Class: \"%s\", SEL: \"%s\"\n" \
+ "#define _z_objc_arg_map(x) ,id: [(id)(x) description].UTF8String, Class: " \
+ "class_getName((Class)(x)), SEL: sel_getName((SEL)(x))\n" \
+ "#else\n" \
+ "#define _z_objc_map\n" \
+ "#define _z_objc_arg_map(x)\n" \
+ "#endif\n" \
+ "\n" \
+ "#define _z_str(x) _Generic((x), _Bool: \"%s\", char: \"%c\", " \
+ "signed char: \"%c\", unsigned char: \"%u\", short: \"%d\", " \
+ "unsigned short: \"%u\", int: \"%d\", unsigned int: \"%u\", " \
+ "long: \"%ld\", unsigned long: \"%lu\", long long: \"%lld\", " \
+ "unsigned long long: \"%llu\", float: \"%f\", double: \"%f\", " \
+ "char*: \"%s\", void*: \"%p\" _z_objc_map)\n"
+
+#define ZC_C_ARG_GENERIC_STR \
+ "#define _z_arg(x) _Generic((x), _Bool: _z_bool_str(x) _z_objc_arg_map(x), default: (x))\n"
+
#ifdef __cplusplus
#include <type_traits>
@@ -126,6 +147,32 @@ inline const char *_zc_fmt(void *)
}
#define _z_str(x) _zc_fmt(x)
+
+#ifdef __OBJC__
+#include <objc/objc.h>
+#include <objc/runtime.h>
+#include <objc/message.h> // for direct calls if needed, but [x description] is fine
+
+inline const char *_zc_fmt(id x)
+{
+ return [[x description] UTF8String];
+}
+inline const char *_zc_fmt(Class x)
+{
+ return class_getName(x);
+}
+inline const char *_zc_fmt(SEL x)
+{
+ return sel_getName(x);
+}
+// BOOL is signed char usually, already handled?
+// "typedef signed char BOOL;" on standard apple headers.
+// If it maps to signed char, `_zc_fmt(signed char)` handles it ("%c").
+// We might want "YES"/"NO" for BOOL.
+// But we can't distinguish typedefs in C++ function overloads easily if underlying type is same.
+// We'll leave BOOL as %c or %d for now to avoid ambiguity errors.
+#endif
+
#endif
#endif
diff --git a/src/main.c b/src/main.c
index b6392cc..f5d8c1b 100644
--- a/src/main.c
+++ b/src/main.c
@@ -172,6 +172,10 @@ int main(int argc, char **argv)
g_config.use_cuda = 1;
g_config.use_cpp = 1; // CUDA implies C++ mode.
}
+ else if (strcmp(arg, "--objc") == 0)
+ {
+ g_config.use_objc = 1;
+ }
else if (strcmp(arg, "--check") == 0)
{
g_config.mode_check = 1;
@@ -304,6 +308,10 @@ int main(int argc, char **argv)
{
temp_source_file = "out.cpp";
}
+ else if (g_config.use_objc)
+ {
+ temp_source_file = "out.m";
+ }
// Codegen to C/C++/CUDA
FILE *out = fopen(temp_source_file, "w");
diff --git a/src/utils/utils.c b/src/utils/utils.c
index 56a7690..d6d9853 100644
--- a/src/utils/utils.c
+++ b/src/utils/utils.c
@@ -533,9 +533,81 @@ char g_cflags[MAX_FLAGS_SIZE] = "";
int g_warning_count = 0;
CompilerConfig g_config = {0};
+// Helper for environment expansion
+static void expand_env_vars(char *dest, size_t dest_size, const char *src)
+{
+ char *d = dest;
+ const char *s = src;
+ size_t remaining = dest_size - 1;
+
+ while (*s && remaining > 0)
+ {
+ if (*s == '$' && *(s + 1) == '{')
+ {
+ const char *end = strchr(s + 2, '}');
+ if (end)
+ {
+ char var_name[256];
+ int len = end - (s + 2);
+ if (len < 255)
+ {
+ strncpy(var_name, s + 2, len);
+ var_name[len] = 0;
+ char *val = getenv(var_name);
+ if (val)
+ {
+ size_t val_len = strlen(val);
+ if (val_len < remaining)
+ {
+ strcpy(d, val);
+ d += val_len;
+ remaining -= val_len;
+ s = end + 1;
+ continue;
+ }
+ }
+ }
+ }
+ }
+ *d++ = *s++;
+ remaining--;
+ }
+ *d = 0;
+}
+
+// Helper to determine active OS
+static int is_os_active(const char *os_name)
+{
+ if (0 == strcmp(os_name, "linux"))
+ {
+#ifdef __linux__
+ return 1;
+#else
+ return 0;
+#endif
+ }
+ else if (0 == strcmp(os_name, "windows"))
+ {
+#ifdef _WIN32
+ return 1;
+#else
+ return 0;
+#endif
+ }
+ else if (0 == strcmp(os_name, "macos") || 0 == strcmp(os_name, "darwin"))
+ {
+#ifdef __APPLE__
+ return 1;
+#else
+ return 0;
+#endif
+ }
+ return 0;
+}
+
void scan_build_directives(ParserContext *ctx, const char *src)
{
- (void)ctx; // Currently unused, reserved for future use
+ (void)ctx;
const char *p = src;
while (*p)
{
@@ -554,103 +626,113 @@ void scan_build_directives(ParserContext *ctx, const char *src)
len++;
}
- char line[2048];
+ char raw_line[2048];
if (len >= 2047)
{
len = 2047;
}
- strncpy(line, start, len);
- line[len] = 0;
+ strncpy(raw_line, start, len);
+ raw_line[len] = 0;
+
+ char line[2048];
+ expand_env_vars(line, sizeof(line), raw_line);
- if (0 == strncmp(line, "link:", 5))
+ char *directive = line;
+ char *colon = strchr(line, ':');
+ if (colon)
{
- char *val = line + 5;
- while (*val == ' ')
+ *colon = 0;
+ if (is_os_active(line))
+ {
+ directive = colon + 1;
+ while (*directive == ' ')
+ {
+ directive++;
+ }
+ }
+ else if (0 == strcmp(line, "linux") || 0 == strcmp(line, "windows") ||
+ 0 == strcmp(line, "macos"))
{
- val++;
+ goto next_line;
}
- if (strlen(g_link_flags) > 0)
+ else
{
- strcat(g_link_flags, " ");
+ *colon = ':';
+ directive = line;
}
- strcat(g_link_flags, val);
}
- else if (0 == strncmp(line, "cflags:", 7))
+
+ // Process Directive
+ if (0 == strncmp(directive, "link:", 5))
{
- char *val = line + 7;
- while (*val == ' ')
+ if (strlen(g_link_flags) > 0)
{
- val++;
+ strcat(g_link_flags, " ");
}
+ strcat(g_link_flags, directive + 5);
+ }
+ else if (0 == strncmp(directive, "cflags:", 7))
+ {
if (strlen(g_cflags) > 0)
{
strcat(g_cflags, " ");
}
- strcat(g_cflags, val);
+ strcat(g_cflags, directive + 7);
}
- else if (0 == strncmp(line, "include:", 8))
+ else if (0 == strncmp(directive, "include:", 8))
{
- char *val = line + 8;
- while (*val == ' ')
- {
- val++;
- }
char flags[2048];
- sprintf(flags, "-I%s", val);
+ sprintf(flags, "-I%s", directive + 8);
if (strlen(g_cflags) > 0)
{
strcat(g_cflags, " ");
}
strcat(g_cflags, flags);
}
- else if (strncmp(line, "lib:", 4) == 0)
+ else if (strncmp(directive, "lib:", 4) == 0)
{
- char *val = line + 4;
- while (*val == ' ')
- {
- val++;
- }
char flags[2048];
- sprintf(flags, "-L%s", val);
+ sprintf(flags, "-L%s", directive + 4);
if (strlen(g_link_flags) > 0)
{
strcat(g_link_flags, " ");
}
strcat(g_link_flags, flags);
}
- else if (strncmp(line, "define:", 7) == 0)
+ else if (strncmp(directive, "framework:", 10) == 0)
{
- char *val = line + 7;
- while (*val == ' ')
+ char flags[2048];
+ sprintf(flags, "-framework %s", directive + 10);
+ if (strlen(g_link_flags) > 0)
{
- val++;
+ strcat(g_link_flags, " ");
}
+ strcat(g_link_flags, flags);
+ }
+ else if (strncmp(directive, "define:", 7) == 0)
+ {
char flags[2048];
- sprintf(flags, "-D%s", val);
+ sprintf(flags, "-D%s", directive + 7);
if (strlen(g_cflags) > 0)
{
strcat(g_cflags, " ");
}
strcat(g_cflags, flags);
}
- else if (0 == strncmp(line, "shell:", 6))
+ else if (0 == strncmp(directive, "shell:", 6))
{
- char *cmd = line + 6;
- // printf("[zprep] Running shell: %s\n", cmd);
- int res = system(cmd);
- if (res != 0)
+ if (system(directive + 6) != 0)
{
- zwarn("Shell directive failed: %s", cmd);
+ zwarn("Shell directive failed: %s", directive + 6);
}
}
- else if (strncmp(line, "get:", 4) == 0)
+ else if (strncmp(directive, "get:", 4) == 0)
{
- char *url = line + 4;
+ char *url = directive + 4;
while (*url == ' ')
{
url++;
}
-
char *filename = strrchr(url, '/');
if (!filename)
{
@@ -660,8 +742,6 @@ void scan_build_directives(ParserContext *ctx, const char *src)
{
filename++;
}
-
- // Check if file exists to avoid redownloading.
FILE *f = fopen(filename, "r");
if (f)
{
@@ -669,16 +749,13 @@ void scan_build_directives(ParserContext *ctx, const char *src)
}
else
{
- printf("[zprep] Downloading %s...\n", filename);
char cmd[8192];
if (z_is_windows())
{
- // On Windows, try curl which is often built-in now
sprintf(cmd, "curl -s -L \"%s\" -o \"%s\"", url, filename);
}
else
{
- // Try wget, then curl.
sprintf(cmd, "wget -q \"%s\" -O \"%s\" || curl -s -L \"%s\" -o \"%s\"", url,
filename, url, filename);
}
@@ -688,40 +765,27 @@ void scan_build_directives(ParserContext *ctx, const char *src)
}
}
}
- else if (strncmp(line, "pkg-config:", 11) == 0)
+ else if (strncmp(directive, "pkg-config:", 11) == 0)
{
- char *libs = line + 11;
- while (*libs == ' ')
- {
- libs++;
- }
-
- if (z_is_windows())
- {
- zwarn("pkg-config is usually not available on Windows. Build directive "
- "'pkg-config:%s' might fail.",
- libs);
- }
-
+ char *libs = directive + 11;
char cmd[4096];
sprintf(cmd, "pkg-config --cflags %s", libs);
FILE *fp = popen(cmd, "r");
if (fp)
{
- char flags[4096];
- flags[0] = 0;
- if (fgets(flags, sizeof(flags), fp))
+ char buf[1024];
+ if (fgets(buf, sizeof(buf), fp))
{
- int len = strlen(flags);
- if (len > 0 && flags[len - 1] == '\n')
+ size_t l = strlen(buf);
+ if (l > 0 && buf[l - 1] == '\n')
{
- flags[len - 1] = 0;
+ buf[l - 1] = 0;
}
if (strlen(g_cflags) > 0)
{
strcat(g_cflags, " ");
}
- strcat(g_cflags, flags);
+ strcat(g_cflags, buf);
}
pclose(fp);
}
@@ -730,32 +794,35 @@ void scan_build_directives(ParserContext *ctx, const char *src)
fp = popen(cmd, "r");
if (fp)
{
- char flags[4096];
- flags[0] = 0;
- if (fgets(flags, sizeof(flags), fp))
+ char buf[1024];
+ if (fgets(buf, sizeof(buf), fp))
{
- int len = strlen(flags);
- if (len > 0 && flags[len - 1] == '\n')
+ size_t l = strlen(buf);
+ if (l > 0 && buf[l - 1] == '\n')
{
- flags[len - 1] = 0;
+ buf[l - 1] = 0;
}
if (strlen(g_link_flags) > 0)
{
strcat(g_link_flags, " ");
}
- strcat(g_link_flags, flags);
+ strcat(g_link_flags, buf);
}
pclose(fp);
}
}
+ else
+ {
+ zwarn("Unknown build directive: '%s'", directive);
+ }
p += len;
}
+ next_line:
while (*p && *p != '\n')
{
p++;
}
-
if (*p == '\n')
{
p++;
diff --git a/src/zprep.h b/src/zprep.h
index a943f3f..84400b3 100644
--- a/src/zprep.h
+++ b/src/zprep.h
@@ -359,6 +359,7 @@ typedef struct
int mode_transpile; ///< 1 if 'transpile' command (to C).
int use_cpp; ///< 1 if --cpp (emit C++ compatible code).
int use_cuda; ///< 1 if --cuda (emit CUDA-compatible code).
+ int use_objc; ///< 1 if --objc (emit Objective-C compatible code).
// GCC Flags accumulator.
char gcc_flags[4096]; ///< Flags passed to the backend compiler.
diff --git a/std/fs.zc b/std/fs.zc
index c19f9f1..7a54005 100644
--- a/std/fs.zc
+++ b/std/fs.zc
@@ -2,21 +2,23 @@ import "./core.zc"
import "./result.zc"
import "./string.zc"
import "./vec.zc"
+import "./mem.zc"
def Z_SEEK_SET = 0;
def Z_SEEK_END = 2;
def Z_F_OK = 0;
+include <dirent.h>
+include <sys/stat.h>
+include <unistd.h>
+include <stdlib.h>
+include <stdio.h>
// TODO: restructure this tomorrow.
raw {
- #include <dirent.h>
- #include <sys/stat.h>
- #include <unistd.h>
-
- // typedef needed for Vec<DirEntry*> generation if inferred
typedef struct DirEntry* DirEntryPtr;
+ // Wrappers for FILE* handling due to opaque pointer casting
void* _z_fs_fopen(char* path, char* mode) {
return fopen(path, mode);
}
@@ -40,7 +42,9 @@ raw {
int64_t _z_fs_ftell(void* stream) {
return (int64_t)ftell((FILE*)stream);
}
-
+
+ // Wrappers needed because C headers declare these with 'const char*'
+ // but Zen C externs generate 'char*', leading to conflicting types.
int _z_fs_access(char* pathname, int mode) {
return access(pathname, mode);
}
@@ -53,6 +57,7 @@ raw {
return rmdir(pathname);
}
+ // Wrappers for DIR* handling
void* _z_fs_opendir(char* name) {
return opendir(name);
}
@@ -61,14 +66,7 @@ raw {
return closedir((DIR*)dir);
}
- void* _z_fs_malloc(size_t size) {
- return malloc(size);
- }
-
- void _z_fs_free(void* ptr) {
- free(ptr);
- }
-
+ // struct stat / struct dirent helpers
int _z_fs_get_metadata(char* path, uint64_t* size, int* is_dir, int* is_file) {
struct stat st;
if (stat(path, &st) != 0) return -1;
@@ -96,6 +94,10 @@ raw {
}
}
+// Direct externs
+extern fn malloc(size: usize) -> void*;
+extern fn free(ptr: void*);
+
extern fn _z_fs_mkdir(path: char*) -> int;
extern fn _z_fs_get_metadata(path: char*, size: U64*, is_dir: int*, is_file: int*) -> int;
extern fn _z_fs_read_entry(dir: void*, out_name: char*, buf_size: int, is_dir: int*) -> int;
@@ -105,13 +107,13 @@ extern fn _z_fs_fread(ptr: void*, size: usize, nmemb: usize, stream: void*) -> u
extern fn _z_fs_fwrite(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize;
extern fn _z_fs_fseek(stream: void*, offset: long, whence: int) -> int;
extern fn _z_fs_ftell(stream: void*) -> long;
+extern fn _z_fs_opendir(name: char*) -> void*;
+extern fn _z_fs_closedir(dir: void*) -> int;
+
extern fn _z_fs_access(pathname: char*, mode: int) -> int;
extern fn _z_fs_unlink(pathname: char*) -> int;
extern fn _z_fs_rmdir(pathname: char*) -> int;
-extern fn _z_fs_opendir(name: char*) -> void*;
-extern fn _z_fs_closedir(dir: void*) -> int;
-extern fn _z_fs_malloc(size: usize) -> void*;
-extern fn _z_fs_free(ptr: void*);
+
struct File {
handle: void*;
@@ -153,7 +155,7 @@ impl File {
let size = _z_fs_ftell(self.handle);
_z_fs_fseek(self.handle, 0, Z_SEEK_SET);
- let buffer: char* = _z_fs_malloc((usize)size + 1);
+ let buffer: char* = malloc((usize)size + 1);
if (buffer == NULL) {
return Result<String>::Err("Out of memory");
}
@@ -162,7 +164,7 @@ impl File {
buffer[read] = 0;
let s = String::new(buffer);
- _z_fs_free(buffer);
+ free(buffer);
let res = Result<String>::Ok(s);
s.forget();
@@ -248,7 +250,7 @@ impl File {
}
let entries = Vec<DirEntry>::new();
- let name_buf: char* = _z_fs_malloc(256);
+ let name_buf: char* = malloc(256);
if (name_buf == NULL) {
_z_fs_closedir(dir);
@@ -277,7 +279,7 @@ impl File {
ent.name.forget();
}
- _z_fs_free(name_buf);
+ free(name_buf);
_z_fs_closedir(dir);
let res = Result< Vec<DirEntry> >::Ok(entries);
diff --git a/std/json.zc b/std/json.zc
index e6a15cd..d373ab9 100644
--- a/std/json.zc
+++ b/std/json.zc
@@ -5,6 +5,7 @@ import "./map.zc"
import "./string.zc"
import "./result.zc"
import "./option.zc"
+import "./mem.zc"
@derive(Eq)
enum JsonType {
@@ -450,3 +451,9 @@ impl JsonValue {
}
}
}
+
+impl Drop for JsonValue {
+ fn drop(self) {
+ self.free();
+ }
+}
diff --git a/std/map.zc b/std/map.zc
index 8e58cdc..70d6ad2 100644
--- a/std/map.zc
+++ b/std/map.zc
@@ -1,6 +1,7 @@
import "./core.zc"
import "./option.zc"
+import "./mem.zc"
raw {
extern size_t __zen_hash_seed;
@@ -248,3 +249,9 @@ impl Map<V> {
};
}
}
+
+impl Drop for Map<V> {
+ fn drop(self) {
+ self.free();
+ }
+}
diff --git a/std/net.zc b/std/net.zc
index 72c3fcb..dd10642 100644
--- a/std/net.zc
+++ b/std/net.zc
@@ -8,6 +8,7 @@ include <errno.h>
import "./core.zc"
import "./result.zc"
import "./string.zc"
+import "./mem.zc"
def Z_AF_INET = 2;
def Z_SOCK_STREAM = 1;
@@ -36,15 +37,11 @@ raw {
if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) return -2;
return 0;
}
-
+
static int _z_net_accept(int fd) {
return accept(fd, NULL, NULL);
}
- static ssize_t _z_net_read(int fd, char* buf, size_t n) {
- return read(fd, (void*)buf, n);
- }
-
static ssize_t _z_net_write(int fd, char* buf, size_t n) {
return write(fd, (const void*)buf, n);
}
@@ -52,11 +49,11 @@ raw {
extern fn socket(domain: int, type: int, proto: int) -> int;
extern fn close(fd: int) -> int;
+extern fn read(fd: int, buf: void*, count: usize) -> isize;
extern fn _z_net_bind(fd: int, host: char*, port: int) -> int;
extern fn _z_net_connect(fd: int, host: char*, port: int) -> int;
extern fn _z_net_accept(fd: int) -> int;
-extern fn _z_net_read(fd: int, buf: char*, n: usize) -> isize;
extern fn _z_net_write(fd: int, buf: char*, n: usize) -> isize;
@@ -66,7 +63,7 @@ struct TcpStream {
impl TcpStream {
fn read(self, buf: char*, len: usize) -> Result<usize> {
- let n = _z_net_read(self.fd, buf, len);
+ let n = read(self.fd, (void*)buf, len);
if (n < 0) return Result<usize>::Err("Read failed");
return Result<usize>::Ok((usize)n);
}
@@ -96,6 +93,12 @@ impl TcpStream {
}
}
+impl Drop for TcpStream {
+ fn drop(self) {
+ self.close();
+ }
+}
+
struct TcpListener {
fd: int;
}
@@ -126,3 +129,9 @@ impl TcpListener {
}
}
}
+
+impl Drop for TcpListener {
+ fn drop(self) {
+ self.close();
+ }
+}
diff --git a/std/thread.zc b/std/thread.zc
index e90943b..98f080e 100644
--- a/std/thread.zc
+++ b/std/thread.zc
@@ -5,6 +5,7 @@ include <unistd.h>
import "./core.zc"
import "./result.zc"
+import "./mem.zc"
raw {
typedef void (*ZenThreadFunc)(void*);
@@ -120,11 +121,17 @@ impl Mutex {
if (self.handle) {
_z_mutex_destroy(self.handle);
free(self.handle);
+ self.handle = NULL;
}
}
}
+impl Drop for Mutex {
+ fn drop(self) {
+ self.free();
+ }
+}
+
fn sleep_ms(ms: int) {
_z_usleep(ms * 1000);
}
-
diff --git a/std/time.zc b/std/time.zc
index 1191821..8e7cc6c 100644
--- a/std/time.zc
+++ b/std/time.zc
@@ -1,18 +1,28 @@
-
import "./core.zc"
+include <time.h>
+include <unistd.h>
+include <sys/time.h>
+include <stdlib.h>
+
raw {
- #include <time.h>
- #include <unistd.h>
- #include <sys/time.h>
-
static uint64_t _time_now_impl(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
return (uint64_t)(tv.tv_sec) * 1000 + (uint64_t)(tv.tv_usec) / 1000;
}
+
+ static long _z_time_time(void) {
+ return (long)time(NULL);
+ }
}
+extern fn srand(seed: U32);
+extern fn rand() -> int;
+extern fn usleep(micros: U32) -> int;
+extern fn _time_now_impl() -> U64;
+extern fn _z_time_time() -> long;
+
struct Duration {
millis: U64;
}
@@ -33,10 +43,8 @@ extern size_t __zen_hash_seed;
impl Time {
fn randomize_hash() {
- raw {
- srand(time(NULL));
- __zen_hash_seed ^= (size_t)rand();
- }
+ srand((U32)_z_time_time());
+ __zen_hash_seed ^= (size_t)rand();
}
fn now() -> U64 {
diff --git a/tests/features/test_build_directives.zc b/tests/features/test_build_directives.zc
index 7edd317..d3f1cba 100644
--- a/tests/features/test_build_directives.zc
+++ b/tests/features/test_build_directives.zc
@@ -1,19 +1,8 @@
-//> link: -lm
-//> cflags: -O2
-// Declare C math function (since we don't have a math stdlib module yet)
-extern fn sin(x: double) -> double;
+//> shell: echo "Env Worked" > ${PWD}/build_dir_env.txt
+//> linux: shell: echo "Linux Worked" > ${PWD}/build_dir_linux.txt
+//> windows: shell: echo "Windows Worked" > ${PWD}/build_dir_windows.txt
-test "test_build_directives" {
- println "Running Build Directives Test...";
- let x = 3.14159 / 2.0; // PI/2
- let s = sin(x);
- // sin(PI/2) should be 1.0
- println "sin(PI/2) = {s}";
-
- if (s > 0.99 && s < 1.01) {
- println "Math Link Success!";
- } else {
- println "Math Link Failure (Value wrong)";
- }
+fn main() {
+ return 0;
}