diff options
| author | Zuhaitz <zuhaitz.zechhub@gmail.com> | 2026-01-31 17:22:17 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-01-31 17:22:17 +0000 |
| commit | 962d659c61212b1a23acfe56dda7cb92b721feda (patch) | |
| tree | ba1637d3885213095b312f81a477c33b1ebca6aa | |
| parent | e521ee7d175393ef37579ebd61ccb7e8d56a397f (diff) | |
| parent | 91ed9fdd65e09bd6cd32e44dd07c390f2cf79c22 (diff) | |
Merge branch 'main' into main
78 files changed, 8969 insertions, 793 deletions
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..fac102a --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,87 @@ +# Zen C Copilot Instructions + +These instructions are **MANDATORY** for all code generation and review tasks in the Zen C project. + +## 1. Memory Management (Critical) +* **Arena Allocation**: The compiler uses a bump-pointer arena allocator. + * **MUST USE**: `xmalloc`, `xcalloc`, `xrealloc`, `xstrdup` (defined in `src/utils/utils.c`). + * **NEVER USE**: Standard `malloc`, `calloc`, `free` (unless interfacing with an external library that strictly requires owned heap memory, like CJSON). +* **Destructors/Freeing**: + * `free(ptr)` is `#defined` to `((void)0)` in `src/zprep.h`. It is a no-op. + * Do **NOT** attempt to free AST nodes or types. `ast_free` is a no-op. + * Memory is reclaimed only when the process exits. Design your data structures accordingly (append-only is fine). + +## 2. AST and Type System +* **Creation**: Use `ast_create(NODE_TYPE)` to allocate new nodes. +* **Type Representation**: + * Use `Type` struct (defined in `src/ast/ast.h`). + * Use `type_new(TYPE_KIND)` helper. + * `type_to_string(t)` and `type_to_c_string(t)` return arena-allocated strings. Do not worry about freeing them. +* **Traversal**: + * The compiler uses **Recursive Descent** with **Switch Statements** on `node->type`. + * Do NOT introduce Visitor patterns or callback tables unless consistent with existing code. + +## 3. Parser Patterns +* **Context**: `ParserContext *ctx` is the god-object. It MUST be passed to almost every function in parsing, analysis, and codegen. + * **Signature Rule**: `ReturnType func_name(ParserContext *ctx, ...)` +* **Token Consumption**: + * Use `expect(lexer, TOKEN_TYPE, "error message")` for mandatory tokens. + * For optional tokens, check `l->token.type` and assume `lexer_next(l)` is used to advance (verify specific helper availability). +* **Error Handling**: + * **Fatal**: `zpanic("msg")` or `zpanic_at(token, "msg")`. Exits immediately (or delegates to LSP handler). + * **Warning**: `zwarn("msg")` or `zwarn_at(token, "msg")`. + * **Semantic Errors**: Prefer `zpanic_at` for type errors to give line/col info. + +## 4. Code Generation (C Backend) +* **Generic Mangling**: + * Generic structs (e.g., `Slice<int>`) are mangled to `Slice_int` **IF AND ONLY IF** the instantiated struct exists (checked via `find_struct_def_codegen`). + * **Fallback**: If the mangled struct (e.g. `Async_int`) does not exist, use the base name (`Async`). This handles opaque types like `Async<T>` correctly. + * See `emit_c_decl` in `src/codegen/codegen_utils.c` for the canonical implementation. +* **Function Signatures**: + * Use `emit_func_signature(ctx, out, node, override_name)` to handle modifiers, return types, and arguments correctly. +* **Output**: Use `fprintf(out, ...)` where `out` is the `FILE*`. + +## 5. Coding Style & Conventions +* **Formatting**: + * Indentation: 4 spaces. + * Braces: **ALWAYS** Use braces `{}` for control flow (`if`, `while`, `for`), even for single lines. + * Opening brace on the **NEXT** line (Allman style). +* **Naming**: + * Structs/Enums: `PascalCase`. + * Functions/Variables: `snake_case`. + * Macros/Constants: `SCREAMING_SNAKE_CASE`. + * Private/Static: No strict prefix, but `static` keyword is mandatory for internal file-scope functions. +* **Iterators**: + * When implementing iteration in compiler (C code): Use `while (node) { ... node = node->next; }` for linked lists (`ASTNode`, `StructRef`). + +## 6. Standard Library (Zen C Definitions) +* **Arrays**: Use `for val in arr` syntax (direct iteration). +* **Vectors**: `Vec<T>` is a dynamic array. +* **Strings**: `string` in Zen C maps to `char*` in C. `kstring` or `zstr` are higher-level wrappers. + +## 7. Common Pitfalls +* **Unused Variables**: The compiler builds with `-Wall -Werror` (or similar strictness). Cast unused vars to void: `(void)var_name;`. +## 8. Zen C Language Rules (For writing .zc files) +* **Syntax**: + * Variables: `let x = 10;`, `let y: const int = 20;`. + * Constants: `def MAX = 100;` (compile-time). + * Functions: `fn name(arg: type) -> ret { ... }`. + * Structs: `struct Point { x: int; y: int; }`. + * Enums: `enum Shape { Circle(float), Rect(float, float) }`. +* **Memory & Resources**: + * **Move Semantics**: Structs/Enums are moved by default on assignment/pass-by-value. + * **Defer**: Use `defer stmt;` to run cleanup at scope exit. + * **Drop**: Implement `impl Drop for T` for RAII. +* **Arrays & Slices**: + * **Iteration**: Use `for val in arr` (direct iteration supported). + * **Slices**: `Slice<T>` is a view. `int[N]` auto-converts to slice in loops. +* **Generics**: + * Syntax: `struct Box<T> { val: T; }`. +* **Concurrency**: + * Use `async fn` and `await` keyword. + * `Async<T>` is the opaque future type. +* **Standard Library**: + * Import with `import "std/io.zc"`, `import "std/vec.zc"`. + * Use `println "msg"` (shorthand) or `printf`. +* **Testing**: + * Use `test "name" { ... }` blocks for unit tests. @@ -188,9 +188,10 @@ clean: @echo "=> Clean complete!" # Test -test: $(TARGET) +test: $(TARGET) $(PLUGINS) ./tests/run_tests.sh ./tests/run_codegen_tests.sh + ./tests/run_example_transpile.sh # Build with alternative compilers zig: @@ -1,6 +1,12 @@ <div align="center"> +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +</div> + +<div align="center"> + # Zen C **Modern Ergonomics. Zero Overhead. Pure C.** @@ -43,9 +49,11 @@ Join the discussion, share demos, ask questions, or report bugs in the official - [Arrays](#arrays) - [Tuples](#tuples) - [Structs](#structs) + - [Opaque Structs](#opaque-structs) - [Enums](#enums) - [Unions](#unions) - [Type Aliases](#type-aliases) + - [Opaque Type Aliases](#opaque-type-aliases) - [4. Functions & Lambdas](#4-functions--lambdas) - [Functions](#functions) - [Const Arguments](#const-arguments) @@ -91,6 +99,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) + - [16. Keywords](#16-keywords) - [Standard Library](#standard-library) - [Tooling](#tooling) - [Language Server (LSP)](#language-server-lsp) @@ -100,6 +109,8 @@ 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) + - [C23 Support](#c23-support) - [Contributing](#contributing) - [Attributions](#attributions) @@ -154,7 +165,7 @@ zc repl ### Environment Variables -You can set `ZC_ROOT` to specify the location of the Standard Library (standard imports like `import "std/vector.zc"`). This allows you to run `zc` from any directory. +You can set `ZC_ROOT` to specify the location of the Standard Library (standard imports like `import "std/vec.zc"`). This allows you to run `zc` from any directory. ```bash export ZC_ROOT=/path/to/Zen-C @@ -187,6 +198,8 @@ let y: const int = 10; // Read-only (Type qualified) // y = 20; // Error: cannot assign to const ``` +> **Type Inference**: Zen C automatically infers types for initialized variables. It compiles to C23 `auto` on supported compilers, or GCC's `__auto_type` extension otherwise. + ### 2. Primitive Types | Type | C Equivalent | Description | @@ -201,6 +214,8 @@ let y: const int = 10; // Read-only (Type qualified) | `char` | `char` | Single character | | `string` | `char*` | C-string (null-terminated) | | `U0`, `u0`, `void` | `void` | Empty type | +| `iN` (for example, `i256`) | `_BitInt(N)` | Arbitrary bit-width signed integer (C23) | +| `uN` (for example, `u42`) | `unsigned _BitInt(N)` | Arbitrary bit-width unsigned integer (C23) | ### 3. Aggregate Types @@ -261,6 +276,29 @@ struct Flags { > **Note**: Structs use [Move Semantics](#move-semantics--copy-safety) by default. Fields can be accessed via `.` even on pointers (Auto-Dereference). +#### Opaque Structs +You can define a struct as `opaque` to restrict access to its fields to the defining module only, while still allowing the struct to be allocated on the stack (size is known). + +```zc +// In user.zc +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // OK: Inside module +} + +// In main.zc +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // Error: Cannot access private field 'id' +} +``` + #### Enums Tagged unions (Sum types) capable of holding data. ```zc @@ -287,6 +325,27 @@ alias ID = int; alias PointMap = Map<string, Point>; ``` +#### Opaque Type Aliases +You can define a type alias as `opaque` to create a new type that is distinct from its underlying type outside of the defining module. This provides strong encapsulation and type safety without the runtime overhead of a wrapper struct. + +```zc +// In library.zc +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // Implicit conversion allowed inside module +} + +// In main.zc +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // Error: Type validation failed + // let h2: Handle = 10; // Error: Type validation failed +} +``` + ### 4. Functions & Lambdas #### Functions @@ -435,8 +494,15 @@ for i in 0..<10 { ... } // Exclusive (Explicit) for i in 0..=10 { ... } // Inclusive (0 to 10) for i in 0..10 step 2 { ... } -// Iterator (Vec, Array, or custom Iterable) -for item in collection { ... } +// Iterator (Vec or custom Iterable) +for item in vec { ... } + +// Iterate over fixed-size arrays directly +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val is int + println "{val}"; +} // While while x < 10 { ... } @@ -478,6 +544,11 @@ Zen C supports operator overloading for user-defined structs by implementing spe | **Index** | `a[i]` | `get(a, i)` | | | `a[i] = v` | `set(a, i, v)` | +> **Note on String Equality**: +> - `string == string` performs **value comparison** (equivalent to `strcmp`). +> - `char* == char*` performs **pointer comparison** (checks memory addresses). +> - Mixed comparisons (e.g. `string == char*`) default to **pointer comparison**. + **Example:** ```zc impl Point { @@ -543,7 +614,7 @@ Zen C supports a shorthand for prompting user input using the `?` prefix. - `? "Enter age: " (age)`: Prints prompt and scans input into the variable `age`. - Format specifiers are automatically inferred based on variable type. -```c +```zc let age: int; ? "How old are you? " (age); println "You are {age} years old."; @@ -875,9 +946,10 @@ Decorate functions and structs to modify compiler behavior. | `@host` | Fn | CUDA: Host function (`__host__`). | | `@comptime` | Fn | Helper function available for compile-time execution. | | `@derive(...)` | Struct | Auto-implement traits. Supports `Debug`, `Eq` (Smart Derive), `Copy`, `Clone`. | +| `@ctype("type")` | Fn Param | Overrides generated C type for a parameter. | | `@<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. @@ -889,7 +961,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: @@ -955,12 +1027,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 @@ -975,6 +1068,29 @@ import "raylib.h" fn main() { ... } ``` +### 16. Keywords + +The following keywords are reserved in Zen C. + +#### Declarations +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### Control Flow +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### Special +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### Constants +`true`, `false`, `null` + +#### C Reserved +The following identifiers are reserved because they are keywords in C11: +`auto`, `case`, `char`, `default`, `do`, `double`, `extern`, `float`, `inline`, `int`, `long`, `register`, `restrict`, `short`, `signed`, `switch`, `typedef`, `unsigned`, `void`, `_Atomic`, `_Bool`, `_Complex`, `_Generic`, `_Imaginary`, `_Noreturn`, `_Static_assert`, `_Thread_local` + +#### Operators +`and`, `or` + --- ## Standard Library @@ -997,6 +1113,13 @@ 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) | +| **`std/process.zc`** | Process execution and management. | [Docs](docs/std/process.md) | --- @@ -1185,7 +1308,7 @@ fn add_kernel(a: float*, b: float*, c: float*, n: int) { } fn main() { - const N = 1024; + def N = 1024; let d_a = cuda_alloc<float>(N); let d_b = cuda_alloc<float>(N); let d_c = cuda_alloc<float>(N); @@ -1227,6 +1350,45 @@ let tid = local_id(); > **Note:** The `--cuda` flag sets `nvcc` as the compiler and implies `--cpp` mode. Requires the NVIDIA CUDA Toolkit. +### C23 Support + +Zen C supports modern C23 features when using a compatible backend compiler (GCC 14+, Clang 14+, TCC (partial)). + +- **`auto`**: Zen C automatically maps type inference to standard C23 `auto` if `__STDC_VERSION__ >= 202300L`. +- **`_BitInt(N)`**: Use `iN` and `uN` types (e.g., `i256`, `u12`, `i24`) to access C23 arbitrary-width integers. + +### 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 @@ -1267,10 +1429,8 @@ make test ## Attributions -This project uses the following third-party libraries: - +This project uses third-party libraries. Full license texts can be found in the `LICENSES/` directory. * **[cJSON](https://github.com/DaveGamble/cJSON)** (MIT License): Used for JSON parsing and generation in the Language Server. - * Copyright (c) 2009-2017 Dave Gamble and cJSON contributors * **[zc-ape](https://github.com/OEvgeny/zc-ape)** (MIT License): The original Actually Portable Executable port of Zen-C by [Eugene Olonov](https://github.com/OEvgeny). * **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (ISC License): The foundational library that makes APE possible. diff --git a/README_ES.md b/README_ES.md new file mode 100644 index 0000000..d73e9ca --- /dev/null +++ b/README_ES.md @@ -0,0 +1,1436 @@ + +<div align="center"> + +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +</div> + +<div align="center"> + +# Zen C + +**Ergonomía Moderna. Cero Sobrecarga. C Puro.** + +[]() +[]() +[]() +[]() + +*Escribe como un lenguaje de alto nivel, ejecuta como C.* + +</div> + +--- + +## Descripción General + +**Zen C** es un lenguaje de programación de sistemas moderno que se compila a `GNU C`/`C11` legible por humanos. Proporciona un conjunto rico de características que incluyen inferencia de tipos, coincidencia de patrones (pattern matching), genéricos, traits, async/await y gestión manual de memoria con capacidades RAII, todo manteniendo una compatibilidad total con el ABI de C. + +## Comunidad + +¡Únete a la discusión, comparte demos, haz preguntas o reporta errores en el servidor oficial de Discord de Zen C! + +- Discord: [Únete aquí](https://discord.com/invite/q6wEsCmkJP) + +--- + +## Índice + +- [Descripción General](#descripción-general) +- [Comunidad](#comunidad) +- [Inicio Rápido](#inicio-rápido) + - [Instalación](#instalación) + - [Uso](#uso) + - [Variables de Entorno](#variables-de-entorno) +- [Referencia del Lenguaje](#referencia-del-lenguaje) + - [1. Variables y Constantes](#1-variables-y-constantes) + - [2. Tipos Primitivos](#2-tipos-primitivos) + - [3. Tipos Agregados](#3-tipos-agregados) + - [Arrays](#arrays) + - [Tuplas](#tuplas) + - [Structs](#structs) + - [Structs Opacos](#structs-opacos) + - [Enums](#enums) + - [Uniones](#uniones) + - [Alias de Tipos](#alias-de-tipos) + - [Alias de Tipos Opacos](#alias-de-tipos-opacos) + - [4. Funciones y Lambdas](#4-funciones-y-lambdas) + - [Funciones](#funciones) + - [Argumentos Const](#argumentos-const) + - [Argumentos por Defecto](#argumentos-por-defecto) + - [Lambdas (Clausuras)](#lambdas-clausuras) + - [Punteros a Funciones Crudos](#punteros-a-funciones-crudos) + - [Funciones Variádicas](#funciones-variádicas) + - [5. Flujo de Control](#5-flujo-de-control) + - [Condicionales](#condicionales) + - [Coincidencia de Patrones](#coincidencia-de-patrones) + - [Bucles](#bucles) + - [Control Avanzado](#control-avanzado) + - [6. Operadores](#6-operadores) + - [Operadores Sobrecargables](#operadores-sobrecargables) + - [Azúcar Sintáctico](#azúcar-sintáctico) + - [7. Impresión e Interpolación de Cadenas](#7-impresión-e-interpolación-de-cadenas) + - [Palabras Clave](#palabras-clave) + - [Abreviaturas](#abreviaturas) + - [Interpolación de Cadenas (F-strings)](#interpolación-de-cadenas-f-strings) + - [Prompts de Entrada (`?`)](#prompts-de-entrada-) + - [8. Gestión de Memoria](#8-gestión-de-memoria) + - [Defer](#defer) + - [Autofree](#autofree) + - [Semántica de Recursos (Movimiento por Defecto)](#semántica-de-recursos-movimiento-por-defecto) + - [RAII / Drop Trait](#raii--drop-trait) + - [9. Programación Orientada a Objetos](#9-programación-orientada-a-objetos) + - [Métodos](#métodos) + - [Traits](#traits) + - [Traits Estándar](#traits-estándar) + - [Composición](#composición) + - [10. Genéricos](#10-genéricos) + - [11. Concurrencia (Async/Await)](#11-concurrencia-asyncawait) + - [12. Metaprogramación](#12-metaprogramación) + - [Comptime](#comptime) + - [Embed](#embed) + - [Plugins](#plugins) + - [Macros de C Genéricas](#macros-de-c-genéricas) + - [13. Atributos](#13-atributos) + - [Atributos Personalizados](#atributos-personalizados) + - [Derivaciones Inteligentes](#derivaciones-inteligentes) + - [14. Ensamblador Inline](#14-ensamblador-inline) + - [Uso Básico](#uso-básico) + - [Volatile](#volatile) + - [Restricciones con Nombre](#restricciones-con-nombre) + - [15. Directivas de Construcción](#15-directivas-de-construcción) + - [16. Palabras Clave](#16-palabras-clave) +- [Biblioteca Estándar](#biblioteca-estándar) +- [Herramientas](#herramientas) + - [Servidor de Lenguaje (LSP)](#servidor-de-lenguaje-lsp) + - [REPL](#repl) +- [Soporte del Compilador y Compatibilidad](#soporte-del-compilador-y-compatibilidad) + - [Estado de la Suite de Pruebas](#estado-de-la-suite-de-pruebas) + - [Construyendo con Zig](#construyendo-con-zig) + - [Interop con C++](#interop-con-c) + - [Interop con CUDA](#interop-con-cuda) + - [Interop con Objective-C](#interop-con-objective-c) +- [Contribuyendo](#contribuyendo) +- [Atribuciones](#atribuciones) + +--- + +## Inicio Rápido + +### Instalación + +```bash +git clone https://github.com/z-libs/Zen-C.git +cd Zen-C +make +sudo make install +``` + +### Construcción Portable (APE) + +Zen C puede compilarse como un **Ejecutable Realmente Portable (APE)** usando [Cosmopolitan Libc](https://github.com/jart/cosmopolitan). Esto produce un único binario (`.com`) que se ejecuta de forma nativa en Linux, macOS, Windows, FreeBSD, OpenBSD y NetBSD en arquitecturas x86_64 y aarch64. + +**Prerrequisitos:** +- Toolchain `cosmocc` (debe estar en tu PATH) + +**Construcción e Instalación:** +```bash +make ape +sudo env "PATH=$PATH" make install-ape +``` + +**Artefactos:** +- `out/bin/zc.com`: El compilador Zen-C portable. Incluye la biblioteca estándar embebida dentro del ejecutable. +- `out/bin/zc-boot.com`: Un instalador bootstrap autónomo para configurar nuevos proyectos Zen-C. + +**Uso:** +```bash +# Ejecutar en cualquier SO compatible +./out/bin/zc.com build hello.zc -o hello +``` + +### Uso + +```bash +# Compilar y ejecutar +zc run hello.zc + +# Construir ejecutable +zc build hello.zc -o hello + +# Shell Interactiva +zc repl +``` + +### Variables de Entorno + +Puedes configurar `ZC_ROOT` para especificar la ubicación de la Biblioteca Estándar (importaciones estándar como `import "std/vector.zc"`). Esto te permite ejecutar `zc` desde cualquier directorio. + +```bash +export ZC_ROOT=/ruta/a/Zen-C +``` + +--- + +## Referencia del Lenguaje + +### 1. Variables y Constantes + +Zen C distingue entre constantes en tiempo de compilación y variables en tiempo de ejecución. + +#### Constantes Manifiestas (`def`) +Valores que existen solo en tiempo de compilación (se pliegan en el código). Úsalos para tamaños de arrays, configuración fija y números mágicos. + +```zc +def MAX_SIZE = 1024; +let buffer: char[MAX_SIZE]; // Tamaño de array válido +``` + +#### Variables (`let`) +Ubicaciones de almacenamiento en memoria. Pueden ser mutables o de solo lectura (`const`). + +```zc +let x = 10; // Mutable +x = 20; // OK + +let y: const int = 10; // Solo lectura (Calificado por tipo) +// y = 20; // Error: no se puede asignar a una constante +``` + +> **Inferencia de tipos**: Zen C infiere automáticamente los tipos para variables inicializadas. Se compila a `auto` de C23 en compiladores compatibles, o a la extensión `__auto_type` de GCC en otros casos. + +### 2. Tipos Primitivos + +| Tipo | Equivalente en C | Descripción | +|:---|:---|:---| +| `int`, `uint` | `int`, `unsigned int` | Entero estándar de la plataforma | +| `I8` .. `I128` o `i8` .. `i128` | `int8_t` .. `__int128_t` | Enteros con signo de ancho fijo | +| `U8` .. `U128` o `u8` .. `u128` | `uint8_t` .. `__uint128_t` | Enteros sin signo de ancho fijo | +| `isize`, `usize` | `ptrdiff_t`, `size_t` | Enteros del tamaño de un puntero | +| `byte` | `uint8_t` | Alias para U8 | +| `F32`, `F64` o `f32`, `f64` | `float`, `double` | Números de coma flotante | +| `bool` | `bool` | `true` o `false` | +| `char` | `char` | Carácter único | +| `string` | `char*` | Cadena de C (terminada en null) | +| `U0`, `u0`, `void` | `void` | Tipo vacío | +| `iN` (ej. `i256`) | `_BitInt(N)` | Entero con signo de ancho arbitrario (C23) | +| `uN` (ej. `u42`) | `unsigned _BitInt(N)` | Entero sin signo de ancho arbitrario (C23) | + +### 3. Tipos Agregados + +#### Arrays +Arrays de tamaño fijo con semántica de valor. +```zc +def SIZE = 5; +let ints: int[SIZE] = [1, 2, 3, 4, 5]; +let zeros: [int; SIZE]; // Inicializado a cero +``` + +#### Tuplas +Agrupa múltiples valores, accede a los elementos por índice. +```zc +let pair = (1, "Hola"); +let x = pair.0; // 1 +let s = pair.1; // "Hola" +``` + +**Múltiples Valores de Retorno** + +Las funciones pueden retornar tuplas para proporcionar múltiples resultados: +```zc +fn sumar_y_restar(a: int, b: int) -> (int, int) { + return (a + b, a - b); +} + +let resultado = sumar_y_restar(3, 2); +let suma = resultado.0; // 5 +let resta = resultado.1; // 1 +``` + +**Desestructuración** + +Las tuplas pueden desestructurarse directamente en variables: +```zc +let (suma, resta) = sumar_y_restar(3, 2); +// suma = 5, resta = 1 +``` + +#### Structs +Estructuras de datos con campos de bits opcionales. +```zc +struct Point { + x: int; + y: int; +} + +// Inicialización de struct +let p = Point { x: 10, y: 20 }; + +// Campos de bits +struct Flags { + valid: U8 : 1; + mode: U8 : 3; +} +``` + +> **Nota**: Los structs usan [Semántica de Movimiento](#semántica-de-recursos-movimiento-por-defecto) por defecto. Los campos se pueden acceder mediante `.` incluso en punteros (Auto-Dereferencia). + +#### Structs Opacos +Puedes definir un struct como `opaque` para restringir el acceso a sus campos solo al módulo que lo define, permitiendo aún que el struct sea asignado en el stack (el tamaño es conocido). + +```zc +// En user.zc +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // OK: Dentro del módulo +} + +// En main.zc +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // Error: No se puede acceder al campo privado 'id' +} +``` + +#### Enums +Uniones etiquetadas (Tipos suma) capaces de contener datos. +```zc +enum Shape { + Circle(float), // Contiene el radio + Rect(float, float), // Contiene ancho y alto + Point // Sin datos +} +``` + +#### Uniones +Uniones estándar de C (acceso inseguro). +```zc +union Data { + i: int; + f: float; +} +``` + +#### Alias de Tipos +Crea un nuevo nombre para un tipo existente. +```zc +alias ID = int; +alias PointMap = Map<string, Point>; +``` + +#### Alias de Tipos Opacos +Puedes definir un alias de tipo como `opaque` para crear un nuevo tipo que sea distinto de su tipo subyacente fuera del módulo que lo define. Esto proporciona una fuerte encapsulación y seguridad de tipos sin la sobrecarga en tiempo de ejecución de un struct envoltorio. + +```zc +// En library.zc +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // Conversión implícita permitida dentro del módulo +} + +// En main.zc +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // Error: Falló la validación de tipos + // let h2: Handle = 10; // Error: Falló la validación de tipos +} +``` + +### 4. Funciones y Lambdas + +#### Funciones +```zc +fn suma(a: int, b: int) -> int { + return a + b; +} + +// Argumentos con nombre soportados en las llamadas +suma(a: 10, b: 20); +``` + +> **Nota**: Los argumentos con nombre deben seguir estrictamente el orden de los parámetros definidos. `suma(b: 20, a: 10)` es inválido. + +#### Argumentos Const +Los argumentos de las funciones pueden marcarse como `const` para imponer una semántica de solo lectura. Este es un calificador de tipo, no una constante manifiesta. + +```zc +fn print_val(v: const int) { + // v = 10; // Error: No se puede asignar a una variable const + println "{v}"; +} +``` + +#### Argumentos por Defecto +Las funciones pueden definir valores por defecto para los argumentos finales. Estos pueden ser literales, expresiones o código válido de Zen C (como constructores de structs). +```zc +// Valor por defecto simple +fn incrementar(val: int, cantidad: int = 1) -> int { + return val + cantidad; +} + +// Valor por defecto por expresión (evaluado en el sitio de la llamada) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// Valor por defecto de tipo struct +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "Modo Debug"; } +} + +fn main() { + incrementar(10); // 11 + offset(5); // 25 + init(); // Imprime "Modo Debug" +} +``` + +#### Lambdas (Clausuras) +Funciones anónimas que pueden capturar su entorno. +```zc +let factor = 2; +let doble = x -> x * factor; // Sintaxis de flecha +let completo = fn(x: int) -> int { return x * factor; }; // Sintaxis de bloque +``` + +#### Punteros a Funciones Crudos +Zen C soporta punteros a funciones de C crudos usando la sintaxis `fn*`. Esto permite una interoperabilidad perfecta con bibliotecas de C que esperan punteros a funciones sin la sobrecarga de las clausuras. + +```zc +// Función que recibe un puntero a función crudo +fn set_callback(cb: fn*(int)) { + cb(42); +} + +// Función que retorna un puntero a función crudo +fn get_callback() -> fn*(int) { + return mi_manejador; +} + +// Se soportan punteros a punteros de funciones (fn**) +let pptr: fn**(int) = &ptr; +``` + +#### Funciones Variádicas +Las funciones pueden aceptar un número variable de argumentos usando `...` y el tipo `va_list`. +```zc +fn log(lvl: int, fmt: char*, ...) { + let ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // Usa stdio de C + va_end(ap); +} +``` + +### 5. Flujo de Control + +#### Condicionales +```zc +if x > 10 { + print("Grande"); +} else if x > 5 { + print("Mediano"); +} else { + print("Pequeño"); +} + +// Ternario +let y = x > 10 ? 1 : 0; +``` + +#### Coincidencia de Patrones (Pattern Matching) +Una alternativa potente al `switch`. +```zc +match val { + 1 => { print "Uno" }, + 2 || 3 => { print "Dos o Tres" }, // OR con || + 4 or 5 => { print "Cuatro o Cinco" }, // OR con 'or' + 6, 7, 8 => { print "Seis a Ocho" }, // OR con coma + 10 .. 15 => { print "10 a 14" }, // Rango exclusivo (Legado) + 10 ..< 15 => { print "10 a 14" }, // Rango exclusivo (Explícito) + 20 ..= 25 => { print "20 a 25" }, // Rango inclusivo + _ => { print "Otro" }, +} + +// Desestructuración de Enums +match shape { + Shape::Circle(r) => println "Radio: {r}", + Shape::Rect(w, h) => println "Área: {w*h}", + Shape::Point => println "Punto" +} +``` + +#### Vinculación por Referencia (Reference Binding) +Para inspeccionar un valor sin tomar posesión de él (sin moverlo), usa la palabra clave `ref` en el patrón. Esto es esencial para tipos que implementan la Semántica de Movimiento (como `Option`, `Result`, structs que no son Copy). + +```zc +let opt = Some(ValorNoCopy{...}); +match opt { + Some(ref x) => { + // 'x' es un puntero al valor dentro de 'opt' + // 'opt' NO se mueve ni se consume aquí + println "{x.field}"; + }, + None => {} +} +``` + +#### Bucles +```zc +// Rango +for i in 0..10 { ... } // Exclusivo (0 al 9) +for i in 0..<10 { ... } // Exclusivo (Explícito) +for i in 0..=10 { ... } // Inclusivo (0 al 10) +for i in 0..10 step 2 { ... } + +// Iterador (Vec o Iterable personalizado) +for item in vec { ... } + +// Iterar sobre arrays de tamaño fijo directamente +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val es int + println "{val}"; +} + +// While +while x < 10 { ... } + +// Infinito con etiqueta +externo: loop { + if terminado { break externo; } +} + +// Repetir N veces +for _ in 0..5 { ... } +``` + + +#### Control Avanzado +```zc +// Guard: Ejecuta else y retorna si la condición es falsa +guard ptr != NULL else { return; } + +// Unless: Si no es verdadero +unless es_valido { return; } +``` + +### 6. Operadores + +Zen C soporta la sobrecarga de operadores para structs definidos por el usuario implementando nombres de métodos específicos. + +#### Operadores Sobrecargables + +| Categoría | Operador | Nombre del Método | +|:---|:---|:---| +| **Aritméticos** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **Comparación** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **Bitwise** | `&`, `|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **Unarios** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **Índice** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +> **Nota sobre la igualdad de cadenas**: +> - `string == string` realiza una **comparación de valores** (equivalente a `strcmp`). +> - `char* == char*` realiza una **comparación de punteros** (comprueba direcciones de memoria). +> - Comparaciones mixtas (ej. `string == char*`) por defecto realizan una **comparación de punteros**. + +**Ejemplo:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +let p3 = p1 + p2; // Llama a p1.add(p2) +``` + +#### Azúcar Sintáctico + +Estos operadores son características integradas del lenguaje y no pueden sobrecargarse directamente. + +| Operador | Nombre | Descripción | +|:---|:---|:---| +| `|>` | Pipeline | `x |> f(y)` se desazucara a `f(x, y)` | +| `??` | Null Coalescing | `val ?? default` retorna `default` si `val` es NULL (punteros) | +| `??=` | Null Assignment | `val ??= init` asigna si `val` es NULL | +| `?.` | Navegación Segura | `ptr?.campo` accede al campo solo si `ptr` no es NULL | +| `?` | Operador Try | `res?` retorna el error si está presente (tipos Result/Option) | + +**Auto-Dereferencia**: +El acceso a campos por puntero (`ptr.campo`) y las llamadas a métodos (`ptr.metodo()`) dereferencian automáticamente el puntero, equivalente a `(*ptr).campo`. + +### 7. Impresión e Interpolación de Cadenas + +Zen C proporciona opciones versátiles para imprimir en la consola, incluyendo palabras clave y abreviaturas concisas. + +#### Palabras Clave + +- `print "texto"`: Imprime en `stdout` sin un salto de línea al final. +- `println "texto"`: Imprime en `stdout` con un salto de línea al final. +- `eprint "texto"`: Imprime en `stderr` sin un salto de línea al final. +- `eprintln "texto"`: Imprime en `stderr` con un salto de línea al final. + +#### Abreviaturas + +Zen C permite usar literales de cadena directamente como sentencias para una impresión rápida: + +- `"Hola Mundo"`: Equivalente a `println "Hola Mundo"`. (Añade salto de línea implícito) +- `"Hola Mundo"..`: Equivalente a `print "Hola Mundo"`. (Sin salto de línea final) +- `!"Error"`: Equivalente a `eprintln "Error"`. (Salida a stderr) +- `!"Error"..`: Equivalente a `eprint "Error"`. (Salida a stderr, sin salto de línea) + +#### Interpolación de Cadenas (F-strings) + +Puedes embeber expresiones directamente dentro de literales de cadena usando la sintaxis `{}`. Esto funciona con todos los métodos de impresión y abreviaturas de cadena. + +```zc +let x = 42; +let nombre = "Zen"; +println "Valor: {x}, Nombre: {nombre}"; +"Valor: {x}, Nombre: {nombre}"; // abreviatura println +``` + +#### Prompts de Entrada (`?`) + +Zen C soporta una abreviatura para solicitar entrada al usuario usando el prefijo `?`. + +- `? "Texto del prompt"`: Imprime el prompt (sin salto de línea) y espera la entrada (lee una línea). +- `? "Ingresa la edad: " (edad)`: Imprime el prompt y escanea la entrada en la variable `edad`. + - Los especificadores de formato se infieren automáticamente según el tipo de variable. + +```zc +let edad: int; +? "¿Cuántos años tienes? " (edad); +println "Tienes {edad} años."; +``` + +### 8. Gestión de Memoria + +Zen C permite la gestión manual de memoria con ayudas ergonómicas. + +#### Defer +Ejecuta código cuando el ámbito actual finaliza. Las sentencias defer se ejecutan en orden LIFO (último en entrar, primero en salir). +```zc +let f = fopen("archivo.txt", "r"); +defer fclose(f); +``` + +> Para prevenir comportamientos indefinidos, las sentencias de flujo de control (`return`, `break`, `continue`, `goto`) **no están permitidas** dentro de un bloque `defer`. + +#### Autofree +Libera automáticamente la variable cuando finaliza el ámbito. +```zc +autofree let tipos = malloc(1024); +``` + +#### Semántica de Recursos (Movimiento por Defecto) +Zen C trata los tipos con destructores (como `File`, `Vec` o punteros de malloc) como **Recursos**. Para prevenir errores de doble liberación (double-free), los recursos no pueden duplicarse implícitamente. + +- **Movimiento por Defecto**: La asignación de una variable de recurso transfiere la posesión. La variable original se vuelve inválida (Movida). +- **Tipos Copy**: Los tipos sin destructores pueden optar por el comportamiento `Copy`, haciendo que la asignación sea una duplicación. + +**Diagnóstico y Filosofía**: +Si ves un error "Use of moved value", el compilador te está diciendo: *"Este tipo posee un recurso (como memoria o un manejador) y copiarlo a ciegas es inseguro."* + +> **Contraste:** A diferencia de C/C++, Zen C no duplica implícitamente los valores que poseen recursos. + +**Argumentos de Función**: +Pasar un valor a una función sigue las mismas reglas que la asignación: los recursos se mueven a menos que se pasen por referencia. + +```zc +fn procesar(r: Recurso) { ... } // 'r' se mueve dentro de la función +fn mirar(r: Recurso*) { ... } // 'r' es prestado (referencia) +``` + +**Clonación Explícita**: +Si *realmente* quieres dos copias de un recurso, hazlo explícito: + +```zc +let b = a.clone(); // Llama al método 'clone' del trait Clone +``` + +**Optar por Copy (Tipos de Valor)**: +Para tipos pequeños sin destructores: + +```zc +struct Point { x: int; y: int; } +impl Copy for Point {} // Optar por la duplicación implícita + +fn main() { + let p1 = Point { x: 1, y: 2 }; + let p2 = p1; // Copiado. p1 sigue siendo válido. +} +``` + +#### RAII / Drop Trait +Implementa `Drop` para ejecutar lógica de limpieza automáticamente. +```zc +impl Drop for MiEstructura { + fn drop(self) { + self.free(); + } +} +``` + +### 9. Programación Orientada a Objetos + +#### Métodos +Define métodos en los tipos usando `impl`. +```zc +impl Point { + // Método estático (convención de constructor) + fn new(x: int, y: int) -> Self { + return Point{x: x, y: y}; + } + + // Método de instancia + fn dist(self) -> float { + return sqrt(self.x * self.x + self.y * self.y); + } +} +``` + +#### Traits +Define un comportamiento compartido. +```zc +struct Circle { radio: f32; } + +trait Dibujable { + fn dibujar(self); +} + +impl Dibujable for Circle { + fn dibujar(self) { ... } +} + +let circulo = Circle{}; +let dibujable: Dibujable = &circulo; +``` + +#### Traits Estándar +Zen C incluye traits estándar que se integran con la sintaxis del lenguaje. + +**Iterable** + +Implementa `Iterable<T>` para habilitar bucles `for-in` para tus tipos personalizados. + +```zc +import "std/iter.zc" + +// Define un Iterador +struct MiIter { + actual: int; + final: int; +} + +impl MiIter { + fn next(self) -> Option<int> { + if self.actual < self.final { + self.actual += 1; + return Option<int>::Some(self.actual - 1); + } + return Option<int>::None(); + } +} + +// Implementa Iterable +impl MiRango { + fn iterator(self) -> MiIter { + return MiIter{actual: self.inicio, final: self.fin}; + } +} + +// Uso en un Bucle +for i in mi_rango { + println "{i}"; +} +``` + +**Drop** + +Implementa `Drop` para definir un destructor que se ejecuta cuando el objeto sale de ámbito (RAII). + +```zc +import "std/mem.zc" + +struct Recurso { + ptr: void*; +} + +impl Drop for Recurso { + fn drop(self) { + if self.ptr != NULL { + free(self.ptr); + } + } +} +``` + +> **Nota:** Si una variable es movida, no se llama a `drop` en la variable original. Se adhiere a la [Semántica de Recursos](#semántica-de-recursos-movimiento-por-defecto). + +**Copy** + +Trait marcador para optar por el comportamiento `Copy` (duplicación implícita) en lugar de la semántica de movimiento. Se usa mediante `@derive(Copy)`. + +> **Regla:** Los tipos que implementan `Copy` no deben definir un destructor (`Drop`). + +```zc +@derive(Copy) +struct Point { x: int; y: int; } + +fn main() { + let p1 = Point{x: 1, y: 2}; + let p2 = p1; // ¡Copiado! p1 sigue siendo válido. +} +``` + +**Clone** + +Implementa `Clone` para permitir la duplicación explícita de tipos que poseen recursos. + +```zc +import "std/mem.zc" + +struct MiBox { val: int; } + +impl Clone for MiBox { + fn clone(self) -> MiBox { + return MiBox{val: self.val}; + } +} + +fn main() { + let b1 = MiBox{val: 42}; + let b2 = b1.clone(); // Copia explícita +} +``` + +#### Composición +Usa `use` para embeber otros structs. Puedes mezclarlos (aplanar campos) o nombrarlos (anidar campos). + +```zc +struct Entity { id: int; } + +struct Player { + // Mezcla (Mixin - Sin nombre): Aplana los campos + use Entity; // Añade 'id' a Player directamente + nombre: string; +} + +struct Match { + // Composición (Con nombre): Anida los campos + use p1: Player; // Accedido mediante match.p1 + use p2: Player; // Accedido mediante match.p2 +} +``` + +### 10. Genéricos + +Plantillas seguras para tipos para Structs y Funciones. + +```zc +// Struct Genérico +struct Box<T> { + item: T; +} + +// Función Genérica +fn identidad<T>(val: T) -> T { + return val; +} + +// Genéricos con múltiples parámetros +struct Par<K, V> { + llave: K; + valor: V; +} +``` + +### 11. Concurrencia (Async/Await) + +Construido sobre pthreads. + +```zc +async fn obtener_datos() -> string { + // Se ejecuta en segundo plano + return "Datos"; +} + +fn main() { + let futuro = obtener_datos(); + let resultado = await futuro; +} +``` + +### 12. Metaprogramación + +#### Comptime +Ejecuta código en tiempo de compilación para generar código fuente o imprimir mensajes. +```zc +comptime { + // Genera código en tiempo de compilación (escrito en stdout) + println "let fecha_compilacion = \"2024-01-01\";"; +} + +println "Fecha de compilación: {fecha_compilacion}"; +``` + +#### Embed +Embebe archivos como los tipos especificados. +```zc +// Por defecto (Slice_char) +let datos = embed "assets/logo.png"; + +// Embed tipado +let texto = embed "shader.glsl" as string; // Embebe como C-string +let rom = embed "bios.bin" as u8[1024]; // Embebe como array fijo +let wav = embed "sound.wav" as u8[]; // Embebe como Slice_u8 +``` + +#### Plugins +Importa plugins del compilador para extender la sintaxis. +```zc +import plugin "regex" +let re = regex! { ^[a-z]+$ }; +``` + +#### Macros de C Genéricas +Pasa macros del preprocesador directamente a C. + +> **Consejo**: Para constantes simples, usa `def` en su lugar. Usa `#define` cuando necesites macros del preprocesador de C o flags de compilación condicional. + +```zc +#define MAX_BUFFER 1024 +``` + +### 13. Atributos + +Decora funciones y structs para modificar el comportamiento del compilador. + +| Atributo | Ámbito | Descripción | +|:---|:---|:---| +| `@must_use` | Fn | Advierte si el valor de retorno es ignorado. | +| `@deprecated("msg")` | Fn/Struct | Advierte sobre el uso con un mensaje. | +| `@inline` | Fn | Sugiere al compilador hacer inlininig. | +| `@noinline` | Fn | Previene el inlining. | +| `@packed` | Struct | Elimina el padding entre campos. | +| `@align(N)` | Struct | Fuerza el alineamiento a N bytes. | +| `@constructor` | Fn | Se ejecuta antes de main. | +| `@destructor` | Fn | Se ejecuta después de que main termine. | +| `@unused` | Fn/Var | Suprime advertencias de variables no usadas. | +| `@weak` | Fn | Enlace de símbolo débil (weak symbol linkage). | +| `@section("nombre")` | Fn | Coloca el código en una sección específica. | +| `@noreturn` | Fn | La función no retorna (ej. exit). | +| `@pure` | Fn | La función no tiene efectos secundarios (sugestión de optimización). | +| `@cold` | Fn | Es poco probable que la función se ejecute (sugestión de predicción de saltos). | +| `@hot` | Fn | La función se ejecuta frecuentemente (sugestión de optimización). | +| `@export` | Fn/Struct | Exporta el símbolo (visibilidad por defecto). | +| `@global` | Fn | CUDA: Punto de entrada del kernel (`__global__`). | +| `@device` | Fn | CUDA: Función de dispositivo (`__device__`). | +| `@host` | Fn | CUDA: Función de host (`__host__`). | +| `@comptime` | Fn | Función auxiliar disponible para ejecución en tiempo de compilación. | +| `@derive(...)` | Struct | Implementa traits automáticamente. Soporta `Debug`, `Eq` (Derivación Inteligente), `Copy`, `Clone`. | +| `@ctype("tipo")` | Parámetro Fn | Sobrescribe el tipo C generado para un parámetro. | +| `@<custom>` | Cualquier | Pasa atributos genéricos a C (ej. `@flatten`, `@alias("nombre")`). | + +#### Atributos Personalizados + +Zen C soporta un potente sistema de **Atributos Personalizados** que te permite usar cualquier `__attribute__` de GCC/Clang directamente en tu código. Cualquier atributo que no sea reconocido explícitamente por el compilador de Zen C es tratado como un atributo genérico y se pasa al código C generado. + +Esto proporciona acceso a características avanzadas del compilador, optimizaciones y directivas del enlazador sin necesidad de soporte explícito en el núcleo del lenguaje. + +#### Mapeo de Sintaxis +Los atributos de Zen C se mapean directamente a atributos de C: +- `@nombre` → `__attribute__((nombre))` +- `@nombre(args)` → `__attribute__((nombre(args)))` +- `@nombre("string")` → `__attribute__((nombre("string")))` + +#### Derivaciones Inteligentes + +Zen C proporciona "Derivaciones Inteligentes" que respetan la Semántica de Movimiento: + +- **`@derive(Eq)`**: Genera un método de igualdad que recibe los argumentos por referencia (`fn eq(self, other: T*)`). + - Al comparar dos structs que no son Copy (`a == b`), el compilador pasa automáticamente `b` por referencia (`&b`) para evitar moverlo. + - Las comprobaciones de igualdad recursivas en los campos también prefieren el acceso por puntero para prevenir la transferencia de posesión. + +### 14. Ensamblador Inline + +Zen C proporciona soporte de primera clase para ensamblador inline, transpilando directamente a `asm` extendido de estilo GCC. + +#### Uso Básico +Escribe ensamblador crudo dentro de bloques `asm`. Las cadenas se concatenan automáticamente. +```zc +asm { + "nop" + "mfence" +} +``` + +#### Volatile +Previene que el compilador optimice y elimine el ensamblador que tiene efectos secundarios. +```zc +asm volatile { + "rdtsc" +} +``` + +#### Restricciones con Nombre +Zen C simplifica la compleja sintaxis de restricciones de GCC con vinculaciones con nombre. + +```zc +// Sintaxis: : out(variable) : in(variable) : clobber(reg) +// Usa la sintaxis de marcador de posición {variable} para legibilidad + +fn sumar(a: int, b: int) -> int { + let resultado: int; + asm { + "add {resultado}, {a}, {b}" + : out(resultado) + : in(a), in(b) + : clobber("cc") + } + return resultado; +} +``` + +| Tipo | Sintaxis | Equivalente GCC | +|:---|:---|:---| +| **Salida** | `: out(variable)` | `"=r"(variable)` | +| **Entrada** | `: in(variable)` | `"r"(variable)` | +| **Clobber** | `: clobber("rax")` | `"rax"` | +| **Memoria** | `: clobber("memory")` | `"memory"` | + +> **Nota:** Cuando uses la sintaxis de Intel (mediante `-masm=intel`), debes asegurarte de que tu construcción esté configurada correctamente (por ejemplo, `//> cflags: -masm=intel`). TCC no soporta el ensamblador con sintaxis Intel. + +### 15. Directivas de Construcción + +Zen C soporta comentarios especiales en la parte superior de tu archivo fuente para configurar el proceso de construcción sin necesidad de un complejo sistema de construcción o Makefile. + +| Directiva | Argumentos | Descripción | +|:---|:---|:---| +| `//> link:` | `-lfoo` o `ruta/a/lib.a` | Enlaza contra una biblioteca o archivo objeto. | +| `//> lib:` | `ruta/a/libs` | Añade una ruta de búsqueda de biblioteca (`-L`). | +| `//> include:` | `ruta/a/headers` | Añade una ruta de búsqueda de cabeceras (`-I`). | +| `//> framework:` | `Cocoa` | Enlaza contra un framework de macOS. | +| `//> cflags:` | `-Wall -O3` | Pasa flags arbitrarios al compilador de C. | +| `//> define:` | `MACRO` o `LLAVE=VAL` | Define una macro del preprocesador (`-D`). | +| `//> pkg-config:` | `gtk+-3.0` | Ejecuta `pkg-config` y añade `--cflags` y `--libs`. | +| `//> shell:` | `comando` | Ejecuta un comando de shell durante la construcción. | +| `//> get:` | `http://url/archivo` | Descarga un archivo si el archivo específico no existe. | + +#### Características + +**1. Protección de SO (OS Guarding)** +Prefija las directivas con el nombre de un SO para aplicarlas solo en plataformas específicas. +Prefijos soportados: `linux:`, `windows:`, `macos:` (o `darwin:`). + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. Expansión de Variables de Entorno** +Usa la sintaxis `${VAR}` para expandir variables de entorno en tus directivas. + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + +#### Ejemplos + +```zc +//> include: ./include +//> lib: ./libs +//> link: -lraylib -lm +//> cflags: -Ofast +//> pkg-config: gtk+-3.0 + +import "raylib.h" + +fn main() { ... } +``` + +### 16. Palabras Clave + +Las siguientes palabras clave están reservadas en Zen C. + +#### Declaraciones +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### Flujo de Control +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### Especiales +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### Constantes +`true`, `false`, `null` + +#### Reservadas de C +Los siguientes identificadores están reservados porque son palabras clave en C11: +`auto`, `case`, `char`, `default`, `do`, `double`, `extern`, `float`, `inline`, `int`, `long`, `register`, `restrict`, `short`, `signed`, `switch`, `typedef`, `unsigned`, `void`, `_Atomic`, `_Bool`, `_Complex`, `_Generic`, `_Imaginary`, `_Noreturn`, `_Static_assert`, `_Thread_local` + +#### Operadores +`and`, `or` + +--- + +## Biblioteca Estándar + +Zen C incluye una biblioteca estándar (`std`) que cubre las funcionalidades esenciales. + +[Explorar la Documentación de la Biblioteca Estándar](docs/std/README.md) + +### Módulos Clave + +| Módulo | Descripción | Docs | +| :--- | :--- | :--- | +| **`std/vec.zc`** | Array dinámico creíble `Vec<T>`. | [Docs](docs/std/vec.md) | +| **`std/string.zc`** | Tipo `String` asignado en el heap con soporte UTF-8. | [Docs](docs/std/string.md) | +| **`std/queue.zc`** | Cola FIFO (Ring Buffer). | [Docs](docs/std/queue.md) | +| **`std/map.zc`** | Mapa Hash Genérico `Map<V>`. | [Docs](docs/std/map.md) | +| **`std/fs.zc`** | Operaciones del sistema de archivos. | [Docs](docs/std/fs.md) | +| **`std/io.zc`** | Entrada/Salida estándar (`print`/`println`). | [Docs](docs/std/io.md) | +| **`std/option.zc`** | Valores opcionales (`Some`/`None`). | [Docs](docs/std/option.md) | +| **`std/result.zc`** | Gestión de errores (`Ok`/`Err`). | [Docs](docs/std/result.md) | +| **`std/path.zc`** | Manipulación de rutas multiplataforma. | [Docs](docs/std/path.md) | +| **`std/env.zc`** | Variables de entorno del proceso. | [Docs](docs/std/env.md) | +| **`std/net.zc`** | Redes TCP (Sockets). | [Docs](docs/std/net.md) | +| **`std/thread.zc`** | Hilos y Sincronización. | [Docs](docs/std/thread.md) | +| **`std/time.zc`** | Medición de tiempo y espera (sleep). | [Docs](docs/std/time.md) | +| **`std/json.zc`** | Parseo y serialización de JSON. | [Docs](docs/std/json.md) | +| **`std/stack.zc`** | Pila LIFO `Stack<T>`. | [Docs](docs/std/stack.md) | +| **`std/set.zc`** | Conjunto Hash Genérico `Set<T>`. | [Docs](docs/std/set.md) | +| **`std/process.zc`** | Ejecución y gestión de procesos. | [Docs](docs/std/process.md) | + +--- + +## Herramientas + +Zen C proporciona un Servidor de Lenguaje y un REPL integrados para mejorar la experiencia de desarrollo. + +### Servidor de Lenguaje (LSP) + +El Servidor de Lenguaje de Zen C (LSP) soporta las características estándar de LSP para integración con editores, proporcionando: + +* **Ir a la Definición** +* **Encontrar Referencias** +* **Información al pasar el ratón (Hover)** +* **Autocompletado** (Nombres de funciones/structs, autocompletado tras punto para métodos/campos) +* **Símbolos del Documento** (Esquema) +* **Ayuda de Firma** +* **Diagnósticos** (Errores sintácticos/semánticos) + +Para iniciar el servidor de lenguaje (normalmente configurado en los ajustes de LSP de tu editor): + +```bash +zc lsp +``` + +Se comunica mediante I/O estándar (JSON-RPC 2.0). + +### REPL + +El bucle Read-Eval-Print te permite experimentar con el código de Zen C de forma interactiva. + +```bash +zc repl +``` + +#### Características + +* **Codificación Interactiva**: Escribe expresiones o sentencias para su evaluación inmediata. +* **Historial Persistente**: Los comandos se guardan en `~/.zprep_history`. +* **Script de Inicio**: Carga automáticamente comandos desde `~/.zprep_init.zc`. + +#### Comandos + +| Comando | Descripción | +|:---|:---| +| `:help` | Muestra los comandos disponibles. | +| `:reset` | Limpia el historial de la sesión actual (variables/funciones). | +| `:vars` | Muestra las variables activas. | +| `:funcs` | Muestra las funciones definidas por el usuario. | +| `:structs` | Muestra los structs definidos por el usuario. | +| `:imports` | Muestra las importaciones activas. | +| `:history` | Muestra el historial de entrada de la sesión. | +| `:type <expr>` | Muestra el tipo de una expresión. | +| `:c <stmt>` | Muestra el código C generado para una sentencia. | +| `:time <expr>` | Benchmark de una expresión (ejecuta 1000 iteraciones). | +| `:edit [n]` | Edita el comando `n` (por defecto: el último) en `$EDITOR`. | +| `:save <file>` | Guarda la sesión actual en un archivo `.zc`. | +| `:load <file>` | Carga y ejecuta un archivo `.zc` en la sesión. | +| `:watch <expr>` | Observa una expresión (se revalúa tras cada entrada). | +| `:unwatch <n>` | Elimina una observación. | +| `:undo` | Elimina el último comando de la sesión. | +| `:delete <n>` | Elimina el comando en el índice `n`. | +| `:clear` | Limpia la pantalla. | +| `:quit` | Sale del REPL. | +| `! <cmd>` | Ejecuta un comando de shell (ej. `!ls`). | + +--- + + +## Soporte del Compilador y Compatibilidad + +Zen C está diseñado para funcionar con la mayoría de los compiladores C11. Algunas características dependen de extensiones de GNU C, pero estas suelen funcionar en otros compiladores. Usa la flag `--cc` para cambiar de backend. + +```bash +zc run app.zc --cc clang +zc run app.zc --cc zig +``` + +### Estado de la Suite de Pruebas + +| Compilador | Tasa de Acierto | Características Soportadas | Limitaciones Conocidas | +|:---|:---:|:---|:---| +| **GCC** | **100%** | Todas las Características | Ninguna. | +| **Clang** | **100%** | Todas las Características | Ninguna. | +| **Zig** | **100%** | Todas las Características | Ninguna. Usa `zig cc` como reemplazo directo del compilador C. | +| **TCC** | **~70%** | Sintaxis Básica, Genéricos, Traits | Sin `__auto_type`, Sin ASM Intel, Sin funciones anidadas. | + +> **Recomendación:** Usa **GCC**, **Clang** o **Zig** para construcciones de producción. TCC es excelente para el prototipado rápido debido a su velocidad de compilación, pero le faltan algunas extensiones de C avanzadas en las que confía Zen C para el soporte total de características. + +### Construyendo con Zig + +El comando `zig cc` de Zig proporciona un reemplazo directo para GCC/Clang con un excelente soporte de compilación cruzada (cross-compilation). Para usar Zig: + +```bash +# Compilar y ejecutar un programa Zen C con Zig +zc run app.zc --cc zig + +# Construir el propio compilador Zen C con Zig +make zig +``` + +### Interop con C++ + +Zen C puede generar código compatible con C++ con la flag `--cpp`, permitiendo una integración perfecta con bibliotecas de C++. + +```bash +# Compilación directa con g++ +zc app.zc --cpp + +# O transpilar para construcción manual +zc transpile app.zc --cpp +g++ out.c mi_lib_cpp.o -o app +``` + +#### Usando C++ en Zen C + +Incluye cabeceras de C++ y usa bloques `raw` para el código C++: + +```zc +include <vector> +include <iostream> + +raw { + std::vector<int> hacer_vec(int a, int b) { + return {a, b}; + } +} + +fn main() { + let v = hacer_vec(1, 2); + raw { std::cout << "Tamaño: " << v.size() << std::endl; } +} +``` + +> **Nota:** La flag `--cpp` cambia el backend a `g++` y emite código compatible con C++ (usa `auto` en lugar de `__auto_type`, sobrecarga de funciones en lugar de `_Generic`, y casts explícitos para `void*`). + +#### Interop con CUDA + +Zen C soporta la programación de GPU transpilando a **CUDA C++**. Esto te permite aprovechar las potentes características de C++ (plantillas, constexpr) dentro de tus kernels mientras mantienes la sintaxis ergonómica de Zen C. + +```bash +# Compilación directa con nvcc +zc run app.zc --cuda + +# O transpilar para construcción manual +zc transpile app.zc --cuda -o app.cu +nvcc app.cu -o app +``` + +#### Atributos Específicos de CUDA + +| Atributo | Equivalente CUDA | Descripción | +|:---|:---|:---| +| `@global` | `__global__` | Función de kernel (se ejecuta en GPU, se llama desde el host) | +| `@device` | `__device__` | Función de dispositivo (se ejecuta en GPU, se llama desde GPU) | +| `@host` | `__host__` | Función de host (explícitamente solo CPU) | + +#### Sintaxis de Lanzamiento de Kernel + +Zen C proporciona una sentencia `launch` limpia para invocar kernels de CUDA: + +```zc +launch nombre_del_kernel(args) with { + grid: num_bloques, + block: hilos_por_bloque, + shared_mem: 1024, // Opcional + stream: mi_stream // Opcional +}; +``` + +Esto se transpila a: `nombre_del_kernel<<<grid, bloque, compartido, stream>>>(args);` + +#### Escribiendo Kernels de CUDA + +Usa la sintaxis de funciones de Zen C con `@global` y la sentencia `launch`: + +```zc +import "std/cuda.zc" + +@global +fn kernel_suma(a: float*, b: float*, c: float*, n: int) { + let i = thread_id(); + if i < n { + c[i] = a[i] + b[i]; + } +} + +fn main() { + def N = 1024; + let d_a = cuda_alloc<float>(N); + let d_b = cuda_alloc<float>(N); + let d_c = cuda_alloc<float>(N); + defer cuda_free(d_a); + defer cuda_free(d_b); + defer cuda_free(d_c); + + // ... inicialización de datos ... + + launch kernel_suma(d_a, d_b, d_c, N) with { + grid: (N + 255) / 256, + block: 256 + }; + + cuda_sync(); +} +``` + +#### Biblioteca Estándar (`std/cuda.zc`) +Zen C proporciona una biblioteca estándar para operaciones comunes de CUDA para reducir los bloques `raw`: + +```zc +import "std/cuda.zc" + +// Gestión de memoria +let d_ptr = cuda_alloc<float>(1024); +cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float)); +defer cuda_free(d_ptr); + +// Sincronización +cuda_sync(); + +// Indexación de hilos (usar dentro de kernels) +let i = thread_id(); // Índice global +let bid = block_id(); +let tid = local_id(); +``` + + +> **Nota:** La flag `--cuda` establece `nvcc` como el compilador e implica el modo `--cpp`. Requiere el NVIDIA CUDA Toolkit. + +### Soporte C23 + +Zen C soporta características modernas de C23 cuando se utiliza un compilador backend compatible (GCC 14+, Clang 14+). + +- **`auto`**: Zen C mapea automáticamente la inferencia de tipos a `auto` estándar de C23 si `__STDC_VERSION__ >= 202300L`. +- **`_BitInt(N)`**: Use tipos `iN` y `uN` (ej. `i256`, `u12`, `i24`) para acceder a enteros de ancho arbitrario de C23. + +### Interop con Objective-C + +Zen C puede compilarse a Objective-C (`.m`) usando la flag `--objc`, permitiéndote usar frameworks de Objective-C (como Cocoa/Foundation) y su sintaxis. + +```bash +# Compilar con clang (o gcc/gnustep) +zc app.zc --objc --cc clang +``` + +#### Usando Objective-C en Zen C + +Usa `include` para las cabeceras y bloques `raw` para la sintaxis de Objective-C (`@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(@"¡Hola desde Objective-C!"); + [pool drain]; + } + println "¡Zen C también funciona!"; +} +``` + +> **Nota:** La interpolación de cadenas de Zen C funciona con objetos de Objective-C (`id`) llamando a `debugDescription` o `description`. + +--- + +## Contribuyendo + +¡Damos la bienvenida a las contribuciones! Ya sea corrigiendo errores, añadiendo documentación o proponiendo nuevas características. + +### Cómo Contribuir +1. **Haz un Fork del Repositorio**: flujo de trabajo estándar de GitHub. +2. **Crea una Rama de Característica**: `git checkout -b feature/NuevaCosa`. +3. **Guías de Código**: + * Sigue el estilo de C existente. + * Asegúrate de que todas las pruebas pasen: `make test`. + * Añade nuevas pruebas para tu característica en `tests/`. +4. **Envía un Pull Request**: Describe tus cambios claramente. + +### Ejecutando Pruebas +La suite de pruebas es tu mejor amiga. + +```bash +# Ejecutar todas las pruebas (GCC) +make test + +# Ejecutar una prueba específica +./zc run tests/test_match.zc + +# Ejecutar con un compilador diferente +./tests/run_tests.sh --cc clang +./tests/run_tests.sh --cc zig +./tests/run_tests.sh --cc tcc +``` + +### Extendiendo el Compilador +* **Parser**: `src/parser/` - Parser de descenso recursivo. +* **Codegen**: `src/codegen/` - Lógica del transpilador (Zen C -> GNU C/C11). +* **Biblioteca Estándar**: `std/` - Escrita en el propio Zen C. + +--- + +## Atribuciones + +Este proyecto utiliza bibliotecas de terceros. Los textos completos de las licencias pueden encontrarse en el directorio `LICENSES/`. + +* **[cJSON](https://github.com/DaveGamble/cJSON)** (Licencia MIT): Usado para el parseo y generación de JSON en el Servidor de Lenguaje. +* **[zc-ape](https://github.com/OEvgeny/zc-ape)** (Licencia MIT): El port original de Ejecutable Realmente Portable de Zen-C por [Eugene Olonov](https://github.com/OEvgeny). +* **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (Licencia ISC): La biblioteca fundamental que hace posible APE. diff --git a/README_ZH_CN.md b/README_ZH_CN.md new file mode 100644 index 0000000..51689f6 --- /dev/null +++ b/README_ZH_CN.md @@ -0,0 +1,1435 @@ + +<div align="center"> + +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +</div> + +<div align="center"> + +# Zen C + +**现代开发体验。零开销。纯净 C。** + +[]() +[]() +[]() +[]() + +*像高级语言一样编写,像 C 一样运行。* + +</div> + +--- + +## 概述 + +**Zen C** 是一种现代系统编程语言,可编译为人类可读的 `GNU C`/`C11`。它提供了一套丰富的特性,包括类型推断、模式匹配、泛型、Trait、async/await 以及具有 RAII 能力的手动内存管理,同时保持 100% 的 C ABI 兼容性。 + +## 社区 + +加入官方 Zen C Discord 服务器,参与讨论、展示 Demo、提问或报告 Bug! + +- Discord: [点击加入](https://discord.com/invite/q6wEsCmkJP) + +--- + +## 目录 + +- [概述](#概述) +- [社区](#社区) +- [快速入门](#快速入门) + - [安装](#安装) + - [用法](#用法) + - [环境变量](#环境变量) +- [语言参考](#语言参考) + - [1. 变量与常量](#1-变量与常量) + - [2. 原始类型](#2-原始类型) + - [3. 复合类型](#3-复合类型) + - [数组](#数组) + - [元组](#元组) + - [结构体](#结构体) + - [不透明结构体](#不透明结构体) + - [枚举](#枚举) + - [联合体](#联合体) + - [类型别名](#类型别名) + - [不透明类型别名](#不透明类型别名) + - [4. 函数与 Lambda](#4-函数与-lambda) + - [函数](#函数) + - [常量参数](#常量参数) + - [默认参数](#默认参数) + - [Lambda (闭包)](#lambda-闭包) + - [原始函数指针](#原始函数指针) + - [变参函数](#变参函数) + - [5. 控制流](#5-控制流) + - [条件语句](#条件语句) + - [模式匹配](#模式匹配) + - [循环](#循环) + - [高级控制](#高级控制) + - [6. 运算符](#6-运算符) + - [可重载运算符](#可重载运算符) + - [语法糖](#语法糖) + - [7. 打印与字符串插值](#7-打印与字符串插值) + - [关键字](#关键字) + - [简写形式](#简写形式) + - [字符串插值 (F-strings)](#字符串插值-f-strings) + - [输入提示 (`?`)](#输入提示-) + - [8. 内存管理](#8-内存管理) + - [Defer](#defer) + - [Autofree](#autofree) + - [资源语义 (默认移动)](#资源语义-默认移动) + - [RAII / Drop Trait](#raii--drop-trait) + - [9. 面向对象编程](#9-面向对象编程) + - [方法](#方法) + - [Trait](#trait) + - [标准 Trait](#标准-trait) + - [组合](#组合) + - [10. 泛型](#10-泛型) + - [11. 并发 (Async/Await)](#11-并发-asyncawait) + - [12. 元编程](#12-元编程) + - [Comptime](#comptime) + - [Embed](#embed) + - [插件](#插件) + - [泛型 C 宏](#泛型-c-宏) + - [13. 属性](#13-属性) + - [自定义属性](#自定义属性) + - [智能派生](#智能派生) + - [14. 内联汇编](#14-内联汇编) + - [基本用法](#基本用法) + - [Volatile](#volatile) + - [命名约束](#命名约束) + - [15. 构建指令](#15-构建指令) + - [16. 关键字](#16-关键字) +- [标准库](#标准库) +- [工具链](#工具链) + - [语言服务器 (LSP)](#语言服务器-lsp) + - [REPL](#repl) +- [编译器支持与兼容性](#编译器支持与兼容性) + - [测试套件状态](#测试套件状态) + - [使用 Zig 构建](#使用-zig-构建) + - [C++ 互操作](#c-互操作) + - [CUDA 互操作](#cuda-互操作) + - [Objective-C 互操作](#objective-c-互操作) +- [贡献](#贡献) +- [致谢与归属](#致谢与归属) + +--- + +## 快速入门 + +### 安装 + +```bash +git clone https://github.com/z-libs/Zen-C.git +cd Zen-C +make +sudo make install +``` + +### 便携式构建 (APE) + +Zen C 可以通过 [Cosmopolitan Libc](https://github.com/jart/cosmopolitan) 编译为 **Actually Portable Executable (APE)**。这将生成一个单个的可执行文件 (`.com`),能够原生运行在 Linux, macOS, Windows, FreeBSD, OpenBSD, 和 NetBSD 上的 x86_64 和 aarch64 架构上。 + +**前提条件:** +- `cosmocc` 工具链(必须在 PATH 中) + +**构建与安装:** +```bash +make ape +sudo env "PATH=$PATH" make install-ape +``` + +**产物:** +- `out/bin/zc.com`: 便携式 Zen-C 编译器。已将标准库嵌入到可执行文件中。 +- `out/bin/zc-boot.com`: 一个自包含的引导安装程序,用于设置新的 Zen-C 项目。 + +**用法:** +```bash +# 在任何支持的操作系统上运行 +./out/bin/zc.com build hello.zc -o hello +``` + +### 用法 + +```bash +# 编译并运行 +zc run hello.zc + +# 构建可执行文件 +zc build hello.zc -o hello + +# 交互式 Shell +zc repl +``` + +### 环境变量 + +你可以设置 `ZC_ROOT` 来指定标准库的位置(标准导入如 `import "std/vector.zc"`)。这允许你从任何目录运行 `zc`。 + +```bash +export ZC_ROOT=/path/to/Zen-C +``` + +--- + +## 语言参考 + +### 1. 变量与常量 + +Zen C 区分编译时常量和运行时变量。 + +#### 清单常量 (`def`) +仅在编译时存在的值(折叠到代码中)。用于数组大小、固定配置和魔术数字。 + +```zc +def MAX_SIZE = 1024; +let buffer: char[MAX_SIZE]; // 有效的数组大小 +``` + +#### 变量 (`let`) +内存中的存储位置。可以是可变的或只读的 (`const`)。 + +```zc +let x = 10; // 可变 +x = 20; // 允许 + +let y: const int = 10; // 只读 (类型修饰) +// y = 20; // 错误:无法赋值给 const 变量 +``` + +> **类型推导**:Zen C 自动推导初始化变量的类型。在支持的编译器上编译为 C23 的 `auto`,否则使用 GCC 的 `__auto_type` 扩展。 + +### 2. 原始类型 + +| 类型 | C 等效类型 | 描述 | +|:---|:---|:---| +| `int`, `uint` | `int`, `unsigned int` | 平台标准整数 | +| `I8` .. `I128` 或 `i8` .. `i128` | `int8_t` .. `__int128_t` | 有符号固定宽度整数 | +| `U8` .. `U128` 或 `u8` .. `u128` | `uint8_t` .. `__uint128_t` | 无符号固定宽度整数 | +| `isize`, `usize` | `ptrdiff_t`, `size_t` | 指针大小的整数 | +| `byte` | `uint8_t` | U8 的别名 | +| `F32`, `F64` 或 `f32`, `f64` | `float`, `double` | 浮点数 | +| `bool` | `bool` | `true` 或 `false` | +| `char` | `char` | 单个字符 | +| `string` | `char*` | C-string (以 null 结尾) | +| `U0`, `u0`, `void` | `void` | 空类型 | +| `iN` (例 `i256`) | `_BitInt(N)` | 任意位宽有符号整数 (C23) | +| `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位宽无符号整数 (C23) | + +### 3. 复合类型 + +#### 数组 +具有值语义的固定大小数组。 +```zc +def SIZE = 5; +let ints: int[SIZE] = [1, 2, 3, 4, 5]; +let zeros: [int; SIZE]; // 零初始化的 +``` + +#### 元组 +将多个值组合在一起,通过索引访问元素。 +```zc +let pair = (1, "Hello"); +let x = pair.0; // 1 +let s = pair.1; // "Hello" +``` + +**多个返回值** + +函数可以返回元组以提供多个结果: +```zc +fn add_and_subtract(a: int, b: int) -> (int, int) { + return (a + b, a - b); +} + +let result = add_and_subtract(3, 2); +let sum = result.0; // 5 +let diff = result.1; // 1 +``` + +**解构** + +元组可以直接解构为多个变量: +```zc +let (sum, diff) = add_and_subtract(3, 2); +// sum = 5, diff = 1 +``` + +#### 结构体 +带有可选位域的数据结构。 +```zc +struct Point { + x: int; + y: int; +} + +// 结构体初始化 +let p = Point { x: 10, y: 20 }; + +// 位域 +struct Flags { + valid: U8 : 1; + mode: U8 : 3; +} +``` + +> **注意**:结构体默认使用 [移动语义](#资源语义-默认移动)。即使是指针,也可以通过 `.` 访问字段(自动解引用)。 + +#### 不透明结构体 +你可以将结构体定义为 `opaque`,以将对其字段的访问限制在定义该结构体的模块内部,同时仍允许在栈上分配该结构体(大小已知)。 + +```zc +// 在 user.zc 中 +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // 允许:在模块内部 +} + +// 在 main.zc 中 +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // 错误:无法访问私有字段 'id' +} +``` + +#### 枚举 +能够持有数据的标签联合 (Sum types)。 +```zc +enum Shape { + Circle(float), // 持有半径 + Rect(float, float), // 持有宽、高 + Point // 不带数据 +} +``` + +#### 联合体 +标准 C 联合体(不安全访问)。 +```zc +union Data { + i: int; + f: float; +} +``` + +#### 类型别名 +为现有类型创建新名称。 +```zc +alias ID = int; +alias PointMap = Map<string, Point>; +``` + +#### 不透明类型别名 +你可以将类型别名定义为 `opaque`,从而在定义模块之外创建一个与基础类型不同的新类型。这提供了强大的封装和类型安全性,而没有包装结构体的运行时开销。 + +```zc +// 在 library.zc 中 +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // 允许在模块内部进行隐式转换 +} + +// 在 main.zc 中 +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // 错误:类型验证失败 + // let h2: Handle = 10; // 错误:类型验证失败 +} +``` + +### 4. 函数与 Lambda + +#### 函数 +```zc +fn add(a: int, b: int) -> int { + return a + b; +} + +// 调用时支持命名参数 +add(a: 10, b: 20); +``` + +> **注意**:命名参数必须严格遵循定义的参数顺序。`add(b: 20, a: 10)` 是无效的。 + +#### 常量参数 +函数参数可以标记为 `const` 以强制执行只读语义。这是一个类型修饰符,而不是清单常量。 + +```zc +fn print_val(v: const int) { + // v = 10; // 错误:无法赋值给 const 变量 + println "{v}"; +} +``` + +#### 默认参数 +函数可以为尾部参数定义默认值。这些值可以是字面量、表达式或有效的 Zen C 代码(如结构体构造函数)。 +```zc +// 简单默认值 +fn increment(val: int, amount: int = 1) -> int { + return val + amount; +} + +// 表达式默认值(在调用处计算) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// 结构体默认值 +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "调试模式"; } +} + +fn main() { + increment(10); // 11 + offset(5); // 25 + init(); // 打印 "调试模式" +} +``` + +#### Lambda (闭包) +可以捕获环境的匿名函数。 +```zc +let factor = 2; +let double = x -> x * factor; // 箭头语法 +let full = fn(x: int) -> int { return x * factor; }; // 块语法 +``` + +#### 原始函数指针 +Zen C 使用 `fn*` 语法支持原始 C 函数指针。这允许与期望函数指针且没有闭包开销的 C 库进行无缝互操作。 + +```zc +// 接受原始函数指针的函数 +fn set_callback(cb: fn*(int)) { + cb(42); +} + +// 返回原始函数指针的函数 +fn get_callback() -> fn*(int) { + return my_handler; +} + +// 支持指向函数指针的指针 (fn**) +let pptr: fn**(int) = &ptr; +``` + +#### 变参函数 +函数可以使用 `...` 和 `va_list` 类型接受可变数量的参数。 +```zc +fn log(lvl: int, fmt: char*, ...) { + let ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // 使用 C stdio + va_end(ap); +} +``` + +### 5. 控制流 + +#### 条件语句 +```zc +if x > 10 { + print("Large"); +} else if x > 5 { + print("Medium"); +} else { + print("Small"); +} + +// 三元运算符 +let y = x > 10 ? 1 : 0; +``` + +#### 模式匹配 +`switch` 的强大替代方案。 +```zc +match val { + 1 => { print "One" }, + 2 || 3 => { print "Two or Three" }, // 使用 || 进行 或 操作 + 4 or 5 => { print "Four or Five" }, // 使用 'or' 进行 或 操作 + 6, 7, 8 => { print "Six to Eight" }, // 使用逗号进行 或 操作 + 10 .. 15 => { print "10 to 14" }, // 左闭右开区间 (旧语法) + 10 ..< 15 => { print "10 to 14" }, // 左闭右开区间 (显式) + 20 ..= 25 => { print "20 to 25" }, // 全闭区间 + _ => { print "Other" }, +} + +// 解构枚举 +match shape { + Shape::Circle(r) => println "半径: {r}", + Shape::Rect(w, h) => println "面积: {w*h}", + Shape::Point => println "点" +} +``` + +#### 引用绑定 +为了在不获取所有权(移动)的情况下检查一个值,在模式中使用 `ref` 关键字。这对于实现了移动语义的类型(如 `Option`, `Result`, 非 Copy 结构体)至关重要。 + +```zc +let opt = Some(NonCopyVal{...}); +match opt { + Some(ref x) => { + // 'x' 是指向 'opt' 内部值的指针 + // 'opt' 在此处不会被移动/消耗 + println "{x.field}"; + }, + None => {} +} +``` + +#### 循環 +```zc +// 區間迭代 +for i in 0..10 { ... } // 左閉右開 (0 到 9) +for i in 0..<10 { ... } // 左閉右開 (顯式) +for i in 0..=10 { ... } // 全閉 (0 到 10) +for i in 0..10 step 2 { ... } + +// 迭代器 (Vec 或自定義 Iterable) +for item in vec { ... } + +// 直接迭代固定大小数组 +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val 是 int + println "{val}"; +} + +// While 循環 +while x < 10 { ... } + +// 帶標籤的無限循環 +outer: loop { + if done { break outer; } +} + +// 重複 N 次 +for _ in 0..5 { ... } +``` + +#### 高级控制 +```zc +// Guard: 如果条件为假,则执行 else 块并返回 +guard ptr != NULL else { return; } + +// Unless: 除非为真(即如果为假) +unless is_valid { return; } +``` + +### 6. 运算符 + +Zen C 通过实现特定的方法名来支持用户定义结构体的运算符重载。 + +#### 可重载运算符 + +| 类别 | 运算符 | 方法名 | +|:---|:---|:---| +| **算术** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **比较** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **位运算** | `&`, `|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **一元** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **索引** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +> **关于字符串相等性的说明**: +> - `string == string` 进行 **值比较**(等同于 `strcmp`)。 +> - `char* == char*` 进行 **指针比较**(检查内存地址)。 +> - 混合比较(例如 `string == char*`)默认为 **指针比较**。 + +**示例:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +let p3 = p1 + p2; // 调用 p1.add(p2) +``` + +#### 语法糖 + +这些运算符是内置语言特性,不能直接重载。 + +| 运算符 | 名称 | 描述 | +|:---|:---|:---| +| `|>` | 管道 | `x |> f(y)` 脱糖为 `f(x, y)` | +| `??` | 空合并 | 如果 `val` 为 NULL,`val ?? default` 返回 `default` (用于指针) | +| `??=` | 空赋值 | 如果 `val` 为 NULL 则赋值 | +| `?.` | 安全导航 | 仅当 `ptr` 不为 NULL 时访问字段 | +| `?` | Try 运算符 | 如果存在错误则返回 (用于 Result/Option 类型) | + +**自动解引用**: +指针字段访问 (`ptr.field`) 和方法调用 (`ptr.method()`) 会自动解引用指针,等同于 `(*ptr).field`。 + +### 7. 打印与字符串插值 + +Zen C 提供了多种控制台打印选项,包括关键字和简洁的简写形式。 + +#### 关键字 + +- `print "text"`: 打印到 `stdout`,不带尾随换行符。 +- `println "text"`: 打印到 `stdout`,带尾随换行符。 +- `eprint "text"`: 打印到 `stderr`,不带尾随换行符。 +- `eprintln "text"`: 打印到 `stderr`,带尾随换行符。 + +#### 简写形式 + +Zen C 允许直接将字符串字面量用作语句来进行快速打印: + +- `"Hello World"`: 等同于 `println "Hello World"`。(隐式添加换行符) +- `"Hello World"..`: 等同于 `print "Hello World"`。(不带尾随换行符) +- `!"Error"`: 等同于 `eprintln "Error"`。(输出到 stderr) +- `!"Error"..`: 等同于 `eprint "Error"`。(输出到 stderr,不带换行符) + +#### 字符串插值 (F-strings) + +你可以使用 `{}` 语法将表达式直接嵌入到字符串字面量中。这适用于所有打印方法和字符串简写。 + +```zc +let x = 42; +let name = "Zen"; +println "值: {x}, 名称: {name}"; +"值: {x}, 名称: {name}"; // 简写形式的 println +``` + +#### 输入提示 (`?`) + +Zen C 支持使用 `?` 前缀进行用户输入提示的简写。 + +- `? "提示文本"`: 打印提示信息(不换行)并等待输入(读取一行)。 +- `? "输入年龄: " (age)`: 打印提示并扫描输入到变量 `age` 中。 + - 格式说明符会根据变量类型自动推断。 + +```zc +let age: int; +? "你多大了? " (age); +println "你 {age} 岁了。"; +``` + +### 8. 内存管理 + +Zen C 允许带有符合人体工程学辅助的手动内存管理。 + +#### Defer +在当前作用域退出时执行代码。Defer 语句按照后进先出 (LIFO) 的顺序执行。 +```zc +let f = fopen("file.txt", "r"); +defer fclose(f); +``` + +> 为了防止未定义行为,`defer` 块内不允许使用控制流语句(`return`, `break`, `continue`, `goto`)。 + +#### Autofree +在作用域退出时自动释放变量。 +```zc +autofree let types = malloc(1024); +``` + +#### 资源语义 (默认移动) +Zen C 将带有析构函数(如 `File`, `Vec`, 或 malloc 的指针)的类型视为 **资源**。为了防止双重释放错误,资源不能被隐式复制。 + +- **默认移动**:分配资源变量会转移所有权。原始变量变得无效(已移动)。 +- **复制类型**:没有析构函数的类型可以申请参与 `Copy` 行为,使赋值变成复制。 + +**诊断与哲学**: +如果你看到错误 "Use of moved value",编译器是在告诉你:*"此类型拥有一个资源(如内存或句柄),盲目复制它是不安全的。"* + +> **对比:** 与 C/C++ 不同,Zen C 不会隐式复制拥有资源的值。 + +**函数参数**: +将值传递给函数遵循与赋值相同的规则:资源会被移动,除非通过引用传递。 + +```zc +fn process(r: Resource) { ... } // 'r' 被移动进函数 +fn peek(r: Resource*) { ... } // 'r' 被借用 (引用) +``` + +**显式克隆**: +如果你 *确实* 想要一个资源的两个副本,请显式执行: + +```zc +let b = a.clone(); // 调用 Clone trait 中的 'clone' 方法 +``` + +**选择性复制 (值类型)**: +对于没有析构函数的小型类型: + +```zc +struct Point { x: int; y: int; } +impl Copy for Point {} // 选择参与隐式复制 + +fn main() { + let p1 = Point { x: 1, y: 2 }; + let p2 = p1; // 已复制。p1 保持有效。 +} +``` + +#### RAII / Drop Trait +实现 `Drop` 以自动运行清理逻辑。 +```zc +impl Drop for MyStruct { + fn drop(self) { + self.free(); + } +} +``` + +### 9. 面向对象编程 + +#### 方法 +使用 `impl` 为类型定义方法。 +```zc +impl Point { + // 静态方法 (构造函数惯例) + fn new(x: int, y: int) -> Self { + return Point{x: x, y: y}; + } + + // 实例方法 + fn dist(self) -> float { + return sqrt(self.x * self.x + self.y * self.y); + } +} +``` + +#### Trait +定义共享行为。 +```zc +struct Circle { radius: f32; } + +trait Drawable { + fn draw(self); +} + +impl Drawable for Circle { + fn draw(self) { ... } +} + +let circle = Circle{}; +let drawable: Drawable = &circle; +``` + +#### 标准 Trait +Zen C 包含与语言语法集成的标准 Trait。 + +**Iterable** + +实现 `Iterable<T>` 以便为你的自定义类型启用 `for-in` 循环。 + +```zc +import "std/iter.zc" + +// 定义一个迭代器 +struct MyIter { + curr: int; + stop: int; +} + +impl MyIter { + fn next(self) -> Option<int> { + if self.curr < self.stop { + self.curr += 1; + return Option<int>::Some(self.curr - 1); + } + return Option<int>::None(); + } +} + +// 实现 Iterable +impl MyRange { + fn iterator(self) -> MyIter { + return MyIter{curr: self.start, stop: self.end}; + } +} + +// 在循环中使用 +for i in my_range { + println "{i}"; +} +``` + +**Drop** + +实现 `Drop` 来定义一个在对象超出范围时运行的析构函数 (RAII)。 + +```zc +import "std/mem.zc" + +struct Resource { + ptr: void*; +} + +impl Drop for Resource { + fn drop(self) { + if self.ptr != NULL { + free(self.ptr); + } + } +} +``` + +> **注意:** 如果一个变量被移动,则原始变量不会调用 `drop`。它遵循 [资源语义](#资源语义-默认移动)。 + +**Copy** + +标记 Trait,用于选择支持 `Copy` 行为(隐式复制)而不是移动语义。通过 `@derive(Copy)` 使用。 + +> **规则:** 实现了 `Copy` 的类型不得定义析构函数 (`Drop`)。 + +```zc +@derive(Copy) +struct Point { x: int; y: int; } + +fn main() { + let p1 = Point{x: 1, y: 2}; + let p2 = p1; // 已复制!p1 保持有效。 +} +``` + +**Clone** + +实现 `Clone` 以允许显式复制拥有资源的类型。 + +```zc +import "std/mem.zc" + +struct MyBox { val: int; } + +impl Clone for MyBox { + fn clone(self) -> MyBox { + return MyBox{val: self.val}; + } +} + +fn main() { + let b1 = MyBox{val: 42}; + let b2 = b1.clone(); // 显式复制 +} +``` + +#### 组合 +使用 `use` 嵌入其他结构体。你可以将它们混合进来(展平字段)或者为它们命名(嵌套字段)。 + +```zc +struct Entity { id: int; } + +struct Player { + // 混入 (未命名): 展平字段 + use Entity; // 直接将 'id' 添加到 Player + name: string; +} + +struct Match { + // 组合 (命名): 嵌套字段 + use p1: Player; // 通过 match.p1 访问 + use p2: Player; // 通过 match.p2 访问 +} +``` + +### 10. 泛型 + +结构体和函数的类型安全模板。 + +```zc +// 泛型结构体 +struct Box<T> { + item: T; +} + +// 泛型函数 +fn identity<T>(val: T) -> T { + return val; +} + +// 多参数泛型 +struct Pair<K, V> { + key: K; + value: V; +} +``` + +### 11. 并发 (Async/Await) + +基于 pthreads 构建。 + +```zc +async fn fetch_data() -> string { + // 在后台运行 + return "Data"; +} + +fn main() { + let future = fetch_data(); + let result = await future; +} +``` + +### 12. 元编程 + +#### Comptime +在编译时运行代码以生成源码或打印消息。 +```zc +comptime { + // 在编译时生成代码 (写入 stdout) + println "let build_date = \"2024-01-01\";"; +} + +println "构建日期: {build_date}"; +``` + +#### Embed +将文件嵌入为指定类型。 +```zc +// 默认 (Slice_char) +let data = embed "assets/logo.png"; + +// 类型化嵌入 +let text = embed "shader.glsl" as string; // 嵌入为 C-string +let rom = embed "bios.bin" as u8[1024]; // 嵌入为固定数组 +let wav = embed "sound.wav" as u8[]; // 嵌入为 Slice_u8 +``` + +#### 插件 +导入编译器插件以扩展语法。 +```zc +import plugin "regex" +let re = regex! { ^[a-z]+$ }; +``` + +#### 泛型 C 宏 +将预处理器宏传递给 C。 + +> **提示**:对于简单的常量,请使用 `def`。当你需要 C 预处理器宏或条件编译标志时,请使用 `#define`。 + +```zc +#define MAX_BUFFER 1024 +``` + +### 13. 属性 + +修饰函数和结构体以修改编译器行为。 + +| 属性 | 作用域 | 描述 | +|:---|:---|:---| +| `@must_use` | 函数 | 如果忽略返回值则发出警告。 | +| `@deprecated("msg")` | 函数/结构体 | 使用时发出带有消息的警告。 | +| `@inline` | 函数 | 提示编译器进行内联。 | +| `@noinline` | 函数 | 防止内联。 | +| `@packed` | 结构体 | 移除字段间的填充。 | +| `@align(N)` | 结构体 | 强制按 N 字节对齐。 | +| `@constructor` | 函数 | 在 main 之前运行。 | +| `@destructor` | 函数 | 在 main 退出后运行。 | +| `@unused` | 函数/变量 | 抑制未使用变量警告。 | +| `@weak` | 函数 | 弱符号链接。 | +| `@section("name")` | 函数 | 将代码放置在特定段中。 | +| `@noreturn` | 函数 | 函数不会返回 (例如 exit)。 | +| `@pure` | 函数 | 函数无副作用 (优化提示)。 | +| `@cold` | 函数 | 函数不太可能被执行 (分支预测提示)。 | +| `@hot` | 函数 | 函数频繁执行 (优化提示)。 | +| `@export` | 函数/结构体 | 导出符号 (默认可见性)。 | +| `@global` | 函数 | CUDA: 内核入口点 (`__global__`)。 | +| `@device` | 函数 | CUDA: 设备函数 (`__device__`)。 | +| `@host` | 函数 | CUDA: 主机函数 (`__host__`)。 | +| `@comptime` | 函数 | 用于编译时执行的辅助函数。 | +| `@derive(...)` | 结构体 | 自动实现 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@ctype("type")` | 函数参数 | 覆盖参数生成的 C 类型。 | +| `@<custom>` | 任意 | 将泛型属性传递给 C (例如 `@flatten`, `@alias("name")`)。 | + +#### 自定义属性 + +Zen C 支持强大的 **自定义属性** 系统,允许你在代码中直接使用任何 GCC/Clang 的 `__attribute__`。任何不被 Zen C 编译器显式识别的属性都会被视为泛型属性并传递给生成的 C 代码。 + +这提供了对高级编译器特性、优化和链接器指令的访问,而无需在语言核心中提供显式支持。 + +#### 语法映射 +Zen C 属性直接映射到 C 属性: +- `@name` → `__attribute__((name))` +- `@name(args)` → `__attribute__((name(args)))` +- `@name("string")` → `__attribute__((name("string")))` + +#### 智能派生 + +Zen C 提供了尊重移动语义的 "智能派生": + +- **`@derive(Eq)`**:生成一个通过引用获取参数的相等性方法 (`fn eq(self, other: T*)`)。 + - 当比较两个非 Copy 结构体 (`a == b`) 时,编译器会自动通过引用传递 `b` (`&b`) 以避免移动它。 + - 字段上的递归相等性检查也会优先使用指针访问,以防止所有权转移。 + +### 14. 内联汇编 + +Zen C 为内联汇编提供了一流支持,直接转译为 GCC 风格的扩展 `asm`。 + +#### 基本用法 +在 `asm` 块内编写原始汇编。字符串会自动拼接。 +```zc +asm { + "nop" + "mfence" +} +``` + +#### Volatile +防止编译器优化掉具有副作用的汇编代码。 +```zc +asm volatile { + "rdtsc" +} +``` + +#### 命名约束 +Zen C 通过命名绑定简化了复杂的 GCC 约束语法。 + +```zc +// 语法: : out(变量) : in(变量) : clobber(寄存器) +// 使用 {变量} 占位符语法以提高可读性 + +fn add(a: int, b: int) -> int { + let result: int; + asm { + "add {result}, {a}, {b}" + : out(result) + : in(a), in(b) + : clobber("cc") + } + return result; +} +``` + +| 类型 | 语法 | GCC 等效项 | +|:---|:---|:---| +| **输出** | `: out(variable)` | `"=r"(variable)` | +| **输入** | `: in(variable)` | `"r"(variable)` | +| **破坏** | `: clobber("rax")` | `"rax"` | +| **内存** | `: clobber("memory")` | `"memory"` | + +> **注意:** 使用 Intel 语法时(通过 `-masm=intel`),必须确保你的构建配置正确(例如,`//> cflags: -masm=intel`)。TCC 不支持 Intel 语法的汇编。 + +### 15. 构建指令 + +Zen C 支持在源文件顶部使用特殊注释来配置构建过程,无需复杂的构建系统或 Makefile。 + +| 指令 | 参数 | 描述 | +|:---|:---|:---| +| `//> link:` | `-lfoo` 或 `path/to/lib.a` | 链接库或对象文件。 | +| `//> lib:` | `path/to/libs` | 添加库搜索路径 (`-L`)。 | +| `//> include:` | `path/to/headers` | 添加包含头文件搜索路径 (`-I`)。 | +| `//> framework:` | `Cocoa` | 链接 macOS Framework。 | +| `//> cflags:` | `-Wall -O3` | 向 C 编译器传递任意标志。 | +| `//> define:` | `MACRO` 或 `KEY=VAL` | 定义预处理器宏 (`-D`)。 | +| `//> pkg-config:` | `gtk+-3.0` | 运行 `pkg-config` 并追加 `--cflags` 和 `--libs`。 | +| `//> shell:` | `command` | 在构建期间执行 shell 命令。 | +| `//> get:` | `http://url/file` | 如果特定文件不存在,则下载该文件。 | + +#### 特性 + +**1. 操作系统守护 (OS Guarding)** +在指令前加上操作系统名称,以使其仅在特定平台上应用。 +受支持的前缀:`linux:`, `windows:`, `macos:` (或 `darwin:`)。 + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. 环境变量展开** +使用 `${VAR}` 语法在指令中展开环境变量。 + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + +#### 示例 + +```zc +//> include: ./include +//> lib: ./libs +//> link: -lraylib -lm +//> cflags: -Ofast +//> pkg-config: gtk+-3.0 + +import "raylib.h" + +fn main() { ... } +``` + +### 16. 关键字 + +以下关键字在 Zen C 中是保留的。 + +#### 声明 +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### 控制流 +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### 特殊 +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### 常量 +`true`, `false`, `null` + +#### C 保留字 +以下标识符是保留的,因为它们是 C11 中的关键字: +`auto`, `case`, `char`, `default`, `do`, `double`, `extern`, `float`, `inline`, `int`, `long`, `register`, `restrict`, `short`, `signed`, `switch`, `typedef`, `unsigned`, `void`, `_Atomic`, `_Bool`, `_Complex`, `_Generic`, `_Imaginary`, `_Noreturn`, `_Static_assert`, `_Thread_local` + +#### 运算符 +`and`, `or` + +--- + +## 标准库 + +Zen C 包含一个涵盖基本功能的标准库 (`std`)。 + +[浏览标准库文档](docs/std/README.md) + +### 核心模块 + +| 模块 | 描述 | 文档 | +| :--- | :--- | :--- | +| **`std/vec.zc`** | 可增长动态数组 `Vec<T>`。 | [文档](docs/std/vec.md) | +| **`std/string.zc`** | 堆分配的 `String` 类型,支持 UTF-8。 | [文档](docs/std/string.md) | +| **`std/queue.zc`** | 先进先出队列 (环形缓冲区)。 | [文档](docs/std/queue.md) | +| **`std/map.zc`** | 泛型哈希表 `Map<V>`。 | [文档](docs/std/map.md) | +| **`std/fs.zc`** | 文件系统操作。 | [文档](docs/std/fs.md) | +| **`std/io.zc`** | 标准输入/输出 (`print`/`println`)。 | [文档](docs/std/io.md) | +| **`std/option.zc`** | 可选值 (`Some`/`None`)。 | [文档](docs/std/option.md) | +| **`std/result.zc`** | 错误处理 (`Ok`/`Err`)。 | [文档](docs/std/result.md) | +| **`std/path.zc`** | 跨平台路径操作。 | [文档](docs/std/path.md) | +| **`std/env.zc`** | 进程环境变量。 | [文档](docs/std/env.md) | +| **`std/net.zc`** | TCP 网络 (套接字)。 | [文档](docs/std/net.md) | +| **`std/thread.zc`** | 线程与同步。 | [文档](docs/std/thread.md) | +| **`std/time.zc`** | 时间测量与睡眠。 | [文档](docs/std/time.md) | +| **`std/json.zc`** | JSON 解析与序列化。 | [文档](docs/std/json.md) | +| **`std/stack.zc`** | 后进先出栈 `Stack<T>`。 | [文档](docs/std/stack.md) | +| **`std/set.zc`** | 泛型哈希集合 `Set<T>`。 | [文档](docs/std/set.md) | +| **`std/process.zc`** | 进程执行与管理。 | [文档](docs/std/process.md) | + +--- + +## 工具链 + +Zen C 提供内置的语言服务器 (LSP) 和 REPL 以增强开发体验。 + +### 语言服务器 (LSP) + +Zen C 语言服务器 (LSP) 支持标准的 LSP 特性,用于编辑器集成: + +* **转到定义** +* **查找引用** +* **悬停信息** +* **补全** (函数/结构体名,方法/字段的点补全) +* **文档符号** (大纲) +* **签名帮助** +* **诊断** (语法/语义错误) + +启动语言服务器(通常在编辑器的 LSP 设置中配置): + +```bash +zc lsp +``` + +它通过标准 I/O (JSON-RPC 2.0) 进行通信。 + +### REPL + +Read-Eval-Print Loop 允许你交互式地尝试 Zen C 代码。 + +```bash +zc repl +``` + +#### 特性 + +* **交互式编码**:输入表达式或语句以立即求值。 +* **持久历史**:命令保存在 `~/.zprep_history` 中。 +* **启动脚本**:自动加载 `~/.zprep_init.zc` 中的命令。 + +#### 命令 + +| 命令 | 描述 | +|:---|:---| +| `:help` | 显示可用命令。 | +| `:reset` | 清除当前会话历史 (变量/函数)。 | +| `:vars` | 显示活跃变量。 | +| `:funcs` | 显示用户定义的函数。 | +| `:structs` | 显示用户定义的结构体。 | +| `:imports` | 显示活跃导入。 | +| `:history` | 显示会话输入历史。 | +| `:type <expr>` | 显示表达式的类型。 | +| `:c <stmt>` | 显示语句生成的 C 代码。 | +| `:time <expr>` | 基准测试表达式 (运行 1000 次迭代)。 | +| `:edit [n]` | 在 `$EDITOR` 中编辑命令 `n` (默认:最后一条)。 | +| `:save <file>` | 将当前会话保存到 `.zc` 文件。 | +| `:load <file>` | 将 `.zc` 文件加载并执行到会话中。 | +| `:watch <expr>` | 监视表达式 (每次输入后重新求值)。 | +| `:unwatch <n>` | 移除监视。 | +| `:undo` | 从会话中移除最后一条命令。 | +| `:delete <n>` | 移除索引为 `n` 的命令。 | +| `:clear` | 清屏。 | +| `:quit` | 退出 REPL。 | +| `! <cmd>` | 运行 shell 命令 (如 `!ls`)。 | + +--- + + +## 编译器支持与兼容性 + +Zen C 旨在与大多数 C11 编译器配合使用。某些特性依赖于 GNU C 扩展,但这些扩展通常在其他编译器中也能工作。使用 `--cc` 标志切换后端。 + +```bash +zc run app.zc --cc clang +zc run app.zc --cc zig +``` + +### 测试套件状态 + +| 编译器 | 通过率 | 受支持特性 | 已知局限性 | +|:---|:---:|:---|:---| +| **GCC** | **100%** | 所有特性 | 无。 | +| **Clang** | **100%** | 所有特性 | 无。 | +| **Zig** | **100%** | 所有特性 | 无。使用 `zig cc` 作为替代 C 编译器。 | +| **TCC** | **~70%** | 基本语法, 泛型, Trait | 不支持 `__auto_type`, 不支持 Intel ASM, 不支持嵌套函数。 | + +> **建议:** 生产环境构建请使用 **GCC**, **Clang**, 或 **Zig**。TCC 非常适合快速原型开发,因为它编译速度极快,但缺少 Zen C 全面支持所需的一些高级 C 扩展。 + +### 使用 Zig 构建 + +Zig 的 `zig cc` 命令提供了 GCC/Clang 的替代方案,具有出色的跨平台编译支持。使用 Zig: + +```bash +# 使用 Zig 编译并运行 Zen C 程序 +zc run app.zc --cc zig + +# 使用 Zig 构建 Zen C 编译器本身 +make zig +``` + +### C++ 互操作 + +Zen C 可以通过 `--cpp` 标志生成 C++ 兼容的代码,从而实现与 C++ 库的无缝集成。 + +```bash +# 直接使用 g++ 编译 +zc app.zc --cpp + +# 或者转译用于手动构建 +zc transpile app.zc --cpp +g++ out.c my_cpp_lib.o -o app +``` + +#### 在 Zen C 中使用 C++ + +包含 C++ 头文件并在 `raw` 块中使用 C++ 代码: + +```zc +include <vector> +include <iostream> + +raw { + std::vector<int> make_vec(int a, int b) { + return {a, b}; + } +} + +fn main() { + let v = make_vec(1, 2); + raw { std::cout << "Size: " << v.size() << std::endl; } +} +``` + +> **注意:** `--cpp` 标志会将后端切换为 `g++` 并发出 C++ 兼容的代码(使用 `auto` 代替 `__auto_type`,使用函数重载代替 `_Generic`,以及对 `void*` 进行显式转换)。 + +#### CUDA 互操作 + +Zen C 通过转译为 **CUDA C++** 来支持 GPU 编程。这使你在维持 Zen C 人体工程学语法的同时,能够利用内核中的强大 C++ 特性(模板、constexpr)。 + +```bash +# 直接使用 nvcc 编译 +zc run app.zc --cuda + +# 或者转译用于手动构建 +zc transpile app.zc --cuda -o app.cu +nvcc app.cu -o app +``` + +#### CUDA 特定属性 + +| 属性 | CUDA 等效项 | 描述 | +|:---|:---|:---| +| `@global` | `__global__` | 内核函数 (运行在 GPU,从主机调用) | +| `@device` | `__device__` | 设备函数 (运行在 GPU,从 GPU 调用) | +| `@host` | `__host__` | 主机函数 (明确仅 CPU 运行) | + +#### 内核启动语法 + +Zen C 提供了一个简洁的 `launch` 语句用于调用 CUDA 内核: + +```zc +launch kernel_name(args) with { + grid: num_blocks, + block: threads_per_block, + shared_mem: 1024, // 可选 + stream: my_stream // 可选 +}; +``` + +这转译为:`kernel_name<<<grid, block, shared, stream>>>(args);` + +#### 编写 CUDA 内核 + +使用带有 `@global` 的 Zen C 函数语法和 `launch` 语句: + +```zc +import "std/cuda.zc" + +@global +fn add_kernel(a: float*, b: float*, c: float*, n: int) { + let i = thread_id(); + if i < n { + c[i] = a[i] + b[i]; + } +} + +fn main() { + def N = 1024; + let d_a = cuda_alloc<float>(N); + let d_b = cuda_alloc<float>(N); + let d_c = cuda_alloc<float>(N); + defer cuda_free(d_a); + defer cuda_free(d_b); + defer cuda_free(d_c); + + // ... 初始化数据 ... + + launch add_kernel(d_a, d_b, d_c, N) with { + grid: (N + 255) / 256, + block: 256 + }; + + cuda_sync(); +} +``` + +#### 标准库 (`std/cuda.zc`) +Zen C 为常见的 CUDA 操作提供了一个标准库,以减少 `raw` 块的使用: + +```zc +import "std/cuda.zc" + +// 内存管理 +let d_ptr = cuda_alloc<float>(1024); +cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float)); +defer cuda_free(d_ptr); + +// 同步 +cuda_sync(); + +// 线程索引 (在内核内部使用) +let i = thread_id(); // 全局索引 +let bid = block_id(); +let tid = local_id(); +``` + + +> **注意:** `--cuda` 标志设置 `nvcc` 为编译器并隐含 `--cpp` 模式。需要安装 NVIDIA CUDA Toolkit。 + +### C23 支持 + +当使用兼容的后端编译器(GCC 14+, Clang 14+)时,Zen C 支持现代 C23特性。 + +- **`auto`**: 如果 `__STDC_VERSION__ >= 202300L`,Zen C 会自动将类型推导映射到标准 C23 `auto`。 +- **`_BitInt(N)`**: 使用 `iN` 和 `uN` 类型(例如 `i256`, `u12`, `i24`)访问 C23 任意位宽整数。 + +### Objective-C 互操作 + +Zen C 可以通过 `--objc` 标志编译为 Objective-C (`.m`),允许你使用 Objective-C 框架(如 Cocoa/Foundation)和语法。 + +```bash +# 使用 clang (或 gcc/gnustep) 编译 +zc app.zc --objc --cc clang +``` + +#### 在 Zen C 中使用 Objective-C + +使用 `include` 包含头文件,并在 `raw` 块中使用 Objective-C 语法 (`@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(@"来自 Objective-C 的问候!"); + [pool drain]; + } + println "Zen C 也能正常工作!"; +} +``` + +> **注意:** Zen C 字符串插值通过调用 `debugDescription` 或 `description` 同样适用于 Objective-C 对象 (`id`)。 + +--- + +## 贡献 + +我们欢迎各类贡献!无论是修复 Bug、完善文档,还是提出新功能建议。 + +### 如何贡献 +1. **Fork 仓库**:标准的 GitHub 工作流程。 +2. **创建功能分支**:`git checkout -b feature/NewThing`。 +3. **代码规范**: + * 遵循现有的 C 风格。 + * 确保所有测试通过:`make test`。 + * 在 `tests/` 中为你的功能添加新测试。 +4. **提交拉取请求**:清晰地描述你的更改。 + +### 运行测试 +测试套件是你最好的朋友。 + +```bash +# 运行所有测试 (GCC) +make test + +# 运行特定的测试 +./zc run tests/test_match.zc + +# 使用不同的编译器运行 +./tests/run_tests.sh --cc clang +./tests/run_tests.sh --cc zig +./tests/run_tests.sh --cc tcc +``` + +### 扩展编译器 +* **解析器 (Parser)**:`src/parser/` - 递归下降解析器。 +* **代码生成 (Codegen)**:`src/codegen/` - 转译逻辑 (Zen C -> GNU C/C11)。 +* **标准库 (Standard Library)**:`std/` - 使用 Zen C 本身编写。 + +--- + +## 致谢与归属 + +本项目使用了第三方库。完整许可证文本可在 `LICENSES/` 目录中找到。 + +* **[cJSON](https://github.com/DaveGamble/cJSON)** (MIT 许可证):用于语言服务器中的 JSON 解析和生成。 +* **[zc-ape](https://github.com/OEvgeny/zc-ape)** (MIT 许可证):由 [Eugene Olonov](https://github.com/OEvgeny) 开发的原版 Zen-C 实际上便携的可执行文件 (APE) 端口。 +* **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (ISC 许可证):使 APE 成为可能的基础库。 diff --git a/README_ZH_TW.md b/README_ZH_TW.md new file mode 100644 index 0000000..6fa0dbd --- /dev/null +++ b/README_ZH_TW.md @@ -0,0 +1,1435 @@ + +<div align="center"> + +[English](README.md) • [简体中文](README_ZH_CN.md) • [繁體中文](README_ZH_TW.md) • [Español](README_ES.md) + +</div> + +<div align="center"> + +# Zen C + +**現代開發體驗。零開銷。純淨 C。** + +[]() +[]() +[]() +[]() + +*像高級語言一樣編寫,像 C 一樣運行。* + +</div> + +--- + +## 概述 + +**Zen C** 是一種現代系統編程語言,可編譯為人類可讀的 `GNU C`/`C11`。它提供了一套豐富的特性,包括類型推斷、模式匹配、泛型、Trait、async/await 以及具有 RAII 能力的手動內存管理,同時保持 100% 的 C ABI 兼容性。 + +## 社區 + +加入官方 Zen C Discord 服務器,參與討論、展示 Demo、提問或報告 Bug! + +- Discord: [點擊加入](https://discord.com/invite/q6wEsCmkJP) + +--- + +## 目錄 + +- [概述](#概述) +- [社區](#社區) +- [快速入門](#快速入門) + - [安裝](#安裝) + - [用法](#用法) + - [環境變量](#環境變量) +- [語言參考](#語言參考) + - [1. 變量與常量](#1-變量與常量) + - [2. 原始類型](#2-原始類型) + - [3. 複合類型](#3-複合類型) + - [數組](#數組) + - [元組](#元組) + - [結構體](#結構體) + - [不透明結構體](#不透明結構體) + - [枚舉](#枚舉) + - [聯合體](#聯合體) + - [類型別名](#類型別名) + - [不透明類型別名](#不透明類型別名) + - [4. 函數與 Lambda](#4-函數與-lambda) + - [函數](#函數) + - [常量參數](#常量參數) + - [默認參數](#默認參數) + - [Lambda (閉包)](#lambda-閉包) + - [原始函數指針](#原始函數指針) + - [變參函數](#變參函數) + - [5. 控制流](#5-控制流) + - [條件語句](#條件語句) + - [模式匹配](#模式匹配) + - [循環](#循環) + - [高級控制](#高級控制) + - [6. 運算符](#6-運算符) + - [可重載運算符](#可重載運算符) + - [語法糖](#語法糖) + - [7. 打印與字符串插值](#7-打印與字符串插值) + - [關鍵字](#關鍵字) + - [簡寫形式](#簡寫形式) + - [字符串插值 (F-strings)](#字符串插值-f-strings) + - [輸入提示 (`?`)](#輸入提示-) + - [8. 內存管理](#8-內存管理) + - [Defer](#defer) + - [Autofree](#autofree) + - [資源語義 (默認移動)](#資源語義-默認移動) + - [RAII / Drop Trait](#raii--drop-trait) + - [9. 面向對象編程](#9-面向對象編程) + - [方法](#方法) + - [Trait](#trait) + - [標準 Trait](#標準-trait) + - [組合](#組合) + - [10. 泛型](#10-泛型) + - [11. 並發 (Async/Await)](#11-並發-asyncawait) + - [12. 元編程](#12-元編程) + - [Comptime](#comptime) + - [Embed](#embed) + - [插件](#插件) + - [泛型 C 宏](#泛型-c-宏) + - [13. 屬性](#13-屬性) + - [自定義屬性](#自定義屬性) + - [智能派生](#智能派生) + - [14. 內聯匯編](#14-內聯匯編) + - [基本用法](#基本用法) + - [Volatile](#volatile) + - [命名約束](#命名約束) + - [15. 構建指令](#15-構建指令) + - [16. 關鍵字](#16-關鍵字) +- [標準庫](#標準庫) +- [工具鏈](#工具鏈) + - [語言服務器 (LSP)](#語言服務器-lsp) + - [REPL](#repl) +- [編譯器支持與兼容性](#編譯器支持與兼容性) + - [測試套件狀態](#測試套件狀態) + - [使用 Zig 構建](#使用-zig-構建) + - [C++ 互操作](#c-互操作) + - [CUDA 互操作](#cuda-互操作) + - [Objective-C 互操作](#objective-c-互操作) +- [貢獻](#貢獻) +- [致謝與歸屬](#致謝與歸屬) + +--- + +## 快速入門 + +### 安裝 + +```bash +git clone https://github.com/z-libs/Zen-C.git +cd Zen-C +make +sudo make install +``` + +### 便攜式構建 (APE) + +Zen C 可以通過 [Cosmopolitan Libc](https://github.com/jart/cosmopolitan) 編譯為 **Actually Portable Executable (APE)**。這將生成一個單個的可執行文件 (`.com`),能夠原生運行在 Linux, macOS, Windows, FreeBSD, OpenBSD, 和 NetBSD 上的 x86_64 和 aarch64 架構上。 + +**前提條件:** +- `cosmocc` 工具鏈(必須在 PATH 中) + +**構建與安裝:** +```bash +make ape +sudo env "PATH=$PATH" make install-ape +``` + +**產物:** +- `out/bin/zc.com`: 便攜式 Zen-C 編譯器。已將標準庫嵌入到可執行文件中。 +- `out/bin/zc-boot.com`: 一個自包含的引導安裝程序,用於設置新的 Zen-C 項目。 + +**用法:** +```bash +# 在任何支持的操作系統上運行 +./out/bin/zc.com build hello.zc -o hello +``` + +### 用法 + +```bash +# 編譯並運行 +zc run hello.zc + +# 構建可執行文件 +zc build hello.zc -o hello + +# 交互式 Shell +zc repl +``` + +### 環境變量 + +你可以設置 `ZC_ROOT` 來指定標準庫的位置(標準導入如 `import "std/vector.zc"`)。這允許你從任何目錄運行 `zc`。 + +```bash +export ZC_ROOT=/path/to/Zen-C +``` + +--- + +## 語言參考 + +### 1. 變量與常量 + +Zen C 區分編譯時常量和運行時變量。 + +#### 清單常量 (`def`) +僅在編譯時存在的值(折疊到代碼中)。用於數組大小、固定配置和魔術數字。 + +```zc +def MAX_SIZE = 1024; +let buffer: char[MAX_SIZE]; // 有效的數組大小 +``` + +#### 變量 (`let`) +內存中的存儲位置。可以是可變的或只讀的 (`const`)。 + +```zc +let x = 10; // 可變 +x = 20; // 允許 + +let y: const int = 10; // 只讀 (類型修飾) +// y = 20; // 錯誤:無法賦值給 const 變量 +``` + +> **型別推導**:Zen C 自動推導初始化變數的型別。在支援的編譯器上編譯為 C23 的 `auto`,否則使用 GCC 的 `__auto_type` 擴充功能。 + +### 2. 原始類型 + +| 類型 | C 等效類型 | 描述 | +|:---|:---|:---| +| `int`, `uint` | `int`, `unsigned int` | 平台標準整數 | +| `I8` .. `I128` 或 `i8` .. `i128` | `int8_t` .. `__int128_t` | 有符號固定寬度整數 | +| `U8` .. `U128` 或 `u8` .. `u128` | `uint8_t` .. `__uint128_t` | 無符號固定寬度整數 | +| `isize`, `usize` | `ptrdiff_t`, `size_t` | 指針大小的整數 | +| `byte` | `uint8_t` | U8 的別名 | +| `F32`, `F64` 或 `f32`, `f64` | `float`, `double` | 浮點數 | +| `bool` | `bool` | `true` 或 `false` | +| `char` | `char` | 單個字符 | +| `string` | `char*` | C-string (以 null 結尾) | +| `U0`, `u0`, `void` | `void` | 空類型 | +| `iN` (例 `i256`) | `_BitInt(N)` | 任意位元寬度有號整數 (C23) | +| `uN` (例 `u42`) | `unsigned _BitInt(N)` | 任意位元寬度無號整數 (C23) | + +### 3. 複合類型 + +#### 數組 +具有值語義的固定大小數組。 +```zc +def SIZE = 5; +let ints: int[SIZE] = [1, 2, 3, 4, 5]; +let zeros: [int; SIZE]; // 零初始化的 +``` + +#### 元組 +將多個值組合在一起,通過索引訪問元素。 +```zc +let pair = (1, "Hello"); +let x = pair.0; // 1 +let s = pair.1; // "Hello" +``` + +**多個返回值** + +函數可以返回元組以提供多個結果: +```zc +fn add_and_subtract(a: int, b: int) -> (int, int) { + return (a + b, a - b); +} + +let result = add_and_subtract(3, 2); +let sum = result.0; // 5 +let diff = result.1; // 1 +``` + +**解構** + +元組可以直接解構為多個變量: +```zc +let (sum, diff) = add_and_subtract(3, 2); +// sum = 5, diff = 1 +``` + +#### 結構體 +帶有可選位域的數據結構。 +```zc +struct Point { + x: int; + y: int; +} + +// 結構體初始化 +let p = Point { x: 10, y: 20 }; + +// 位域 +struct Flags { + valid: U8 : 1; + mode: U8 : 3; +} +``` + +> **注意**:結構體默認使用 [移動語義](#資源語義-默認移動)。即使是指針,也可以通過 `.` 訪問字段(自動解引用)。 + +#### 不透明結構體 +你可以將結構體定義為 `opaque`,以將對其字段的訪問限制在定義該結構體的模塊內部,同時仍允許在棧上分配該結構體(大小已知)。 + +```zc +// 在 user.zc 中 +opaque struct User { + id: int; + name: string; +} + +fn new_user(name: string) -> User { + return User{id: 1, name: name}; // 允許:在模塊內部 +} + +// 在 main.zc 中 +import "user.zc"; + +fn main() { + let u = new_user("Alice"); + // let id = u.id; // 錯誤:無法訪問私有字段 'id' +} +``` + +#### 枚舉 +能夠持有數據的標籤聯合 (Sum types)。 +```zc +enum Shape { + Circle(float), // 持有半徑 + Rect(float, float), // 持有寬、高 + Point // 不帶數據 +} +``` + +#### 聯合體 +標準 C 聯合體(不安全訪問)。 +```zc +union Data { + i: int; + f: float; +} +``` + +#### 類型別名 +為現有類型創建新名稱。 +```zc +alias ID = int; +alias PointMap = Map<string, Point>; +``` + +#### 不透明類型別名 +你可以將類型別名定義為 `opaque`,從而在定義模塊之外創建一個與基礎類型不同的新類型。這提供了強大的封裝和類型安全性,而沒有包裝結構體的運行時開銷。 + +```zc +// 在 library.zc 中 +opaque alias Handle = int; + +fn make_handle(v: int) -> Handle { + return v; // 允許在模塊內部進行隱式轉換 +} + +// 在 main.zc 中 +import "library.zc"; + +fn main() { + let h: Handle = make_handle(42); + // let i: int = h; // 錯誤:類型驗證失敗 + // let h2: Handle = 10; // 錯誤:類型驗證失敗 +} +``` + +### 4. 函數與 Lambda + +#### 函數 +```zc +fn add(a: int, b: int) -> int { + return a + b; +} + +// 調用時支持命名參數 +add(a: 10, b: 20); +``` + +> **注意**:命名參數必須嚴格遵循定義的參數順序。`add(b: 20, a: 10)` 是無效的。 + +#### 常量參數 +函數參數可以標記為 `const` 以強制執行只讀語義。這是一個類型修飾符,而不是清單常量。 + +```zc +fn print_val(v: const int) { + // v = 10; // 錯誤:無法賦值給 const 變量 + println "{v}"; +} +``` + +#### 默認參數 +函數可以為尾部參數定義默認值。這些值可以是字面量、表達式或有效的 Zen C 代碼(如結構體構造函數)。 +```zc +// 簡單默認值 +fn increment(val: int, amount: int = 1) -> int { + return val + amount; +} + +// 表達式默認值(在調用處計算) +fn offset(val: int, pad: int = 10 * 2) -> int { + return val + pad; +} + +// 結構體默認值 +struct Config { debug: bool; } +fn init(cfg: Config = Config { debug: true }) { + if cfg.debug { println "調試模式"; } +} + +fn main() { + increment(10); // 11 + offset(5); // 25 + init(); // 打印 "調試模式" +} +``` + +#### Lambda (閉包) +可以捕獲環境的匿名函數。 +```zc +let factor = 2; +let double = x -> x * factor; // 箭頭語法 +let full = fn(x: int) -> int { return x * factor; }; // 塊語法 +``` + +#### 原始函數指針 +Zen C 使用 `fn*` 語法支持原始 C 函數指針。這允許與期望函數指針且沒有閉包開銷的 C 庫進行無縫互操作。 + +```zc +// 接受原始函數指針的函數 +fn set_callback(cb: fn*(int)) { + cb(42); +} + +// 返回原始函數指針的函數 +fn get_callback() -> fn*(int) { + return my_handler; +} + +// 支持指向函數指針的指針 (fn**) +let pptr: fn**(int) = &ptr; +``` + +#### 變參函數 +函數可以使用 `...` 和 `va_list` 類型接受可變數量的參數。 +```zc +fn log(lvl: int, fmt: char*, ...) { + let ap: va_list; + va_start(ap, fmt); + vprintf(fmt, ap); // 使用 C stdio + va_end(ap); +} +``` + +### 5. 控制流 + +#### 条件语句 +```zc +if x > 10 { + print("Large"); +} else if x > 5 { + print("Medium"); +} else { + print("Small"); +} + +// 三元運算符 +let y = x > 10 ? 1 : 0; +``` + +#### 模式匹配 +`switch` 的強大替代方案。 +```zc +match val { + 1 => { print "One" }, + 2 || 3 => { print "Two or Three" }, // 使用 || 進行 或 操作 + 4 or 5 => { print "Four or Five" }, // 使用 'or' 進行 或 操作 + 6, 7, 8 => { print "Six to Eight" }, // 使用逗號進行 或 操作 + 10 .. 15 => { print "10 to 14" }, // 左閉右開區間 (舊語法) + 10 ..< 15 => { print "10 to 14" }, // 左閉右開區間 (顯式) + 20 ..= 25 => { print "20 to 25" }, // 全閉區間 + _ => { print "Other" }, +} + +// 解構枚舉 +match shape { + Shape::Circle(r) => println "半徑: {r}", + Shape::Rect(w, h) => println "面積: {w*h}", + Shape::Point => println "點" +} +``` + +#### 引用綁定 +為了在不獲取所有權(移動)的情況下檢查一個值,在模式中使用 `ref` 關鍵字。這對於實現了移動語義的類型(如 `Option`, `Result`, 非 Copy 結構體)至關重要。 + +```zc +let opt = Some(NonCopyVal{...}); +match opt { + Some(ref x) => { + // 'x' 是指向 'opt' 內部值的指針 + // 'opt' 在此處不會被移動/消耗 + println "{x.field}"; + }, + None => {} +} +``` + +#### 循環 +```zc +// 區間迭代 +for i in 0..10 { ... } // 左閉右開 (0 到 9) +for i in 0..<10 { ... } // 左閉右開 (顯式) +for i in 0..=10 { ... } // 全閉 (0 到 10) +for i in 0..10 step 2 { ... } + +// 迭代器 (Vec 或自定義 Iterable) +for item in vec { ... } + +// 直接迭代固定大小數組 +let arr: int[5] = [1, 2, 3, 4, 5]; +for val in arr { + // val 是 int + println "{val}"; +} + +// While 循環 +while x < 10 { ... } + +// 帶標籤的無限循環 +outer: loop { + if done { break outer; } +} + +// 重複 N 次 +for _ in 0..5 { ... } +``` + +#### 高級控制 +```zc +// Guard: 如果條件為假,則執行 else 塊並返回 +guard ptr != NULL else { return; } + +// Unless: 除非為真(即如果為假) +unless is_valid { return; } +``` + +### 6. 運算符 + +Zen C 通過實現特定的方法名來支持用戶定義結構體的運算符重載。 + +#### 可重載運算符 + +| 類別 | 運算符 | 方法名 | +|:---|:---|:---| +| **算術** | `+`, `-`, `*`, `/`, `%` | `add`, `sub`, `mul`, `div`, `rem` | +| **比較** | `==`, `!=` | `eq`, `neq` | +| | `<`, `>`, `<=`, `>=` | `lt`, `gt`, `le`, `ge` | +| **位運算** | `&`, `|`, `^` | `bitand`, `bitor`, `bitxor` | +| | `<<`, `>>` | `shl`, `shr` | +| **一元** | `-` | `neg` | +| | `!` | `not` | +| | `~` | `bitnot` | +| **索引** | `a[i]` | `get(a, i)` | +| | `a[i] = v` | `set(a, i, v)` | + +> **關於字符串相等性的說明**: +> - `string == string` 進行 **值比較**(等同於 `strcmp`)。 +> - `char* == char*` 進行 **指針比較**(檢查內存地址)。 +> - 混合比較(例如 `string == char*`)默認為 **指針比較**。 + +**示例:** +```zc +impl Point { + fn add(self, other: Point) -> Point { + return Point{x: self.x + other.x, y: self.y + other.y}; + } +} + +let p3 = p1 + p2; // 調用 p1.add(p2) +``` + +#### 語法糖 + +這些運算符是內置語言特性,不能直接重載。 + +| 運算符 | 名稱 | 描述 | +|:---|:---|:---| +| `|>` | 管道 | `x |> f(y)` 脫糖為 `f(x, y)` | +| `??` | 空合併 | 如果 `val` 為 NULL,`val ?? default` 返回 `default` (用於指針) | +| `??=` | 空賦值 | 如果 `val` 為 NULL 則賦值 | +| `?.` | 安全導航 | 僅當 `ptr` 不為 NULL 時訪問字段 | +| `?` | Try 運算符 | 如果存在錯誤則返回 (用於 Result/Option 類型) | + +**自動解引用**: +指針字段訪問 (`ptr.field`) 和方法調用 (`ptr.method()`) 會自動解引用指針,等同於 `(*ptr).field`。 + +### 7. 打印與字符串插值 + +Zen C 提供了多種控制台打印選項,包括關鍵字和簡潔的簡寫形式。 + +#### 關鍵字 + +- `print "text"`: 打印到 `stdout`,不帶尾隨換行符。 +- `println "text"`: 打印到 `stdout`,帶尾隨換行符。 +- `eprint "text"`: 打印到 `stderr`,不帶尾隨換行符。 +- `eprintln "text"`: 打印到 `stderr`,帶尾隨換行符。 + +#### 簡寫形式 + +Zen C 允許直接將字符串字面量用作語句來進行快速打印: + +- `"Hello World"`: 等同於 `println "Hello World"`。(隱式添加換行符) +- `"Hello World"..`: 等同於 `print "Hello World"`。(不帶尾隨換行符) +- `!"Error"`: 等同於 `eprintln "Error"`。(輸出到 stderr) +- `!"Error"..`: 等同於 `eprint "Error"`。(輸出到 stderr,不帶換行符) + +#### 字符串插值 (F-strings) + +你可以使用 `{}` 語法將表達式直接嵌入到字符串字面量中。這適用於所有打印方法和字符串簡寫。 + +```zc +let x = 42; +let name = "Zen"; +println "值: {x}, 名稱: {name}"; +"值: {x}, 名稱: {name}"; // 簡寫形式的 println +``` + +#### 輸入提示 (`?`) + +Zen C 支持使用 `?` 前綴進行用戶輸入提示的簡寫。 + +- `? "提示文本"`: 打印提示信息(不換行)並等待輸入(讀取一行)。 +- `? "輸入年齡: " (age)`: 打印提示並掃描輸入到變量 `age` 中。 + - 格式說明符會根據變量類型自動推斷。 + +```zc +let age: int; +? "你多大了? " (age); +println "你 {age} 歲了。"; +``` + +### 8. 內存管理 + +Zen C 允許帶有符合人體工程學輔助的手動內存管理。 + +#### Defer +在當前作用域退出時執行代碼。Defer 語句按照後進先出 (LIFO) 的順序執行。 +```zc +let f = fopen("file.txt", "r"); +defer fclose(f); +``` + +> 為了防止未定義行為,`defer` 塊內不允許使用控制流語句(`return`, `break`, `continue`, `goto`)。 + +#### Autofree +在作用域退出時自動釋放變量。 +```zc +autofree let types = malloc(1024); +``` + +#### 資源語義 (默認移動) +Zen C 將帶有析構函數(如 `File`, `Vec`, 或 malloc 的指針)的類型視為 **資源**。為了防止雙重釋放錯誤,資源不能被隱式複製。 + +- **默認移動**:分配資源變量會轉移所有權。原始變量變得無效(已移動)。 +- **複製類型**:沒有析構函數的類型可以申請參與 `Copy` 行為,使賦值變成複製。 + +**診斷與哲學**: +如果你看到錯誤 "Use of moved value",編譯器是在告訴你:*"此類型擁有一個資源(如內存或句柄),盲目複製它是不安全的。"* + +> **對比:** 與 C/C++ 不同,Zen C 不會隱式複製擁有資源的值。 + +**函數參數**: +將值傳遞給函數遵循與賦值相同的規則:資源會被移動,除非通過引用傳遞。 + +```zc +fn process(r: Resource) { ... } // 'r' 被移動進函數 +fn peek(r: Resource*) { ... } // 'r' 被借用 (引用) +``` + +**顯式克隆**: +如果你 *確實* 想要一個資源的兩個副本,請顯式執行: + +```zc +let b = a.clone(); // 調用 Clone trait 中的 'clone' 方法 +``` + +**選擇性複製 (值類型)**: +對於沒有析構函數的小型類型: + +```zc +struct Point { x: int; y: int; } +impl Copy for Point {} // 選擇參與隱式複製 + +fn main() { + let p1 = Point { x: 1, y: 2 }; + let p2 = p1; // 已複製。p1 保持有效。 +} +``` + +#### RAII / Drop Trait +實現 `Drop` 以自動運行清理邏輯。 +```zc +impl Drop for MyStruct { + fn drop(self) { + self.free(); + } +} +``` + +### 9. 面向對象編程 + +#### 方法 +使用 `impl` 為類型定義方法。 +```zc +impl Point { + // 靜態方法 (構造函數慣例) + fn new(x: int, y: int) -> Self { + return Point{x: x, y: y}; + } + + // 實例方法 + fn dist(self) -> float { + return sqrt(self.x * self.x + self.y * self.y); + } +} +``` + +#### Trait +定義共享行為。 +```zc +struct Circle { radius: f32; } + +trait Drawable { + fn draw(self); +} + +impl Drawable for Circle { + fn draw(self) { ... } +} + +let circle = Circle{}; +let drawable: Drawable = &circle; +``` + +#### 標準 Trait +Zen C 包含與語言語法集成的標準 Trait。 + +**Iterable** + +實現 `Iterable<T>` 以便為你的自定義類型啟用 `for-in` 循環。 + +```zc +import "std/iter.zc" + +// 定義一個迭代器 +struct MyIter { + curr: int; + stop: int; +} + +impl MyIter { + fn next(self) -> Option<int> { + if self.curr < self.stop { + self.curr += 1; + return Option<int>::Some(self.curr - 1); + } + return Option<int>::None(); + } +} + +// 實現 Iterable +impl MyRange { + fn iterator(self) -> MyIter { + return MyIter{curr: self.start, stop: self.end}; + } +} + +// 在循環中使用 +for i in my_range { + println "{i}"; +} +``` + +**Drop** + +實現 `Drop` 來定義一個在對象超出範圍時運行的析構函數 (RAII)。 + +```zc +import "std/mem.zc" + +struct Resource { + ptr: void*; +} + +impl Drop for Resource { + fn drop(self) { + if self.ptr != NULL { + free(self.ptr); + } + } +} +``` + +> **注意:** 如果一個變量被移動,則原始變量不會調用 `drop`。它遵循 [資源語義](#資源語義-默認移動)。 + +**Copy** + +標記 Trait,用於選擇支持 `Copy` 行為(隱式複製)而不是移動語義。通過 `@derive(Copy)` 使用。 + +> **規則:** 實現了 `Copy` 的類型不得定義析構函數 (`Drop`)。 + +```zc +@derive(Copy) +struct Point { x: int; y: int; } + +fn main() { + let p1 = Point{x: 1, y: 2}; + let p2 = p1; // 已複製!p1 保持有效。 +} +``` + +**Clone** + +實現 `Clone` 以允許顯式複製擁有資源的類型。 + +```zc +import "std/mem.zc" + +struct MyBox { val: int; } + +impl Clone for MyBox { + fn clone(self) -> MyBox { + return MyBox{val: self.val}; + } +} + +fn main() { + let b1 = MyBox{val: 42}; + let b2 = b1.clone(); // 顯式複製 +} +``` + +#### 組合 +使用 `use` 嵌入其他結構體。你可以將它們混合進來(展平字段)或者為它們命名(嵌套字段)。 + +```zc +struct Entity { id: int; } + +struct Player { + // 混入 (未命名): 展平字段 + use Entity; // 直接將 'id' 添加到 Player + name: string; +} + +struct Match { + // 組合 (命名): 嵌套字段 + use p1: Player; // 通過 match.p1 訪問 + use p2: Player; // 通過 match.p2 訪問 +} +``` + +### 10. 泛型 + +結構體和函數的類型安全模板。 + +```zc +// 泛型結構體 +struct Box<T> { + item: T; +} + +// 泛型函數 +fn identity<T>(val: T) -> T { + return val; +} + +// 多參數泛型 +struct Pair<K, V> { + key: K; + value: V; +} +``` + +### 11. 並發 (Async/Await) + +基於 pthreads 構建。 + +```zc +async fn fetch_data() -> string { + // 在後台運行 + return "Data"; +} + +fn main() { + let future = fetch_data(); + let result = await future; +} +``` + +### 12. 元編程 + +#### Comptime +在編譯時運行代碼以生成源碼或打印消息。 +```zc +comptime { + // 在編譯時生成代碼 (寫入 stdout) + println "let build_date = \"2024-01-01\";"; +} + +println "構建日期: {build_date}"; +``` + +#### Embed +將文件嵌入為指定類型。 +```zc +// 默認 (Slice_char) +let data = embed "assets/logo.png"; + +// 類型化嵌入 +let text = embed "shader.glsl" as string; // 嵌入為 C-string +let rom = embed "bios.bin" as u8[1024]; // 嵌入為固定數組 +let wav = embed "sound.wav" as u8[]; // 嵌入為 Slice_u8 +``` + +#### 插件 +導入編譯器插件以擴展語法。 +```zc +import plugin "regex" +let re = regex! { ^[a-z]+$ }; +``` + +#### 泛型 C 宏 +將預處理器宏傳遞給 C。 + +> **提示**:對於簡單的常量,請使用 `def`。當你需要 C 預處理器宏或條件編譯標誌時,請使用 `#define`。 + +```zc +#define MAX_BUFFER 1024 +``` + +### 13. 屬性 + +修飾函數和結構體以修改編譯器行為。 + +| 屬性 | 作用域 | 描述 | +|:---|:---|:---| +| `@must_use` | 函數 | 如果忽略返回值則發出警告。 | +| `@deprecated("msg")` | 函數/結構體 | 使用時發出帶有消息的警告。 | +| `@inline` | 函數 | 提示編譯器進行內聯。 | +| `@noinline` | 函數 | 防止內聯。 | +| `@packed` | 結構體 | 移除字段間的填充。 | +| `@align(N)` | 結構體 | 強制按 N 字节對齊。 | +| `@constructor` | 函數 | 在 main 之前運行。 | +| `@destructor` | 函數 | 在 main 退出後運行。 | +| `@unused` | 函數/變量 | 抑制未使用變量警告。 | +| `@weak` | 函數 | 弱符號鏈接。 | +| `@section("name")` | 函數 | 將代碼放置在特定段中。 | +| `@noreturn` | 函數 | 函數不會返回 (例如 exit)。 | +| `@pure` | 函數 | 函數無副作用 (優化提示)。 | +| `@cold` | 函數 | 函數不太可能被執行 (分支預測提示)。 | +| `@hot` | 函數 | 函數頻繁執行 (優化提示)。 | +| `@export` | 函數/結構體 | 導出符號 (默認可見性)。 | +| `@global` | 函數 | CUDA: 內核入口點 (`__global__`)。 | +| `@device` | 函數 | CUDA: 設備函數 (`__device__`)。 | +| `@host` | 函數 | CUDA: 主機函數 (`__host__`)。 | +| `@comptime` | 函數 | 用於編譯時執行的輔助函數。 | +| `@derive(...)` | 結構體 | 自動實現 Trait。支持 `Debug`, `Eq` (智能派生), `Copy`, `Clone`。 | +| `@ctype("type")` | 函數參數 | 覆蓋參數生成的 C 類型。 | +| `@<custom>` | 任意 | 將泛型屬性傳遞給 C (例如 `@flatten`, `@alias("name")`)。 | + +#### 自定義屬性 + +Zen C 支持強大的 **自定義屬性** 系統,允許你在代碼中直接使用任何 GCC/Clang 的 `__attribute__`。任何不被 Zen C 編譯器顯式識別的屬性都會被視為泛型屬性並傳遞給生成的 C 代碼。 + +這提供了對高級編譯器特性、優化和鏈接器指令的訪問,而無需在語言核心中提供顯式支持。 + +#### 語法映射 +Zen C 屬性直接映射到 C 屬性: +- `@name` → `__attribute__((name))` +- `@name(args)` → `__attribute__((name(args)))` +- `@name("string")` → `__attribute__((name("string")))` + +#### 智能派生 + +Zen C 提供了尊重移動語義的 "智能派生": + +- **`@derive(Eq)`**:生成一個通過引用獲取參數的相等性方法 (`fn eq(self, other: T*)`)。 + - 當比較兩個非 Copy 結構體 (`a == b`) 時,編譯器會自動通過引用傳遞 `b` (`&b`) 以避免移動它。 + - 字段上的遞歸相等性檢查也會優先使用指針訪問,以防止所有權轉移。 + +### 14. 內聯匯編 + +Zen C 為內聯匯編提供了一流支持,直接轉譯為 GCC 風格的擴展 `asm`。 + +#### 基本用法 +在 `asm` 塊內編寫原始匯編。字符串會自動拼接。 +```zc +asm { + "nop" + "mfence" +} +``` + +#### Volatile +防止編譯器優化掉具有副作用的匯編代碼。 +```zc +asm volatile { + "rdtsc" +} +``` + +#### 命名約束 +Zen C 通過命名綁定簡化了複雜的 GCC 約束語法。 + +```zc +// 語法: : out(變量) : in(變量) : clobber(寄存器) +// 使用 {變量} 佔位符語法以提高可讀性 + +fn add(a: int, b: int) -> int { + let result: int; + asm { + "add {result}, {a}, {b}" + : out(result) + : in(a), in(b) + : clobber("cc") + } + return result; +} +``` + +| 類型 | 語法 | GCC 等效項 | +|:---|:---|:---| +| **輸出** | `: out(variable)` | `"=r"(variable)` | +| **輸入** | `: in(variable)` | `"r"(variable)` | +| **破壞** | `: clobber("rax")` | `"rax"` | +| **內存** | `: clobber("memory")` | `"memory"` | + +> **注意:** 使用 Intel 語法時(通過 `-masm=intel`),必須確保你的構建配置正確(例如,`//> cflags: -masm=intel`)。TCC 不支持 Intel 語法的匯編。 + +### 15. 構建指令 + +Zen C 支持在源文件頂部使用特殊註釋來配置構建過程,無需複雜的構建系統或 Makefile。 + +| 指令 | 參數 | 描述 | +|:---|:---|:---| +| `//> link:` | `-lfoo` 或 `path/to/lib.a` | 鏈接庫或對象文件。 | +| `//> lib:` | `path/to/libs` | 添加庫搜索路徑 (`-L`)。 | +| `//> include:` | `path/to/headers` | 添加包含頭文件搜索路徑 (`-I`)。 | +| `//> framework:` | `Cocoa` | 鏈接 macOS Framework。 | +| `//> cflags:` | `-Wall -O3` | 向 C 編譯器傳遞任意標誌。 | +| `//> define:` | `MACRO` 或 `KEY=VAL` | 定義預處理器宏 (`-D`)。 | +| `//> pkg-config:` | `gtk+-3.0` | 運行 `pkg-config` 並追加 `--cflags` 和 `--libs`。 | +| `//> shell:` | `command` | 在構建期間執行 shell 命令。 | +| `//> get:` | `http://url/file` | 如果特定文件不存在,則下載該文件。 | + +#### 特性 + +**1. 操作系統守護 (OS Guarding)** +在指令前加上操作系統名稱,以使其僅在特定平台上應用。 +受支持的前綴:`linux:`, `windows:`, `macos:` (或 `darwin:`)。 + +```zc +//> linux: link: -lm +//> windows: link: -lws2_32 +//> macos: framework: Cocoa +``` + +**2. 環境變量展開** +使用 `${VAR}` 語法在指令中展開環境變量。 + +```zc +//> include: ${HOME}/mylib/include +//> lib: ${ZC_ROOT}/std +``` + +#### 示例 + +```zc +//> include: ./include +//> lib: ./libs +//> link: -lraylib -lm +//> cflags: -Ofast +//> pkg-config: gtk+-3.0 + +import "raylib.h" + +fn main() { ... } +``` + +### 16. 關鍵字 + +以下關鍵字在 Zen C 中是保留的。 + +#### 聲明 +`alias`, `def`, `enum`, `fn`, `impl`, `import`, `let`, `module`, `opaque`, `struct`, `trait`, `union`, `use` + +#### 控制流 +`async`, `await`, `break`, `catch`, `continue`, `defer`, `else`, `for`, `goto`, `guard`, `if`, `loop`, `match`, `return`, `try`, `unless`, `while` + +#### 特殊 +`asm`, `assert`, `autofree`, `comptime`, `const`, `embed`, `launch`, `ref`, `sizeof`, `static`, `test`, `volatile` + +#### 常量 +`true`, `false`, `null` + +#### C 保留字 +以下標識符是保留的,因為它們是 C11 中的關鍵字: +`auto`, `case`, `char`, `default`, `do`, `double`, `extern`, `float`, `inline`, `int`, `long`, `register`, `restrict`, `short`, `signed`, `switch`, `typedef`, `unsigned`, `void`, `_Atomic`, `_Bool`, `_Complex`, `_Generic`, `_Imaginary`, `_Noreturn`, `_Static_assert`, `_Thread_local` + +#### 運算符 +`and`, `or` + +--- + +## 標準庫 + +Zen C 包含一個涵蓋基本功能的標準庫 (`std`)。 + +[瀏覽標準庫文檔](docs/std/README.md) + +### 核心模塊 + +| 模塊 | 描述 | 文檔 | +| :--- | :--- | :--- | +| **`std/vec.zc`** | 可增長動態數組 `Vec<T>`。 | [文檔](docs/std/vec.md) | +| **`std/string.zc`** | 堆分配的 `String` 類型,支持 UTF-8。 | [文檔](docs/std/string.md) | +| **`std/queue.zc`** | 先進先出隊列 (環形緩衝區)。 | [文檔](docs/std/queue.md) | +| **`std/map.zc`** | 泛型哈希表 `Map<V>`。 | [文檔](docs/std/map.md) | +| **`std/fs.zc`** | 文件系統操作。 | [文檔](docs/std/fs.md) | +| **`std/io.zc`** | 標準輸入/輸出 (`print`/`println`)。 | [文檔](docs/std/io.md) | +| **`std/option.zc`** | 可選值 (`Some`/`None`)。 | [文檔](docs/std/option.md) | +| **`std/result.zc`** | 錯誤處理 (`Ok`/`Err`)。 | [文檔](docs/std/result.md) | +| **`std/path.zc`** | 跨平台路徑操作。 | [文檔](docs/std/path.md) | +| **`std/env.zc`** | 進程環境變量。 | [文檔](docs/std/env.md) | +| **`std/net.zc`** | TCP 網絡 (套接字)。 | [文檔](docs/std/net.md) | +| **`std/thread.zc`** | 線程與同步。 | [文檔](docs/std/thread.md) | +| **`std/time.zc`** | 時間測量與睡眠。 | [文檔](docs/std/time.md) | +| **`std/json.zc`** | JSON 解析與序列化。 | [文檔](docs/std/json.md) | +| **`std/stack.zc`** | 後進先出棧 `Stack<T>`。 | [文檔](docs/std/stack.md) | +| **`std/set.zc`** | 泛型哈希集合 `Set<T>`。 | [文檔](docs/std/set.md) | +| **`std/process.zc`** | 進程執行與管理。 | [文檔](docs/std/process.md) | + +--- + +## 工具鏈 + +Zen C 提供內置的語言服務器 (LSP) 和 REPL 以增強開發體驗。 + +### 語言服務器 (LSP) + +Zen C 語言服務器 (LSP) 支持標準的 LSP 特性,用於編輯器集成: + +* **轉到定義** +* **查找引用** +* **懸停信息** +* **補全** (函數/結構體名,方法/字段的點補全) +* **文檔符號** (大綱) +* **簽名幫助** +* **診斷** (語法/語義錯誤) + +啟動語言服務器(通常在編輯器的 LSP 設置中配置): + +```bash +zc lsp +``` + +它通過標準 I/O (JSON-RPC 2.0) 進行通信。 + +### REPL + +Read-Eval-Print Loop 允許你交互式地嘗試 Zen C 代碼。 + +```bash +zc repl +``` + +#### 特性 + +* **交互式編碼**:輸入表達式或語句以立即求值。 +* **持久歷史**:命令保存在 `~/.zprep_history` 中。 +* **啟動腳本**:自動加載 `~/.zprep_init.zc` 中的命令。 + +#### 命令 + +| 命令 | 描述 | +|:---|:---| +| `:help` | 顯示可用命令。 | +| `:reset` | 清除當前會話歷史 (變量/函數)。 | +| `:vars` | 顯示活躍變量。 | +| `:funcs` | 顯示用戶定義的函數。 | +| `:structs` | 顯示用戶定義的結構體。 | +| `:imports` | 顯示活躍導入。 | +| `:history` | 顯示會話輸入歷史。 | +| `:type <expr>` | 顯示表達式的類型。 | +| `:c <stmt>` | 顯示語句生成的 C 代碼。 | +| `:time <expr>` | 基准測試表達式 (運行 1000 次迭代)。 | +| `:edit [n]` | 在 `$EDITOR` 中編輯命令 `n` (默認:最後一條)。 | +| `:save <file>` | 將當前會話保存到 `.zc` 文件。 | +| `:load <file>` | 將 `.zc` 文件加載並執行到會話中。 | +| `:watch <expr>` | 監視表達式 (每次輸入後重新求值)。 | +| `:unwatch <n>` | 移除監視。 | +| `:undo` | 從會話中移除最後一條命令。 | +| `:delete <n>` | 移除索引為 `n` 的命令。 | +| `:clear` | 清屏。 | +| `:quit` | 退出 REPL。 | +| `! <cmd>` | 運行 shell 命令 (如 `!ls`)。 | + +--- + + +## 編譯器支持與兼容性 + +Zen C 旨在與大多數 C11 編譯器配合使用。某些特性依賴於 GNU C 擴展,但這些擴展通常在其他編譯器中也能工作。使用 `--cc` 標誌切換後端。 + +```bash +zc run app.zc --cc clang +zc run app.zc --cc zig +``` + +### 測試套件狀態 + +| 編譯器 | 通過率 | 受支持特性 | 已知局限性 | +|:---|:---:|:---|:---| +| **GCC** | **100%** | 所有特性 | 無。 | +| **Clang** | **100%** | 所有特性 | 無。 | +| **Zig** | **100%** | 所有特性 | 無。使用 `zig cc` 作為替代 C 編譯器。 | +| **TCC** | **~70%** | 基本語法, 泛型, Trait | 不支持 `__auto_type`, 不支持 Intel ASM, 不支持嵌套函數。 | + +> **建議:** 生產環境構建請使用 **GCC**, **Clang**, 或 **Zig**。TCC 非常適合快速原型開發,因為它編譯速度極快,但缺少 Zen C 全面支持所需的一些高級 C 擴展。 + +### 使用 Zig 構建 + +Zig 的 `zig cc` 命令提供了 GCC/Clang 的替代方案,具有出色的跨平台編譯支持。使用 Zig: + +```bash +# 使用 Zig 編譯並運行 Zen C 程序 +zc run app.zc --cc zig + +# 使用 Zig 構建 Zen C 編譯器本身 +make zig +``` + +### C++ 互操作 + +Zen C 可以通過 `--cpp` 標誌生成 C++ 兼容的代碼,從而實現與 C++ 庫的無縫集成。 + +```bash +# 直接使用 g++ 編譯 +zc app.zc --cpp + +# 或者轉譯用於手動構建 +zc transpile app.zc --cpp +g++ out.c my_cpp_lib.o -o app +``` + +#### 在 Zen C 中使用 C++ + +包含 C++ 頭文件並在 `raw` 塊中使用 C++ 代碼: + +```zc +include <vector> +include <iostream> + +raw { + std::vector<int> make_vec(int a, int b) { + return {a, b}; + } +} + +fn main() { + let v = make_vec(1, 2); + raw { std::cout << "Size: " << v.size() << std::endl; } +} +``` + +> **注意:** `--cpp` 標誌會將後端切換為 `g++` 並發出 C++ 兼容的代碼(使用 `auto` 代替 `__auto_type`,使用函數重載代替 `_Generic`,以及對 `void*` 進行顯式轉換)。 + +#### CUDA 互操作 + +Zen C 通過轉譯為 **CUDA C++** 來支持 GPU 編程。這使你在維持 Zen C 人體工程學語法的同時,能夠利用內核中的強大 C++ 特性(模板、constexpr)。 + +```bash +# 直接使用 nvcc 編譯 +zc run app.zc --cuda + +# 或者轉譯用於手動構建 +zc transpile app.zc --cuda -o app.cu +nvcc app.cu -o app +``` + +#### CUDA 特定屬性 + +| 屬性 | CUDA 等效項 | 描述 | +|:---|:---|:---| +| `@global` | `__global__` | 內核函數 (運行在 GPU,從主機調用) | +| `@device` | `__device__` | 設備函數 (運行在 GPU,從 GPU 調用) | +| `@host` | `__host__` | 主機函數 (明確僅 CPU 運行) | + +#### 內核啟動語法 + +Zen C 提供了一個簡潔的 `launch` 語句用於調用 CUDA 內核: + +```zc +launch kernel_name(args) with { + grid: num_blocks, + block: threads_per_block, + shared_mem: 1024, // 可選 + stream: my_stream // 可選 +}; +``` + +這轉譯為:`kernel_name<<<grid, block, shared, stream>>>(args);` + +#### 編寫 CUDA 內核 + +使用帶有 `@global` 的 Zen C 函數語法和 `launch` 語句: + +```zc +import "std/cuda.zc" + +@global +fn add_kernel(a: float*, b: float*, c: float*, n: int) { + let i = thread_id(); + if i < n { + c[i] = a[i] + b[i]; + } +} + +fn main() { + def N = 1024; + let d_a = cuda_alloc<float>(N); + let d_b = cuda_alloc<float>(N); + let d_c = cuda_alloc<float>(N); + defer cuda_free(d_a); + defer cuda_free(d_b); + defer cuda_free(d_c); + + // ... 初始化數據 ... + + launch add_kernel(d_a, d_b, d_c, N) with { + grid: (N + 255) / 256, + block: 256 + }; + + cuda_sync(); +} +``` + +#### 標準庫 (`std/cuda.zc`) +Zen C 為常見的 CUDA 操作提供了一個標準庫,以減少 `raw` 塊的使用: + +```zc +import "std/cuda.zc" + +// 內存管理 +let d_ptr = cuda_alloc<float>(1024); +cuda_copy_to_device(d_ptr, h_ptr, 1024 * sizeof(float)); +defer cuda_free(d_ptr); + +// 同步 +cuda_sync(); + +// 線程索引 (在內核內部使用) +let i = thread_id(); // 全局索引 +let bid = block_id(); +let tid = local_id(); +``` + + +> **注意:** `--cuda` 標誌設置 `nvcc` 為編譯器並隱含 `--cpp` 模式。需要安裝 NVIDIA CUDA Toolkit。 + +### C23 支援 + +當使用相容的後端編譯器(GCC 14+, Clang 14+)時,Zen C 支援現代 C23 特性。 + +- **`auto`**: 如果 `__STDC_VERSION__ >= 202300L`,Zen C 會自動將型別推導映射到標準 C23 `auto`。 +- **`_BitInt(N)`**: 使用 `iN` 和 `uN` 型別(例如 `i256`, `u12`, `i24`)存取 C23 任意位元寬度整數。 + +### Objective-C 互操作 + +Zen C 可以通過 `--objc` 標誌編譯為 Objective-C (`.m`),允許你使用 Objective-C 框架(如 Cocoa/Foundation)和語法。 + +```bash +# 使用 clang (或 gcc/gnustep) 編譯 +zc app.zc --objc --cc clang +``` + +#### 在 Zen C 中使用 Objective-C + +使用 `include` 包含頭文件,並在 `raw` 塊中使用 Objective-C 語法 (`@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(@"來自 Objective-C 的問候!"); + [pool drain]; + } + println "Zen C 也能正常工作!"; +} +``` + +> **注意:** Zen C 字符串插值通過調用 `debugDescription` 或 `description` 同樣適用於 Objective-C 對象 (`id`)。 + +--- + +## 貢獻 + +我們歡迎各類貢獻!無論是修復 Bug、完善文檔,還是提出新功能建議。 + +### 如何貢獻 +1. **Fork 倉庫**:標準的 GitHub 工作流程。 +2. **創建功能分支**:`git checkout -b feature/NewThing`。 +3. **代碼規範**: + * 遵循現有的 C 風格。 + * 確保所有測試通過:`make test`。 + * 在 `tests/` 中為你的功能添加新測試。 +4. **提交拉取請求**:清晰地描述你的更改。 + +### 運行測試 +測試套件是你最好的朋友。 + +```bash +# 運行所有測試 (GCC) +make test + +# 運行特定的測試 +./zc run tests/test_match.zc + +# 使用不同的編譯器運行 +./tests/run_tests.sh --cc clang +./tests/run_tests.sh --cc zig +./tests/run_tests.sh --cc tcc +``` + +### 擴展編譯器 +* **解析器 (Parser)**:`src/parser/` - 遞歸下降解析器。 +* **代碼生成 (Codegen)**:`src/codegen/` - 轉譯邏輯 (Zen C -> GNU C/C11)。 +* **標準庫 (Standard Library)**:`std/` - 使用 Zen C 本身編寫。 + +--- + +## 致謝與歸属 + +本項目使用了第三方庫。完整許可證文本可在 `LICENSES/` 目錄中找到。 + +* **[cJSON](https://github.com/DaveGamble/cJSON)** (MIT 許可證):用於語言服務器中的 JSON 解析和生成。 +* **[zc-ape](https://github.com/OEvgeny/zc-ape)** (MIT 許可證):由 [Eugene Olonov](https://github.com/OEvgeny) 開發的原版 Zen-C 實際上便攜的可執行文件 (APE) 端口。 +* **[Cosmopolitan Libc](https://github.com/jart/cosmopolitan)** (ISC 許可證):使 APE 成為可能納基礎庫。 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..3cbf8f8 100644 --- a/docs/std/README.md +++ b/docs/std/README.md @@ -3,10 +3,17 @@ - [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. +- [Process](./process.md) - Process execution and management. - [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..ce35d64 --- /dev/null +++ b/docs/std/json.md @@ -0,0 +1,83 @@ +# 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. + +#### Serialization + +- **`fn to_string(self) -> String`** + Serializes the JSON value to a string representation. + +- **`fn stringify(self, buf: String*)`** + Internal recursive serialization method that appends to a string buffer. + +**Example:** +```zc +let obj = JsonValue::object(); +obj.set("name", JsonValue::string("Alice")); +obj.set("age", JsonValue::number(30.0)); + +let json_str = obj.to_string(); +println "{json_str.c_str()}"; // {"name":"Alice","age":30} +json_str.free(); +obj.free(); +``` + +**Features:** +- Proper escaping of special characters: `\"`, `\\`, `\n`, `\t`, `\r`, `\b`, `\f` +- Numbers formatted with `%.15g` for precision +- Recursive serialization for nested objects and arrays +- Round-trip compatible with `parse()` + +#### 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/process.md b/docs/std/process.md new file mode 100644 index 0000000..31485ee --- /dev/null +++ b/docs/std/process.md @@ -0,0 +1,57 @@ +# Standard Library: Process (`std/process.zc`) + +The process module allows you to spawn and interact with child processes. + +## Usage + +```zc +import "std/process.zc" + +fn main() { + let output = Command::new("echo") + .arg("hello") + .output(); + + if (output.exit_code == 0) { + output.stdout.print(); + // Or access raw C string: output.stdout.c_str() + } +} +``` + +## Structs + +### Command + +A builder for spawning a process. + +```zc +struct Command { + program: String; + args: Vec<String>; +} +``` + +#### Methods + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **new** | `Command::new(program: char*) -> Command` | Creates a new Command for the given program. | +| **arg** | `arg(self, arg: char*) -> Command*` | Adds an argument to the command. Returns the command pointer for chaining. | +| **output** | `output(self) -> Output` | Executes the command as a child process, waiting for it to finish and collecting all of its stdout. | +| **status** | `status(self) -> int` | Executes the command as a child process and returns the exit status code. Does not capture output (output goes to stdout/stderr). | + +### Output + +The output of a finished process. + +```zc +struct Output { + stdout: String; + exit_code: int; +} +``` + +#### Methods + +`Output` implements `Drop` to automatically free the captured `stdout` string. 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/slice.md b/docs/std/slice.md new file mode 100644 index 0000000..f029995 --- /dev/null +++ b/docs/std/slice.md @@ -0,0 +1,93 @@ +# Standard Library: Slice (`std/slice.zc`) + +`Slice<T>` is a lightweight, non-owning view into a contiguous sequence of elements. It's particularly useful for working with fixed-size arrays and enabling iteration. + +## Usage + +```zc +import "std/slice.zc" + +fn main() { + let arr: int[5] = [1, 2, 3, 4, 5]; + + // Direct iteration (auto-imports std/slice.zc) + for val in arr { + println "{val}"; + } + + // Manual slice creation + let slice = Slice<int>::from_array((int*)(&arr), 5); + for val in slice { + println "{val}"; + } +} +``` + +## Structure + +```zc +struct Slice<T> { + data: T*; + len: usize; +} +``` + +## Methods + +### Construction + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **from_array** | `Slice<T>::from_array(arr: T*, len: usize) -> Slice<T>` | Creates a slice view over an array. | +| **new** | `Slice<T>::new(data: T*, len: usize) -> Slice<T>` | Alias for `from_array` (backwards compat). | + +### Iteration + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **iterator** | `iterator(self) -> SliceIter<T>` | Returns an iterator for `for-in` loops. | + +`SliceIter<T>` implements the iterator protocol with a `next() -> Option<T>` method. + +### Access & Query + +| Method | Signature | Description | +| :--- | :--- | :--- | +| **length** | `length(self) -> usize` | Returns the number of elements. | +| **is_empty** | `is_empty(self) -> bool` | Returns true if length is 0. | +| **get** | `get(self, idx: usize) -> Option<T>` | Returns the element at index, or None if out of bounds. | +| **at** | `at(self, idx: usize) -> Option<T>` | Alias for `get`. | + +## Examples + +### Iterating over fixed-size arrays + +```zc +// std/slice.zc is auto-imported when using for-in on arrays +let numbers: int[3] = [10, 20, 30]; + +for n in numbers { + println "Number: {n}"; +} +``` + +### Safe indexed access + +```zc +import "std/slice.zc" + +let arr: int[3] = [1, 2, 3]; +let slice = Slice<int>::from_array((int*)(&arr), 3); + +let opt = slice.get(1); +if (!opt.is_none()) { + println "Value: {opt.unwrap()}"; // Prints: Value: 2 +} +``` + +## Notes + +- `Slice<T>` does not own its data — it's just a view +- No memory management needed (no `free()` method) +- **Auto-import**: `std/slice.zc` is automatically imported when using `for val in arr` on a fixed-size array +- The array pointer cast `(T*)(&arr)` is required for fixed-size arrays 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/string.md b/docs/std/string.md index 1f89e0f..a2f63f5 100644 --- a/docs/std/string.md +++ b/docs/std/string.md @@ -49,8 +49,12 @@ struct String { | Method | Signature | Description | | :--- | :--- | :--- | | **append** | `append(self, other: String*)` | Appends another string to this one. | +| **append_c** | `append_c(self, s: char*)` | Appends a C string literal. Uses value receiver. | +| **append_c_ptr** | `append_c_ptr(ptr: String*, s: char*)` | Appends a C string literal using pointer receiver for guaranteed mutation. | | **add** | `add(self, other: String*) -> String` | Concatenates this string and another into a new String. | +**Note:** When passing `String*` to functions that need to mutate, use `append_c_ptr` instead of `append_c` for reliable mutation. + ### Access & Query | Method | Signature | Description | 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/data_structures/stack.zc b/examples/data_structures/stack.zc index 4a8593a..ea6a20a 100644 --- a/examples/data_structures/stack.zc +++ b/examples/data_structures/stack.zc @@ -1,11 +1,11 @@ import "std.zc" -struct Stack<T> { +struct MyStack<T> { data: Vec<T>; } -impl Stack<T> { +impl MyStack<T> { fn new() -> Self { return Self { data: Vec<T>::new() }; } @@ -43,7 +43,7 @@ impl Stack<T> { fn main() { "[Integer Stack]"; - let int_stack = Stack<int>::new(); + let int_stack = MyStack<int>::new(); defer int_stack.free(); "Pushing: 10, 20, 30"; @@ -64,7 +64,7 @@ fn main() { ""; "[String Stack]"; - let str_stack = Stack<String>::new(); + let str_stack = MyStack<String>::new(); defer str_stack.free(); str_stack.push(String::new("First")); diff --git a/examples/graphics/mandelbrot.zc b/examples/graphics/mandelbrot.zc index 04f61b9..3a0662e 100644 --- a/examples/graphics/mandelbrot.zc +++ b/examples/graphics/mandelbrot.zc @@ -5,35 +5,37 @@ struct Complex { im: float, } -fn complex_make(re: float, im: float) -> Complex { - return Complex { re: re, im: im }; -} +impl Complex { + fn new(re: float, im: float) -> Complex { + return Complex { re: re, im: im }; + } -fn complex_add(a: Complex, b: Complex) -> Complex { - return Complex { re: a.re + b.re, im: a.im + b.im }; -} + fn add(self, b: Complex) -> Complex { + return Complex { re: self.re + b.re, im: self.im + b.im }; + } -fn complex_mul(a: Complex, b: Complex) -> Complex { - return Complex { - re: a.re * b.re - a.im * b.im, - im: a.re * b.im + a.im * b.re - }; -} + fn mul(self, b: Complex) -> Complex { + return Complex { + re: self.re * b.re - self.im * b.im, + im: self.re * b.im + self.im * b.re + }; + } -fn complex_abs2(z: Complex) -> float { - return z.re * z.re + z.im * z.im; -} + fn abs2(self) -> float { + return self.re * self.re + self.im * self.im; + } -fn complex_print(z: Complex) { - println "{z.re}{z.im}i"; + fn print(self) { + println "{self.re}{self.im}i"; + } } fn pick_char(iter: int, max_iter: int, edge_chars: char[], edge_count: int) -> char { if (iter >= max_iter) { return ' '; } if (iter <= 0) { return edge_chars[0]; } - let t: float = ((float)iter) / ((float)max_iter); - let idx: int = (int)(t * ((float)(edge_count - 1))); + let t = (float)iter / (float)max_iter; + let idx = (int)(t * (float)(edge_count - 1)); if (idx < 0) { idx = 0; } if (idx >= edge_count) { idx = edge_count - 1; } @@ -42,32 +44,30 @@ fn pick_char(iter: int, max_iter: int, edge_chars: char[], edge_count: int) -> c } fn main() { - let width: int = 120; - let height: int = 40; - let max_iter: int = 200; + let width = 120; + let height = 40; + let max_iter = 200; - let edge_chars: char[12] = [ '#', '@', '%', '8', '&', '*', '+', '=', '-', ':', '.', ',' ]; - let edge_count: int = 12; + let edge_chars: char[] = [ '#', '@', '%', '8', '&', '*', '+', '=', '-', ':', '.', ',' ]; + let edge_count = 12; - let min_re: float = -2.2; - let max_re: float = 1.0; - let min_im: float = -1.2; - let max_im: float = 1.2; + let min_re = -2.2; + let max_re = 1.0; + let min_im = -1.2; + let max_im = 1.2; for y in 0..height { - let im: float = - max_im - (max_im - min_im) * ( ((float)y) / ((float)(height - 1)) ); + let im = max_im - (max_im - min_im) * ((float)y / (float)(height - 1)); for x in 0..width { - let re: float = - min_re + (max_re - min_re) * ( ((float)x) / ((float)(width - 1)) ); + let re = min_re + (max_re - min_re) * ((float)x / (float)(width - 1)); - let c: Complex = complex_make(re, im); - let z: Complex = complex_make(0.0, 0.0); + let c = Complex::new(re, im); + let z = Complex::new(0.0, 0.0); - let iter: int = 0; - while (iter < max_iter and complex_abs2(z) <= 4.0) { - z = complex_add(complex_mul(z, z), c); + let iter = 0; + while (iter < max_iter and z.abs2() <= 4.0) { + z = z.mul(z).add(c); iter += 1; } diff --git a/examples/networking/echo_server.zc b/examples/networking/echo_server.zc index 072c3a2..2934923 100644 --- a/examples/networking/echo_server.zc +++ b/examples/networking/echo_server.zc @@ -1,11 +1,13 @@ import "std/net.zc" +def SIZE = 1024; + fn main() { "Starting Echo Server on 127.0.0.1:8080..."; let listener_res = TcpListener::bind("127.0.0.1", 8080); - if listener_res.is_err() { + guard listener_res.is_ok() else { !"Failed to bind: {listener_res.err}"; return 1; } @@ -19,9 +21,9 @@ fn main() { let stream = client_res.unwrap(); defer stream.close(); - let buf: char[1024]; - - let read_res = stream.read(buf, 1024); + let buf = (char*)malloc(SIZE); + defer free(buf); + let read_res = stream.read(buf, SIZE); if read_res.is_ok() { let bytes = read_res.unwrap(); 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/examples/process/exec.zc b/examples/process/exec.zc new file mode 100644 index 0000000..712a356 --- /dev/null +++ b/examples/process/exec.zc @@ -0,0 +1,20 @@ + +import "std/process.zc"; +import "std/string.zc"; + +fn main() { + "Executing 'ls -la' using std/process..."; + + let output = Command::new("ls") + .arg("-l") + .arg("-a") + .output(); + + if output.exit_code == 0 { + "--- Output ---"; + output.stdout.print(); + "--------------"; + } else { + !"Command failed with exit code: {output.exit_code}"; + } +} diff --git a/examples/tools/mini_grep.zc b/examples/tools/mini_grep.zc index 39fec07..54338bf 100644 --- a/examples/tools/mini_grep.zc +++ b/examples/tools/mini_grep.zc @@ -30,7 +30,7 @@ fn str_find_case(haystack: string, needle: string, ignore_case: bool) -> Option< let n = needle; let n_len = strlen(needle); - while (*h != 0) + while (*h != '\0') { let is_match: bool = true; for i in 0..n_len @@ -52,7 +52,7 @@ fn str_find_case(haystack: string, needle: string, ignore_case: bool) -> Option< return Option<string>::None(); } -fn print_highlight(line: string, match_ptr: string, match_len: usize, config: GrepConfig) { +fn print_highlight(line: string, match_ptr: string, match_len: usize, config: GrepConfig*) { if (!config.color) { "{line}"; @@ -84,7 +84,7 @@ fn print_highlight(line: string, match_ptr: string, match_len: usize, config: Gr "{current}"; } -fn grep_file(path: string, config: GrepConfig) -> Result<int> { +fn grep_file(path: string, config: GrepConfig*) -> Result<int> { let content_str: String = File::read_all(path)?; defer content_str.destroy(); let content = content_str.c_str(); @@ -95,7 +95,7 @@ fn grep_file(path: string, config: GrepConfig) -> Result<int> { let q_len = strlen(config.query); - while (*ptr != 0) + while (*ptr != '\0') { if (*ptr == '\n') { @@ -163,7 +163,7 @@ fn grep_file(path: string, config: GrepConfig) -> Result<int> { return Result<int>::Ok(0); } -fn visit_dir(path: string, config: GrepConfig) { +fn visit_dir(path: string, config: GrepConfig*) { let entries_res = File::read_dir(path); guard entries_res.is_ok() else return; @@ -287,7 +287,7 @@ fn main(argc: int, argv: string*) { if (config.recursive) { - visit_dir(config.path, config); + visit_dir(config.path, &config); } else { @@ -296,7 +296,7 @@ fn main(argc: int, argv: string*) } else { - let res = grep_file(config.path, config); + let res = grep_file(config.path, &config); if (res.is_err()) { !"Error: {res.err}"; return 1; diff --git a/src/ast/ast.c b/src/ast/ast.c index 0799845..439a9f5 100644 --- a/src/ast/ast.c +++ b/src/ast/ast.c @@ -100,6 +100,7 @@ int is_integer_type(Type *t) t->kind == TYPE_I64 || t->kind == TYPE_U64 || t->kind == TYPE_USIZE || t->kind == TYPE_ISIZE || t->kind == TYPE_BYTE || t->kind == TYPE_RUNE || t->kind == TYPE_UINT || t->kind == TYPE_I128 || t->kind == TYPE_U128 || + t->kind == TYPE_BITINT || t->kind == TYPE_UBITINT || (t->kind == TYPE_STRUCT && t->name && (0 == strcmp(t->name, "int8_t") || 0 == strcmp(t->name, "uint8_t") || 0 == strcmp(t->name, "int16_t") || 0 == strcmp(t->name, "uint16_t") || @@ -168,6 +169,18 @@ int type_eq(Type *a, Type *b) { return 0 == strcmp(a->name, b->name); } + if (a->kind == TYPE_ALIAS) + { + if (a->alias.is_opaque_alias) + { + if (b->kind != TYPE_ALIAS || !b->alias.is_opaque_alias) + { + return 0; + } + return 0 == strcmp(a->name, b->name); + } + return type_eq(a->inner, b); + } if (a->kind == TYPE_POINTER || a->kind == TYPE_ARRAY) { return type_eq(a->inner, b->inner); @@ -250,6 +263,18 @@ static char *type_to_string_impl(Type *t) return xstrdup("int"); case TYPE_FLOAT: return xstrdup("float"); + case TYPE_BITINT: + { + char *res = xmalloc(32); + sprintf(res, "i%d", t->array_size); + return res; + } + case TYPE_UBITINT: + { + char *res = xmalloc(32); + sprintf(res, "u%d", t->array_size); + return res; + } case TYPE_POINTER: { @@ -340,6 +365,8 @@ static char *type_to_string_impl(Type *t) } return xstrdup(t->name); } + case TYPE_ALIAS: + return xstrdup(t->name); default: return xstrdup("unknown"); @@ -438,6 +465,18 @@ static char *type_to_c_string_impl(Type *t) return xstrdup("int"); case TYPE_FLOAT: return xstrdup("float"); + case TYPE_BITINT: + { + char *res = xmalloc(32); + sprintf(res, "_BitInt(%d)", t->array_size); + return res; + } + case TYPE_UBITINT: + { + char *res = xmalloc(40); + sprintf(res, "unsigned _BitInt(%d)", t->array_size); + return res; + } case TYPE_POINTER: { @@ -524,6 +563,9 @@ static char *type_to_c_string_impl(Type *t) case TYPE_GENERIC: return xstrdup(t->name); + case TYPE_ALIAS: + return type_to_c_string(t->inner); + case TYPE_ENUM: return xstrdup(t->name); diff --git a/src/ast/ast.h b/src/ast/ast.h index b272cae..4498d7c 100644 --- a/src/ast/ast.h +++ b/src/ast/ast.h @@ -58,6 +58,9 @@ typedef enum TYPE_ARRAY, ///< Fixed size array [N]. TYPE_FUNCTION, ///< Function pointer or reference. TYPE_GENERIC, ///< Generic type parameter (T). + TYPE_ALIAS, ///< Opaque type alias. + TYPE_BITINT, ///< C23 _BitInt(N). + TYPE_UBITINT, ///< C23 unsigned _BitInt(N). TYPE_UNKNOWN ///< Unknown/unresolved type. } TypeKind; @@ -74,7 +77,7 @@ typedef struct Type int is_const; ///< 1 if const-qualified. int is_explicit_struct; ///< 1 if defined with "struct" keyword explicitly. int is_raw; // Raw function pointer (fn*) - int array_size; ///< Size for fixed-size arrays. + int array_size; ///< Size for fixed-size arrays. For TYPE_BITINT, this is the bit width. union { int is_varargs; ///< 1 if function type is variadic. @@ -84,6 +87,11 @@ typedef struct Type int has_drop; ///< 1 if type implements Drop trait (RAII). int has_iterable; ///< 1 if type implements Iterable trait. } traits; + struct + { + int is_opaque_alias; + char *alias_defined_in_file; + } alias; }; } Type; @@ -221,6 +229,8 @@ struct ASTNode int cuda_device; // @device -> __device__ int cuda_host; // @host -> __host__ + char **c_type_overrides; // @ctype("...") per parameter + Attribute *attributes; // Custom attributes } func; @@ -263,6 +273,8 @@ struct ASTNode { char *alias; char *original_type; + int is_opaque; + char *defined_in_file; } type_alias; struct @@ -436,6 +448,8 @@ struct ASTNode Attribute *attributes; // Custom attributes char **used_structs; // Names of structs used/mixed-in int used_struct_count; + int is_opaque; + char *defined_in_file; // File where the struct is defined (for privacy check) } strct; struct diff --git a/src/codegen/codegen.c b/src/codegen/codegen.c index 7c58943..37415c2 100644 --- a/src/codegen/codegen.c +++ b/src/codegen/codegen.c @@ -1,4 +1,3 @@ - #include "codegen.h" #include "zprep.h" #include "../constants.h" @@ -59,12 +58,58 @@ static void codegen_var_expr(ParserContext *ctx, ASTNode *node, FILE *out) if (node->var_ref.suggestion && !ctx->silent_warnings) { char msg[256]; - sprintf(msg, "Undefined variable '%s'", node->var_ref.name); char help[256]; - sprintf(help, "Did you mean '%s'?", node->var_ref.suggestion); + snprintf(msg, sizeof(msg), "Undefined variable '%s'", node->var_ref.name); + snprintf(help, sizeof(help), "Did you mean '%s'?", node->var_ref.suggestion); zwarn_at(node->token, "%s\n = help: %s", msg, help); } } + + // Check for static method call pattern: Type::method or Slice<T>::method + char *double_colon = strstr(node->var_ref.name, "::"); + if (double_colon) + { + // Extract type name and method name + int type_len = double_colon - node->var_ref.name; + char *type_name = xmalloc(type_len + 1); + strncpy(type_name, node->var_ref.name, type_len); + type_name[type_len] = 0; + + char *method_name = double_colon + 2; // Skip :: + + // Handle generic types: Slice<int> -> Slice_int + char mangled_type[256]; + if (strchr(type_name, '<')) + { + // Generic type - need to mangle it + char *lt = strchr(type_name, '<'); + char *gt = strchr(type_name, '>'); + + if (lt && gt) + { + // Extract base type and type argument + *lt = 0; + char *type_arg = lt + 1; + *gt = 0; + + sprintf(mangled_type, "%s_%s", type_name, type_arg); + } + else + { + strcpy(mangled_type, type_name); + } + } + else + { + strcpy(mangled_type, type_name); + } + + // Output as Type__method + fprintf(out, "%s__%s", mangled_type, method_name); + free(type_name); + return; + } + fprintf(out, "%s", node->var_ref.name); } @@ -146,7 +191,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else if ((strcmp(node->binary.op, "==") == 0 || strcmp(node->binary.op, "!=") == 0)) { char *t1 = infer_type(ctx, node->binary.left); - int is_ptr = 0; if (t1) { @@ -161,19 +205,16 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } int resolved = 0; ASTNode *alias = global_user_structs; - if (alias) + while (alias) { - while (alias) + if (alias->type == NODE_TYPE_ALIAS && + strcmp(check, alias->type_alias.alias) == 0) { - if (alias->type == NODE_TYPE_ALIAS && - strcmp(check, alias->type_alias.alias) == 0) - { - check = alias->type_alias.original_type; - resolved = 1; - break; - } - alias = alias->next; + check = alias->type_alias.original_type; + resolved = 1; + break; } + alias = alias->next; } if (!resolved) { @@ -183,10 +224,9 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } int is_basic = IS_BASIC_TYPE(t1); - ASTNode *def = t1 ? find_struct_def(ctx, t1) : NULL; - if (t1 && def && (def->type == NODE_STRUCT || def->type == NODE_ENUM) && !is_basic && - !is_ptr) + + if (t1 && def && (def->type == NODE_STRUCT || def->type == NODE_ENUM) && !is_basic && !is_ptr) { char *base = t1; if (strncmp(base, "struct ", 7) == 0) @@ -239,7 +279,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else if (t1 && (strcmp(t1, "string") == 0 || strcmp(t1, "char*") == 0 || strcmp(t1, "const char*") == 0)) { - // Check if comparing to NULL - don't use strcmp for NULL comparisons int is_null_compare = 0; if (node->binary.right->type == NODE_EXPR_VAR && strcmp(node->binary.right->var_ref.name, "NULL") == 0) @@ -251,10 +290,21 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { is_null_compare = 1; } + else if (node->binary.right->type == NODE_EXPR_LITERAL && + node->binary.right->literal.type_kind == LITERAL_INT && + node->binary.right->literal.int_val == 0) + { + is_null_compare = 1; + } + else if (node->binary.left->type == NODE_EXPR_LITERAL && + node->binary.left->literal.type_kind == LITERAL_INT && + node->binary.left->literal.int_val == 0) + { + is_null_compare = 1; + } if (is_null_compare) { - // Direct pointer comparison for NULL fprintf(out, "("); codegen_expression(ctx, node->binary.left, out); fprintf(out, " %s ", node->binary.op); @@ -285,6 +335,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) codegen_expression(ctx, node->binary.right, out); fprintf(out, ")"); } + if (t1) free(t1); } else { @@ -348,7 +399,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) if (def && def->type == NODE_ENUM) { char mangled[256]; - sprintf(mangled, "%s_%s", target->var_ref.name, method); + snprintf(mangled, sizeof(mangled), "%s_%s", target->var_ref.name, method); FuncSig *sig = find_func(ctx, mangled); if (sig) { @@ -357,15 +408,13 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) int arg_idx = 0; while (arg) { - if (arg_idx > 0 && arg) + if (arg_idx > 0) { fprintf(out, ", "); } - Type *param_t = - (arg_idx < sig->total_args) ? sig->arg_types[arg_idx] : NULL; + Type *param_t = (arg_idx < sig->total_args) ? sig->arg_types[arg_idx] : NULL; - // Tuple Packing Logic if (param_t && param_t->kind == TYPE_STRUCT && strncmp(param_t->name, "Tuple_", 6) == 0 && sig->total_args == 1 && node->call.arg_count > 1) @@ -383,7 +432,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) arg = arg->next; } fprintf(out, "}"); - break; // All args consumed + break; } codegen_expression(ctx, arg, out); @@ -403,7 +452,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) char *ptr = strchr(clean, '*'); if (ptr) { - *ptr = 0; + *ptr = '\0'; } char *base = clean; @@ -412,11 +461,43 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) base += 7; } + char *mangled_base = base; + char base_buf[256]; + + // Mangle generic types: Slice<int> -> Slice_int, Vec<Point> -> Vec_Point + char *lt = strchr(base, '<'); + if (lt) + { + char *gt = strchr(lt, '>'); + if (gt) + { + int prefix_len = lt - base; + int arg_len = gt - lt - 1; + snprintf(base_buf, 255, "%.*s_%.*s", prefix_len, base, arg_len, lt + 1); + mangled_base = base_buf; + } + } + if (!strchr(type, '*') && target->type == NODE_EXPR_CALL) { - fprintf(out, "({ %s _t = ", type); + char *type_mangled = type; + char type_buf[256]; + char *t_lt = strchr(type, '<'); + if (t_lt) + { + char *t_gt = strchr(t_lt, '>'); + if (t_gt) + { + int p_len = t_lt - type; + int a_len = t_gt - t_lt - 1; + snprintf(type_buf, 255, "%.*s_%.*s", p_len, type, a_len, t_lt + 1); + type_mangled = type_buf; + } + } + + fprintf(out, "({ %s _t = ", type_mangled); codegen_expression(ctx, target, out); - fprintf(out, "; %s__%s(&_t", base, method); + fprintf(out, "; %s__%s(&_t", mangled_base, method); ASTNode *arg = node->call.args; while (arg) { @@ -429,36 +510,34 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else { // Mixin Lookup Logic - char *call_base = base; + char *call_base = mangled_base; + int need_cast = 0; char mixin_func_name[128]; - sprintf(mixin_func_name, "%s__%s", base, method); + snprintf(mixin_func_name, sizeof(mixin_func_name), "%s__%s", call_base, method); char *resolved_method_suffix = NULL; if (!find_func(ctx, mixin_func_name)) { - // Try resolving as a trait method: Struct__Trait_Method StructRef *ref = ctx->parsed_impls_list; while (ref) { - if (ref->node && ref->node->type == NODE_IMPL_TRAIT) + if (ref->node && ref->node->type == NODE_IMPL_TRAIT && + strcmp(ref->node->impl_trait.target_type, base) == 0) { - if (strcmp(ref->node->impl_trait.target_type, base) == 0) + char trait_mangled[256]; + snprintf(trait_mangled, sizeof(trait_mangled), "%s__%s_%s", base, + ref->node->impl_trait.trait_name, method); + if (find_func(ctx, trait_mangled)) { - char trait_mangled[256]; - sprintf(trait_mangled, "%s__%s_%s", base, - ref->node->impl_trait.trait_name, method); - if (find_func(ctx, trait_mangled)) - { - char *suffix = - xmalloc(strlen(ref->node->impl_trait.trait_name) + - strlen(method) + 2); - sprintf(suffix, "%s_%s", ref->node->impl_trait.trait_name, - method); - resolved_method_suffix = suffix; - break; - } + size_t suffix_len = strlen(ref->node->impl_trait.trait_name) + + strlen(method) + 2; + char *suffix = xmalloc(suffix_len); + snprintf(suffix, suffix_len, "%s_%s", + ref->node->impl_trait.trait_name, method); + resolved_method_suffix = suffix; + break; } } ref = ref->next; @@ -473,15 +552,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) if (it->impl_node && it->impl_node->type == NODE_IMPL_TRAIT) { tname = it->impl_node->impl_trait.trait_name; - } - if (tname) - { char trait_mangled[512]; - sprintf(trait_mangled, "%s__%s_%s", base, tname, method); + snprintf(trait_mangled, sizeof(trait_mangled), + "%s__%s_%s", base, tname, method); if (find_func(ctx, trait_mangled)) { - char *suffix = xmalloc(strlen(tname) + strlen(method) + 2); - sprintf(suffix, "%s_%s", tname, method); + size_t suffix_len = strlen(tname) + strlen(method) + 2; + char *suffix = xmalloc(suffix_len); + snprintf(suffix, suffix_len, "%s_%s", tname, method); resolved_method_suffix = suffix; break; } @@ -496,15 +574,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } else { - // Method not found on primary struct, check mixins ASTNode *def = find_struct_def(ctx, base); if (def && def->type == NODE_STRUCT && def->strct.used_structs) { for (int k = 0; k < def->strct.used_struct_count; k++) { char mixin_check[128]; - sprintf(mixin_check, "%s__%s", def->strct.used_structs[k], - method); + snprintf(mixin_check, sizeof(mixin_check), "%s__%s", + def->strct.used_structs[k], method); if (find_func(ctx, mixin_check)) { call_base = def->strct.used_structs[k]; @@ -534,11 +611,19 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) arg = arg->next; } fprintf(out, ")"); + + if (resolved_method_suffix) + { + free(resolved_method_suffix); + } } free(clean); + free(type); return; } + if (type) free(type); } + if (node->call.callee->type == NODE_EXPR_VAR) { ASTNode *def = find_struct_def(ctx, node->call.callee->var_ref.name); @@ -594,26 +679,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) if (node->call.arg_names && node->call.callee->type == NODE_EXPR_VAR) { - char *fn_name = node->call.callee->var_ref.name; - FuncSig *sig = find_func(ctx, fn_name); - - if (sig && sig->arg_types) - { - for (int p = 0; p < sig->total_args; p++) - { - ASTNode *arg = node->call.args; - - for (int i = 0; i < node->call.arg_count && arg; i++, arg = arg->next) - { - if (node->call.arg_names[i] && p < node->call.arg_count) - { - - // For now, emit in order provided... - } - } - } - } - ASTNode *arg = node->call.args; int first = 1; while (arg) @@ -660,11 +725,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) strncmp(param_t->name, "Tuple_", 6) == 0 && sig->total_args == 1 && node->call.arg_count > 1) { - // Implicit Tuple Packing: - // Function expects 1 Tuple argument, but call has multiple args -> Pack - // them fprintf(out, "(%s){", param_t->name); - ASTNode *curr = arg; int first_field = 1; while (curr) @@ -679,8 +740,6 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } fprintf(out, "}"); handled = 1; - - // Advance main loop iterator to end arg = NULL; } } @@ -689,7 +748,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { if (arg == NULL) { - break; // Tuple packed all args + break; } } else @@ -714,16 +773,12 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) case NODE_EXPR_MEMBER: if (strcmp(node->member.field, "len") == 0) { - if (node->member.target->type_info) + if (node->member.target->type_info && + node->member.target->type_info->kind == TYPE_ARRAY && + node->member.target->type_info->array_size > 0) { - if (node->member.target->type_info->kind == TYPE_ARRAY) - { - if (node->member.target->type_info->array_size > 0) - { - fprintf(out, "%d", node->member.target->type_info->array_size); - break; - } - } + fprintf(out, "%d", node->member.target->type_info->array_size); + break; } } @@ -746,17 +801,14 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) else { codegen_expression(ctx, node->member.target, out); - // Verify actual type instead of trusting is_pointer_access flag char *lt = infer_type(ctx, node->member.target); int actually_ptr = 0; if (lt && (lt[strlen(lt) - 1] == '*' || strstr(lt, "*"))) { actually_ptr = 1; } - if (lt) - { - free(lt); - } + if (lt) free(lt); + char *field = node->member.field; if (field && field[0] >= '0' && field[0] <= '9') { @@ -779,7 +831,8 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) is_slice_struct = 1; } } - if (node->index.array->resolved_type) + + if (!is_slice_struct && node->index.array->resolved_type) { if (strncmp(node->index.array->resolved_type, "Slice_", 6) == 0) { @@ -794,10 +847,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { is_slice_struct = 1; } - if (inferred) - { - free(inferred); - } + if (inferred) free(inferred); } if (is_slice_struct) @@ -896,7 +946,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, "/* UNSAFE: Full Slice on unknown size */ 0; "); + fprintf(out, "0; "); } } @@ -912,6 +962,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) fprintf(out, "(Slice_%s){ .data = _arr + _start, .len = _len, .cap = _len }; })", tname); } + if (tname && strcmp(tname, "unknown") != 0) free(tname); break; } case NODE_BLOCK: @@ -1002,9 +1053,7 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) break; case NODE_PLUGIN: { - // Plugin registry - declare external plugins ZPlugin *found = zptr_find_plugin(node->plugin_stmt.plugin_name); - if (found) { ZApi api = {.filename = g_current_filename ? g_current_filename : "input.zc", @@ -1084,9 +1133,9 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) break; } case NODE_EXPR_CAST: - fprintf(out, "(%s)(", node->cast.target_type); + fprintf(out, "((%s)(", node->cast.target_type); codegen_expression(ctx, node->cast.expr, out); - fprintf(out, ")"); + fprintf(out, "))"); break; case NODE_EXPR_SIZEOF: if (node->size_of.target_type) @@ -1117,20 +1166,19 @@ void codegen_expression(ParserContext *ctx, ASTNode *node, FILE *out) { Type *t = node->reflection.target_type; if (node->reflection.kind == 0) - { // @type_name + { char *s = codegen_type_to_string(t); fprintf(out, "\"%s\"", s); free(s); } else - { // @fields + { if (t->kind != TYPE_STRUCT || !t->name) { fprintf(out, "((void*)0)"); break; } char *sname = t->name; - // Find definition ASTNode *def = find_struct_def(ctx, sname); if (!def) { diff --git a/src/codegen/codegen.h b/src/codegen/codegen.h index b3e971d..89614f7 100644 --- a/src/codegen/codegen.h +++ b/src/codegen/codegen.h @@ -48,7 +48,7 @@ char *replace_string_type(const char *args); const char *parse_original_method_name(const char *mangled); void emit_auto_type(ParserContext *ctx, ASTNode *init_expr, Token t, FILE *out); char *codegen_type_to_string(Type *t); -void emit_func_signature(FILE *out, ASTNode *func, const char *name_override); +void emit_func_signature(ParserContext *ctx, FILE *out, ASTNode *func, const char *name_override); char *strip_template_suffix(const char *name); int emit_move_invalidation(ParserContext *ctx, ASTNode *node, FILE *out); void codegen_expression_with_move(ParserContext *ctx, ASTNode *node, FILE *out); @@ -66,7 +66,7 @@ void emit_trait_defs(ASTNode *node, FILE *out); void emit_enum_protos(ASTNode *node, FILE *out); void emit_globals(ParserContext *ctx, ASTNode *node, FILE *out); void emit_lambda_defs(ParserContext *ctx, FILE *out); -void emit_protos(ASTNode *node, FILE *out); +void emit_protos(ParserContext *ctx, ASTNode *node, FILE *out); void emit_impl_vtables(ParserContext *ctx, FILE *out); /** diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c index 5fb9f54..31bd2ee 100644 --- a/src/codegen/codegen_decl.c +++ b/src/codegen/codegen_decl.c @@ -3,6 +3,7 @@ #include "../parser/parser.h" #include "../zprep.h" #include "codegen.h" +#include "compat.h" #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -12,7 +13,7 @@ static void emit_freestanding_preamble(FILE *out) fputs("#include <stddef.h>\n#include <stdint.h>\n#include " "<stdbool.h>\n#include <stdarg.h>\n", out); - fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out); + fputs(ZC_TCC_COMPAT_STR, out); fputs("typedef size_t usize;\ntypedef char* string;\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", @@ -49,6 +50,7 @@ void emit_preamble(ParserContext *ctx, FILE *out) else { // Standard hosted preamble. + fputs("#define _GNU_SOURCE\n", out); fputs("#include <stdio.h>\n#include <stdlib.h>\n#include " "<stddef.h>\n#include <string.h>\n", out); @@ -84,20 +86,18 @@ void emit_preamble(ParserContext *ctx, FILE *out) else { // C mode + fputs("#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202300L\n", out); + fputs("#define ZC_AUTO auto\n", out); + fputs("#else\n", out); fputs("#define ZC_AUTO __auto_type\n", out); + fputs("#endif\n", out); fputs("#define ZC_CAST(T, x) ((T)(x))\n", out); - fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out); + fputs(ZC_TCC_COMPAT_STR, 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); @@ -107,12 +107,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); @@ -700,7 +699,7 @@ void emit_globals(ParserContext *ctx, ASTNode *node, FILE *out) } // Emit function prototypes -void emit_protos(ASTNode *node, FILE *out) +void emit_protos(ParserContext *ctx, ASTNode *node, FILE *out) { ASTNode *f = node; while (f) @@ -723,7 +722,7 @@ void emit_protos(ASTNode *node, FILE *out) } else { - emit_func_signature(out, f, NULL); + emit_func_signature(ctx, out, f, NULL); fprintf(out, ";\n"); } } @@ -801,7 +800,7 @@ void emit_protos(ASTNode *node, FILE *out) } else { - emit_func_signature(out, m, proto); + emit_func_signature(ctx, out, m, proto); fprintf(out, ";\n"); } diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c index a140070..82fc3ce 100644 --- a/src/codegen/codegen_main.c +++ b/src/codegen/codegen_main.c @@ -448,6 +448,39 @@ void codegen_node(ParserContext *ctx, ASTNode *node, FILE *out) emit_type_aliases(kids, out); // Emit local aliases (redundant but safe) emit_trait_defs(kids, out); + // Also emit traits from parsed_globals_list (from auto-imported files like std/mem.zc) + // but only if they weren't already emitted from kids + StructRef *trait_ref = ctx->parsed_globals_list; + while (trait_ref) + { + if (trait_ref->node && trait_ref->node->type == NODE_TRAIT) + { + // Check if this trait was already in kids (explicitly imported) + int already_in_kids = 0; + ASTNode *k = kids; + while (k) + { + if (k->type == NODE_TRAIT && k->trait.name && trait_ref->node->trait.name && + strcmp(k->trait.name, trait_ref->node->trait.name) == 0) + { + already_in_kids = 1; + break; + } + k = k->next; + } + + if (!already_in_kids) + { + // Create a temporary single-node list for emit_trait_defs + ASTNode *saved_next = trait_ref->node->next; + trait_ref->node->next = NULL; + emit_trait_defs(trait_ref->node, out); + trait_ref->node->next = saved_next; + } + } + trait_ref = trait_ref->next; + } + // Track emitted raw statements to prevent duplicates EmittedContent *emitted_raw = NULL; @@ -616,7 +649,7 @@ void codegen_node(ParserContext *ctx, ASTNode *node, FILE *out) } } - emit_protos(merged_funcs, out); + emit_protos(ctx, merged_funcs, out); emit_impl_vtables(ctx, out); diff --git a/src/codegen/codegen_stmt.c b/src/codegen/codegen_stmt.c index bd0c816..7828ecf 100644 --- a/src/codegen/codegen_stmt.c +++ b/src/codegen/codegen_stmt.c @@ -750,7 +750,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) { fprintf(out, "inline "); } - emit_func_signature(out, node, NULL); + emit_func_signature(ctx, out, node, NULL); fprintf(out, "\n"); fprintf(out, "{\n"); char *prev_ret = g_current_func_ret_type; @@ -884,8 +884,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " __auto_type %s = _tmp_%d.%s;\n", node->destruct.names[0], id, - check); + fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[0], id, check); } } else @@ -903,8 +902,8 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " __auto_type %s = _tmp_%d.%s;\n", node->destruct.names[i], - id, field); + fprintf(out, " ZC_AUTO %s = _tmp_%d.%s;\n", node->destruct.names[i], id, + field); } } else @@ -916,8 +915,8 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, " __auto_type %s = _tmp_%d.v%d;\n", node->destruct.names[i], - id, i); + fprintf(out, " ZC_AUTO %s = _tmp_%d.v%d;\n", node->destruct.names[i], id, + i); } } } @@ -1528,7 +1527,7 @@ void codegen_node_single(ParserContext *ctx, ASTNode *node, FILE *out) } else { - fprintf(out, "__auto_type"); + fprintf(out, "ZC_AUTO"); } fprintf(out, " _z_ret_mv = "); codegen_expression(ctx, node->ret.value, out); diff --git a/src/codegen/codegen_utils.c b/src/codegen/codegen_utils.c index 8de3cf6..08707cc 100644 --- a/src/codegen/codegen_utils.c +++ b/src/codegen/codegen_utils.c @@ -41,7 +41,7 @@ char *strip_template_suffix(const char *name) } // Helper to emit C declaration (handle arrays, function pointers correctly) -void emit_c_decl(FILE *out, const char *type_str, const char *name) +void emit_c_decl(ParserContext *ctx, FILE *out, const char *type_str, const char *name) { char *bracket = strchr(type_str, '['); char *generic = strchr(type_str, '<'); @@ -64,9 +64,34 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) } else if (generic && (!bracket || generic < bracket)) { - // Strip generic part for C output - int base_len = generic - type_str; - fprintf(out, "%.*s %s", base_len, type_str, name); + char mangled_candidate[256]; + char *gt = strchr(generic, '>'); + int success = 0; + + if (gt) + { + int base_len = generic - type_str; + int arg_len = gt - generic - 1; + + // Limit check + if (base_len + arg_len + 2 < 256) + { + snprintf(mangled_candidate, 256, "%.*s_%.*s", base_len, type_str, arg_len, + generic + 1); + + if (find_struct_def_codegen(ctx, mangled_candidate)) + { + fprintf(out, "%s %s", mangled_candidate, name); + success = 1; + } + } + } + + if (!success) + { + int base_len = generic - type_str; + fprintf(out, "%.*s %s", base_len, type_str, name); + } if (bracket) { @@ -87,8 +112,7 @@ void emit_c_decl(FILE *out, const char *type_str, const char *name) // Helper to emit variable declarations with array types. void emit_var_decl_type(ParserContext *ctx, FILE *out, const char *type_str, const char *var_name) { - (void)ctx; - emit_c_decl(out, type_str, var_name); + emit_c_decl(ctx, out, type_str, var_name); } // Find struct definition @@ -411,6 +435,10 @@ char *infer_type(ParserContext *ctx, ASTNode *node) char *inner = infer_type(ctx, node->unary.operand); if (inner) { + if (strcmp(inner, "string") == 0) + { + return xstrdup("char"); + } char *ptr = strchr(inner, '*'); if (ptr) { @@ -640,7 +668,7 @@ char *codegen_type_to_string(Type *t) } // Emit function signature using Type info for correct C codegen -void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) +void emit_func_signature(ParserContext *ctx, FILE *out, ASTNode *func, const char *name_override) { if (!func || func->type != NODE_FUNCTION) { @@ -710,7 +738,12 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } char *type_str = NULL; - if (func->func.arg_types && func->func.arg_types[i]) + // Check for @ctype override first + if (func->func.c_type_overrides && func->func.c_type_overrides[i]) + { + type_str = xstrdup(func->func.c_type_overrides[i]); + } + else if (func->func.arg_types && func->func.arg_types[i]) { type_str = codegen_type_to_string(func->func.arg_types[i]); } @@ -720,13 +753,14 @@ void emit_func_signature(FILE *out, ASTNode *func, const char *name_override) } const char *name = ""; + if (func->func.param_names && func->func.param_names[i]) { name = func->func.param_names[i]; } // check if array type - emit_c_decl(out, type_str, name); + emit_c_decl(ctx, out, type_str, name); free(type_str); } if (func->func.is_varargs) diff --git a/src/codegen/compat.h b/src/codegen/compat.h index f2d221a..f8d9a4e 100644 --- a/src/codegen/compat.h +++ b/src/codegen/compat.h @@ -14,7 +14,11 @@ #define ZC_EXTERN_C_END } #else /* C mode */ -#define ZC_AUTO __auto_type ///< Auto type inference. +#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202300L +#define ZC_AUTO auto ///< C23 standard auto. +#else +#define ZC_AUTO __auto_type ///< GCC/Clang extension. +#endif #define ZC_CAST(T, x) ((T)(x)) ///< Explicit cast. #define ZC_REINTERPRET(T, x) ((T)(x)) ///< Reinterpret cast. #define ZC_EXTERN_C ///< Extern "C" (no-op in C). @@ -22,6 +26,58 @@ #define ZC_EXTERN_C_END #endif +#ifdef __TINYC__ +/* TCC compatibility */ +#ifndef __auto_type +#define __auto_type __typeof__ +#endif + +#ifndef __builtin_expect +#define __builtin_expect(x, v) (x) +#endif + +#ifndef __builtin_unreachable +#define __builtin_unreachable() +#endif +#endif + +/* Centralized string definition for codegen emission */ +#define ZC_TCC_COMPAT_STR \ + "#ifdef __TINYC__\n" \ + "#ifndef __auto_type\n" \ + "#define __auto_type __typeof__\n" \ + "#endif\n" \ + "\n" \ + "#ifndef __builtin_expect\n" \ + "#define __builtin_expect(x, v) (x)\n" \ + "#endif\n" \ + "\n" \ + "#ifndef __builtin_unreachable\n" \ + "#define __builtin_unreachable()\n" \ + "#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> @@ -95,6 +151,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/lexer/token.c b/src/lexer/token.c index decabbe..095815d 100644 --- a/src/lexer/token.c +++ b/src/lexer/token.c @@ -149,6 +149,14 @@ Token lexer_next(Lexer *l) { return (Token){TOK_DEF, s, 3, start_line, start_col}; } + if (len == 5 && strncmp(s, "trait", 5) == 0) + { + return (Token){TOK_TRAIT, s, 5, start_line, start_col}; + } + if (len == 4 && strncmp(s, "impl", 4) == 0) + { + return (Token){TOK_IMPL, s, 4, start_line, start_col}; + } if (len == 8 && strncmp(s, "autofree", 8) == 0) { return (Token){TOK_AUTOFREE, s, 8, start_line, start_col}; @@ -193,6 +201,10 @@ Token lexer_next(Lexer *l) { return (Token){TOK_OR, s, 2, start_line, start_col}; } + if (len == 6 && strncmp(s, "opaque", 6) == 0) + { + return (Token){TOK_OPAQUE, s, 6, start_line, start_col}; + } // F-Strings if (len == 1 && s[0] == 'f' && s[1] == '"') diff --git a/src/lsp/lsp_analysis.c b/src/lsp/lsp_analysis.c index 088bede..0367d93 100644 --- a/src/lsp/lsp_analysis.c +++ b/src/lsp/lsp_analysis.c @@ -454,11 +454,80 @@ void lsp_completion(const char *uri, int line, int col, int id) cJSON_AddStringToObject(item, "label", s->name); cJSON_AddNumberToObject(item, "kind", 22); // Struct char detail[256]; - sprintf(detail, "struct %s", s->name); + sprintf(detail, "%sstruct %s", + (s->node && s->node->type == NODE_STRUCT && s->node->strct.is_opaque) + ? "opaque " + : "", + s->name); cJSON_AddStringToObject(item, "detail", detail); cJSON_AddItemToArray(items, item); s = s->next; } + + // Globals and Constants + StructRef *g = g_project->ctx->parsed_globals_list; + while (g) + { + if (g->node) + { + cJSON *item = cJSON_CreateObject(); + char *name = + (g->node->type == NODE_CONST) ? g->node->var_decl.name : g->node->var_decl.name; + cJSON_AddStringToObject(item, "label", name); + cJSON_AddNumberToObject(item, "kind", 21); // Constant/Variable + char detail[256]; + sprintf(detail, "%s %s", (g->node->type == NODE_CONST) ? "const" : "var", name); + cJSON_AddStringToObject(item, "detail", detail); + cJSON_AddItemToArray(items, item); + } + g = g->next; + } + + // Enums + StructRef *e = g_project->ctx->parsed_enums_list; + while (e) + { + if (e->node) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "label", e->node->enm.name); + cJSON_AddNumberToObject(item, "kind", 13); // Enum + char detail[256]; + sprintf(detail, "enum %s", e->node->enm.name); + cJSON_AddStringToObject(item, "detail", detail); + cJSON_AddItemToArray(items, item); + } + e = e->next; + } + + // Type Aliases + TypeAlias *ta = g_project->ctx->type_aliases; + while (ta) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "label", ta->alias); + cJSON_AddNumberToObject(item, "kind", 8); // Interface/Reference + char detail[256]; + sprintf(detail, "alias %s = %s", ta->alias, ta->original_type); + cJSON_AddStringToObject(item, "detail", detail); + cJSON_AddItemToArray(items, item); + ta = ta->next; + } + + // Keywords + const char *keywords[] = { + "fn", "struct", "enum", "alias", "return", "if", "else", "for", "while", + "break", "continue", "true", "false", "int", "char", "bool", "string", "void", + "import", "module", "test", "assert", "defer", "sizeof", "opaque", "unsafe", "asm", + "trait", "impl", "u8", "u16", "u32", "u64", "i8", "i16", "i32", + "i64", "f32", "f64", "usize", "isize", "const", "var", NULL}; + for (int i = 0; keywords[i]; i++) + { + cJSON *item = cJSON_CreateObject(); + cJSON_AddStringToObject(item, "label", keywords[i]); + cJSON_AddNumberToObject(item, "kind", 14); // Keyword + cJSON_AddItemToArray(items, item); + } } cJSON_AddItemToObject(root, "result", items); diff --git a/src/lsp/lsp_index.c b/src/lsp/lsp_index.c index 285dec3..975e153 100644 --- a/src/lsp/lsp_index.c +++ b/src/lsp/lsp_index.c @@ -151,7 +151,32 @@ void lsp_walk_node(LSPIndex *idx, ASTNode *node) else if (node->type == NODE_STRUCT) { char hover[256]; - sprintf(hover, "struct %s", node->strct.name); + if (node->strct.is_opaque) + { + sprintf(hover, "opaque struct %s", node->strct.name); + } + else + { + sprintf(hover, "struct %s", node->strct.name); + } + lsp_index_add_def(idx, node->token, hover, node); + } + else if (node->type == NODE_ENUM) + { + char hover[256]; + sprintf(hover, "enum %s", node->enm.name); + lsp_index_add_def(idx, node->token, hover, node); + } + else if (node->type == NODE_TYPE_ALIAS) + { + char hover[256]; + sprintf(hover, "alias %s = %s", node->type_alias.alias, node->type_alias.original_type); + lsp_index_add_def(idx, node->token, hover, node); + } + else if (node->type == NODE_TRAIT) + { + char hover[256]; + sprintf(hover, "trait %s", node->trait.name); lsp_index_add_def(idx, node->token, hover, node); } @@ -196,6 +221,31 @@ void lsp_walk_node(LSPIndex *idx, ASTNode *node) lsp_walk_node(idx, node->call.callee); lsp_walk_node(idx, node->call.args); break; + case NODE_MATCH: + lsp_walk_node(idx, node->match_stmt.expr); + lsp_walk_node(idx, node->match_stmt.cases); + break; + case NODE_MATCH_CASE: + lsp_walk_node(idx, node->match_case.guard); + lsp_walk_node(idx, node->match_case.body); + break; + case NODE_FOR: + lsp_walk_node(idx, node->for_stmt.init); + lsp_walk_node(idx, node->for_stmt.condition); + lsp_walk_node(idx, node->for_stmt.step); + lsp_walk_node(idx, node->for_stmt.body); + break; + case NODE_FOR_RANGE: + lsp_walk_node(idx, node->for_range.start); + lsp_walk_node(idx, node->for_range.end); + lsp_walk_node(idx, node->for_range.body); + break; + case NODE_LOOP: + lsp_walk_node(idx, node->loop_stmt.body); + break; + case NODE_DEFER: + lsp_walk_node(idx, node->defer_stmt.stmt); + break; default: break; } @@ -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"); @@ -362,8 +370,9 @@ int main(int argc, char **argv) // If using cosmocc, it handles these usually, but keeping them is okay for Linux targets snprintf(cmd, sizeof(cmd), "%s %s %s %s %s -o %s %s %s %s -I./src %s", g_config.cc, - g_config.gcc_flags, g_cflags, g_config.is_freestanding ? "-ffreestanding" : "", "", - outfile, temp_source_file, math_flag, thread_flag, g_link_flags); + g_config.gcc_flags, g_cflags, g_config.is_freestanding ? "-ffreestanding" : "", + g_config.quiet ? "-w" : "", outfile, temp_source_file, math_flag, thread_flag, + g_link_flags); if (g_config.verbose) { diff --git a/src/parser/parser.h b/src/parser/parser.h index cf57971..23c2920 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -261,6 +261,8 @@ typedef struct TypeAlias char *alias; ///< New type name. char *original_type; ///< Original type. struct TypeAlias *next; + int is_opaque; + char *defined_in_file; } TypeAlias; /** @@ -514,12 +516,9 @@ char *sanitize_mangled_name(const char *name); /** * @brief Registers a type alias. */ -void register_type_alias(ParserContext *ctx, const char *alias, const char *original); - -/** - * @brief Finds a type alias. - */ -const char *find_type_alias(ParserContext *ctx, const char *alias); +TypeAlias *find_type_alias_node(ParserContext *ctx, const char *name); +void register_type_alias(ParserContext *ctx, const char *alias, const char *original, int is_opaque, + const char *defined_in_file); /** * @brief Registers an implementation. @@ -562,7 +561,8 @@ ASTNode *parse_arrow_lambda_multi(ParserContext *ctx, Lexer *l, char **param_nam * @brief Parses and converts arguments. */ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out, - Type ***types_out, char ***names_out, int *is_varargs_out); + Type ***types_out, char ***names_out, int *is_varargs_out, + char ***ctype_overrides_out); /** * @brief Checks if a file has been imported. @@ -681,10 +681,6 @@ void register_selective_import(ParserContext *ctx, const char *symbol, const cha SelectiveImport *find_selective_import(ParserContext *ctx, const char *name); // Type Aliases -/** - * @brief Registers a type alias. - */ -void register_type_alias(ParserContext *ctx, const char *alias, const char *original); /** * @brief Finds a type alias. @@ -741,6 +737,11 @@ FuncSig *find_func(ParserContext *ctx, const char *name); Type *parse_type_formal(ParserContext *ctx, Lexer *l); /** + * @brief Checks compatibility of opaque aliases (allows access within defining file). + */ +int check_opaque_alias_compat(ParserContext *ctx, Type *a, Type *b); + +/** * @brief Parses a type. */ char *parse_type(ParserContext *ctx, Lexer *l); @@ -889,7 +890,7 @@ ASTNode *parse_def(ParserContext *ctx, Lexer *l); /** * @brief Parses a type alias. */ -ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l); +ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l, int is_opaque); /** * @brief Parses a function definition. @@ -899,7 +900,7 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async); /** * @brief Parses a struct definition. */ -ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union); +ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union, int is_opaque); /** * @brief Parses an enum definition. @@ -963,4 +964,4 @@ char *patch_self_args(const char *args, const char *struct_name); */ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l); -#endif // PARSER_H +#endif // PARSER_H
\ No newline at end of file diff --git a/src/parser/parser_core.c b/src/parser/parser_core.c index e9a418e..43137b1 100644 --- a/src/parser/parser_core.c +++ b/src/parser/parser_core.c @@ -338,7 +338,7 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) } else if (0 == strncmp(t.start, "struct", 6) && 6 == t.len) { - s = parse_struct(ctx, l, 0); + s = parse_struct(ctx, l, 0, 0); if (s && s->type == NODE_STRUCT) { s->strct.is_packed = attr_packed; @@ -436,7 +436,7 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) } else if (0 == strncmp(t.start, "type", 4) && 4 == t.len) { - s = parse_type_alias(ctx, l); + s = parse_type_alias(ctx, l, 0); } else if (0 == strncmp(t.start, "raw", 3) && 3 == t.len) { @@ -482,9 +482,31 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) lexer_next(l); } } + else if (t.type == TOK_OPAQUE) + { + lexer_next(l); // eat opaque + Token next = lexer_peek(l); + if (0 == strncmp(next.start, "struct", 6) && 6 == next.len) + { + s = parse_struct(ctx, l, 0, 1); + if (s && s->type == NODE_STRUCT) + { + s->strct.is_packed = attr_packed; + s->strct.align = attr_align; + } + } + else if (next.type == TOK_ALIAS) + { + s = parse_type_alias(ctx, l, 1); + } + else + { + zpanic_at(next, "Expected 'struct' or 'alias' after 'opaque'"); + } + } else if (t.type == TOK_ALIAS) { - s = parse_type_alias(ctx, l); + s = parse_type_alias(ctx, l, 0); } else if (t.type == TOK_ASYNC) { @@ -506,7 +528,7 @@ ASTNode *parse_program_nodes(ParserContext *ctx, Lexer *l) else if (t.type == TOK_UNION) { - s = parse_struct(ctx, l, 1); + s = parse_struct(ctx, l, 1, 0); } else if (t.type == TOK_TRAIT) { @@ -700,7 +722,7 @@ static ASTNode *generate_derive_impls(ParserContext *ctx, ASTNode *strct, char * { // Simplistic Debug for now, I know. code = xmalloc(1024); - sprintf(code, "impl %s { fn to_string(self) -> char* { return \"%s { ... }\"; } }", + sprintf(code, "impl %s { fn to_string(self) -> char* { return \"%s {{ ... }}\"; } }", name, name); } else if (0 == strcmp(trait, "Copy")) diff --git a/src/parser/parser_decl.c b/src/parser/parser_decl.c index 98f46e1..93a124d 100644 --- a/src/parser/parser_decl.c +++ b/src/parser/parser_decl.c @@ -99,10 +99,11 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) int count; Type **arg_types; char **param_names; + char **ctype_overrides; int is_varargs = 0; - char *args = - parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, ¶m_names, &is_varargs); + char *args = parse_and_convert_args(ctx, l, &defaults, &count, &arg_types, ¶m_names, + &is_varargs, &ctype_overrides); char *ret = "void"; Type *ret_type_obj = type_new(TYPE_VOID); @@ -191,6 +192,7 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async) node->func.defaults = defaults; node->func.ret_type_info = ret_type_obj; node->func.is_varargs = is_varargs; + node->func.c_type_overrides = ctype_overrides; if (gen_param) { @@ -579,6 +581,10 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) { type_obj->inner = init->type_info->inner; // Shallow copy for inner } + if (init->type_info->kind == TYPE_ALIAS) + { + type_obj->alias = init->type_info->alias; + } // Copy function type args for lambda/closure support if (init->type_info->args && init->type_info->arg_count > 0) { @@ -631,6 +637,59 @@ ASTNode *parse_var_decl(ParserContext *ctx, Lexer *l) // Register in symbol table with actual token add_symbol_with_token(ctx, name, type, type_obj, name_tok); + if (init && type_obj) + { + Type *t = init->type_info; + if (!t && init->type == NODE_EXPR_VAR) + { + t = find_symbol_type_info(ctx, init->var_ref.name); + } + + // Literal type construction for validation + Type *temp_literal_type = NULL; + if (!t && init->type == NODE_EXPR_LITERAL) + { + if (init->literal.type_kind == LITERAL_INT) + { + temp_literal_type = type_new(TYPE_INT); + } + else if (init->literal.type_kind == LITERAL_FLOAT) + { + temp_literal_type = type_new(TYPE_FLOAT); + } + else if (init->literal.type_kind == LITERAL_STRING) + { + temp_literal_type = type_new(TYPE_STRING); + } + else if (init->literal.type_kind == LITERAL_CHAR) + { + temp_literal_type = type_new(TYPE_CHAR); + } + t = temp_literal_type; + } + + // Special case for literals: if implicit conversion works + if (t && !type_eq(type_obj, t)) + { + // Allow integer compatibility if types are roughly ints (lax check in type_eq handles + // most, but let's be safe) + if (!check_opaque_alias_compat(ctx, type_obj, t)) + { + char *expected = type_to_string(type_obj); + char *got = type_to_string(t); + zpanic_at(init->token, "Type validation failed. Expected '%s', but got '%s'", + expected, got); + free(expected); + free(got); + } + } + + if (temp_literal_type) + { + free(temp_literal_type); // Simple free, shallow + } + } + // NEW: Capture Const Integer Values if (init && init->type == NODE_EXPR_LITERAL && init->literal.type_kind == LITERAL_INT) { @@ -839,7 +898,7 @@ ASTNode *parse_def(ParserContext *ctx, Lexer *l) return o; } -ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l) +ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l, int is_opaque) { lexer_next(l); // consume 'type' or 'alias' Token n = lexer_next(l); @@ -859,8 +918,11 @@ ASTNode *parse_type_alias(ParserContext *ctx, Lexer *l) strncpy(node->type_alias.alias, n.start, n.len); node->type_alias.alias[n.len] = 0; node->type_alias.original_type = o; + node->type_alias.is_opaque = is_opaque; + node->type_alias.defined_in_file = g_current_filename ? xstrdup(g_current_filename) : NULL; - register_type_alias(ctx, node->type_alias.alias, o); + register_type_alias(ctx, node->type_alias.alias, o, is_opaque, + node->type_alias.defined_in_file); return node; } diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c index df2dcf6..7c53d96 100644 --- a/src/parser/parser_expr.c +++ b/src/parser/parser_expr.c @@ -1,3 +1,43 @@ +#include "../codegen/codegen.h" + +int check_opaque_alias_compat(ParserContext *ctx, Type *a, Type *b) +{ + if (!a || !b) + { + return 0; + } + + int a_is_opaque = (a->kind == TYPE_ALIAS && a->alias.is_opaque_alias); + int b_is_opaque = (b->kind == TYPE_ALIAS && b->alias.is_opaque_alias); + + if (!a_is_opaque && !b_is_opaque) + { + return 1; + } + + if (a_is_opaque) + { + if (a->alias.alias_defined_in_file && g_current_filename && + strcmp(a->alias.alias_defined_in_file, g_current_filename) == 0) + { + return check_opaque_alias_compat(ctx, a->inner, b); + } + return 0; + } + + if (b_is_opaque) + { + if (b->alias.alias_defined_in_file && g_current_filename && + strcmp(b->alias.alias_defined_in_file, g_current_filename) == 0) + { + return check_opaque_alias_compat(ctx, a, b->inner); + } + return 0; + } + + return 0; +} + #include "../zen/zen_facts.h" #include "parser.h" #include <ctype.h> @@ -108,6 +148,8 @@ int is_type_copy(ParserContext *ctx, Type *t) case TYPE_POINTER: // Pointers are Copy case TYPE_FUNCTION: case TYPE_ENUM: // Enums are integers + case TYPE_BITINT: + case TYPE_UBITINT: return 1; case TYPE_STRUCT: @@ -116,6 +158,14 @@ int is_type_copy(ParserContext *ctx, Type *t) { return 1; } + + // If the struct is NOT defined (opaque/C type) and does NOT implement Drop, + // treat it as Copy (C behavior). + if (!find_struct_def(ctx, t->name) && !check_impl(ctx, "Drop", t->name)) + { + return 1; + } + return 0; case TYPE_ARRAY: @@ -124,6 +174,13 @@ int is_type_copy(ParserContext *ctx, Type *t) // but if it's a value assignment, C doesn't support it anyway unless wrapped in struct. return 0; + case TYPE_ALIAS: + if (t->alias.is_opaque_alias) + { + return 1; + } + return is_type_copy(ctx, t->inner); + default: return 1; } @@ -935,6 +992,17 @@ static ASTNode *create_fstring_block(ParserContext *ctx, const char *content) free(txt); } + // Handle escape {{ + if (brace[1] == '{') + { + ASTNode *cat = ast_create(NODE_RAW_STMT); + cat->raw_stmt.content = xstrdup("strcat(_b, \"{\");"); + tail->next = cat; + tail = cat; + cur = brace + 2; + continue; + } + char *end_brace = strchr(brace, '}'); if (!end_brace) { @@ -1072,6 +1140,7 @@ static ASTNode *create_fstring_block(ParserContext *ctx, const char *content) static ASTNode *parse_int_literal(Token t) { ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_INT; node->type_info = type_new(TYPE_INT); char *s = token_strdup(t); @@ -1093,6 +1162,7 @@ static ASTNode *parse_int_literal(Token t) static ASTNode *parse_float_literal(Token t) { ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_FLOAT; node->literal.float_val = atof(t.start); node->type_info = type_new(TYPE_F64); @@ -1100,9 +1170,32 @@ static ASTNode *parse_float_literal(Token t) } // Parse string literal -static ASTNode *parse_string_literal(Token t) +static ASTNode *parse_string_literal(ParserContext *ctx, Token t) { + // Check for implicit interpolation + int has_interpolation = 0; + for (int i = 1; i < t.len - 1; i++) + { + if (t.start[i] == '{') + { + has_interpolation = 1; + break; + } + } + + if (has_interpolation) + { + + char *inner = xmalloc(t.len); + strncpy(inner, t.start + 1, t.len - 2); + inner[t.len - 2] = 0; + ASTNode *node = create_fstring_block(ctx, inner); + free(inner); + return node; + } + ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_STRING; node->literal.string_val = xmalloc(t.len); strncpy(node->literal.string_val, t.start + 1, t.len - 2); @@ -1126,6 +1219,7 @@ static ASTNode *parse_fstring_literal(ParserContext *ctx, Token t) static ASTNode *parse_char_literal(Token t) { ASTNode *node = ast_create(NODE_EXPR_LITERAL); + node->token = t; node->literal.type_kind = LITERAL_CHAR; node->literal.string_val = token_strdup(t); node->type_info = type_new(TYPE_I8); @@ -1276,7 +1370,7 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) } else if (t.type == TOK_STRING) { - node = parse_string_literal(t); + node = parse_string_literal(ctx, t); } else if (t.type == TOK_FSTRING) { @@ -2018,6 +2112,20 @@ ASTNode *parse_primary(ParserContext *ctx, Lexer *l) sprintf(prefixed, "%s_%s", ctx->current_module_prefix, acc); struct_name = prefixed; } + + // Opaque Struct Check + ASTNode *def = find_struct_def(ctx, struct_name); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(lexer_peek(l), + "Cannot initialize opaque struct '%s' outside its module", + struct_name); + } + } lexer_next(l); node = ast_create(NODE_EXPR_STRUCT_INIT); node->struct_init.struct_name = struct_name; @@ -3592,7 +3700,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } else if (t->kind == TYPE_STRING) { - strcat(fmt, "%s"); + strcat(fmt, "%ms"); } else if (t->kind == TYPE_CHAR || t->kind == TYPE_I8 || t->kind == TYPE_U8 || t->kind == TYPE_BYTE) @@ -4012,6 +4120,30 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) node->member.field = token_strdup(field); node->member.is_pointer_access = 1; + // Opaque Check + int is_ptr_dummy = 0; + char *alloc_name = NULL; + char *sname = + resolve_struct_name_from_type(ctx, lhs->type_info, &is_ptr_dummy, &alloc_name); + if (sname) + { + ASTNode *def = find_struct_def(ctx, sname); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(field, "Cannot access private field '%s' of opaque struct '%s'", + node->member.field, sname); + } + } + if (alloc_name) + { + free(alloc_name); + } + } + node->type_info = get_field_type(ctx, lhs->type_info, node->member.field); if (node->type_info) { @@ -4041,6 +4173,30 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) node->member.field = token_strdup(field); node->member.is_pointer_access = 2; + // Opaque Check + int is_ptr_dummy = 0; + char *alloc_name = NULL; + char *sname = + resolve_struct_name_from_type(ctx, lhs->type_info, &is_ptr_dummy, &alloc_name); + if (sname) + { + ASTNode *def = find_struct_def(ctx, sname); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(field, "Cannot access private field '%s' of opaque struct '%s'", + node->member.field, sname); + } + } + if (alloc_name) + { + free(alloc_name); + } + } + node->type_info = get_field_type(ctx, lhs->type_info, node->member.field); if (node->type_info) { @@ -4620,6 +4776,30 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) node->member.field = token_strdup(field); node->member.is_pointer_access = 0; + // Opaque Check + int is_ptr_dummy = 0; + char *alloc_name = NULL; + char *sname = + resolve_struct_name_from_type(ctx, lhs->type_info, &is_ptr_dummy, &alloc_name); + if (sname) + { + ASTNode *def = find_struct_def(ctx, sname); + if (def && def->type == NODE_STRUCT && def->strct.is_opaque) + { + if (!def->strct.defined_in_file || + (g_current_filename && + strcmp(def->strct.defined_in_file, g_current_filename) != 0)) + { + zpanic_at(field, "Cannot access private field '%s' of opaque struct '%s'", + node->member.field, sname); + } + } + if (alloc_name) + { + free(alloc_name); + } + } + node->member.field = token_strdup(field); node->member.is_pointer_access = 0; @@ -5241,6 +5421,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) // This gives a warning as "unused" but it's needed for the rewrite. char *r_name = resolve_struct_name_from_type(ctx, rhs->type_info, &is_rhs_ptr, &r_alloc); + (void)r_name; if (r_alloc) { free(r_alloc); @@ -5463,7 +5644,17 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) char *t1 = type_to_string(lhs->type_info); char *t2 = type_to_string(rhs->type_info); // Skip type check if either operand is void* (escape hatch type) + // or if either operand is a generic type parameter (T, K, V, etc.) int skip_check = (strcmp(t1, "void*") == 0 || strcmp(t2, "void*") == 0); + if (lhs->type_info->kind == TYPE_GENERIC || rhs->type_info->kind == TYPE_GENERIC) + { + skip_check = 1; + } + // Also check if type name is a single uppercase letter (common generic param) + if ((strlen(t1) == 1 && isupper(t1[0])) || (strlen(t2) == 1 && isupper(t2[0]))) + { + skip_check = 1; + } // Allow comparing pointers/strings with integer literal 0 (NULL) if (!skip_check) @@ -5507,7 +5698,8 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec) } else { - if (type_eq(lhs->type_info, rhs->type_info)) + if (type_eq(lhs->type_info, rhs->type_info) || + check_opaque_alias_compat(ctx, lhs->type_info, rhs->type_info)) { bin->type_info = lhs->type_info; } diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c index ab5b89c..0677cf5 100644 --- a/src/parser/parser_stmt.c +++ b/src/parser/parser_stmt.c @@ -14,6 +14,118 @@ char *curr_func_ret = NULL; char *run_comptime_block(ParserContext *ctx, Lexer *l); +extern char *g_current_filename; + +/** + * @brief Auto-imports std/slice.zc if not already imported. + * + * This is called when array iteration is detected in for-in loops, + * to ensure the Slice<T>, SliceIter<T>, and Option<T> templates are available. + */ +static void auto_import_std_slice(ParserContext *ctx) +{ + // Check if already imported via templates + GenericTemplate *t = ctx->templates; + while (t) + { + if (strcmp(t->name, "Slice") == 0) + { + return; // Already have the Slice template + } + t = t->next; + } + + // Try to find and import std/slice.zc + static const char *std_paths[] = {"std/slice.zc", "./std/slice.zc", NULL}; + static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL}; + + char resolved_path[1024]; + int found = 0; + + // First, try relative to current file + if (g_current_filename) + { + char *current_dir = xstrdup(g_current_filename); + char *last_slash = strrchr(current_dir, '/'); + if (last_slash) + { + *last_slash = 0; + snprintf(resolved_path, sizeof(resolved_path), "%s/std/slice.zc", current_dir); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + free(current_dir); + } + + // Try relative paths + if (!found) + { + for (int i = 0; std_paths[i] && !found; i++) + { + if (access(std_paths[i], R_OK) == 0) + { + strncpy(resolved_path, std_paths[i], sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + found = 1; + } + } + } + + // Try system paths + if (!found) + { + for (int i = 0; system_paths[i] && !found; i++) + { + snprintf(resolved_path, sizeof(resolved_path), "%s/std/slice.zc", system_paths[i]); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + } + + if (!found) + { + return; // Could not find std/slice.zc, instantiate_generic will error + } + + // Canonicalize path + char *real_fn = realpath(resolved_path, NULL); + if (real_fn) + { + strncpy(resolved_path, real_fn, sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + free(real_fn); + } + + // Check if already imported + if (is_file_imported(ctx, resolved_path)) + { + return; + } + mark_file_imported(ctx, resolved_path); + + // Load and parse the file + char *src = load_file(resolved_path); + if (!src) + { + return; // Could not load file + } + + Lexer i; + lexer_init(&i, src); + + // Save and restore filename context + char *saved_fn = g_current_filename; + g_current_filename = resolved_path; + + // Parse the slice module contents + parse_program_nodes(ctx, &i); + + g_current_filename = saved_fn; +} static void check_assignment_condition(ASTNode *cond) { @@ -462,13 +574,7 @@ ASTNode *parse_defer(ParserContext *ctx, Lexer *l) } else { - s = ast_create(NODE_RAW_STMT); - char *raw_content = consume_and_rewrite(ctx, l); - // consume_and_rewrite strips the semicolon, so we must add it back for proper C generation - char *safe_content = xmalloc(strlen(raw_content) + 2); - sprintf(safe_content, "%s;", raw_content); - free(raw_content); - s->raw_stmt.content = safe_content; + s = parse_statement(ctx, l); } ctx->in_defer_block = prev_in_defer; @@ -1139,6 +1245,7 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *obj_expr = start_expr; char *iter_method = "iterator"; + ASTNode *slice_decl = NULL; // Track if we need to add a slice declaration // Check for reference iteration: for x in &vec if (obj_expr->type == NODE_EXPR_UNARY && obj_expr->unary.op && @@ -1148,6 +1255,80 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) iter_method = "iter_ref"; } + // Check for array iteration: wrap with Slice<T>::from_array + if (obj_expr->type_info && obj_expr->type_info->kind == TYPE_ARRAY && + obj_expr->type_info->array_size > 0) + { + // Create a var decl for the slice + slice_decl = ast_create(NODE_VAR_DECL); + slice_decl->var_decl.name = xstrdup("__zc_arr_slice"); + + // Build type string: Slice<elem_type> + char *elem_type_str = type_to_string(obj_expr->type_info->inner); + char slice_type[256]; + sprintf(slice_type, "Slice<%s>", elem_type_str); + slice_decl->var_decl.type_str = xstrdup(slice_type); + + ASTNode *from_array_call = ast_create(NODE_EXPR_CALL); + ASTNode *static_method = ast_create(NODE_EXPR_VAR); + + // The function name for static methods is Type::method format + char func_name[512]; + snprintf(func_name, 511, "%s::from_array", slice_type); + static_method->var_ref.name = xstrdup(func_name); + + from_array_call->call.callee = static_method; + + // Create arguments + ASTNode *arr_addr = ast_create(NODE_EXPR_UNARY); + arr_addr->unary.op = xstrdup("&"); + arr_addr->unary.operand = obj_expr; + + ASTNode *arr_cast = ast_create(NODE_EXPR_CAST); + char cast_type[256]; + sprintf(cast_type, "%s*", elem_type_str); + arr_cast->cast.target_type = xstrdup(cast_type); + arr_cast->cast.expr = arr_addr; + + ASTNode *size_arg = ast_create(NODE_EXPR_LITERAL); + size_arg->literal.type_kind = LITERAL_INT; + size_arg->literal.int_val = obj_expr->type_info->array_size; + char size_buf[32]; + sprintf(size_buf, "%d", obj_expr->type_info->array_size); + size_arg->literal.string_val = xstrdup(size_buf); + + arr_cast->next = size_arg; + from_array_call->call.args = arr_cast; + from_array_call->call.arg_count = 2; + + slice_decl->var_decl.init_expr = from_array_call; + + // Manually trigger generic instantiation for Slice<T> + // This ensures that Slice_int, Slice_float, etc. structures are generated + // First, ensure std/slice.zc is imported (auto-import if needed) + auto_import_std_slice(ctx); + Token dummy_tok = {0}; + instantiate_generic(ctx, "Slice", elem_type_str, elem_type_str, dummy_tok); + + // Instantiate SliceIter and Option too for the loop logic + char iter_type[256]; + sprintf(iter_type, "SliceIter<%s>", elem_type_str); + instantiate_generic(ctx, "SliceIter", elem_type_str, elem_type_str, dummy_tok); + + char option_type[256]; + sprintf(option_type, "Option<%s>", elem_type_str); + instantiate_generic(ctx, "Option", elem_type_str, elem_type_str, dummy_tok); + + // Replace obj_expr with a reference to the slice variable + ASTNode *slice_ref = ast_create(NODE_EXPR_VAR); + slice_ref->var_ref.name = xstrdup("__zc_arr_slice"); + slice_ref->resolved_type = + xstrdup(slice_type); // Explicitly set type for codegen + obj_expr = slice_ref; + + free(elem_type_str); + } + // var __it = obj.iterator(); ASTNode *it_decl = ast_create(NODE_VAR_DECL); it_decl->var_decl.name = xstrdup("__it"); @@ -1188,6 +1369,34 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) stmts_tail = node; \ } + char *iter_type_ptr = NULL; + char *option_type_ptr = NULL; + + if (slice_decl) + { + char *slice_t = slice_decl->var_decl.type_str; + char *start = strchr(slice_t, '<'); + if (start) + { + char *end = strrchr(slice_t, '>'); + if (end) + { + int len = end - start - 1; + char *elem = xmalloc(len + 1); + strncpy(elem, start + 1, len); + elem[len] = 0; + + iter_type_ptr = xmalloc(256); + sprintf(iter_type_ptr, "SliceIter<%s>", elem); + + option_type_ptr = xmalloc(256); + sprintf(option_type_ptr, "Option<%s>", elem); + + free(elem); + } + } + } + // var __opt = __it.next(); ASTNode *opt_decl = ast_create(NODE_VAR_DECL); opt_decl->var_decl.name = xstrdup("__opt"); @@ -1198,6 +1407,10 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_next = ast_create(NODE_EXPR_MEMBER); ASTNode *it_ref = ast_create(NODE_EXPR_VAR); it_ref->var_ref.name = xstrdup("__it"); + if (iter_type_ptr) + { + it_ref->resolved_type = xstrdup(iter_type_ptr); + } memb_next->member.target = it_ref; memb_next->member.field = xstrdup("next"); call_next->call.callee = memb_next; @@ -1210,15 +1423,22 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_is_none = ast_create(NODE_EXPR_MEMBER); ASTNode *opt_ref1 = ast_create(NODE_EXPR_VAR); opt_ref1->var_ref.name = xstrdup("__opt"); + if (option_type_ptr) + { + opt_ref1->resolved_type = xstrdup(option_type_ptr); + } memb_is_none->member.target = opt_ref1; memb_is_none->member.field = xstrdup("is_none"); call_is_none->call.callee = memb_is_none; + call_is_none->call.args = NULL; + call_is_none->call.arg_count = 0; - ASTNode *break_stmt = ast_create(NODE_BREAK); - + // if (__opt.is_none()) break; ASTNode *if_break = ast_create(NODE_IF); if_break->if_stmt.condition = call_is_none; + ASTNode *break_stmt = ast_create(NODE_BREAK); if_break->if_stmt.then_body = break_stmt; + if_break->if_stmt.else_body = NULL; APPEND_STMT(if_break); // var <user_var> = __opt.unwrap(); @@ -1231,25 +1451,28 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) ASTNode *memb_unwrap = ast_create(NODE_EXPR_MEMBER); ASTNode *opt_ref2 = ast_create(NODE_EXPR_VAR); opt_ref2->var_ref.name = xstrdup("__opt"); + if (option_type_ptr) + { + opt_ref2->resolved_type = xstrdup(option_type_ptr); + } memb_unwrap->member.target = opt_ref2; memb_unwrap->member.field = xstrdup("unwrap"); call_unwrap->call.callee = memb_unwrap; + call_unwrap->call.args = NULL; + call_unwrap->call.arg_count = 0; user_var_decl->var_decl.init_expr = call_unwrap; APPEND_STMT(user_var_decl); - // User Body + // User body statements enter_scope(ctx); add_symbol(ctx, var_name, NULL, NULL); - ASTNode *user_body_node; - if (lexer_peek(l).type == TOK_LBRACE) + // Body block + ASTNode *stmt = parse_statement(ctx, l); + ASTNode *user_body_node = stmt; + if (stmt && stmt->type != NODE_BLOCK) { - user_body_node = parse_block(ctx, l); - } - else - { - ASTNode *stmt = parse_statement(ctx, l); ASTNode *blk = ast_create(NODE_BLOCK); blk->block.statements = stmt; user_body_node = blk; @@ -1262,10 +1485,21 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l) loop_body->block.statements = stmts_head; while_loop->while_stmt.body = loop_body; - // Wrap entire thing in a block to scope _it + // Wrap entire thing in a block to scope __it (and __zc_arr_slice if present) ASTNode *outer_block = ast_create(NODE_BLOCK); - it_decl->next = while_loop; - outer_block->block.statements = it_decl; + if (slice_decl) + { + // Chain: slice_decl -> it_decl -> while_loop + slice_decl->next = it_decl; + it_decl->next = while_loop; + outer_block->block.statements = slice_decl; + } + else + { + // Chain: it_decl -> while_loop + it_decl->next = while_loop; + outer_block->block.statements = it_decl; + } return outer_block; } @@ -1877,7 +2111,7 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) lexer_next(&lookahead); TokenType next_type = lexer_peek(&lookahead).type; - if (next_type == TOK_SEMICOLON || next_type == TOK_DOTDOT) + if (next_type == TOK_SEMICOLON || next_type == TOK_DOTDOT || next_type == TOK_RBRACE) { Token t = lexer_next(l); // consume string @@ -1894,8 +2128,7 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) inner[t.len - 2] = 0; } - // ; means println (end of line), .. means print (continuation) - int is_ln = (next_type == TOK_SEMICOLON); + int is_ln = (next_type == TOK_SEMICOLON || next_type == TOK_RBRACE); char **used_syms = NULL; int used_count = 0; char *code = @@ -1913,6 +2146,7 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l) lexer_next(l); // consume optional ; } } + // If TOK_RBRACE, do not consume it, so parse_block can see it and terminate loop. ASTNode *n = ast_create(NODE_RAW_STMT); // Append semicolon to Statement Expression to make it a valid statement @@ -3185,7 +3419,7 @@ char *run_comptime_block(ParserContext *ctx, Lexer *l) ASTNode *fn = ref->node; if (fn && fn->type == NODE_FUNCTION && fn->func.is_comptime) { - emit_func_signature(f, fn, NULL); + emit_func_signature(ctx, f, fn, NULL); fprintf(f, ";\n"); codegen_node_single(ctx, fn, f); } diff --git a/src/parser/parser_struct.c b/src/parser/parser_struct.c index 84450ba..e53b56c 100644 --- a/src/parser/parser_struct.c +++ b/src/parser/parser_struct.c @@ -12,6 +12,114 @@ #include "zprep_plugin.h" #include "../codegen/codegen.h" +extern char *g_current_filename; + +/** + * @brief Auto-imports std/mem.zc if not already imported. + * + * This is called when the Drop trait is used (impl Drop for X). + */ +static void auto_import_std_mem(ParserContext *ctx) +{ + // Check if Drop trait is already registered (means mem.zc was imported) + if (check_impl(ctx, "Drop", "__trait_marker__")) + { + // Check_impl returns 0 if not found, but we need a different check + // Let's check if we can find any indicator that mem.zc was loaded + } + + // Try to find and import std/mem.zc + static const char *std_paths[] = {"std/mem.zc", "./std/mem.zc", NULL}; + static const char *system_paths[] = {"/usr/local/share/zenc", "/usr/share/zenc", NULL}; + + char resolved_path[1024]; + int found = 0; + + // First, try relative to current file + if (g_current_filename) + { + char *current_dir = xstrdup(g_current_filename); + char *last_slash = strrchr(current_dir, '/'); + if (last_slash) + { + *last_slash = 0; + snprintf(resolved_path, sizeof(resolved_path), "%s/std/mem.zc", current_dir); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + free(current_dir); + } + + // Try relative paths + if (!found) + { + for (int i = 0; std_paths[i] && !found; i++) + { + if (access(std_paths[i], R_OK) == 0) + { + strncpy(resolved_path, std_paths[i], sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + found = 1; + } + } + } + + // Try system paths + if (!found) + { + for (int i = 0; system_paths[i] && !found; i++) + { + snprintf(resolved_path, sizeof(resolved_path), "%s/std/mem.zc", system_paths[i]); + if (access(resolved_path, R_OK) == 0) + { + found = 1; + } + } + } + + if (!found) + { + return; // Could not find std/mem.zc + } + + // Canonicalize path + char *real_fn = realpath(resolved_path, NULL); + if (real_fn) + { + strncpy(resolved_path, real_fn, sizeof(resolved_path) - 1); + resolved_path[sizeof(resolved_path) - 1] = '\0'; + free(real_fn); + } + + // Check if already imported + if (is_file_imported(ctx, resolved_path)) + { + return; + } + mark_file_imported(ctx, resolved_path); + + // Load and parse the file + char *src = load_file(resolved_path); + if (!src) + { + return; // Could not load file + } + + Lexer i; + lexer_init(&i, src); + + // Save and restore filename context + char *saved_fn = g_current_filename; + g_current_filename = resolved_path; + + // Parse the mem module contents + parse_program_nodes(ctx, &i); + + g_current_filename = saved_fn; +} + // Trait Parsing ASTNode *parse_trait(ParserContext *ctx, Lexer *l) { @@ -100,7 +208,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l) char **param_names = NULL; int is_varargs = 0; char *args = parse_and_convert_args(ctx, l, &defaults, &arg_count, &arg_types, ¶m_names, - &is_varargs); + &is_varargs, NULL); char *ret = xstrdup("void"); if (lexer_peek(l).type == TOK_ARROW) @@ -149,6 +257,7 @@ ASTNode *parse_trait(ParserContext *ctx, Lexer *l) } register_trait(name); + add_to_global_list(ctx, n_node); // Track for codegen (VTable emission) return n_node; } @@ -206,6 +315,12 @@ ASTNode *parse_impl(ParserContext *ctx, Lexer *l) register_generic(ctx, target_gen_param); } + // Auto-import std/mem.zc if implementing Drop, Copy, or Clone traits + if (strcmp(name1, "Drop") == 0 || strcmp(name1, "Copy") == 0 || strcmp(name1, "Clone") == 0) + { + auto_import_std_mem(ctx); + } + register_impl(ctx, name1, name2); // RAII: Check for "Drop" trait implementation @@ -652,7 +767,7 @@ ASTNode *parse_impl(ParserContext *ctx, Lexer *l) } } -ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) +ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union, int is_opaque) { lexer_next(l); // eat struct or union @@ -705,6 +820,7 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) n->strct.is_union = is_union; n->strct.fields = NULL; n->strct.is_incomplete = 1; + n->strct.is_opaque = is_opaque; return n; } @@ -800,10 +916,6 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) ASTNode *nf = ast_create(NODE_FIELD); nf->field.name = xstrdup(f->field.name); nf->field.type = xstrdup(f->field.type); - // Copy type info? Ideally deep copy or ref - // For now, we leave it NULL or shallow copy if needed, but mixins usually - // aren't generic params themselves in the same way. - // Let's shallow copy for safety if it exists. nf->type_info = f->type_info; if (!h) @@ -829,7 +941,6 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) free(use_name); continue; } - // --------------------------------------- if (t.type == TOK_IDENT) { @@ -904,8 +1015,10 @@ ASTNode *parse_struct(ParserContext *ctx, Lexer *l, int is_union) node->strct.generic_params = gps; node->strct.generic_param_count = gp_count; node->strct.is_union = is_union; + node->strct.is_opaque = is_opaque; node->strct.used_structs = temp_used_structs; node->strct.used_struct_count = temp_used_count; + node->strct.defined_in_file = g_current_filename ? xstrdup(g_current_filename) : NULL; if (gp_count > 0) { @@ -1102,7 +1215,7 @@ ASTNode *parse_enum(ParserContext *ctx, Lexer *l) node->enm.name = ename; node->enm.variants = h; - node->enm.generic_param = gp; // 3. Store generic param + node->enm.generic_param = gp; // Store generic param if (gp) { diff --git a/src/parser/parser_type.c b/src/parser/parser_type.c index c01f061..49e961c 100644 --- a/src/parser/parser_type.c +++ b/src/parser/parser_type.c @@ -33,12 +33,25 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) char *name = token_strdup(t); // Check for alias - const char *aliased = find_type_alias(ctx, name); - if (aliased) + TypeAlias *alias_node = find_type_alias_node(ctx, name); + if (alias_node) { free(name); Lexer tmp; - lexer_init(&tmp, aliased); + lexer_init(&tmp, alias_node->original_type); + + if (alias_node->is_opaque) + { + Type *underlying = parse_type_formal(ctx, &tmp); + Type *wrapper = type_new(TYPE_ALIAS); + wrapper->name = xstrdup(alias_node->alias); + wrapper->inner = underlying; + wrapper->alias.is_opaque_alias = 1; + wrapper->alias.alias_defined_in_file = + alias_node->defined_in_file ? xstrdup(alias_node->defined_in_file) : NULL; + return wrapper; + } + return parse_type_formal(ctx, &tmp); } @@ -287,6 +300,90 @@ Type *parse_type_base(ParserContext *ctx, Lexer *l) free(name); return type_new(TYPE_I16); } + + // C23 BitInt Support (i42, u256, etc.) + if ((name[0] == 'i' || name[0] == 'u') && isdigit(name[1])) + { + // Verify it is a purely numeric suffix + int valid = 1; + for (size_t k = 1; k < strlen(name); k++) + { + if (!isdigit(name[k])) + { + valid = 0; + break; + } + } + if (valid) + { + int width = atoi(name + 1); + if (width > 0) + { + // Map standard widths to standard types for standard ABI/C compabitility + if (name[0] == 'i') + { + if (width == 8) + { + free(name); + return type_new(TYPE_I8); + } + if (width == 16) + { + free(name); + return type_new(TYPE_I16); + } + if (width == 32) + { + free(name); + return type_new(TYPE_I32); + } + if (width == 64) + { + free(name); + return type_new(TYPE_I64); + } + if (width == 128) + { + free(name); + return type_new(TYPE_I128); + } + } + else + { + if (width == 8) + { + free(name); + return type_new(TYPE_U8); + } + if (width == 16) + { + free(name); + return type_new(TYPE_U16); + } + if (width == 32) + { + free(name); + return type_new(TYPE_U32); + } + if (width == 64) + { + free(name); + return type_new(TYPE_U64); + } + if (width == 128) + { + free(name); + return type_new(TYPE_U128); + } + } + + Type *t = type_new(name[0] == 'u' ? TYPE_UBITINT : TYPE_BITINT); + t->array_size = width; + free(name); + return t; + } + } + } if (strcmp(name, "u16") == 0) { free(name); diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c index 4e85500..28d2c11 100644 --- a/src/parser/parser_utils.c +++ b/src/parser/parser_utils.c @@ -402,23 +402,33 @@ void add_to_struct_list(ParserContext *ctx, ASTNode *node) ctx->parsed_structs_list = r; } -void register_type_alias(ParserContext *ctx, const char *alias, const char *original) +void register_type_alias(ParserContext *ctx, const char *alias, const char *original, int is_opaque, + const char *defined_in_file) { TypeAlias *ta = xmalloc(sizeof(TypeAlias)); ta->alias = xstrdup(alias); ta->original_type = xstrdup(original); + ta->is_opaque = is_opaque; + ta->defined_in_file = defined_in_file ? xstrdup(defined_in_file) : NULL; ta->next = ctx->type_aliases; ctx->type_aliases = ta; } const char *find_type_alias(ParserContext *ctx, const char *alias) { + TypeAlias *ta = find_type_alias_node(ctx, alias); + return ta ? ta->original_type : NULL; +} + +TypeAlias *find_type_alias_node(ParserContext *ctx, const char *alias) +{ TypeAlias *ta = ctx->type_aliases; while (ta) { if (strcmp(ta->alias, alias) == 0) { - return ta->original_type; + // printf("DEBUG: Found Alias '%s' (Opaque: %d)\n", alias, ta->is_opaque); + return ta; } ta = ta->next; } @@ -3382,7 +3392,8 @@ char *consume_and_rewrite(ParserContext *ctx, Lexer *l) } char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, int *count_out, - Type ***types_out, char ***names_out, int *is_varargs_out) + Type ***types_out, char ***names_out, int *is_varargs_out, + char ***ctype_overrides_out) { Token t = lexer_next(l); if (t.type != TOK_LPAREN) @@ -3396,18 +3407,52 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, char **defaults = xmalloc(sizeof(char *) * 16); Type **types = xmalloc(sizeof(Type *) * 16); char **names = xmalloc(sizeof(char *) * 16); + char **ctype_overrides = xmalloc(sizeof(char *) * 16); for (int i = 0; i < 16; i++) { defaults[i] = NULL; types[i] = NULL; names[i] = NULL; + ctype_overrides[i] = NULL; } if (lexer_peek(l).type != TOK_RPAREN) { while (1) { + // Check for @ctype("...") before parameter + char *ctype_override = NULL; + if (lexer_peek(l).type == TOK_AT) + { + lexer_next(l); // eat @ + Token attr = lexer_next(l); + if (attr.type == TOK_IDENT && attr.len == 5 && strncmp(attr.start, "ctype", 5) == 0) + { + if (lexer_next(l).type != TOK_LPAREN) + { + zpanic_at(lexer_peek(l), "Expected ( after @ctype"); + } + Token ctype_tok = lexer_next(l); + if (ctype_tok.type != TOK_STRING) + { + zpanic_at(ctype_tok, "@ctype requires a string argument"); + } + // Extract string content (strip quotes) + ctype_override = xmalloc(ctype_tok.len - 1); + strncpy(ctype_override, ctype_tok.start + 1, ctype_tok.len - 2); + ctype_override[ctype_tok.len - 2] = 0; + if (lexer_next(l).type != TOK_RPAREN) + { + zpanic_at(lexer_peek(l), "Expected ) after @ctype string"); + } + } + else + { + zpanic_at(attr, "Unknown parameter attribute @%.*s", attr.len, attr.start); + } + } + Token t = lexer_next(l); // Handle 'self' if (t.type == TOK_IDENT && strncmp(t.start, "self", 4) == 0 && t.len == 4) @@ -3460,6 +3505,7 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, types[count] = type_new_ptr(type_new(TYPE_VOID)); add_symbol(ctx, "self", "void*", types[count]); } + ctype_overrides[count] = ctype_override; count++; } else @@ -3504,11 +3550,20 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, } else { - strcat(buf, type_str); + // Use @ctype override if present + if (ctype_override) + { + strcat(buf, ctype_override); + } + else + { + strcat(buf, type_str); + } strcat(buf, " "); strcat(buf, name); } + ctype_overrides[count] = ctype_override; count++; if (lexer_peek(l).type == TOK_OP && is_token(lexer_peek(l), "=")) @@ -3583,6 +3638,10 @@ char *parse_and_convert_args(ParserContext *ctx, Lexer *l, char ***defaults_out, *count_out = count; *types_out = types; *names_out = names; + if (ctype_overrides_out) + { + *ctype_overrides_out = ctype_overrides; + } return buf; } diff --git a/src/repl/repl.c b/src/repl/repl.c index cb63293..b04b2c2 100644 --- a/src/repl/repl.c +++ b/src/repl/repl.c @@ -6,22 +6,977 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <termios.h> +#include <unistd.h> +#include <ctype.h> ASTNode *parse_program(ParserContext *ctx, Lexer *l); static int is_header_line(const char *line) { - return (strncmp(line, "import ", 7) == 0 || strncmp(line, "include ", 8) == 0 || - strncmp(line, "#include", 8) == 0); + // Skip whitespace + while (*line && (*line == ' ' || *line == '\t')) + { + line++; + } + if (strncmp(line, "struct", 6) == 0) + { + return 1; + } + if (strncmp(line, "impl", 4) == 0) + { + return 1; + } + if (strncmp(line, "fn", 2) == 0) + { + return 1; + } + if (strncmp(line, "use", 3) == 0) + { + return 1; + } + if (strncmp(line, "include", 7) == 0) + { + return 1; + } + if (strncmp(line, "typedef", 7) == 0) + { + return 1; + } + if (strncmp(line, "enum", 4) == 0) + { + return 1; + } + if (strncmp(line, "const", 5) == 0) + { + return 1; + } + if (strncmp(line, "def", 3) == 0) + { + return 1; + } + if (strncmp(line, "#include", 8) == 0) + { + return 1; + } + if (strncmp(line, "import", 6) == 0) + { + return 1; + } + + return 0; +} + +static void repl_error_callback(void *data, Token t, const char *msg) +{ + (void)data; + (void)t; + fprintf(stderr, "\033[1;31merror:\033[0m %s\n", msg); +} + +static struct termios orig_termios; +static int raw_mode_enabled = 0; + +static void disable_raw_mode() +{ + if (raw_mode_enabled) + { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); + raw_mode_enabled = 0; + } +} + +static void enable_raw_mode() +{ + if (!raw_mode_enabled) + { + tcgetattr(STDIN_FILENO, &orig_termios); + atexit(disable_raw_mode); + struct termios raw = orig_termios; + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= (CS8); + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); + raw_mode_enabled = 1; + } +} + +static const char *KEYWORDS[] = { + "fn", "struct", "var", "let", "def", "const", "return", "if", + "else", "for", "while", "do", "switch", "case", "default", "break", + "continue", "typedef", "enum", "union", "sizeof", "typeof", "import", "include", + "defer", "guard", "match", "impl", "trait", "comptime", "asm", "plugin", + "true", "false", "null", "NULL", NULL}; + +static const char *TYPES[] = {"void", "int", "char", "float", "double", "long", + "short", "unsigned", "signed", "bool", NULL}; + +static int find_matching_brace(const char *buf, int pos) +{ + if (pos < 0 || pos >= (int)strlen(buf)) + { + return -1; + } + char c = buf[pos]; + int dir = 0; + char match = 0; + if (c == '{') + { + match = '}'; + dir = 1; + } + else if (c == '(') + { + match = ')'; + dir = 1; + } + else if (c == '[') + { + match = ']'; + dir = 1; + } + else if (c == '}') + { + match = '{'; + dir = -1; + } + else if (c == ')') + { + match = '('; + dir = -1; + } + else if (c == ']') + { + match = '['; + dir = -1; + } + else + { + return -1; + } + + int depth = 1; + int p = pos + dir; + int len = strlen(buf); + while (p >= 0 && p < len) + { + if (buf[p] == c) + { + depth++; + } + else if (buf[p] == match) + { + depth--; + if (depth == 0) + { + return p; + } + } + p += dir; + } + return -1; +} + +// Calculate visible length of a string (ignoring ANSI codes) +static int get_visible_length(const char *str) +{ + int len = 0; + int in_esc = 0; + while (*str) + { + if (*str == '\033') + { + in_esc = 1; + } + else if (in_esc) + { + if (*str == 'm' || *str == 'K') // End of SGR or EL + { + in_esc = 0; + } + if (isalpha(*str)) + { + in_esc = 0; // Terminating char + } + } + else + { + len++; + } + str++; + } + return len; +} + +// Simple syntax highlighter for the REPL +static void repl_highlight(const char *buf, int cursor_pos); + +static int is_definition_of(const char *code, const char *name) +{ + Lexer l; + lexer_init(&l, code); + Token t = lexer_next(&l); + int is_header = 0; + + if (t.type == TOK_UNION) + { + is_header = 1; + } + else if (t.type == TOK_IDENT) + { + if ((t.len == 2 && strncmp(t.start, "fn", 2) == 0) || + (t.len == 6 && strncmp(t.start, "struct", 6) == 0) || + (t.len == 4 && strncmp(t.start, "enum", 4) == 0) || + (t.len == 7 && strncmp(t.start, "typedef", 7) == 0) || + (t.len == 5 && strncmp(t.start, "const", 5) == 0)) + { + is_header = 1; + } + } + + if (is_header) + { + Token name_tok = lexer_next(&l); + if (name_tok.type == TOK_IDENT) + { + if (strlen(name) == (size_t)name_tok.len && + strncmp(name, name_tok.start, name_tok.len) == 0) + { + return 1; + } + } + } + return 0; +} + +static void repl_highlight(const char *buf, int cursor_pos) +{ + const char *p = buf; + + int match_pos = -1; + int brace_pos = -1; + + // Check under cursor + if (find_matching_brace(buf, cursor_pos) != -1) + { + brace_pos = cursor_pos; + match_pos = find_matching_brace(buf, cursor_pos); + } + // Check before cursor (common behavior when typing) + else if (cursor_pos > 0 && find_matching_brace(buf, cursor_pos - 1) != -1) + { + brace_pos = cursor_pos - 1; + match_pos = find_matching_brace(buf, cursor_pos - 1); + } + + while (*p) + { + long idx = p - buf; + + // Highlight matching braces + if (idx == brace_pos || idx == match_pos) + { + printf("\033[1;44;37m"); // Bright White on Blue background + putchar(*p); + printf("\033[0m"); + p++; + continue; + } + + if (strncmp(p, "//", 2) == 0) + { + printf("\033[1;30m"); + printf("%s", p); + printf("\033[0m"); + break; + } + else if (*p == ':' && isalpha(p[1])) + { + printf("\033[1;35m"); + while (*p && !isspace(*p)) + { + putchar(*p); + p++; + } + printf("\033[0m"); + } + else if (isdigit(*p)) + { + printf("\033[1;35m"); + while (isdigit(*p) || *p == '.' || *p == 'x' || *p == 'X') + { + putchar(*p); + p++; + } + printf("\033[0m"); + } + else if (*p == '"' || *p == '\'') + { + char quote = *p; + printf("\033[1;32m"); + putchar(*p); + p++; + while (*p && *p != quote) + { + if (*p == '\\' && p[1]) + { + putchar(*p); + p++; + } + putchar(*p); + p++; + } + if (*p == quote) + { + putchar(*p); + p++; + } + printf("\033[0m"); + } + else if (strchr(",;.", *p)) + { + printf("\033[1;30m"); + putchar(*p); + printf("\033[0m"); + p++; + } + else if (strchr("{}[]()", *p)) + { + printf("\033[0;36m"); + putchar(*p); + printf("\033[0m"); + p++; + } + else if (strchr("+-*/=<>!&|^~%", *p)) + { + printf("\033[1;37m"); + putchar(*p); + printf("\033[0m"); + p++; + } + else if (isalpha(*p) || *p == '_') + { + const char *start = p; + while (isalnum(*p) || *p == '_') + { + p++; + } + int len = p - start; + char word[256]; + if (len < 256) + { + strncpy(word, start, len); + word[len] = 0; + + int is_keyword = 0; + for (int i = 0; KEYWORDS[i]; i++) + { + if (strcmp(word, KEYWORDS[i]) == 0) + { + is_keyword = 1; + break; + } + } + + int is_type = 0; + if (!is_keyword) + { + for (int i = 0; TYPES[i]; i++) + { + if (strcmp(word, TYPES[i]) == 0) + { + is_type = 1; + break; + } + } + } + + int is_func = 0; + if (!is_keyword && !is_type) + { + const char *peek = p; + while (*peek && isspace(*peek)) + { + peek++; + } + if (*peek == '(') + { + is_func = 1; + } + } + + int is_const = 0; + if (!is_keyword && !is_type && !is_func && len > 1) + { + int all_upper = 1; + int has_upper = 0; + for (int i = 0; word[i]; i++) + { + if (islower(word[i])) + { + all_upper = 0; + } + if (isupper(word[i])) + { + has_upper = 1; + } + } + if (all_upper && has_upper) + { + is_const = 1; + } + } + + if (is_keyword) + { + printf("\033[1;36m"); + } + else if (is_type) + { + printf("\033[1;33m"); + } + else if (is_func) + { + printf("\033[1;34m"); + } + else if (is_const) + { + printf("\033[1;31m"); + } + + printf("%s", word); + printf("\033[0m"); + } + else + { + printf("%.*s", len, start); + } + } + else + { + putchar(*p); + p++; + } + } +} + +static char *repl_complete(const char *buf, int pos) +{ + int start = pos; + while (start > 0 && (isalnum(buf[start - 1]) || buf[start - 1] == '_' || + buf[start - 1] == ':' || buf[start - 1] == '!')) + { + start--; + } + + int len = pos - start; + if (len == 0) + { + return NULL; + } + + char prefix[256]; + if (len >= 255) + { + return NULL; + } + strncpy(prefix, buf + start, len); + prefix[len] = 0; + + char *match = NULL; + int match_count = 0; + + for (int i = 0; KEYWORDS[i]; i++) + { + if (strncmp(KEYWORDS[i], prefix, len) == 0) + { + match = (char *)KEYWORDS[i]; + match_count++; + } + } + + static const char *COMMANDS[] = { + ":help", ":reset", ":imports", ":vars", ":funcs", ":structs", ":history", ":type", + ":time", ":c", ":doc", ":run", ":edit", ":save", ":load", ":watch", + ":unwatch", ":undo", ":delete", ":clear", ":quit", NULL}; + + if (prefix[0] == ':') + { + for (int i = 0; COMMANDS[i]; i++) + { + if (strncmp(COMMANDS[i], prefix, len) == 0) + { + match = (char *)COMMANDS[i]; + match_count++; + } + } + } + + if (match_count == 1) + { + return strdup(match + len); + } + + return NULL; +} + +static char *repl_readline(const char *prompt, char **history, int history_len, int indent_level) +{ + enable_raw_mode(); + + int buf_size = 1024; + char *buf = malloc(buf_size); + buf[0] = 0; + int len = 0; + int pos = 0; + + if (indent_level > 0) + { + for (int i = 0; i < indent_level * 4; i++) + { + if (len >= buf_size - 1) + { + buf_size *= 2; + buf = realloc(buf, buf_size); + } + buf[len++] = ' '; + } + buf[len] = 0; + pos = len; + } + + int history_idx = history_len; + char *saved_current_line = NULL; + + int in_search_mode = 0; + char search_buf[256]; + search_buf[0] = 0; + int search_match_idx = -1; + + printf("\r\033[K%s", prompt); + repl_highlight(buf, pos); + fflush(stdout); + + while (1) + { + char c; + if (read(STDIN_FILENO, &c, 1) != 1) + { + break; + } + + if (c == '\x1b') + { + char seq[3]; + if (read(STDIN_FILENO, &seq[0], 1) != 1) + { + continue; + } + if (read(STDIN_FILENO, &seq[1], 1) != 1) + { + continue; + } + + if (seq[0] == '[') + { + if (seq[1] == 'A') + { + if (history_idx > 0) + { + if (history_idx == history_len) + { + if (saved_current_line) + { + free(saved_current_line); + } + saved_current_line = strdup(buf); + } + history_idx--; + if (history_idx >= 0 && history_idx < history_len) + { + free(buf); + buf = strdup(history[history_idx]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + } + } + } + else if (seq[1] == 'B') + { + if (history_idx < history_len) + { + history_idx++; + free(buf); + if (history_idx == history_len) + { + if (saved_current_line) + { + buf = strdup(saved_current_line); + } + else + { + buf = strdup(""); + } + } + else + { + buf = strdup(history[history_idx]); + } + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + } + } + else if (seq[1] == 'C') + { + if (pos < len) + { + pos++; + } + } + else if (seq[1] == 'D') + { + if (pos > 0) + { + pos--; + } + } + else if (seq[1] == 'H') + { + pos = 0; + } + else if (seq[1] == 'F') + { + pos = len; + } + } + } + else if (c == 127 || c == 8) + { + if (pos > 0) + { + memmove(buf + pos - 1, buf + pos, len - pos + 1); + len--; + pos--; + } + } + else if (c == '\r' || c == '\n') + { + printf("\r\n"); + break; + } + else if (c == 3) + { + printf("^C\r\n"); + free(buf); + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + return strdup(""); + } + else if (c == 4) + { + if (len == 0) + { + free(buf); + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + return NULL; + } + } + else if (c == '\t') + { + char *completion = repl_complete(buf, pos); + if (completion) + { + int clen = strlen(completion); + if (len + clen < buf_size - 1) + { + // Insert completion + memmove(buf + pos + clen, buf + pos, len - pos + 1); + memcpy(buf + pos, completion, clen); + len += clen; + pos += clen; + } + free(completion); + } + } + else if (c == 18) + { + if (!in_search_mode) + { + in_search_mode = 1; + search_buf[0] = 0; + search_match_idx = history_len; + } + + int found = -1; + int start_idx = search_match_idx - 1; + if (start_idx >= history_len) + { + start_idx = history_len - 1; + } + + for (int i = start_idx; i >= 0; i--) + { + if (strstr(history[i], search_buf)) + { + found = i; + break; + } + } + + if (found != -1) + { + search_match_idx = found; + free(buf); + buf = strdup(history[found]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + history_idx = found; // Sync history navigation + } + } + else if (in_search_mode) + { + if (c == 127 || c == 8) // Backspace + { + int sl = strlen(search_buf); + if (sl > 0) + { + search_buf[sl - 1] = 0; + search_match_idx = history_len; + int found = -1; + for (int i = history_len - 1; i >= 0; i--) + { + if (strstr(history[i], search_buf)) + { + found = i; + break; + } + } + if (found != -1) + { + search_match_idx = found; + free(buf); + buf = strdup(history[found]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + history_idx = found; + } + } + } + else if (c == '\r' || c == '\n' || c == 27 || c == 7 || + c == 3) // Enter/Esc/Ctrl+G/Ctrl+C + { + in_search_mode = 0; + if (c == 3) + { + // Abort + free(buf); + buf = strdup(""); + len = 0; + pos = 0; + printf("^C\r\n"); + return buf; + } + if (c == 7) + { + // Keep current match + } + else if (c == '\r' || c == '\n') + { + printf("\r\n"); + break; + } + } + else if (!iscntrl(c)) + { + int sl = strlen(search_buf); + if (sl < 255) + { + search_buf[sl] = c; + search_buf[sl + 1] = 0; + + int found = -1; + for (int i = history_len - 1; i >= 0; i--) + { + if (strstr(history[i], search_buf)) + { + found = i; + break; + } + } + if (found != -1) + { + search_match_idx = found; + free(buf); + buf = strdup(history[found]); + buf_size = strlen(buf) + 1; + len = strlen(buf); + pos = len; + history_idx = found; + } + } + } + } + else if (c == 1) + { + pos = 0; + } + else if (c == 5) + { + pos = len; + } + else if (c == 12) + { + printf("\033[2J\033[H"); + } + else if (c == 21) + { + if (pos > 0) + { + memmove(buf, buf + pos, len - pos + 1); + len -= pos; + pos = 0; + } + } + else if (c == 11) + { + buf[pos] = 0; + len = pos; + } + else if (c == 14) + { + printf("^N\r\n"); + free(buf); + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + return strdup(":reset"); + } + else if (!iscntrl(c)) + { + if (len >= buf_size - 1) + { + buf_size *= 2; + buf = realloc(buf, buf_size); + } + memmove(buf + pos + 1, buf + pos, len - pos + 1); + buf[pos] = c; + len++; + pos++; + } + + if (in_search_mode) + { + printf("\r\033[K(reverse-i-search)`%s': %s", search_buf, buf); + } + else + { + printf("\r\033[K%s", prompt); + repl_highlight(buf, pos); + int prompt_len = get_visible_length(prompt); + if (pos + prompt_len > 0) + { + printf("\r\033[%dC", pos + prompt_len); + } + else + { + printf("\r"); + } + } + + fflush(stdout); + } + + if (saved_current_line) + { + free(saved_current_line); + } + disable_raw_mode(); + + return buf; +} + +static void repl_get_code(char **history, int len, char **out_global, char **out_main) +{ + size_t total_len = 0; + for (int i = 0; i < len; i++) + { + total_len += strlen(history[i]) + 2; + } + + char *global_buf = malloc(total_len + 1); + char *main_buf = malloc(total_len + 1); + global_buf[0] = 0; + main_buf[0] = 0; + + int brace_depth = 0; + int in_global = 0; + + for (int i = 0; i < len; i++) + { + char *line = history[i]; + + if (brace_depth == 0) + { + if (is_header_line(line)) + { + in_global = 1; + } + else + { + in_global = 0; + } + } + + if (in_global) + { + strcat(global_buf, line); + strcat(global_buf, "\n"); + } + else + { + strcat(main_buf, line); + strcat(main_buf, " "); + } + + for (char *p = line; *p; p++) + { + if (*p == '{') + { + brace_depth++; + } + else if (*p == '}') + { + brace_depth--; + } + } + } + + *out_global = global_buf; + *out_main = main_buf; } void run_repl(const char *self_path) { - printf("\033[1;36mZen C REPL (v0.1)\033[0m\n"); + printf("\033[1;36mZen C REPL (%s)\033[0m\n", ZEN_VERSION); printf("Type 'exit' or 'quit' to leave.\n"); printf("Type :help for commands.\n"); - // Dynamic history. int history_cap = 64; int history_len = 0; char **history = xmalloc(history_cap * sizeof(char *)); @@ -69,7 +1024,6 @@ void run_repl(const char *self_path) history_path[0] = 0; } - // Watch list. char *watches[16]; int watches_len = 0; for (int i = 0; i < 16; i++) @@ -77,7 +1031,6 @@ void run_repl(const char *self_path) watches[i] = NULL; } - // Load startup file (~/.zprep_init.zc) if exists if (home) { char init_path[512]; @@ -128,21 +1081,39 @@ void run_repl(const char *self_path) while (1) { - if (brace_depth > 0 || paren_depth > 0) + char cwd[1024]; + char prompt_text[1280]; + if (getcwd(cwd, sizeof(cwd))) { - printf("... "); + char *base = strrchr(cwd, '/'); + if (base) + { + base++; + } + else + { + base = cwd; + } + snprintf(prompt_text, sizeof(prompt_text), "\033[1;32m%s >>>\033[0m ", base); } else { - printf("\033[1;32m>>>\033[0m "); + strcpy(prompt_text, "\033[1;32m>>>\033[0m "); } - if (!fgets(line_buf, sizeof(line_buf), stdin)) + const char *prompt = (brace_depth > 0 || paren_depth > 0) ? "... " : prompt_text; + int indent = (brace_depth > 0) ? brace_depth : 0; + char *rline = repl_readline(prompt, history, history_len, indent); + + if (!rline) { break; } + strncpy(line_buf, rline, sizeof(line_buf) - 2); + line_buf[sizeof(line_buf) - 2] = 0; + strcat(line_buf, "\n"); + free(rline); - // Handle commands (only on fresh line). if (NULL == input_buffer) { size_t len = strlen(line_buf); @@ -162,8 +1133,13 @@ void run_repl(const char *self_path) break; } - // Commands - if (cmd_buf[0] == ':' || cmd_buf[0] == '!') + if (cmd_buf[0] == '!') + { + int ret = system(cmd_buf + 1); + printf("(exit code: %d)\n", ret); + continue; + } + if (cmd_buf[0] == ':') { if (0 == strcmp(cmd_buf, ":help")) { @@ -183,14 +1159,21 @@ void run_repl(const char *self_path) printf(" :edit [n] Edit command n (default: last) in $EDITOR\n"); printf(" :save <f> Save session to file\n"); printf(" :load <f> Load file into session\n"); - printf(" :load <f> Load file into session\n"); - printf(" :watch <x> Watch expression output\n"); - printf(" :unwatch <n> Remove watch n\n"); printf(" :undo Remove last command\n"); printf(" :delete <n> Remove command at index n\n"); + printf(" :watch <x> Watch expression output\n"); + printf(" :unwatch <n> Remove watch n\n"); printf(" :clear Clear screen\n"); printf(" ! <cmd> Run shell command\n"); printf(" :quit Exit REPL\n"); + printf("\nShortcuts:\n"); + printf(" Up/Down History navigation\n"); + printf(" Tab Completion\n"); + printf(" Ctrl+A Go to start\n"); + printf(" Ctrl+E Go to end\n"); + printf(" Ctrl+L Clear screen\n"); + printf(" Ctrl+U Clear line to start\n"); + printf(" Ctrl+K Clear line to end\n"); continue; } else if (0 == strcmp(cmd_buf, ":reset")) @@ -207,9 +1190,107 @@ void run_repl(const char *self_path) { break; } + else if (0 == strncmp(cmd_buf, ":show ", 6)) + { + char *name = cmd_buf + 6; + while (*name && isspace(*name)) + { + name++; + } + + int found = 0; + printf("Source definition for '%s':\n", name); + + for (int i = history_len - 1; i >= 0; i--) + { + if (is_definition_of(history[i], name)) + { + printf(" \033[90m// Found in history:\033[0m\n"); + printf(" "); + repl_highlight(history[i], -1); + printf("\n"); + found = 1; + break; + } + } + + if (found) + { + continue; + } + + printf("Source definition for '%s':\n", name); + + size_t show_code_size = 4096; + for (int i = 0; i < history_len; i++) + { + show_code_size += strlen(history[i]) + 2; + } + char *show_code = malloc(show_code_size); + strcpy(show_code, ""); + for (int i = 0; i < history_len; i++) + { + strcat(show_code, history[i]); + strcat(show_code, "\n"); + } + + ParserContext ctx = {0}; + ctx.is_repl = 1; + ctx.skip_preamble = 1; + ctx.is_fault_tolerant = 1; + ctx.on_error = repl_error_callback; + Lexer l; + lexer_init(&l, show_code); + ASTNode *nodes = parse_program(&ctx, &l); + + ASTNode *search = nodes; + if (search && search->type == NODE_ROOT) + { + search = search->root.children; + } + + for (ASTNode *n = search; n; n = n->next) + { + if (n->type == NODE_FUNCTION && 0 == strcmp(n->func.name, name)) + { + printf(" fn %s(%s) -> %s\n", n->func.name, + n->func.args ? n->func.args : "", + n->func.ret_type ? n->func.ret_type : "void"); + found = 1; + break; + } + else if (n->type == NODE_STRUCT && 0 == strcmp(n->strct.name, name)) + { + printf(" struct %s {\n", n->strct.name); + for (ASTNode *field = n->strct.fields; field; field = field->next) + { + if (field->type == NODE_FIELD) + { + printf(" %s: %s;\n", field->field.name, field->field.type); + } + else if (field->type == NODE_VAR_DECL) + { + // Fields might be VAR_DECLs in some parses? No, usually + // NODE_FIELD for structs. + printf(" %s: %s;\n", field->var_decl.name, + field->var_decl.type_str); + } + } + printf(" }\n"); + found = 1; + break; + } + } + if (!found) + { + printf(" (not found)\n"); + } + free(show_code); + continue; + } else if (0 == strcmp(cmd_buf, ":clear")) { - printf("\033[2J\033[H"); // ANSI clear screen + printf("\033[2J\033[H"); continue; } else if (0 == strcmp(cmd_buf, ":undo")) @@ -289,9 +1370,7 @@ void run_repl(const char *self_path) const char *editor = getenv("EDITOR"); if (!editor) { - editor = "nano"; // Default fallback, - // 'cause I know some of you - // don't know how to exit Vim. + editor = "nano"; } char cmd[1024]; @@ -300,7 +1379,6 @@ void run_repl(const char *self_path) if (0 == status) { - // Read back file. FILE *fr = fopen(edit_path, "r"); if (fr) { @@ -415,23 +1493,15 @@ void run_repl(const char *self_path) FILE *f = fopen(filename, "w"); if (f) { - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - fprintf(f, "%s\n", history[i]); - } - } - // Write main function body. - fprintf(f, "\nfn main() {\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - fprintf(f, " %s\n", history[i]); - } - } - fprintf(f, "}\n"); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + fprintf(f, "%s\n", global_code); + fprintf(f, "\nfn main() {\n%s\n}\n", main_code); + + free(global_code); + free(main_code); fclose(f); printf("Session saved to %s\n", filename); } @@ -501,36 +1571,21 @@ void run_repl(const char *self_path) else if (0 == strcmp(cmd_buf, ":vars") || 0 == strcmp(cmd_buf, ":funcs") || 0 == strcmp(cmd_buf, ":structs")) { - size_t code_size = 4096; - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } - char *code = malloc(code_size + 128); - strcpy(code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "fn main() { "); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, " "); - } - } - strcat(code, " }"); + size_t code_size = strlen(global_code) + strlen(main_code) + 128; + char *code = malloc(code_size); + sprintf(code, "%s\nfn main() { %s }", global_code, main_code); + free(global_code); + free(main_code); ParserContext ctx = {0}; ctx.is_repl = 1; ctx.skip_preamble = 1; + ctx.is_fault_tolerant = 1; + ctx.on_error = repl_error_callback; Lexer l; lexer_init(&l, code); @@ -553,11 +1608,27 @@ void run_repl(const char *self_path) break; } } - printf("Variables:\n"); + + // Generate probe code to print values + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + // Generate probe code to print values + size_t probe_size = strlen(global_code) + strlen(main_code) + 4096; + char *probe_code = malloc(probe_size); + + sprintf(probe_code, + "%s\nfn main() { _z_suppress_stdout(); %s _z_restore_stdout(); " + "printf(\"Variables:\\n\"); ", + global_code, main_code); + free(global_code); + free(main_code); + + int found_vars = 0; if (main_func && main_func->func.body && main_func->func.body->type == NODE_BLOCK) { - int found = 0; for (ASTNode *s = main_func->func.body->block.statements; s; s = s->next) { @@ -565,19 +1636,92 @@ void run_repl(const char *self_path) { char *t = s->var_decl.type_str ? s->var_decl.type_str : "Inferred"; - printf(" %s: %s\n", s->var_decl.name, t); - found = 1; + // Heuristic for format + char fmt[64]; + char val_expr[128]; + + if (s->var_decl.type_str) + { + if (strcmp(t, "int") == 0 || strcmp(t, "i32") == 0) + { + strcpy(fmt, "%d"); + strcpy(val_expr, s->var_decl.name); + } + else if (strcmp(t, "i64") == 0) + { + strcpy(fmt, "%ld"); + sprintf(val_expr, "(long)%s", s->var_decl.name); + } + else if (strcmp(t, "float") == 0 || + strcmp(t, "double") == 0 || + strcmp(t, "f32") == 0 || strcmp(t, "f64") == 0) + { + strcpy(fmt, "%f"); + strcpy(val_expr, s->var_decl.name); + } + else if (strcmp(t, "bool") == 0) + { + strcpy(fmt, "%s"); + sprintf(val_expr, "%s ? \"true\" : \"false\"", + s->var_decl.name); + } + else if (strcmp(t, "string") == 0 || + strcmp(t, "char*") == 0) + { + strcpy(fmt, "\\\"%s\\\""); + strcpy(val_expr, s->var_decl.name); + } // quote strings + else if (strcmp(t, "char") == 0) + { + strcpy(fmt, "'%c'"); + strcpy(val_expr, s->var_decl.name); + } + else + { + // Fallback: address + strcpy(fmt, "@%p"); + sprintf(val_expr, "(void*)&%s", s->var_decl.name); + } + } + else + { + // Inferred: Safe fallback? Or try to guess? + // For now, minimal safety: print address + strcpy(fmt, "? @%p"); + sprintf(val_expr, "(void*)&%s", s->var_decl.name); + } + + char print_stmt[512]; + snprintf(print_stmt, sizeof(print_stmt), + "printf(\" %s (%s): %s\\n\", %s); ", s->var_decl.name, + t, fmt, val_expr); + strcat(probe_code, print_stmt); + found_vars = 1; } } - if (!found) - { - printf(" (none)\n"); - } } - else + + if (!found_vars) { - printf(" (none)\n"); + strcat(probe_code, "printf(\" (none)\\n\");"); + } + + strcat(probe_code, " }"); + + // Execute + char tmp_path[256]; + snprintf(tmp_path, sizeof(tmp_path), "/tmp/zen_repl_vars_%d.zc", getpid()); + FILE *f = fopen(tmp_path, "w"); + if (f) + { + fprintf(f, "%s", probe_code); + fclose(f); + char cmd[512]; + snprintf(cmd, sizeof(cmd), "%s run -q %s", self_path, tmp_path); + system(cmd); + remove(tmp_path); } + free(probe_code); } else if (0 == strcmp(cmd_buf, ":funcs")) { @@ -621,36 +1765,21 @@ void run_repl(const char *self_path) { char *expr = cmd_buf + 6; - size_t probe_size = 4096; - for (int i = 0; i < history_len; i++) - { - probe_size += strlen(history[i]) + 2; - } + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); - char *probe_code = malloc(probe_size + strlen(expr) + 256); - strcpy(probe_code, ""); + size_t probe_size = + strlen(global_code) + strlen(main_code) + strlen(expr) + 4096; + char *probe_code = malloc(probe_size); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, "\n"); - } - } - - strcat(probe_code, "fn main() { _z_suppress_stdout(); "); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, " "); - } - } + sprintf(probe_code, "%s\nfn main() { _z_suppress_stdout(); %s", global_code, + main_code); + free(global_code); + free(main_code); strcat(probe_code, " raw { typedef struct { int _u; } __REVEAL_TYPE__; } "); - strcat(probe_code, " var _z_type_probe: __REVEAL_TYPE__; _z_type_probe = ("); + strcat(probe_code, " let _z_type_probe: __REVEAL_TYPE__; _z_type_probe = ("); strcat(probe_code, expr); strcat(probe_code, "); }"); @@ -686,18 +1815,51 @@ void run_repl(const char *self_path) int found = 0; while (fgets(buf, sizeof(buf), p)) { - char *marker = "right operand has type '"; - char *start = strstr(buf, marker); + char *start = strstr(buf, "from type "); + char quote = 0; + if (!start) + { + start = strstr(buf, "incompatible type "); + } + if (start) { - start += strlen(marker); - char *end = strchr(start, '\''); - if (end) + char *q = strchr(start, '\''); + if (!q) { - *end = 0; - printf("\033[1;36mType: %s\033[0m\n", start); - found = 1; - break; + q = strstr(start, "\xe2\x80\x98"); + } + + if (q) + { + if (*q == '\'') + { + start = q + 1; + quote = '\''; + } + else + { + start = q + 3; + quote = 0; + } + + char *end = NULL; + if (quote) + { + end = strchr(start, quote); + } + else + { + end = strstr(start, "\xe2\x80\x99"); + } + + if (end) + { + *end = 0; + printf("\033[1;36mType: %s\033[0m\n", start); + found = 1; + break; + } } } } @@ -716,33 +1878,21 @@ void run_repl(const char *self_path) // Benchmark an expression. char *expr = cmd_buf + 6; - size_t code_size = 4096; - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } - char *code = malloc(code_size + strlen(expr) + 256); - strcpy(code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + size_t code_size = + strlen(global_code) + strlen(main_code) + strlen(expr) + 4096; + char *code = malloc(code_size); + + sprintf(code, + "%s\ninclude \"time.h\"\nfn main() { _z_suppress_stdout();\n%s " + "_z_restore_stdout();\n", + global_code, main_code); + free(global_code); + free(main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "include \"time.h\"\n"); - strcat(code, "fn main() { _z_suppress_stdout();\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, " "); - } - } - strcat(code, "_z_restore_stdout();\n"); strcat(code, "raw { clock_t _start = clock(); }\n"); strcat(code, "for _i in 0..1000 { "); strcat(code, expr); @@ -800,17 +1950,11 @@ void run_repl(const char *self_path) while (brace_depth > 0) { - printf("... "); - char more[1024]; - if (!fgets(more, sizeof(more), stdin)) + char *more = repl_readline("... ", history, history_len, brace_depth); + if (!more) { break; } - size_t mlen = strlen(more); - if (mlen > 0 && more[mlen - 1] == '\n') - { - more[--mlen] = 0; - } strcat(expr_buf, "\n"); strcat(expr_buf, more); for (char *p = more; *p; p++) @@ -824,35 +1968,20 @@ void run_repl(const char *self_path) brace_depth--; } } + free(more); } - size_t code_size = 4096 + strlen(expr_buf); - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } - char *code = malloc(code_size + 128); - strcpy(code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "fn main() {\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, " "); - } - } - strcat(code, expr_buf); - strcat(code, "\n}"); + size_t code_size = + strlen(global_code) + strlen(main_code) + strlen(expr_buf) + 128; + char *code = malloc(code_size); + + sprintf(code, "%s\nfn main() { %s %s }", global_code, main_code, expr_buf); + free(global_code); + free(main_code); free(expr_buf); char tmp_path[256]; @@ -877,33 +2006,16 @@ void run_repl(const char *self_path) } else if (0 == strcmp(cmd_buf, ":run")) { - size_t code_size = 4096; - for (int i = 0; i < history_len; i++) - { - code_size += strlen(history[i]) + 2; - } + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + size_t code_size = strlen(global_code) + strlen(main_code) + 128; char *code = malloc(code_size); - strcpy(code, ""); - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "fn main() {\n"); - for (int i = 0; i < history_len; i++) - { - if (!is_header_line(history[i])) - { - strcat(code, " "); - strcat(code, history[i]); - strcat(code, "\n"); - } - } - strcat(code, "}\n"); + sprintf(code, "%s\nfn main() { %s }", global_code, main_code); + free(global_code); + free(main_code); char tmp_path[256]; sprintf(tmp_path, "/tmp/zprep_repl_run_%d.zc", rand()); @@ -1082,7 +2194,6 @@ void run_repl(const char *self_path) } if (!found) { - // Fallback: try man pages, show only SYNOPSIS. char man_cmd[256]; sprintf(man_cmd, "man 3 %s 2>/dev/null | sed -n '/^SYNOPSIS/,/^[A-Z]/p' | " @@ -1187,7 +2298,6 @@ void run_repl(const char *self_path) continue; } - // Add to history. if (history_len >= history_cap) { history_cap *= 2; @@ -1201,40 +2311,20 @@ void run_repl(const char *self_path) brace_depth = 0; paren_depth = 0; - size_t total_size = 4096; - for (int i = 0; i < history_len; i++) - { - total_size += strlen(history[i]) + 2; - } + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len, &global_code, &main_code); + + size_t total_size = strlen(global_code) + strlen(main_code) + 4096; if (watches_len > 0) { - total_size += 16 * 1024; // Plenty of space for watches. Yeah static ik. + total_size += 16 * 1024; } char *full_code = malloc(total_size); - strcpy(full_code, ""); - - // Hoisting pass. - for (int i = 0; i < history_len; i++) - { - if (is_header_line(history[i])) - { - strcat(full_code, history[i]); - strcat(full_code, "\n"); - } - } - - strcat(full_code, "fn main() { _z_suppress_stdout(); "); - - for (int i = 0; i < history_len - 1; i++) - { - if (is_header_line(history[i])) - { - continue; - } - strcat(full_code, history[i]); - strcat(full_code, " "); - } + sprintf(full_code, "%s\nfn main() { _z_suppress_stdout(); %s", global_code, main_code); + free(global_code); + free(main_code); strcat(full_code, "_z_restore_stdout(); "); @@ -1249,6 +2339,8 @@ void run_repl(const char *self_path) ParserContext ctx = {0}; ctx.is_repl = 1; ctx.skip_preamble = 1; + ctx.is_fault_tolerant = 1; + ctx.on_error = repl_error_callback; Lexer l; lexer_init(&l, check_buf); ASTNode *node = parse_statement(&ctx, &l); @@ -1272,33 +2364,17 @@ void run_repl(const char *self_path) if (is_expr) { - size_t probesz = 4096; - for (int i = 0; i < history_len - 1; i++) - { - probesz += strlen(history[i]) + 2; - } - char *probe_code = malloc(probesz + strlen(last_line) + 512); - strcpy(probe_code, ""); + char *global_code = NULL; + char *main_code = NULL; + repl_get_code(history, history_len - 1, &global_code, &main_code); - for (int i = 0; i < history_len - 1; i++) - { - if (is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, "\n"); - } - } - - strcat(probe_code, "fn main() { _z_suppress_stdout(); "); + size_t probesz = strlen(global_code) + strlen(main_code) + strlen(last_line) + 4096; + char *probe_code = malloc(probesz); - for (int i = 0; i < history_len - 1; i++) - { - if (!is_header_line(history[i])) - { - strcat(probe_code, history[i]); - strcat(probe_code, " "); - } - } + sprintf(probe_code, "%s\nfn main() { _z_suppress_stdout(); %s", global_code, + main_code); + free(global_code); + free(main_code); strcat(probe_code, " raw { typedef struct { int _u; } __REVEAL_TYPE__; } "); strcat(probe_code, " var _z_type_probe: __REVEAL_TYPE__; _z_type_probe = ("); @@ -1356,10 +2432,9 @@ void run_repl(const char *self_path) if (watches_len > 0) { - strcat(full_code, "; "); // separator. + strcat(full_code, "; "); for (int i = 0; i < watches_len; i++) { - // Use printf for label, then print "{expr}" for value. char wbuf[1024]; sprintf(wbuf, "printf(\"\\033[90mwatch:%s = \\033[0m\"); print \"{%s}\"; " @@ -1401,7 +2476,8 @@ void run_repl(const char *self_path) FILE *hf = fopen(history_path, "w"); if (hf) { - for (int i = 0; i < history_len; i++) + int start = history_len > 1000 ? history_len - 1000 : 0; + for (int i = start; i < history_len; i++) { fprintf(hf, "%s\n", history[i]); } @@ -1409,11 +2485,14 @@ void run_repl(const char *self_path) } } - for (int i = 0; i < history_len; i++) + if (history) { - free(history[i]); + for (int i = 0; i < history_len; i++) + { + free(history[i]); + } + free(history); } - free(history); if (input_buffer) { free(input_buffer); 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 e248871..84400b3 100644 --- a/src/zprep.h +++ b/src/zprep.h @@ -108,6 +108,7 @@ typedef enum TOK_PREPROC, ///< Preprocessor directive (#...). TOK_ALIAS, ///< 'alias' keyword. TOK_COMMENT, ///< Comment (usually skipped). + TOK_OPAQUE, ///< 'opaque' keyword. TOK_UNKNOWN ///< Unknown token. } TokenType; @@ -358,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. @@ -19,3 +19,5 @@ import "./std/queue.zc" import "./std/env.zc" import "./std/slice.zc" import "./std/regex.zc" +import "./std/process.zc" + diff --git a/std/core.zc b/std/core.zc index f450517..7379db4 100644 --- a/std/core.zc +++ b/std/core.zc @@ -7,11 +7,11 @@ include <stdarg.h> let __zen_hash_seed: usize = 14695981039346656037; -raw { -void _zen_panic(const char* file, int line, const char* func, const char* msg) { +extern fn exit(code: int); + +fn _zen_panic(file: const char*, line: int, func: const char*, msg: const char*) { fprintf(stderr, "%s:%d (%s): Panic: %s\n", file, line, func, msg); exit(1); } -} #define panic(msg) _zen_panic(__FILE__, __LINE__, __func__, msg)
\ No newline at end of file diff --git a/std/cuda.zc b/std/cuda.zc index c6a9403..8fc6545 100644 --- a/std/cuda.zc +++ b/std/cuda.zc @@ -101,6 +101,8 @@ fn cuda_ok() -> bool { } +// Minimal raw block: required for cudaDeviceProp struct field access +// The cudaDeviceProp struct cannot be declared in Zen-C without type conflicts raw { void _z_cuda_get_props(int dev, char* name, size_t* total_mem, int* sm_count, int* major, int* minor, int* max_threads, int* warp_size) { struct cudaDeviceProp prop; @@ -2,23 +2,12 @@ import "./core.zc" import "./option.zc" import "./string.zc" -raw { - char *_z_env_get(char *name) { - return getenv(name); - } - - int _z_env_set(char *name, char *value, int overwrite) { - return setenv(name, value, overwrite); - } - - int _z_env_unset(char *name) { - return unsetenv(name); - } -} +include <stdlib.h> -extern fn _z_env_get(name: char*) -> char*; -extern fn _z_env_set(name: char*, value: char*, overwrite: int) -> int; -extern fn _z_env_unset(name: char*) -> int; +// Direct externs with const char* to match C stdlib declarations +extern fn getenv(name: const char*) -> char*; +extern fn setenv(name: const char*, value: const char*, overwrite: int) -> int; +extern fn unsetenv(name: const char*) -> int; @derive(Eq) enum EnvRes { @@ -30,7 +19,7 @@ struct Env {} impl Env { fn get(name: string) -> Option<string> { - let value: string = _z_env_get(name); + let value: string = getenv(name); if (value == NULL) { return Option<string>::None(); } @@ -39,7 +28,7 @@ impl Env { } fn get_dup(name: string) -> Option<String> { - let value: string = _z_env_get(name); + let value: string = getenv(name); if (value == NULL) { return Option<String>::None(); } @@ -52,13 +41,13 @@ impl Env { } fn set(name: string, value: string) -> EnvRes { - let ret: int = _z_env_set(name, value, 1); + let ret: int = setenv(name, value, 1); return (ret == 0) ? EnvRes::OK() : EnvRes::ERR(); } fn unset(name: string) -> EnvRes { - let ret: int = _z_env_unset(name); + let ret: int = unsetenv(name); return (ret == 0) ? EnvRes::OK() : EnvRes::ERR(); } @@ -2,22 +2,32 @@ 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; - -// TODO: restructure this tomorrow. +include <dirent.h> +include <sys/stat.h> +include <unistd.h> +include <stdlib.h> +include <stdio.h> + +// Direct externs for simple functions with const char* parameters +extern fn access(pathname: const char*, mode: int) -> int; +extern fn unlink(pathname: const char*) -> int; +extern fn rmdir(pathname: const char*) -> int; +extern fn malloc(size: usize) -> void*; +extern fn free(ptr: void*); + +// Minimal raw block: required for opaque FILE*/DIR* types and C struct access +// These cannot be expressed in Zen-C extern declarations without type conflicts raw { - #include <dirent.h> - #include <sys/stat.h> - #include <unistd.h> - - // typedef needed for Vec<DirEntry*> generation if inferred typedef struct DirEntry* DirEntryPtr; - void* _z_fs_fopen(char* path, char* mode) { + // FILE* wrappers - fopen/fclose/etc use FILE* which conflicts with void* + void* _z_fs_fopen(const char* path, const char* mode) { return fopen(path, mode); } @@ -40,20 +50,9 @@ raw { int64_t _z_fs_ftell(void* stream) { return (int64_t)ftell((FILE*)stream); } - - int _z_fs_access(char* pathname, int mode) { - return access(pathname, mode); - } - - int _z_fs_unlink(char* pathname) { - return unlink(pathname); - } - - int _z_fs_rmdir(char* pathname) { - return rmdir(pathname); - } - - void* _z_fs_opendir(char* name) { + + // DIR* wrappers - opendir/closedir/readdir use DIR* which conflicts with void* + void* _z_fs_opendir(const char* name) { return opendir(name); } @@ -61,15 +60,8 @@ raw { return closedir((DIR*)dir); } - void* _z_fs_malloc(size_t size) { - return malloc(size); - } - - void _z_fs_free(void* ptr) { - free(ptr); - } - - int _z_fs_get_metadata(char* path, uint64_t* size, int* is_dir, int* is_file) { + // struct stat access - cannot define matching Zen-C struct for stat + int _z_fs_get_metadata(const char* path, uint64_t* size, int* is_dir, int* is_file) { struct stat st; if (stat(path, &st) != 0) return -1; *size = st.st_size; @@ -78,6 +70,7 @@ raw { return 0; } + // struct dirent access - readdir returns struct dirent* int _z_fs_read_entry(void* dir, char* out_name, int buf_size, int* is_dir) { struct dirent* ent = readdir((DIR*)dir); if (!ent) return 0; @@ -87,7 +80,8 @@ raw { return 1; } - int _z_fs_mkdir(char* path) { + // mkdir has different signatures on Windows vs POSIX + int _z_fs_mkdir(const char* path) { #ifdef _WIN32 return mkdir(path); #else @@ -96,22 +90,18 @@ raw { } } -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_mkdir(path: const char*) -> int; +extern fn _z_fs_get_metadata(path: const 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; -extern fn _z_fs_fopen(path: char*, mode: char*) -> void*; +extern fn _z_fs_fopen(path: const char*, mode: const char*) -> void*; extern fn _z_fs_fclose(stream: void*) -> int; extern fn _z_fs_fread(ptr: void*, size: usize, nmemb: usize, stream: void*) -> usize; 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_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_fseek(stream: void*, offset: I64, whence: int) -> int; +extern fn _z_fs_ftell(stream: void*) -> I64; +extern fn _z_fs_opendir(name: const 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 +143,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 +152,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(); @@ -201,7 +191,7 @@ impl File { } fn exists(path: char*) -> bool { - return _z_fs_access(path, Z_F_OK) == 0; + return access(path, Z_F_OK) == 0; } fn metadata(path: char*) -> Result<Metadata> { @@ -228,14 +218,14 @@ impl File { } fn remove_file(path: char*) -> Result<bool> { - if (_z_fs_unlink(path) != 0) { + if (unlink(path) != 0) { return Result<bool>::Err("Failed to remove file"); } return Result<bool>::Ok(true); } fn remove_dir(path: char*) -> Result<bool> { - if (_z_fs_rmdir(path) != 0) { + if (rmdir(path) != 0) { return Result<bool>::Err("Failed to remove directory"); } return Result<bool>::Ok(true); @@ -248,7 +238,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 +267,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); @@ -2,37 +2,51 @@ import "./core.zc" import "./string.zc" -raw { - int _z_vprintf(char* fmt, va_list ap) { - return vprintf((const char*)fmt, ap); - } - - int _z_vsnprintf(char* str, size_t size, char* fmt, va_list ap) { - return vsnprintf(str, size, (const char*)fmt, ap); - } +include <stdio.h> +include <stdarg.h> - void* _z_get_stdin() { return stdin; } - int _z_get_eof() { return EOF; } +// These work directly with const char* in extern declarations +extern fn vprintf(fmt: const char*, ap: va_list) -> int; + +// vsnprintf is problematic on macOS because it's a macro that expands to a builtin with a different signature +// so we wrap it in a C function to avoid the conflict +extern fn _z_vsnprintf(str: char*, size: usize, fmt: const char*, ap: va_list) -> int; + +// EOF is typically -1, but we define it for portability +def Z_EOF = -1; + +// Minimal raw block: only for truly opaque FILE* types that can't be +// represented in Zen-C extern declarations without type conflicts. +// These wrappers use void* to avoid FILE* declaration conflicts. +raw { + void* _z_get_stdin(void) { return stdin; } int _z_fgetc(void* stream) { return fgetc((FILE*)stream); } + int _z_vsnprintf(char* str, size_t size, const char* fmt, va_list ap) { + return vsnprintf(str, size, fmt, ap); + } } -extern fn _z_vprintf(fmt: char*, ap: va_list) -> int; -extern fn _z_vsnprintf(str: char*, size: usize, fmt: char*, ap: va_list) -> int; +extern fn _z_get_stdin() -> void*; +extern fn _z_fgetc(stream: void*) -> int; fn format(fmt: char*, ...) -> char* { static let buffer: char[1024]; let ap: va_list; va_start(ap, fmt); + _z_vsnprintf(buffer, 1024, fmt, ap); va_end(ap); + return (char*)buffer; } fn format_into(buffer: char*, size: usize, fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); + let ret = _z_vsnprintf(buffer, size, fmt, ap); va_end(ap); + return ret; } @@ -42,15 +56,17 @@ fn format_new(fmt: char*, ...) -> char* { let ap: va_list; va_start(ap, fmt); + _z_vsnprintf(buffer, 1024, fmt, ap); va_end(ap); + return buffer; } fn print(fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vprintf(fmt, ap); + let ret = vprintf(fmt, ap); va_end(ap); return ret; } @@ -58,7 +74,7 @@ fn print(fmt: char*, ...) -> int { fn println(fmt: char*, ...) -> int { let ap: va_list; va_start(ap, fmt); - let ret = _z_vprintf(fmt, ap); + let ret = vprintf(fmt, ap); va_end(ap); puts(""); return ret + 1; @@ -72,11 +88,10 @@ fn readln() -> char* { let c: int; let std_in = _z_get_stdin(); - let eof_val = _z_get_eof(); while (true) { c = _z_fgetc(std_in); - if (c == eof_val) break; + if (c == Z_EOF) break; if (c == 10) break; // '\n' if (len + 1 >= cap) { @@ -93,7 +108,7 @@ fn readln() -> char* { len = len + 1; } - if (len == 0 && c == eof_val) { + if (len == 0 && c == Z_EOF) { free(line); return NULL; } diff --git a/std/json.zc b/std/json.zc index e6a15cd..9f5cf73 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,74 @@ impl JsonValue { } } } + +impl Drop for JsonValue { + fn drop(self) { + self.free(); + } +} + +impl JsonValue { + fn to_string(self) -> String { + let s = String::new(""); + self.stringify(&s); + return s; + } + + fn stringify(self, buf: String*) { + if (self.kind.tag == JsonType::JSON_NULL().tag) { + buf.append_c_ptr("null"); + } else if (self.kind.tag == JsonType::JSON_BOOL().tag) { + if (self.bool_val) { buf.append_c_ptr("true"); } else { buf.append_c_ptr("false"); } + } else if (self.kind.tag == JsonType::JSON_NUMBER().tag) { + let tmp: char[64]; + sprintf((char*)tmp, "%.15g", self.number_val); // Use %.15g for precision + buf.append_c_ptr((char*)tmp); + } else if (self.kind.tag == JsonType::JSON_STRING().tag) { + buf.append_c_ptr("\""); + let p = self.string_val; + let len = strlen(p); + for (let i = 0; i < len; i = i + 1) { + let c = p[i]; + if (c == '"') buf.append_c_ptr("\\\""); + else if (c == '\\') buf.append_c_ptr("\\\\"); + else if (c == '\n') buf.append_c_ptr("\\n"); + else if (c == '\t') buf.append_c_ptr("\\t"); + else if (c == '\r') buf.append_c_ptr("\\r"); + else if (c == '\b') buf.append_c_ptr("\\b"); + else if (c == '\f') buf.append_c_ptr("\\f"); + else { + let tmp: char[2]; tmp[0] = c; tmp[1] = 0; + buf.append_c_ptr((char*)tmp); + } + } + buf.append_c_ptr("\""); + } else if (self.kind.tag == JsonType::JSON_ARRAY().tag) { + buf.append_c_ptr("["); + let v = self.array_val; + for (let i: usize = 0; i < v.length(); i = i + 1) { + if (i > 0) buf.append_c_ptr(","); + let item = v.get(i); + (*item).stringify(buf); + } + buf.append_c_ptr("]"); + } else if (self.kind.tag == JsonType::JSON_OBJECT().tag) { + buf.append_c_ptr("{{"); + let m = self.object_val; + let first = true; + for (let i: usize = 0; i < m.capacity(); i = i + 1) { + if (m.is_slot_occupied(i)) { + if (!first) buf.append_c_ptr(","); + first = false; + let key = m.key_at(i); + buf.append_c_ptr("\""); + buf.append_c_ptr(key); // Assuming keys are simple for now, but really should escape them too + buf.append_c_ptr("\":"); + let val = m.val_at(i); + val.stringify(buf); + } + } + buf.append_c_ptr("}"); + } + } +} @@ -1,17 +1,22 @@ import "./core.zc" import "./option.zc" +import "./mem.zc" -raw { - extern size_t __zen_hash_seed; - size_t _map_hash_str(const char* str) { - size_t hash = __zen_hash_seed; - while (*str) { - hash ^= (unsigned char)*str++; - hash *= 1099511628211UL; - } - return hash; +// Pure Zen-C string hash using FNV-1a algorithm +fn _map_hash_str(str: const char*) -> usize { + let hash = __zen_hash_seed; + let i: usize = 0; + + while (str[i] != 0) { + // Cast char to U8 for unsigned byte value + let b: U8 = (U8)str[i]; + hash = hash ^ (usize)b; + hash = hash * (usize)1099511628211; + i = i + 1; } + + return hash; } struct Map<V> { @@ -248,3 +253,9 @@ impl Map<V> { }; } } + +impl Drop for Map<V> { + fn drop(self) { + self.free(); + } +} @@ -49,28 +49,7 @@ impl Box<T> { } } -struct Slice<T> { - data: T*; - len: usize; -} - -impl Slice<T> { - fn new(data: T*, len: usize) -> Self { - return Self { data: data, len: len }; - } - - fn get(self, i: usize) -> T { - return self.data[i]; - } - - fn set(self, i: usize, val: T) { - self.data[i] = val; - } - - fn is_empty(self) -> bool { - return self.len == 0; - } -} +// Note: Slice<T> is defined in std/slice.zc with iteration support fn mem_zero<T>(ptr: T*, count: usize) { memset(ptr, 0, sizeof(T) * count); @@ -8,12 +8,22 @@ 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; +// Direct externs for simple socket functions +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; + +// Minimal raw block: required for struct sockaddr_in usage +// These functions encapsulate sockaddr_in setup because the struct layout +// cannot be declared in Zen-C without type conflicts with C headers. +// Also includes inet_pton, htons, bind, connect, listen, accept wrappers. raw { - static int _z_net_bind(int fd, char *host, int port) { + static int _z_net_bind(int fd, const char *host, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -27,7 +37,7 @@ raw { return 0; } - static int _z_net_connect(int fd, char *host, int port) { + static int _z_net_connect(int fd, const char *host, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); @@ -36,28 +46,20 @@ 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) { + static ssize_t _z_net_write(int fd, const char* buf, size_t n) { return write(fd, (const void*)buf, n); } } -extern fn socket(domain: int, type: int, proto: int) -> int; -extern fn close(fd: int) -> int; - -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_bind(fd: int, host: const char*, port: int) -> int; +extern fn _z_net_connect(fd: int, host: const 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; +extern fn _z_net_write(fd: int, buf: const char*, n: usize) -> isize; struct TcpStream { @@ -66,7 +68,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 +98,12 @@ impl TcpStream { } } +impl Drop for TcpStream { + fn drop(self) { + self.close(); + } +} + struct TcpListener { fd: int; } @@ -126,3 +134,9 @@ impl TcpListener { } } } + +impl Drop for TcpListener { + fn drop(self) { + self.close(); + } +} diff --git a/std/process.zc b/std/process.zc new file mode 100644 index 0000000..3ce43b6 --- /dev/null +++ b/std/process.zc @@ -0,0 +1,136 @@ + +import "./core.zc"; +import "./vec.zc"; +import "./mem.zc"; +import "./string.zc"; +import "./option.zc"; + +include <stdio.h> +include <stdlib.h> + +// system() can be externed directly with const char* +extern fn system(command: const char*) -> int; + +// Minimal raw block: only for opaque FILE* types +// popen/pclose/fgets use FILE* which conflicts with void* +raw { + void *_z_popen(const char *command, const char *type) { + return (void *)popen(command, type); + } + + int _z_pclose(void *stream) { + return pclose((FILE *)stream); + } + + char *_z_fgets(char *s, int size, void *stream) { + return fgets(s, size, (FILE *)stream); + } +} + +extern fn _z_popen(command: const char*, type: const char*) -> void*; +extern fn _z_pclose(stream: void*) -> int; +extern fn _z_fgets(s: char*, size: int, stream: void*) -> char*; + +struct Output { + stdout: String; + exit_code: int; +} + +struct Command { + program: String; + args: Vec<String>; +} + +impl Command { + fn new(program: char*) -> Command { + return Command { + program: String::from(program), + args: Vec<String>::new() + }; + } + + fn arg(self, arg: char*) -> Command* { + self.args.push(String::from(arg)); + return self; + } + + fn _build_cmd(self) -> String { + let cmd_str = self.program.substring(0, self.program.length()); + + for arg in &self.args { + let space = String::from(" "); + cmd_str.append(&space); + space.free(); + + cmd_str.append(arg); + } + + return cmd_str; + } + + fn output(self) -> Output { + let cmd_str = self._build_cmd(); + let cmd_c = cmd_str.c_str(); + + let fp = _z_popen(cmd_c, "r"); + + if (fp == 0) { + cmd_str.free(); + // TODO: Better error handling... + return Output { + stdout: String::from(""), + exit_code: -1 + }; + } + + let out = String::from(""); + let buf_size: usize = 1024; + let buf = (char*)malloc(buf_size); + + while (true) { + let res = _z_fgets(buf, (int)buf_size, fp); + if (res == 0) break; + + let chunk = String::from(buf); + out.append(&chunk); + chunk.free(); + } + + let code = _z_pclose(fp); + free(buf); + cmd_str.free(); + + return Output { + stdout: out, + exit_code: code + }; + } + + fn status(self) -> int { + let cmd_str = self._build_cmd(); + let code = system(cmd_str.c_str()); + cmd_str.free(); + return code; + } + + fn free(self) { + self.program.free(); + + for s in &self.args { + s.free(); + } + self.args.free(); + } +} + +impl Drop for Command { + fn drop(self) { + self.free(); + } +} + +impl Drop for Output { + fn drop(self) { + self.stdout.free(); + } +} @@ -2,17 +2,17 @@ import "./core.zc" import "./option.zc" -raw { - extern size_t __zen_hash_seed; - size_t _set_hash(const void* data, size_t len) { - size_t hash = __zen_hash_seed; - const unsigned char* bytes = (const unsigned char*)data; - for (size_t i = 0; i < len; i++) { - hash ^= bytes[i]; - hash *= 1099511628211UL; - } - return hash; +// Pure Zen-C generic hash using FNV-1a algorithm +fn _set_hash(data: const void*, len: usize) -> usize { + let hash = __zen_hash_seed; + let bytes: U8* = (U8*)data; + + for (let i: usize = 0; i < len; i = i + 1) { + hash = hash ^ (usize)bytes[i]; + hash = hash * (usize)1099511628211; } + + return hash; } struct Set<T> { diff --git a/std/slice.zc b/std/slice.zc index 778c6ed..c757fbd 100644 --- a/std/slice.zc +++ b/std/slice.zc @@ -1,10 +1,50 @@ +import "./option.zc" + struct Slice<T> { data: T*; len: usize; } +struct SliceIter<T> { + data: T*; + count: usize; + idx: usize; +} + +impl SliceIter<T> { + fn next(self) -> Option<T> { + if (self.idx < self.count) { + let item = self.data[self.idx]; + self.idx = self.idx + 1; + return Option<T>::Some(item); + } + return Option<T>::None(); + } + + fn iterator(self) -> SliceIter<T> { + return *self; + } +} + impl Slice<T> { + fn from_array(ptr: T*, len: usize) -> Slice<T> { + return Slice<T> { data: ptr, len: len }; + } + + // Alias for backwards compatibility with std/mem.zc + fn new(data: T*, len: usize) -> Slice<T> { + return Slice<T> { data: data, len: len }; + } + + fn iterator(self) -> SliceIter<T> { + return SliceIter<T> { + data: self.data, + count: self.len, + idx: 0 + }; + } + fn length(self) -> usize { return self.len; } diff --git a/std/string.zc b/std/string.zc index fe5b0ad..0bc9539 100644 --- a/std/string.zc +++ b/std/string.zc @@ -55,6 +55,28 @@ impl String { } } + fn append_c(self, s: char*) { + if (self.vec.len > 0) { + self.vec.len = self.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + self.vec.push(s[i]); + } + self.vec.push(0); + } + + fn append_c_ptr(ptr: String*, s: char*) { + if (ptr.vec.len > 0) { + ptr.vec.len = ptr.vec.len - 1; + } + let len = strlen(s); + for (let i = 0; i < len; i = i + 1) { + ptr.vec.push(s[i]); + } + ptr.vec.push(0); + } + fn add(self, other: String*) -> String { let new_s = String::from(self.c_str()); new_s.append(other); diff --git a/std/thread.zc b/std/thread.zc index e90943b..16f3ca1 100644 --- a/std/thread.zc +++ b/std/thread.zc @@ -5,7 +5,13 @@ include <unistd.h> import "./core.zc" import "./result.zc" +import "./mem.zc" +// Essential raw block: required for pthread operations and closure trampolining +// This block cannot be eliminated because: +// 1. z_closure_T is an internal compiler type for Zen-C closures +// 2. pthread_t, pthread_mutex_t are opaque types that can't be extern'd with void* +// 3. The trampoline function needs to cast and execute Zen-C closures raw { typedef void (*ZenThreadFunc)(void*); @@ -120,11 +126,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..fa764ed 100644 --- a/std/time.zc +++ b/std/time.zc @@ -1,18 +1,31 @@ - import "./core.zc" +include <time.h> +include <unistd.h> +include <sys/time.h> +include <stdlib.h> + +// Minimal raw block: required because gettimeofday() uses struct timeval +// which can't be declared in Zen-C without type conflicts, and time() +// has conflicting type signature (time_t* vs void*) 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 int64_t _z_time_time(void) { + return (int64_t)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() -> I64; + struct Duration { millis: U64; } @@ -33,10 +46,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/_opaque_alias_lib.zc b/tests/features/_opaque_alias_lib.zc new file mode 100644 index 0000000..7ca4abc --- /dev/null +++ b/tests/features/_opaque_alias_lib.zc @@ -0,0 +1,13 @@ +opaque alias Handle = int; + +fn new_handle(v: int) -> Handle { + return v; // Implicit cast int -> Handle (OK in module) +} + +fn get_val(h: Handle) -> int { + return h; // Implicit cast Handle -> int (OK in module) +} + +fn compare_handles(a: Handle, b: Handle) -> bool { + return a == b; // Strict equality (OK) +} diff --git a/tests/features/_opaque_lib.zc b/tests/features/_opaque_lib.zc new file mode 100644 index 0000000..de4d4c4 --- /dev/null +++ b/tests/features/_opaque_lib.zc @@ -0,0 +1,15 @@ +opaque struct SecretBox { + value: int; +} + +fn new_box(v: int) -> SecretBox { + return SecretBox { value: v }; +} + +fn get_value(b: SecretBox*) -> int { + return b.value; +} + +fn set_value(b: SecretBox*, v: int) { + b.value = v; +} 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; } diff --git a/tests/features/test_implicit_fstring.zc b/tests/features/test_implicit_fstring.zc new file mode 100644 index 0000000..85a6c86 --- /dev/null +++ b/tests/features/test_implicit_fstring.zc @@ -0,0 +1,19 @@ + +test "implicit_fstring_interpolation" { + let result = 123; + let s = "{result}"; + // Should be "123" + assert(strcmp(s, "123") == 0, "Implicit f-string failed"); +} + +test "implicit_fstring_complex" { + let a = 10; + let b = 20; + let s = "Sum: {a + b}"; + assert(strcmp(s, "Sum: 30") == 0, "Complex implicit f-string failed"); +} + +test "no_interpolation" { + let s = "Hello World"; + assert(strcmp(s, "Hello World") == 0, "Plain string failed"); +} diff --git a/tests/features/test_opaque.zc b/tests/features/test_opaque.zc new file mode 100644 index 0000000..5c84b2a --- /dev/null +++ b/tests/features/test_opaque.zc @@ -0,0 +1,18 @@ +import "_opaque_lib.zc"; + +fn main() { + let b = new_box(42); + + // Stack allocation should work (size known) + let b2: SecretBox; + b2 = b; + + // Public methods should work + let v = get_value(&b2); + assert(v == 42, "Value should be 42"); + + set_value(&b2, 100); + assert(get_value(&b2) == 100, "Value should be 100"); + + println "Opaque struct test passed"; +} diff --git a/tests/features/test_opaque_alias.zc b/tests/features/test_opaque_alias.zc new file mode 100644 index 0000000..062fa1f --- /dev/null +++ b/tests/features/test_opaque_alias.zc @@ -0,0 +1,13 @@ +import "_opaque_alias_lib.zc"; + +fn main() { + let h = new_handle(42); + let v = get_val(h); + + assert(v == 42, "Opaque Alias FAIL"); + + let h2 = new_handle(42); + assert(compare_handles(h, h2), "Equality FAIL"); + + println "Opaque Alias OK"; +} diff --git a/tests/features/test_traits_suite.zc b/tests/features/test_traits_suite.zc index 205bdf6..2ff8378 100644 --- a/tests/features/test_traits_suite.zc +++ b/tests/features/test_traits_suite.zc @@ -90,7 +90,7 @@ test "test_derive" { // Debug let s = p1.to_string(); - assert(strcmp(s, "Point { ... }") == 0, "Debug string matches"); + assert(strcmp(s, "Point {{ ... }}") == 0, "Debug string matches"); // Clone let p2 = p1.clone(); diff --git a/tests/memory/test_memory_safety.zc b/tests/memory/test_memory_safety.zc index a5cc960..b672cc9 100644 --- a/tests/memory/test_memory_safety.zc +++ b/tests/memory/test_memory_safety.zc @@ -1,5 +1,6 @@ import "std/mem.zc" +import "std/slice.zc" // ** Globals ** let DROP_COUNT = 0; @@ -127,11 +128,13 @@ test "test_slice" { let data: int[5] = [1, 2, 3, 4, 5]; let s = Slice<int>::new(&data[0], 5); f" Slice len: {(int)s.len}"; - let v2 = s.get(2); + let opt_v2 = s.get(2); + let v2 = opt_v2.unwrap(); f" Slice[2]: {v2}"; assert(v2 == 3, "Slice get failed"); - s.set(0, 99); - let v0 = s.get(0); + s.data[0] = 99; + let opt_v0 = s.get(0); + let v0 = opt_v0.unwrap(); f" After set: Slice[0] = {v0}"; assert(v0 == 99, "Slice set failed"); " ✓ Slice works!"; diff --git a/tests/memory/test_unsafe.zc b/tests/memory/test_unsafe.zc index fe1150f..6114d6c 100644 --- a/tests/memory/test_unsafe.zc +++ b/tests/memory/test_unsafe.zc @@ -54,3 +54,18 @@ test "test_static_local" { assert b == 2; assert c == 3; } + +struct CastFoo { + val: int; +} + +fn test_cast_precedence_helper(ptr: void*) -> int { + return ((CastFoo*)ptr)->val; +} + +test "test_cast_precedence" { + let f = CastFoo{val: 42}; + let ptr = (void*)&f; + let val = test_cast_precedence_helper(ptr); + assert(val == 42, "Cast precedence failed"); +} diff --git a/tests/run_example_transpile.sh b/tests/run_example_transpile.sh new file mode 100755 index 0000000..c08a3ea --- /dev/null +++ b/tests/run_example_transpile.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +ZC="./zc" +EXAMPLES_DIR="examples" +FAIL_COUNT=0 +PASS_COUNT=0 + +echo "Running Example Transpilation Tests..." + +while IFS= read -r file; do + echo -n "Transpiling $file... " + + OUTPUT=$($ZC transpile "$file" 2>&1) + EXIT_CODE=$? + + if [ $EXIT_CODE -eq 0 ]; then + echo "PASS" + PASS_COUNT=$((PASS_COUNT + 1)) + [ -f "out.c" ] && rm "out.c" + [ -f "a.out" ] && rm "a.out" + else + echo "FAIL" + echo "$OUTPUT" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + +done < <(find "$EXAMPLES_DIR" -name "*.zc") + +echo "----------------------------------------" +echo "Summary:" +echo "-> Passed: $PASS_COUNT" +echo "-> Failed: $FAIL_COUNT" +echo "----------------------------------------" + +if [ $FAIL_COUNT -ne 0 ]; then + exit 1 +fi + +exit 0 diff --git a/tests/std/test_direct_array_iteration.zc b/tests/std/test_direct_array_iteration.zc new file mode 100644 index 0000000..359951f --- /dev/null +++ b/tests/std/test_direct_array_iteration.zc @@ -0,0 +1,37 @@ +import "std/slice.zc" + +test "direct array iteration" { + let arr: int[5] = [1, 2, 3, 4, 5]; + + let sum = 0; + for val in arr { + sum = sum + val; + } + + assert(sum == 15, "Sum should be 1+2+3+4+5 = 15"); +} + +test "direct array iteration with different types" { + let floats: float[3] = [1.5, 2.5, 3.0]; + let count = 0; + + for f in floats { + count = count + 1; + } + + assert(count == 3, "Should iterate over all 3 elements"); +} + +// TODO: Nested array iteration needs special handling +// test "nested array iteration" { +// let matrix: int[2][3] = [[1, 2, 3], [4, 5, 6]]; +// let total = 0; +// +// for row in matrix { +// for val in row { +// total = total + val; +// } +// } +// +// assert(total == 21, "Sum should be 1+2+3+4+5+6 = 21"); +// } diff --git a/tests/std/test_env.zc b/tests/std/test_env.zc index 25d5bc1..4b68712 100644 --- a/tests/std/test_env.zc +++ b/tests/std/test_env.zc @@ -30,7 +30,7 @@ test "test_std_env_get_dup" { assert(env_var.is_some(), "env_var should have a value"); let value = env_var.unwrap(); - assert(value.c_str() == "ok3", "value should be ok3"); + assert(strcmp(value.c_str(), "ok3") == 0, "value should be ok3"); value.free(); diff --git a/tests/std/test_json_serialization.zc b/tests/std/test_json_serialization.zc new file mode 100644 index 0000000..9fd5b32 --- /dev/null +++ b/tests/std/test_json_serialization.zc @@ -0,0 +1,149 @@ +import "std/json.zc" +import "std/io.zc" + +test "primitives" { + // Null + let v = JsonValue::null(); + let s = v.to_string(); + let expected = String::from("null"); + if (!s.eq(&expected)) { + panic("Null serialization failed"); + } + expected.free(); + s.free(); + + // Bool True + v = JsonValue::bool(true); + s = v.to_string(); + expected = String::from("true"); + if (!s.eq(&expected)) { + panic("Bool true serialization failed"); + } + expected.free(); + s.free(); + + // Bool False + v = JsonValue::bool(false); + s = v.to_string(); + expected = String::from("false"); + if (!s.eq(&expected)) { + panic("Bool false serialization failed"); + } + expected.free(); + s.free(); + + // Number Int + v = JsonValue::number(123.0); + s = v.to_string(); + expected = String::from("123"); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("Number 123 serialization failed"); + } + expected.free(); + s.free(); + + // Number Float + v = JsonValue::number(12.5); + s = v.to_string(); + expected = String::from("12.5"); + if (!s.eq(&expected)) { + panic("Number 12.5 serialization failed"); + } + expected.free(); + s.free(); + + // String Simple + v = JsonValue::string("hello"); + s = v.to_string(); + expected = String::from("\"hello\""); + if (!s.eq(&expected)) { + println "{s.c_str()}"; + panic("String hello serialization failed"); + } + expected.free(); + s.free(); + + // String Escaped + v = JsonValue::string("hello \"world\""); + s = v.to_string(); + expected = String::from("\"hello \\\"world\\\"\""); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("String escaped serialization failed"); + } + expected.free(); + s.free(); +} + +test "array" { + let v = JsonValue::array(); + v.push(JsonValue::number(1.0)); + v.push(JsonValue::bool(true)); + v.push(JsonValue::string("a")); + + let s = v.to_string(); + let expected = String::from("[1,true,\"a\"]"); + if (!s.eq(&expected)) { + println "Got: {s.c_str()}"; + panic("Array serialization failed"); + } + expected.free(); + s.free(); +} + +test "object" { + let v = JsonValue::object(); + v.set("key", JsonValue::string("value")); + + let s = v.to_string(); + // Round trip verification to avoid parser bug with literals + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Object round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip not object"); + + let val_opt = (*parsed).get_string("key"); + if (val_opt.is_none()) panic("Round trip missing 'key'"); + + let val_str = val_opt.unwrap(); + if (strcmp(val_str, "value") != 0) panic("Round trip wrong value"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +} + +test "nested" { + // {"arr":[1,2]} + let v = JsonValue::object(); + let arr = JsonValue::array(); + arr.push(JsonValue::number(1.0)); + arr.push(JsonValue::number(2.0)); + v.set("arr", arr); + + let s = v.to_string(); + + // Round trip + let parsed_res = JsonValue::parse(s.c_str()); + if (parsed_res.is_err()) { + panic("Round trip parse failed"); + } + let parsed = parsed_res.unwrap(); + if (!parsed.is_object()) panic("Round trip type mismatch"); + + let arr_opt = (*parsed).get_array("arr"); + if (arr_opt.is_none()) panic("Round trip missing arr"); + + let arr_ptr = arr_opt.unwrap(); + if (!(*arr_ptr).is_array()) panic("Inner not array"); + if ((*arr_ptr).len() != 2) panic("Wrong array length"); + + // Cleanup + (*parsed).free(); + free(parsed); + s.free(); +}
\ No newline at end of file diff --git a/tests/std/test_process.zc b/tests/std/test_process.zc new file mode 100644 index 0000000..a3ba6ce --- /dev/null +++ b/tests/std/test_process.zc @@ -0,0 +1,26 @@ + +import "std/process.zc"; +import "std/string.zc"; + +test "process output" { + let cmd = Command::new("echo"); + cmd.arg("hello"); + + let out = cmd.output(); + + assert(out.exit_code == 0); + // echo usually outputs newline + assert(out.stdout.contains('h')); + assert(out.stdout.contains('e')); + assert(out.stdout.contains('l')); + assert(out.stdout.contains('o')); + + // out is dropped automatically + // cmd is dropped automatically +} + +test "process status" { + let cmd = Command::new("true"); // true command returns 0 + let status = cmd.status(); + assert(status == 0); +} diff --git a/tests/std/test_slice_iteration.zc b/tests/std/test_slice_iteration.zc new file mode 100644 index 0000000..b7eddf4 --- /dev/null +++ b/tests/std/test_slice_iteration.zc @@ -0,0 +1,29 @@ +import "std/slice.zc" +import "std/io.zc" + +test "slice from array iteration" { + let ints: int[5] = [1, 2, 3, 4, 5]; + let slice = Slice<int>::from_array((int*)(&ints), 5); + + // Test iteration + let sum = 0; + for val in slice { + sum = sum + val; + } + + if (sum != 15) { + panic("Slice iteration failed: expected sum 15"); + } +} + +test "slice methods" { + let arr: int[3] = [10, 20, 30]; + let slice = Slice<int>::from_array((int*)(&arr), 3); + + if (slice.length() != 3) panic("Slice length wrong"); + if (slice.is_empty()) panic("Slice should not be empty"); + + let opt = slice.get(1); + if (opt.is_none()) panic("Slice get failed"); + if (opt.unwrap() != 20) panic("Slice get returned wrong value"); +} |
