diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..b0992c0e
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+*.lx linguist-language=Lua
diff --git a/.github/workflows/doxygen.yml b/.github/workflows/doxygen.yml
index 5f92a9df..bbd807db 100644
--- a/.github/workflows/doxygen.yml
+++ b/.github/workflows/doxygen.yml
@@ -5,6 +5,9 @@ on:
branches:
- main
+permissions:
+ contents: write
+
jobs:
build:
runs-on: ubuntu-latest
@@ -19,6 +22,9 @@ jobs:
- name: Run Doxygen
run: doxygen Doxyfile
+ - name: Verify Doxygen output
+ run: ls -la docs/doxygen/
+
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
@@ -28,16 +34,6 @@ jobs:
user_name: 'github-actions[bot]'
user_email: 'github-actions[bot]@users.noreply.github.com'
commit_message: 'Update Doxygen documentation'
- keep_files: false # DON'T keep previous files
- force_orphan: true # Optional: force orphan branch for fresh history
- enable_jekyll: false # No Jekyll on gh-pages
-
- - name: Enable GitHub Pages via API
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: |
- curl -X POST \
- -H "Authorization: token $GITHUB_TOKEN" \
- -H "Accept: application/vnd.github.v3+json" \
- https://api.github.com/repos/${{ github.repository }}/pages \
- -d '{"source":{"branch":"gh-pages","path":"/"}}'
+ keep_files: false
+ force_orphan: true
+ enable_jekyll: false
diff --git a/.gitignore b/.gitignore
index 62c77f9c..3a84b3a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,9 @@ build
obj
lux
+# autotools
+autom4te.cache
+
# Linux executables (files without extensions)
*
!*/
@@ -22,6 +25,9 @@ docs/xml/
*.o
*.s
+# Ignore IDE directories
+.vscode
+
# ignore the lsp-client server node modules
node_modules
package-lock.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
index eb4d8854..551c6737 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,41 +1,44 @@
{
"files.associations": {
- "type.h": "c",
- "array": "c",
- "atomic": "c",
- "bit": "c",
- "charconv": "c",
- "cmath": "c",
- "compare": "c",
- "concepts": "c",
- "string": "c",
- "unordered_map": "c",
- "exception": "c",
- "algorithm": "c",
- "memory": "c",
- "memory_resource": "c",
- "optional": "c",
- "random": "c",
- "string_view": "c",
- "type_traits": "c",
- "utility": "c",
- "functional": "c",
- "format": "c",
- "iosfwd": "c",
- "istream": "c",
- "limits": "c",
- "new": "c",
- "numeric": "c",
- "queue": "c",
- "ranges": "c",
- "span": "c",
- "sstream": "c",
- "streambuf": "c",
- "system_error": "c",
- "tuple": "c",
- "typeinfo": "c",
- "variant": "c",
- "vector": "c",
- "help.h": "c"
+ "*.py": "python",
+ "*.rpy": "renpy",
+ "type.h": "c",
+ "array": "c",
+ "atomic": "c",
+ "bit": "c",
+ "charconv": "c",
+ "cmath": "c",
+ "compare": "c",
+ "concepts": "c",
+ "string": "c",
+ "unordered_map": "c",
+ "exception": "c",
+ "algorithm": "c",
+ "memory": "c",
+ "memory_resource": "c",
+ "optional": "c",
+ "random": "c",
+ "string_view": "c",
+ "type_traits": "c",
+ "utility": "c",
+ "functional": "c",
+ "format": "c",
+ "iosfwd": "c",
+ "istream": "c",
+ "limits": "c",
+ "new": "c",
+ "numeric": "c",
+ "queue": "c",
+ "ranges": "c",
+ "span": "c",
+ "sstream": "c",
+ "streambuf": "c",
+ "system_error": "c",
+ "tuple": "c",
+ "typeinfo": "c",
+ "variant": "c",
+ "vector": "c",
+ "help.h": "c",
+ "llvm.h": "c"
}
}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 86615bea..a74e89a2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,8 +1,8 @@
-# Contributing to Lux
+# Contributing to Luma
-Thank you for your interest in contributing to Lux, a simple yet powerful systems programming language!
+Thank you for your interest in contributing to Luma, a simple yet powerful systems programming language!
-This document outlines the guidelines and best practices to help you contribute effectively. Whether you're reporting bugs, suggesting features, or submitting code, we appreciate your help making Lux better for everyone.
+This document outlines the guidelines and best practices to help you contribute effectively. Whether you're reporting bugs, suggesting features, or submitting code, we appreciate your help making Luma better for everyone.
## Table of Contents
@@ -24,7 +24,7 @@ By participating in this project, you agree to abide by the Contributor Covenant
### Reporting Issues
-If you find a bug or unexpected behavior in Lux, please open an issue on the GitHub repository with:
+If you find a bug or unexpected behavior in Luma, please open an issue on the GitHub repository with:
- A clear and descriptive title
- Steps to reproduce the problem
@@ -58,18 +58,20 @@ To set up a local development environment:
1. Ensure you have the required dependencies installed (e.g., cmake, ninja, gcc or your preferred compiler).
-2. Clone the Lux repository:
- ```bash
- git clone https://github.com/your-username/lux.git
- cd lux
- ```
+2. Clone the Luma repository:
+
+```bash
+git clone https://github.com/your-username/luma.git
+cd luma
+```
3. Build the project following instructions in the README (or specific build scripts).
4. Run to ensure everything is working:
- ```bash
- ./lux
- ```
+
+```bash
+./luma
+```
## Style Guide
@@ -93,4 +95,4 @@ By contributing, you agree that your contributions will be licensed under the MI
---
-**Thank you for helping make Lux awesome!**
\ No newline at end of file
+**Thank you for helping make Luma awesome!**
diff --git a/Doxyfile b/Doxyfile
index 790916d0..8b2cf610 100644
--- a/Doxyfile
+++ b/Doxyfile
@@ -41,7 +41,7 @@ DOXYFILE_ENCODING = UTF-8
# title of most generated pages and in a few other places.
# The default value is: My Project.
-PROJECT_NAME = "Lux"
+PROJECT_NAME = "Luma"
# The PROJECT_NUMBER tag can be used to enter a project or revision number. This
# could be handy for archiving the generated documentation or if some version
@@ -2730,4 +2730,4 @@ GENERATE_LEGEND = YES
# files.
# The default value is: YES.
-DOT_CLEANUP = YES
\ No newline at end of file
+DOT_CLEANUP = YES
diff --git a/INSTALL.md b/INSTALL.md
new file mode 100644
index 00000000..a24a6892
--- /dev/null
+++ b/INSTALL.md
@@ -0,0 +1,215 @@
+# Luma Compiler v0.1.0 - Installation Guide
+
+## Quick Start
+
+### Linux / macOS
+
+```bash
+# Extract the tarball
+tar -xzf luma-v0.1.0-linux-x86_64.tar.gz
+cd luma-v0.1.0
+
+# Install (system-wide, requires sudo)
+sudo ./install.sh
+
+# OR install for current user only
+./install.sh
+```
+
+### Windows
+
+1. Extract `luma-v0.1.0-windows-x86_64.zip`
+2. Open Command Prompt as Administrator (for system-wide install)
+3. Run `install.bat`
+
+OR simply run `install.bat` without admin for user-only installation.
+
+---
+
+## Standard Library Paths
+
+The Luma compiler searches for `std/` imports in the following order:
+
+### Linux / macOS
+1. **System-wide**: `/usr/local/lib/luma/std/`
+2. **User-local**: `~/.luma/std/`
+3. **Current directory**: `./std/`
+
+### Windows
+1. **System-wide**: `C:\Program Files\luma\std\`
+2. **User-local**: `%USERPROFILE%\.luma\std\`
+3. **Current directory**: `.\std\`
+
+---
+
+## Manual Installation
+
+If you prefer to install manually:
+
+### Linux / macOS
+
+```bash
+# Create directories
+sudo mkdir -p /usr/local/bin
+sudo mkdir -p /usr/local/lib/luma/std
+
+# Copy files
+sudo cp luma /usr/local/bin/
+sudo cp -r std/* /usr/local/lib/luma/std/
+
+# Make executable
+sudo chmod +x /usr/local/bin/luma
+```
+
+**For user-local installation:**
+
+```bash
+mkdir -p ~/.local/bin
+mkdir -p ~/.luma/std
+
+cp luma ~/.local/bin/
+cp -r std/* ~/.luma/std/
+
+# Add to PATH (add to ~/.bashrc or ~/.zshrc)
+export PATH="$PATH:$HOME/.local/bin"
+```
+
+### Windows
+
+1. Create directories:
+ - `C:\Program Files\luma\bin` (or `%USERPROFILE%\.luma\bin`)
+ - `C:\Program Files\luma\std` (or `%USERPROFILE%\.luma\std`)
+
+2. Copy `luma.exe` to the `bin` directory
+3. Copy `std/` contents to the `std` directory
+4. Add the `bin` directory to your PATH environment variable
+
+---
+
+## Verifying Installation
+
+After installation, verify with:
+
+```bash
+luma --version
+```
+
+You should see:
+```
+Luma Compiler v0.1.0
+```
+
+---
+
+## Using the Standard Library
+
+Once installed, you can import standard library modules:
+
+```luma
+import std/io
+import std/math
+
+fn main() {
+ io.println("Hello from Luma!")
+}
+```
+
+The compiler will automatically find these files in the installed standard library.
+
+---
+
+## Development Setup
+
+If you're developing with Luma and want to use a local `std/` directory:
+
+1. Create a `std/` folder in your project root
+2. Place your standard library files there
+3. The compiler will use these files first before checking system paths
+
+This is useful for:
+- Testing standard library changes
+- Using a custom/modified standard library
+- Working offline without installed standard library
+
+---
+
+## Troubleshooting
+
+### "Could not find standard library file"
+
+If you see this error, the compiler couldn't find the requested `std/` file.
+
+**Solution:**
+1. Verify installation: Check that files exist in the standard library directory
+2. Re-run the installer
+3. Check file permissions
+4. Use absolute paths as a workaround
+
+To see which paths are being searched:
+```bash
+luma your_file.lx
+# If std/ import fails, search paths will be displayed
+```
+
+### "luma: command not found" (Linux/macOS)
+
+The binary directory is not in your PATH.
+
+**Solution:**
+Add to `~/.bashrc` or `~/.zshrc`:
+```bash
+export PATH="$PATH:$HOME/.local/bin"
+```
+
+Then reload: `source ~/.bashrc`
+
+### PATH issues (Windows)
+
+**Solution:**
+1. Search for "Environment Variables" in Windows
+2. Edit your user's PATH variable
+3. Add: `%USERPROFILE%\.luma\bin` or `C:\Program Files\luma\bin`
+4. Restart Command Prompt
+
+---
+
+## Uninstalling
+
+### Linux / macOS
+
+```bash
+# System-wide
+sudo rm /usr/local/bin/luma
+sudo rm -rf /usr/local/lib/luma
+
+# User-local
+rm ~/.local/bin/luma
+rm -rf ~/.luma
+```
+
+### Windows
+
+1. Delete the installation directory
+ - `C:\Program Files\luma` or
+ - `%USERPROFILE%\.luma`
+2. Remove from PATH environment variable
+
+---
+
+## Building from Source
+
+See the main README.md for build instructions if you want to compile Luma yourself.
+
+---
+
+## Support
+
+- GitHub Issues: [your-repo-url]
+- Documentation: [docs-url]
+- Discord: [discord-link]
+
+---
+
+## License
+
+Luma Compiler is licensed under the MIT License.
diff --git a/Makefile b/Makefile
index 04504df1..0d523018 100644
--- a/Makefile
+++ b/Makefile
@@ -1,101 +1,156 @@
-# include config.mk
+# ============================================================
+# Luma / Lux Build System (Cross-Platform)
+# Works on Linux, macOS, and Windows
+# ============================================================
-# # LLVM configuration
-# LLVM_CFLAGS := $(shell llvm-config --cflags)
-# LLVM_LDFLAGS := $(shell llvm-config --ldflags --system-libs --libs core analysis bitwriter target)
+# Define target executable (must be defined before config.mk is included)
+BIN := luma
-# # Add LLVM flags to existing flags
-# override CFLAGS += $(LLVM_CFLAGS)
-# override LDFLAGS += $(LLVM_LDFLAGS)
+# Include default config
+include config.default.mk
-# # Detect platform and define commands
+# Optional: include user config if present
+ifneq ($(wildcard config.mk),)
include config.mk
+endif
+
+# ------------------------------------------------------------
+# Paths & Files
+# ------------------------------------------------------------
-# Detect platform and define commands
+# Detect OS and set appropriate commands
ifeq ($(OS),Windows_NT)
- SHELL := cmd.exe
- MKDIR = if not exist "$(subst /,\,$1)" mkdir "$(subst /,\,$1)"
- RMDIR = if exist "$(subst /,\,$1)" rmdir /s /q "$(subst /,\,$1)"
- DEL = if exist "$(subst /,\,$1)" del /q "$(subst /,\,$1)"
- EXE = .exe
- PATHSEP = \\
+ # Windows commands
+ SHELL := cmd.exe
+ MKDIR = if not exist "$(subst /,\,$(1))" mkdir "$(subst /,\,$(1))"
+ RMDIR = if exist "$(subst /,\,$(1))" rmdir /s /q "$(subst /,\,$(1))"
+ DEL = if exist "$(1)" del /q "$(1)"
+ RM = del /q
+ EXE = .exe
else
- SHELL := /bin/sh
- MKDIR = mkdir -p $1
- RMDIR = rm -rf $1
- DEL = rm -f $1
- EXE =
- PATHSEP = /
+ # Unix commands
+ SHELL := /bin/bash
+ MKDIR = mkdir -p $(1)
+ RMDIR = rm -rf $(1)
+ DEL = rm -f $(1)
+ RM = rm -f
+ EXE =
endif
-BIN := luma$(EXE)
+# ------------------------------------------------------------
+# Targets
+# ------------------------------------------------------------
-.PHONY: all clean debug test llvm-test
+.PHONY: all clean debug test check llvm-test view-ir run-llvm compile-native help
-all: $(BIN)
+# Default target
+all: $(BIN)$(EXE)
-$(BIN): $(OBJ_FILES)
- $(call MKDIR,$(@D))
+# Build binary
+$(BIN)$(EXE): $(OBJ_FILES)
+ifeq ($(OS),Windows_NT)
+ @if not exist "$(dir $@)" mkdir "$(subst /,\,$(dir $@))"
+else
+ @mkdir -p $(dir $@)
+endif
$(CC) -o $@ $^ $(LDFLAGS)
+# Compile .c → .o
+ifeq ($(OS),Windows_NT)
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
- $(call MKDIR,$(dir $@))
+ @if not exist "$(subst /,\,$(dir $@))" mkdir "$(subst /,\,$(dir $@))"
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
+else
+$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
+ @mkdir -p $(dir $@)
+ $(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
+endif
+# Debug build
debug: CFLAGS += -g
debug: all
-# Test targets
-test: $(BIN)
+# ------------------------------------------------------------
+# Testing & LLVM Targets
+# ------------------------------------------------------------
+
+test: $(BIN)$(EXE)
@echo "Running basic tests..."
- ./$(BIN) --help
+ifeq ($(OS),Windows_NT)
+ $(BIN)$(EXE) --help || exit 0
+else
+ ./$(BIN) --help || true
+endif
+
+check: test
-llvm-test: $(BIN)
+llvm-test: $(BIN)$(EXE)
@echo "Testing LLVM IR generation..."
- @echo "fn add(a: int, b: int) -> int { return a + b; }" > test_simple.lx
- @echo "fn main() -> int { let x: int = 42; let y: int = 24; return add(x, y); }" >> test_simple.lx
+ @echo fn add(a: int, b: int) -^> int { return a + b; } > test_simple.lx
+ @echo fn main() -^> int { let x: int = 42; let y: int = 24; return add(x, y); } >> test_simple.lx
+ifeq ($(OS),Windows_NT)
+ $(BIN)$(EXE) test_simple.luma --save
+ @dir *.bc *.ll 2>nul || echo No LLVM output files generated
+ @del /q test_simple.lx 2>nul
+else
./$(BIN) test_simple.luma --save
- @echo "Generated files:"
@ls -la *.bc *.ll 2>/dev/null || echo "No LLVM output files generated"
- @echo "Cleaning up test file..."
@rm -f test_simple.lx
+endif
-# View generated LLVM IR
view-ir: output.ll
- @echo "=== Generated LLVM IR ==="
+ @echo === Generated LLVM IR ===
+ifeq ($(OS),Windows_NT)
+ @type output.ll
+else
@cat output.ll
+endif
-# Run the generated LLVM bitcode with lli (LLVM interpreter)
run-llvm: output.bc
- @echo "Running with LLVM interpreter..."
+ @echo Running with LLVM interpreter...
lli output.bc
-# Compile LLVM IR to native executable
compile-native: output.ll
- @echo "Compiling LLVM IR to native executable..."
+ @echo Compiling LLVM IR to native executable...
llc output.ll -o output.s
gcc output.s -o program
- @echo "Native executable created: ./program"
+ @echo Native executable created: ./program
+
+# ------------------------------------------------------------
+# Cleanup
+# ------------------------------------------------------------
clean:
- $(call RMDIR,$(OBJ_DIR))
- $(call RMDIR,"output")
- $(call DEL,$(BIN))
+ @echo Cleaning build artifacts...
+ifeq ($(OS),Windows_NT)
+ @if exist "build" rmdir /s /q "build"
+ @if exist "$(BIN).exe" del /q "$(BIN).exe"
+ @if exist "$(BIN)" del /q "$(BIN)"
+ @echo Cleaning LLVM output files...
+ @if exist "output.bc" del /q "output.bc"
+ @if exist "output.ll" del /q "output.ll"
+ @if exist "output.s" del /q "output.s"
+ @if exist "program.exe" del /q "program.exe"
+ @if exist "program" del /q "program"
+else
+ @rm -rf $(OBJ_DIR)
+ @rm -f $(BIN)
@echo "Cleaning LLVM output files..."
- $(call DEL,output.bc)
- $(call DEL,output.ll)
- $(call DEL,output.s)
- $(call DEL,program)
+ @rm -f output.bc output.ll output.s program
+endif
+
+# ------------------------------------------------------------
+# Help
+# ------------------------------------------------------------
-# Help target
help:
- @echo "Available targets:"
- @echo " all - Build the compiler"
- @echo " debug - Build with debug symbols"
- @echo " test - Run basic tests"
- @echo " llvm-test - Test LLVM IR generation"
- @echo " view-ir - View generated LLVM IR"
- @echo " run-llvm - Run generated bitcode with lli"
- @echo " compile-native - Compile LLVM IR to native executable"
- @echo " clean - Remove all build artifacts and generated files"
- @echo " help - Show this help"
+ @echo Available targets:
+ @echo all - Build the compiler
+ @echo debug - Build with debug symbols
+ @echo test - Run basic tests
+ @echo llvm-test - Test LLVM IR generation
+ @echo view-ir - View generated LLVM IR
+ @echo run-llvm - Run generated bitcode with lli
+ @echo compile-native - Compile LLVM IR to native executable
+ @echo clean - Remove all build artifacts
+ @echo help - Show this help
diff --git a/README.md b/README.md
index 40622839..1ff98d2d 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,12 @@
# Luma
+
*A low-level compiled alternative to C, C++, and more!*
-
+
-
- Why? •
- Goals •
- Performance •
- Static Analysis & Ownership •
- Status •
- Getting Started •
- Join Us
-
+[Why?](#why) • [Goals](#language-goals) • [Performance](#performance) • [Static Analysis & Ownership](#static-analysis-and-ownership) • [Status](#project-status) • [Getting Started](#getting-started) • [Join Us](#join-us)
---
@@ -60,6 +53,7 @@ Build succeeded! Written to '3d_test' (51ms)
```
**Real-world metrics:**
+
- **51ms**: Complete 3D graphics app with math, memory management, strings, and terminal effects
- **+1ms**: Memory safety analysis overhead (essentially free)
- **Sub-100ms**: Typical compilation times for most projects
@@ -72,6 +66,7 @@ $ ls -lh 3d_test_stripped
```
**Comparable to C** — Luma produces tiny, efficient binaries:
+
- **24KB**: Stripped 3D graphics application
- **29KB**: With debug symbols
- **Zero runtime**: No garbage collector, no hidden allocations
@@ -79,12 +74,12 @@ $ ls -lh 3d_test_stripped
### Comparison Table
| Language | Compile Time Range | Your Test | Binary Size |
-|----------|-------------------|-----------|-------------|
-| **Luma** | **50-52ms** | **51ms** | **24KB** |
-| C/C++ | 100-800ms | ~300ms | 40-80KB |
-| Rust | 2-15s | ~3-5s | 150-400KB |
-| Go | 100-400ms | ~200ms | 1.5-2MB |
-| Zig | 200-600ms | ~400ms | 30-50KB |
+|----------|--------------------|-----------|-------------|
+| C/C++ | 100-800ms | ~300ms | 40-80KB |
+| Rust | 2-15s | ~3-5s | 150-400KB |
+| Go | 100-400ms | ~200ms | 1.5-2MB |
+| Zig | 200-600ms | ~400ms | 30-50KB |
+| **Luma** | **50-52ms** | **51ms** | **24KB** |
---
@@ -105,6 +100,7 @@ $ ls -lh 3d_test_stripped
Luma performs **end-of-type-check static analysis** to ensure memory safety without runtime overhead.
The analyzer checks for:
+
- Memory allocated but never freed
- Double frees
- Use-after-free errors
@@ -141,6 +137,7 @@ pub const main = fn() int {
```
**Key Features:**
+
- `#returns_ownership` — Function returns newly allocated memory
- `#takes_ownership` — Function takes responsibility for freeing memory
- `defer` — Ensures cleanup happens at scope exit
@@ -152,9 +149,10 @@ pub const main = fn() int {
**Current Phase:** Early Development
-Luma is currently in active development. Core language features are being implemented and the compiler architecture is being established.
+Luma is currently in active development. Core language features are being implemented and the compiler architecture is being established.
**What Works:**
+
- ✅ Complete lexer and parser
- ✅ Full type system with structs, enums, functions
- ✅ Static memory analysis with ownership tracking
@@ -182,6 +180,7 @@ You'll need the following tools installed:
**Important:** Luma requires LLVM 20.0 or higher due to critical bug fixes in the constant generation system.
**Known Issues:**
+
- **LLVM 19.1.x**: Contains a regression that causes crashes during code generation (`illegal hardware instruction` errors)
- **LLVM 18.x and older**: Not tested, may have compatibility issues
@@ -196,6 +195,7 @@ llvm-config --version
#### Linux Install
**Arch Linux:**
+
```bash
sudo pacman -S llvm
# For development headers:
@@ -203,6 +203,7 @@ sudo pacman -S llvm-libs
```
**Fedora/RHEL:**
+
```bash
sudo dnf update llvm llvm-devel llvm-libs
# Or install specific version:
@@ -210,12 +211,14 @@ sudo dnf install llvm20-devel llvm20-libs
```
**Ubuntu/Debian:**
+
```bash
sudo apt update
sudo apt install llvm-20-dev
```
**macOS (Homebrew):**
+
```bash
brew install llvm
```
@@ -223,20 +226,22 @@ brew install llvm
### Common Issues
**"illegal hardware instruction" during compilation:**
+
- This indicates an LLVM version incompatibility
- Upgrade to LLVM 20.0+ to resolve this issue
- See [LLVM Version Requirements](#llvm-version-requirements) above
**Missing LLVM development headers:**
+
```bash
# Install development packages
sudo dnf install llvm-devel # Fedora/RHEL
sudo apt install llvm-dev # Ubuntu/Debian
```
-# Building LLVM on Windows
+## Building LLVM on Windows
-## Prerequisites
+### Windows Prerequisites
Install the required tools using Scoop:
@@ -245,56 +250,64 @@ Install the required tools using Scoop:
scoop install python ninja cmake mingw
```
-## Build Steps
+### Build Steps
1. Clone the LLVM repository:
+
```bash
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
```
2. Configure the build:
+
```bash
cmake -S llvm -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang;lld" -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_ASM_COMPILER=gcc
```
3. Build LLVM (adjust `-j8` based on your CPU cores):
+
```bash
ninja -C build -j8
```
-## Notes
+### Notes
- Build time: 30 minutes to several hours depending on hardware
- Disk space required: ~15-20 GB for full build
- RAM usage: Can use 8+ GB during compilation
- If you encounter memory issues, reduce parallelism: `ninja -C build -j4` or `ninja -C build -j1`
-## After Build
+### After Build
The compiled binaries will be located in `build/bin/`
-### Add to PATH (Optional but Recommended)
+#### Add to PATH (Optional but Recommended)
To use `clang`, `lld`, and other LLVM tools from anywhere, add the build directory to your PATH:
-**Option 1: Temporary (current session only)**
+##### Option 1: Temporary (current session only)
+
```cmd
set PATH=%PATH%;C:\path\to\your\llvm-project\build\bin
```
-**Option 2: Permanent**
+##### Option 2: Permanent
+
1. Open System Properties → Advanced → Environment Variables
2. Edit the `PATH` variable for your user or system
3. Add the full path to your `build\bin` directory (e.g., `C:\Users\yourname\Desktop\llvm-project\build\bin`)
-**Option 3: Using PowerShell (permanent)**
+##### Option 3: Using PowerShell (permanent)
+
```powershell
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ";C:\path\to\your\llvm-project\build\bin", "User")
```
-### Verify Installation
+#### Verify Installation
+
After adding to PATH, open a new command prompt and test:
+
```bash
clang --version
lld --version
@@ -303,9 +316,9 @@ llvm-config --version
---
-## Examples
+### Examples
-### Hello World
+#### Hello World
```luma
@module "main"
@@ -317,6 +330,7 @@ pub const main = fn () int {
```
Compile and run:
+
```bash
$ luma hello.lx
[========================================] 100% - Completed (15ms)
@@ -326,9 +340,10 @@ $ ./output
Hello, World!
```
-### 3D Graphics (Real Example)
+#### 3D Graphics (Real Example)
See [tests/3d_spinning_cube.lx](tests/3d_spinning_cube.lx) for a complete 3D graphics application that:
+
- Renders rotating 3D cubes
- Uses sine/cosine lookup tables for performance
- Manages memory safely with `defer`
@@ -336,12 +351,13 @@ See [tests/3d_spinning_cube.lx](tests/3d_spinning_cube.lx) for a complete 3D gra
---
-## Join Us
+### Join Us
Interested in contributing to Luma? We'd love to have you!
+
- Check out our [GitHub repository](https://github.com/TheDevConnor/luma)
- Join our [Discord community](https://bit.ly/lux-discord)
-- Look at the doxygen-generated docs for architecture details [here](https://thedevconnor.github.io/Luma/)
+- Look at the [doxygen-generated](https://thedevconnor.github.io/Luma/) docs for architecture details
- If you would like to contribute, please read our [contribution guidelines](CONTRIBUTING.md).
---
diff --git a/c_test_files/ast.c b/c_test_files/ast.c
new file mode 100644
index 00000000..ec861259
--- /dev/null
+++ b/c_test_files/ast.c
@@ -0,0 +1,107 @@
+#include
+#include
+
+// Define an enum to distinguish node types
+typedef enum {
+ NODE_TYPE_NUMBER,
+ NODE_TYPE_BINARY_OP,
+ NODE_TYPE_UNARY_OP
+} NodeType;
+
+// Base struct for all AST nodes
+typedef struct ASTNode {
+ NodeType type;
+ // Common fields like line number, column number can go here
+} ASTNode;
+
+// Specific struct for number literals
+typedef struct NumberNode {
+ ASTNode base; // Embed the base struct
+ int value;
+} NumberNode;
+
+// Specific struct for binary operations (e.g., +, -, *, /)
+typedef struct BinaryOpNode {
+ ASTNode base; // Embed the base struct
+ char operator;
+ struct ASTNode *left;
+ struct ASTNode *right;
+} BinaryOpNode;
+
+// Specific struct for unary operations (e.g., negation)
+typedef struct UnaryOpNode {
+ ASTNode base; // Embed the base struct
+ char operator;
+ struct ASTNode *operand;
+} UnaryOpNode;
+
+// Function to create a number node
+ASTNode *create_number_node(int value) {
+ NumberNode *node = (NumberNode *)malloc(sizeof(NumberNode));
+ node->base.type = NODE_TYPE_NUMBER;
+ node->value = value;
+ return (ASTNode *)node;
+}
+
+// Function to create a binary operation node
+ASTNode *create_binary_op_node(char op, ASTNode *left, ASTNode *right) {
+ BinaryOpNode *node = (BinaryOpNode *)malloc(sizeof(BinaryOpNode));
+ node->base.type = NODE_TYPE_BINARY_OP;
+ node->operator = op;
+ node->left = left;
+ node->right = right;
+ return (ASTNode *)node;
+}
+
+// Function to create a unary operation node
+ASTNode *create_unary_op_node(char op, ASTNode *operand) {
+ UnaryOpNode *node = (UnaryOpNode *)malloc(sizeof(UnaryOpNode));
+ node->base.type = NODE_TYPE_UNARY_OP;
+ node->operator = op;
+ node->operand = operand;
+ return (ASTNode *)node;
+}
+
+// Function to free AST nodes (recursive)
+void free_ast(ASTNode *node) {
+ if (node == NULL) {
+ return;
+ }
+
+ switch (node->type) {
+ case NODE_TYPE_NUMBER:
+ // No child nodes to free
+ break;
+ case NODE_TYPE_BINARY_OP: {
+ BinaryOpNode *bin_node = (BinaryOpNode *)node;
+ free_ast(bin_node->left);
+ free_ast(bin_node->right);
+ break;
+ }
+ case NODE_TYPE_UNARY_OP: {
+ UnaryOpNode *un_node = (UnaryOpNode *)node;
+ free_ast(un_node->operand);
+ break;
+ }
+ }
+ free(node);
+}
+
+// Example usage:
+int main() {
+ // Represents (5 + 3) * 2
+ ASTNode *five = create_number_node(5);
+ ASTNode *three = create_number_node(3);
+ ASTNode *add = create_binary_op_node('+', five, three);
+ ASTNode *two = create_number_node(2);
+ ASTNode *multiply = create_binary_op_node('*', add, two);
+
+ // You would typically have a function to traverse and interpret the AST here
+ // For demonstration, we'll just free it.
+ printf("AST created successfully.\n");
+
+ free_ast(multiply);
+ printf("AST freed.\n");
+
+ return 0;
+}
\ No newline at end of file
diff --git a/c_test_files/tetris.c b/c_test_files/tetris.c
index 780bd96c..0e1692c1 100644
--- a/c_test_files/tetris.c
+++ b/c_test_files/tetris.c
@@ -57,7 +57,7 @@ const char *tetromino[7] = {
".XX..XX.........", // O
".XX.XX..........", // S
".X...XXX........", // T
- "XX...XX........." // Z
+ "XX...XX........." // Z
};
// ANSI color codes for each piece
@@ -126,7 +126,8 @@ void draw_screen(int currentPiece, int currentRotation, int currentX,
// Draw header with box drawing characters
printf("\x1b[0m");
printf("╔════════════════════════════╗ ╔═══════════════╗\n");
- printf("║ \x1b[1;97mT E T R I S\x1b[0m ║ ║ \x1b[1mNEXT PIECE\x1b[0m ║\n");
+ printf("║ \x1b[1;97mT E T R I S\x1b[0m ║ ║ \x1b[1mNEXT "
+ "PIECE\x1b[0m ║\n");
printf("╠════════════════════════════╣ ║ ║\n");
// Draw visible area with next piece preview
diff --git a/config.mk b/config.default.mk
similarity index 56%
rename from config.mk
rename to config.default.mk
index 839f6bcc..51ee94ee 100644
--- a/config.mk
+++ b/config.default.mk
@@ -1,29 +1,30 @@
-# config.mk
+# config.default.mk
-CC = g++
-CFLAGS = -Wall -Wextra -std=c17 -Wno-unused-variable -O2 -x c # -x c tells g++ to treat files as C code
-LDFLAGS =
-INCLUDES = -Isrc
+CC ?= gcc
+CFLAGS ?= -Wall -Wextra -std=c17 -Wno-unused-variable -O2
+LDFLAGS ?=
+INCLUDES ?= -Isrc
# LLVM configuration - request all necessary components
LLVM_CFLAGS := $(shell llvm-config --cflags)
LLVM_LDFLAGS := $(shell llvm-config --ldflags)
LLVM_LIBS := $(shell llvm-config --libs --system-libs all)
-# Alternative: specify exactly what you need
-# LLVM_LIBS := $(shell llvm-config --libs --system-libs core analysis bitwriter target irreader asmparser mcparser mc bitreader support demangle)
-
# Add LLVM flags to existing flags
override CFLAGS += $(LLVM_CFLAGS)
-override LDFLAGS += $(LLVM_LDFLAGS) $(LLVM_LIBS)
+override LDFLAGS += $(LLVM_LDFLAGS) $(LLVM_LIBS) -lstdc++
SRC_DIR = src
OBJ_DIR = build
+# Recursive function to find all .c files
define find_c_sources
$(wildcard $(1)/*.c) \
$(foreach d,$(wildcard $(1)/*),$(call find_c_sources,$(d)))
endef
+# Find all source files recursively
SRC_FILES := $(call find_c_sources,$(SRC_DIR))
-OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRC_FILES))
+
+# Generate object file paths
+OBJ_FILES := $(patsubst $(SRC_DIR)/%.c,$(OBJ_DIR)/%.o,$(SRC_FILES))
\ No newline at end of file
diff --git a/config.mk.in b/config.mk.in
new file mode 100644
index 00000000..bc5018f2
--- /dev/null
+++ b/config.mk.in
@@ -0,0 +1,14 @@
+CC = @CC@
+CFLAGS += @CXXFLAGS@
+LDFLAGS += @LDFLAGS@
+PREFIX = @prefix@
+
+INSTALL ?= install
+DESTDIR ?= $(PREFIX)/bin
+
+.PHONY: install
+
+install: $(BIN)
+ $(INSTALL) -d $(DESTDIR)
+ $(INSTALL) -s -m 0755 $(BIN) $(DESTDIR)/$(BIN)
+ @echo "Installed to $(DESTDIR)/$(BIN)"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 00000000..0de8e859
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,21 @@
+AC_INIT([luma], [git], [https://github.com/TheDevConnor/Luma])
+AC_PREREQ([2.69])
+AC_CONFIG_SRCDIR([src/main.c]) # sanity check to make sure someone didn't pass a garbage --srcdir
+#AM_INIT_AUTOMAKE([foreign 1.16 dist-bzip2])
+
+AC_PROG_CC
+AC_LANG(C)
+AC_SUBST(CC)
+# connor, i have no idea why you're expecting CC to be a C++ compiler.
+# that's what CXX is for.
+
+CXXFLAGS="$CXXFLAGS $CFLAGS $CPPFLAGS" # grab any env for flags
+LDFLAGS="$LDFLAGS"
+AC_SUBST(CXXFLAGS)
+AC_SUBST(LDFLAGS)
+
+AC_PREFIX_DEFAULT(/usr/local)
+AC_SUBST(prefix)
+
+AC_CONFIG_FILES([config.mk])
+AC_OUTPUT
diff --git a/default.nix b/default.nix
new file mode 100644
index 00000000..09acb8b6
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,41 @@
+{ stdenv
+, pkg-config
+, llvmPackages
+, autoconf
+, doxygen
+, graphviz-nox
+}:
+
+llvmPackages.stdenv.mkDerivation {
+ pname = "luma";
+ version = "0.0.0"; # TODO: versioning
+
+ src = ./.;
+
+ nativeBuildInputs = [
+ pkg-config
+ autoconf
+ doxygen
+ graphviz-nox
+ ];
+
+ outputs = [
+ "out"
+ "doc"
+ ];
+
+ preConfigure = ''
+ autoconf
+ '';
+
+ postInstall = ''
+ doxygen Doxyfile
+ mv docs/doxygen $doc
+ '';
+
+ buildInputs = [
+ llvmPackages.libllvm
+ ];
+
+ doCheck = true;
+}
diff --git a/docs/docs.md b/docs/docs.md
index bbc864d7..bcd08d14 100644
--- a/docs/docs.md
+++ b/docs/docs.md
@@ -46,24 +46,24 @@ Here's a complete Luma program that demonstrates the core language features:
```luma
@module "main"
-const Point = struct {
+const Point -> struct {
x: int,
y: int,
- distance_to = fn (other: Point) float {
+ distance_to -> fn (other: Point) float {
let dx: int = other.x - x;
let dy: int = other.y - y;
return sqrt(cast(dx * dx + dy * dy));
}
};
-const Status = enum {
+const Status -> enum {
Active,
Inactive,
Pending,
};
-const main = fn () int {
+pub const main -> fn () int {
let origin: Point = Point { x: 0, y: 0 };
let destination: Point = Point { x: 3, y: 4 };
let current_status: Status = Status::Active;
@@ -100,7 +100,7 @@ Luma provides a straightforward type system with both primitive and compound typ
| Type | Description | Size |
|------|-------------|------|
| `int` | Signed integer | 64-bit |
-| `uint` | Unsigned integer | 64-bit |
+| `uint` | Unsigned integer | 64-bit | (not yet added)
| `float` | Floating point | 32-bit |
| `double` | Floating point | 64-bit |
| `bool` | Boolean | 1 byte |
@@ -112,7 +112,7 @@ Luma provides a straightforward type system with both primitive and compound typ
Enums provide type-safe constants with clean syntax:
```luma
-const Direction = enum {
+const Direction -> enum {
North,
South,
East,
@@ -127,13 +127,13 @@ const current_direction: Direction = Direction::North;
Structures group related data with optional access control:
```luma
-const Point = struct {
+const Point -> struct {
x: int,
y: int
};
// With explicit access modifiers
-const Player = struct {
+const Player -> struct {
pub:
name: str,
score: int,
@@ -141,7 +141,7 @@ priv:
internal_id: uint,
// Methods can be defined inside structs
- get_display_name = fn () str {
+ get_display_name -> fn () str {
return name + " (" + str(score) + " pts)";
}
};
@@ -160,7 +160,7 @@ const player: Player = Player {
---
-## Generics
+## Generics (Not yet supported)
Luma supports generic programming through templates, enabling you to write code that works with multiple types while maintaining type safety and zero-cost abstractions.
@@ -218,7 +218,7 @@ const main = fn() int {
Structs can also be generic, allowing you to create container types and data structures that work with any type:
```luma
-const Box = struct {
+const Box -> struct {
value: T,
get = fn() T {
@@ -230,7 +230,7 @@ const Box = struct {
}
};
-const Pair = struct {
+const Pair -> struct {
first: T,
second: U
};
@@ -287,10 +287,10 @@ Luma uses the `const` keyword as a **unified declaration mechanism** for all top
```luma
const NUM: int = 42; // Immutable variable
-const Direction = enum { North, South, East, West }; // Enum definition
-const Point = struct { x: int, y: int }; // Struct definition
-const Box = struct { value: T }; // Generic struct
-const add = fn (a: int, b: int) int { // Function definition
+const Direction -> enum { North, South, East, West }; // Enum definition
+const Point -> struct { x: int, y: int }; // Struct definition
+const Box -> struct { value: T }; // Generic struct
+const add -> fn (a: int, b: int) int { // Function definition
return a + b;
};
const max = fn(a: T, b: T) T { // Generic function
@@ -315,7 +315,7 @@ const max = fn(a: T, b: T) T { // Generic function
const x: int = 5;
x = 10; // ❌ Error: `x` is immutable
-const add = fn (a: int, b: int) int { return a + b; };
+const add -> fn (a: int, b: int) int { return a + b; };
add = something_else; // ❌ Error: cannot reassign function binding
```
@@ -326,7 +326,7 @@ add = something_else; // ❌ Error: cannot reassign function binding
Inside functions, use `let` to declare local variables:
```luma
-const main = fn () int {
+const main -> fn () int {
let x: int = 10; // Mutable local variable
x = 20; // ✅ Can be reassigned
@@ -356,17 +356,17 @@ Functions are first-class values in Luma.
```luma
// Basic function
-const add = fn (a: int, b: int) int {
+const add -> fn (a: int, b: int) int {
return a + b;
};
// Function with no parameters
-const greet = fn () void {
+const greet -> fn () void {
outputln("Hello!");
};
// Function with no return value
-const print_number = fn (n: int) void {
+const print_number -> fn (n: int) void {
outputln("Number: ", n);
};
```
@@ -374,7 +374,7 @@ const print_number = fn (n: int) void {
### Function Calls
```luma
-const main = fn () int {
+const main -> fn () int {
let result: int = add(5, 3);
outputln("5 + 3 = ", result);
@@ -390,11 +390,11 @@ const main = fn () int {
Parameters are passed by value by default:
```luma
-const modify = fn (x: int) void {
+const modify -> fn (x: int) void {
x = 100; // Modifies local copy only
};
-const main = fn () int {
+const main -> fn () int {
let num: int = 10;
modify(num);
outputln(num); // Still 10
@@ -405,11 +405,11 @@ const main = fn () int {
To modify the caller's variable, use pointers:
```luma
-const modify_ptr = fn (x: *int) void {
+const modify_ptr -> fn (x: *int) void {
*x = 100; // Modifies original value
};
-const main = fn () int {
+const main -> fn () int {
let num: int = 10;
modify_ptr(&num); // Pass address
outputln(num); // Now 100
@@ -421,24 +421,24 @@ const main = fn () int {
```luma
// Single return value
-const square = fn (x: int) int {
+const square -> fn (x: int) int {
return x * x;
};
// Multiple return values via struct
-const DivResult = struct {
+const DivResult -> struct {
quotient: int,
remainder: int
};
-const divide = fn (a: int, b: int) DivResult {
+const divide -> fn (a: int, b: int) DivResult {
return DivResult {
quotient: a / b,
remainder: a % b
};
};
-const main = fn () int {
+const main -> fn () int {
let result: DivResult = divide(17, 5);
outputln("17 / 5 = ", result.quotient, " R ", result.remainder);
return 0;
@@ -448,7 +448,7 @@ const main = fn () int {
### Early Returns
```luma
-const find_positive = fn (numbers: *int, size: int) int {
+const find_positive -> fn (numbers: *int, size: int) int {
loop [i: int = 0](i < size) : (++i) {
if (numbers[i] > 0) {
return numbers[i]; // Early return
@@ -610,7 +610,7 @@ Luma provides powerful pattern matching through `switch` statements that work wi
```luma
@module "main"
-const WeekDay = enum {
+const WeekDay -> enum {
Sunday,
Monday,
Tuesday,
@@ -620,7 +620,7 @@ const WeekDay = enum {
Saturday,
};
-const classify_day = fn (day: WeekDay) void {
+const classify_day -> fn (day: WeekDay) void {
switch (day) {
WeekDay::Monday, WeekDay::Tuesday, WeekDay::Wednesday,
WeekDay::Thursday, WeekDay::Friday =>
@@ -630,7 +630,7 @@ const classify_day = fn (day: WeekDay) void {
}
}
-pub const main = fn () int {
+pub const main -> fn () int {
classify_day(WeekDay::Monday); // Output: Weekday => 1
classify_day(WeekDay::Saturday); // Output: Weekend => 6
return 0;
@@ -642,7 +642,7 @@ pub const main = fn () int {
When you need to handle unexpected values or want a catch-all case, use the default wildcard pattern `_`:
```luma
-const handle_status_code = fn (code: int) void {
+const handle_status_code -> fn (code: int) void {
switch (code) {
200 => outputln("OK");
404 => outputln("Not Found");
@@ -685,7 +685,7 @@ Use the `@use` directive to import other modules:
@use "math" as math
@use "string" as str
-const main = fn () int {
+const main -> fn () int {
// Access imported functions with namespace
let result: double = math::sqrt(25.0);
let len: int = str::strlen("hello");
@@ -719,7 +719,7 @@ outputln(...) // Print values with newline
Both functions are **variadic** - they accept any number of arguments of any type:
```luma
-const main = fn () int {
+const main -> fn () int {
output("Hello", " ", "World"); // Hello World
outputln("The answer is:", 42); // The answer is: 42\n
@@ -740,7 +740,7 @@ input(prompt: *char) -> T // Read typed input
The `input` function is generic and reads a value of the specified type:
```luma
-const main = fn () int {
+const main -> fn () int {
let name: *char = input<*char>("Enter your name: ");
let age: int = input("Enter your age: ");
let height: double = input("Enter height (meters): ");
@@ -762,7 +762,7 @@ system(command: *char) -> int // Execute system command
Execute shell commands from your program:
```luma
-const main = fn () int {
+const main -> fn () int {
system("clear"); // Clear terminal (Linux/Mac)
system("stty -icanon -echo"); // Configure terminal
return 0;
@@ -778,7 +778,7 @@ sizeof -> int // Size of type in bytes
Get the size of any type at compile time:
```luma
-const main = fn () int {
+const main -> fn () int {
outputln("int: ", sizeof); // 8
outputln("char: ", sizeof); // 1
outputln("double: ", sizeof); // 8
@@ -806,7 +806,7 @@ cast(expression)
### Numeric Conversions
```luma
-const main = fn () int {
+const main -> fn () int {
// Integer to float
let i: int = 42;
let f: float = cast(i); // 42.0
@@ -826,7 +826,7 @@ const main = fn () int {
### Pointer Casting
```luma
-const main = fn () int {
+const main -> fn () int {
// void* to typed pointer
let raw: *void = alloc(sizeof);
let typed: *int = cast<*int>(raw);
@@ -845,7 +845,7 @@ const main = fn () int {
### Pointer to Integer (and back)
```luma
-const main = fn () int {
+const main -> fn () int {
let ptr: *char = cast<*char>(alloc(10));
defer free(ptr);
@@ -883,7 +883,7 @@ const PRIMES: [int; 5] = [2, 3, 5, 7, 11];
### Array Initialization
```luma
-const main = fn () int {
+const main -> fn () int {
// Uninitialized (contains garbage)
let data: [int; 5];
@@ -903,7 +903,7 @@ const main = fn () int {
### Array Access
```luma
-const main = fn () int {
+const main -> fn () int {
let numbers: [int; 5] = [10, 20, 30, 40, 50];
// Read elements
@@ -931,7 +931,7 @@ const main = fn () int {
String literals are null-terminated character arrays:
```luma
-const main = fn () int {
+const main -> fn () int {
// String literal - type is *char
let message: *char = "Hello, World!";
outputln(message);
@@ -945,7 +945,7 @@ const main = fn () int {
Single characters use single quotes:
```luma
-const main = fn () int {
+const main -> fn () int {
let letter: char = 'A'; // Character literal
let newline: char = '\n'; // Escape sequence
let tab: char = '\t'; // Tab character
@@ -976,7 +976,7 @@ Luma supports pointer arithmetic for low-level memory manipulation.
### Basic Pattern
```luma
-const main = fn () int {
+const main -> fn () int {
let arr: *int = cast<*int>(alloc(5 * sizeof));
defer free(arr);
@@ -1010,7 +1010,7 @@ Use `pub` to export items from a module:
// Public - accessible from other modules
pub const PI: double = 3.14159265359;
-pub const sqrt = fn (x: double) double {
+pub const sqrt -> fn (x: double) double {
return x;
}
@@ -1021,7 +1021,7 @@ const INTERNAL_CONSTANT: int = 42;
### Struct Access Control
```luma
-const Person = struct {
+const Person -> struct {
pub:
name: *char,
age: int,
@@ -1049,7 +1049,7 @@ sizeof -> int // Size of type
### Example Usage
```luma
-const main = fn () int {
+const main -> fn () int {
// Allocate memory
let ptr: *int = cast<*int>(alloc(sizeof));
@@ -1068,7 +1068,7 @@ const main = fn () int {
Ensure cleanup with `defer` statements that execute when leaving scope:
```luma
-const process_data = fn () void {
+const process_data -> fn () void {
let buffer: *int = cast<*int>(alloc(100 * sizeof));
defer free(buffer); // Guaranteed to run when function exits
@@ -1111,12 +1111,12 @@ Marks functions that allocate and return pointers:
```luma
#returns_ownership
-const create_buffer = fn (size: int) *int {
+const create_buffer -> fn (size: int) *int {
let buffer: *int = cast<*int>(alloc(size * sizeof));
return buffer; // Caller now owns this memory
}
-const main = fn () int {
+const main -> fn () int {
let data: *int = create_buffer(100);
defer free(data); // Caller must free
return 0;
@@ -1129,12 +1129,12 @@ Marks functions that take ownership of pointer arguments:
```luma
#takes_ownership
-const consume_buffer = fn (buffer: *int) void {
+const consume_buffer -> fn (buffer: *int) void {
outputln("Processing: ", *buffer);
free(buffer); // Function owns and frees the buffer
}
-const main = fn () int {
+const main -> fn () int {
let data: *int = cast<*int>(alloc(sizeof));
*data = 42;
@@ -1158,13 +1158,13 @@ Luma's compiler includes a static analyzer that tracks memory at compile time:
**Example:**
```luma
-const good_memory_usage = fn () void {
+const good_memory_usage -> fn () void {
let ptr: *int = cast<*int>(alloc(sizeof));
defer free(ptr); // ✅ Analyzer confirms cleanup
*ptr = 42;
}
-const bad_memory_usage = fn () void {
+const bad_memory_usage -> fn () void {
let ptr: *int = cast<*int>(alloc(sizeof));
*ptr = 42;
// ❌ Compiler error: memory leak - ptr never freed
@@ -1184,7 +1184,7 @@ const SUCCESS: int = 0;
const ERROR_NULL_PTR: int = -1;
const ERROR_OUT_OF_BOUNDS: int = -2;
-const safe_divide = fn (a: int, b: int, result: *int) int {
+const safe_divide -> fn (a: int, b: int, result: *int) int {
if (b == 0) {
return ERROR_OUT_OF_BOUNDS;
}
@@ -1197,7 +1197,7 @@ const safe_divide = fn (a: int, b: int, result: *int) int {
return SUCCESS;
}
-const main = fn () int {
+const main -> fn () int {
let quotient: int;
let status: int = safe_divide(10, 2, "ient);
@@ -1214,7 +1214,7 @@ const main = fn () int {
### Pattern 2: Nullable Pointers
```luma
-const find_element = fn (arr: *int, size: int, value: int) *int {
+const find_element -> fn (arr: *int, size: int, value: int) *int {
loop [i: int = 0](i < size) : (++i) {
if (arr[i] == value) {
let addr: int = cast(arr) + (i * sizeof);
@@ -1224,7 +1224,7 @@ const find_element = fn (arr: *int, size: int, value: int) *int {
return cast<*int>(0); // Not found - return null
}
-const main = fn () int {
+const main -> fn () int {
let numbers: [int; 5] = [10, 20, 30, 40, 50];
let found: *int = find_element(cast<*int>(&numbers), 5, 30);
@@ -1241,13 +1241,13 @@ const main = fn () int {
### Pattern 3: Result Struct
```luma
-const Result = struct {
+const Result -> struct {
success: bool,
value: int,
error_code: int
};
-const safe_operation = fn (x: int) Result {
+const safe_operation -> fn (x: int) Result {
if (x < 0) {
return Result {
success: false,
@@ -1263,7 +1263,7 @@ const safe_operation = fn (x: int) Result {
};
}
-const main = fn () int {
+const main -> fn () int {
let result: Result = safe_operation(-5);
if (!result.success) {
@@ -1352,7 +1352,7 @@ const max = fn(a: T, b: T) T {
**Struct layout is predictable:**
```luma
-const Point = struct {
+const Point -> struct {
x: int, // Offset 0, 8 bytes
y: int // Offset 8, 8 bytes
}; // Total: 16 bytes
@@ -1368,7 +1368,7 @@ let arr: [int; 10]; // 80 contiguous bytes
**Stack allocation is fast:**
```luma
-const fast_function = fn () void {
+const fast_function -> fn () void {
let buffer: [int; 1024]; // Stack allocated - instant
// Use buffer...
} // Automatically cleaned up
@@ -1376,7 +1376,7 @@ const fast_function = fn () void {
**Heap allocation has overhead:**
```luma
-const slower_function = fn () void {
+const slower_function -> fn () void {
let buffer: *int = cast<*int>(alloc(1024 * sizeof));
defer free(buffer);
// Use buffer...
@@ -1413,10 +1413,10 @@ free(buffer);
**4. Avoid unnecessary copying:**
```luma
// Bad: pass large struct by value
-const process = fn (data: LargeStruct) void { }
+const process -> fn (data: LargeStruct) void { }
// Good: pass by pointer
-const process_fast = fn (data: *LargeStruct) void { }
+const process_fast -> fn (data: *LargeStruct) void { }
```
### Performance Summary
@@ -1478,7 +1478,7 @@ math::fib(n, a, b) // Fibonacci
```luma
@use "math" as math
-const main = fn () int {
+const main -> fn () int {
let angle: double = math::PI / 4.0;
let sine: double = math::sin(angle);
outputln("sin(π/4) = ", sine);
@@ -1519,7 +1519,7 @@ mem::memeq(a, b, n) // Check equality
```luma
@use "memory" as mem
-const main = fn () int {
+const main -> fn () int {
let buffer: *void = mem::calloc(10, sizeof);
defer free(buffer);
@@ -1560,7 +1560,7 @@ string::cat(dest, s1, s2) // Concatenate
```luma
@use "string" as string
-const main = fn () int {
+const main -> fn () int {
let name: *char = "Alice";
let len: int = string::strlen(name);
outputln("Length: ", len);
@@ -1613,7 +1613,7 @@ fx::RESET
```luma
@use "termfx" as fx
-const main = fn () int {
+const main -> fn () int {
output(fx::CLEAR_SCREEN, fx::CURSOR_HOME);
output(fx::BOLD, fx::RED, "ERROR: ", fx::RESET);
outputln("Something went wrong!");
@@ -1643,7 +1643,7 @@ term::getpass(prompt) // Get password (hidden input)
@use "terminal" as term
@use "string" as string
-const main = fn () int {
+const main -> fn () int {
outputln("Press any key...");
let key: char = term::getch();
outputln("You pressed: ", string::from_char(key));
@@ -1664,12 +1664,12 @@ const main = fn () int {
@module "mymodule"
// Private helper
-const helper = fn (x: int) int {
+const helper -> fn (x: int) int {
return x * 2;
}
// Public function
-pub const process = fn (data: *int, size: int) void {
+pub const process -> fn (data: *int, size: int) void {
loop [i: int = 0](i < size) : (++i) {
data[i] = helper(data[i]);
}
@@ -1686,7 +1686,7 @@ pub const VERSION: int = 1;
@use "mymodule" as mm
-const main = fn () int {
+const main -> fn () int {
let data: [int; 5] = [1, 2, 3, 4, 5];
mm::process(cast<*int>(&data), 5);
outputln("Version: ", mm::VERSION);
@@ -1802,4 +1802,4 @@ Luma is a modern systems programming language that provides:
The language is designed for programmers who want the performance and control of C with modern safety features and ergonomics.
-For more examples, see the standard library modules and test files included with the language distribution.
\ No newline at end of file
+For more examples, see the standard library modules and test files included with the language distribution.
diff --git a/docs/mainpage.dox b/docs/mainpage.dox
index fd0e129b..8bf75ba7 100644
--- a/docs/mainpage.dox
+++ b/docs/mainpage.dox
@@ -1,18 +1,18 @@
/**
- * @mainpage Lux
+ * @mainpage Luma
*
- * @image html assets/lux.png width=120
+ * @image html assets/luma.png width=120
*
* @section tagline A low-level compiled alternative to C, C++, and more!
*
* @section intro Introduction
- * Lux is a modern systems programming language designed to provide the performance
+ * Luma is a modern systems programming language designed to provide the performance
* and control of low-level languages while maintaining developer productivity and
* code clarity. Built from the ground up to address common pain points in systems programming.
*
* @section why Why?
* Modern systems programming often involves a trade-off between performance,
- * safety, and developer experience. Lux aims to bridge this gap by providing:
+ * safety, and developer experience. Luma aims to bridge this gap by providing:
* - **Direct hardware access** without sacrificing code readability
* - **Predictable performance** characteristics for systems-critical applications
* - **Developer-friendly tooling** that doesn't compromise on compile speed
@@ -27,7 +27,7 @@
*
* @section status Project Status
* **Current Phase:** Early Development
- * Lux is currently in active development. Core language features are being implemented
+ * Luma is currently in active development. Core language features are being implemented
* and the compiler architecture is being established.
*
* **What's Working:**
@@ -49,7 +49,7 @@
* rankdir=LR;
* node [shape=box, style=rounded, fontsize=12, fontname="Helvetica"];
*
- * SourceCode [label="Lux Source Code (.lx)"];
+ * SourceCode [label="Luma Source Code (.lx)"];
* Lexer [label="Lexer"];
* Parser [label="Pratt Parser"];
* TypeChecker [label="Type Checker"];
@@ -65,9 +65,9 @@
* *(usage instructions coming soon)*
*
* @section join_us Join Us
- * Interested in contributing to Lux? We'd love to have you!
+ * Interested in contributing to Luma? We'd love to have you!
* *(links coming soon)*
*
* ---
- * **Built with ❤️ by the Lux community**
+ * **Built with ❤️ by the Luma community**
*/
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 00000000..e54e9ae7
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,61 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "inputs": {
+ "systems": "systems"
+ },
+ "locked": {
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1762361079,
+ "narHash": "sha256-lz718rr1BDpZBYk7+G8cE6wee3PiBUpn8aomG/vLLiY=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "ffcdcf99d65c61956d882df249a9be53e5902ea5",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "ref": "nixpkgs-unstable",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ },
+ "systems": {
+ "locked": {
+ "lastModified": 1681028828,
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+ "owner": "nix-systems",
+ "repo": "default",
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nix-systems",
+ "repo": "default",
+ "type": "github"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 00000000..1112ddb1
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,20 @@
+{
+ description = "Luma";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ in
+ {
+ packages = rec {
+ default = luma;
+ luma = pkgs.callPackage ./default.nix { };
+ };
+ }
+ );
+}
diff --git a/ideas.md b/ideas.md
index edda7499..40b2ca02 100644
--- a/ideas.md
+++ b/ideas.md
@@ -1,27 +1,29 @@
-## Fixing C:
- - context free grammar
- - no preprocessor
- - strong typing
- - no declaration order
- - defined fixed sized primitives
- - replace unsigned with natural that can overflow
- - no integral promotion
- - checked integer arithmetic
- - bit array primitive, don't conflate bit operations with integers
- - texts literals not null terminated
- - remove varargs
- - named function parameters
- - defined expression evaluation order
- - remove comma operator
- - product type with undefined layout
- - object sizes can be 0
- - remove pointer arithmetic
- - arrays have value semantics, remove array implicit conversion to pointer
- - modules, including modulemap
- - (probably many more, maybe starting with C not a great idea)
+# Fixing C
+
+- context free grammar
+- no preprocessor
+- strong typing
+- no declaration order
+- defined fixed sized primitives
+- replace unsigned with natural that can overflow
+- no integral promotion
+- checked integer arithmetic
+- bit array primitive, don't conflate bit operations with integers
+- texts literals not null terminated
+- remove varargs
+- named function parameters
+- defined expression evaluation order
+- remove comma operator
+- product type with undefined layout
+- object sizes can be 0
+- remove pointer arithmetic
+- arrays have value semantics, remove array implicit conversion to pointer
+- modules, including modulemap
+- (probably many more, maybe starting with C not a great idea)
## NOTE: for runtime constants on switch cases
-```
+
+```txt
LLVM IRError: Case values must be compile-time constants
Error: Case value must be a compile-time constant
Basic Block in function 'main' does not have terminator!
@@ -30,7 +32,8 @@ LLVM ERROR: Broken module found, compilation aborted!
```
## Linked List Ideas
-```lux
+
+```luma
;; Syntax will change on somethins
const Link = struct {
tag: int,
@@ -65,7 +68,7 @@ const link_list_length = fn (list: *Link) int {
};
```
-# C Interoperability in Luma
+## C Interoperability in Luma
## Overview
@@ -109,7 +112,9 @@ pub const main = fn () int {
```
## impl and its unique features
-# simple example
+
+## simple example
+
impl [func list...] -> [struct list...] {
if (1 > 0) {
define a function one way
@@ -117,39 +122,66 @@ impl [func list...] -> [struct list...] {
define the same function another way
}
}
-# The goals of the impl is to implement functions for structs.
-# It should have the ability to conditionally make functions.
-# The functions are `injected` into the struct and can reference
-# `self` which is a pointer to the struct AstNode that should already be there.
-# There should be two types of functions for this instance. Runtime ready, and compile time optional.
-# with a tag of #runtime, the function will be compiled and optionally ran during runtime.
-# two instances of the same function can only run one at a time, and must be derived from an expression.
-# a #compile tag from a function within a conditional will be optionally compiled, and so only one available at runtime.
-# Why the two? One use case for @run_time is to allow dynamic function assignment. Lets say you must work with an api.
-# This api does not respond with the same data, same type of data and so on. This means you can write multiple capture() functions.
-# Yes this is function overloading, but conditionally, and can be programmed for the potential context the appliction will be in.
-# For @compile_time, it optionally compiles one of the implementations of the function. Say you need portability, you can use the same
-# source code and target specific architectures. This can be thought of #IF_WINDOWS bullshit from C, you can conditionaly compile
-# one function or another, but in a nice and effecient way.
+
+## The goals of the impl is to implement functions for structs
+
+## It should have the ability to conditionally make functions
+
+## The functions are `injected` into the struct and can reference
+
+## `self` which is a pointer to the struct AstNode that should already be there
+
+## There should be two types of functions for this instance. Runtime ready, and compile time optional
+
+## with a tag of #runtime, the function will be compiled and optionally ran during runtime
+
+## two instances of the same function can only run one at a time, and must be derived from an expression
+
+## a #compile tag from a function within a conditional will be optionally compiled, and so only one available at runtime
+
+## Why the two? One use case for @run_time is to allow dynamic function assignment. Lets say you must work with an api
+
+## This api does not respond with the same data, same type of data and so on. This means you can write multiple capture() functions
+
+## Yes this is function overloading, but conditionally, and can be programmed for the potential context the appliction will be in
+
+## For @compile_time, it optionally compiles one of the implementations of the function. Say you need portability, you can use the same
+
+## source code and target specific architectures. This can be thought of #IF_WINDOWS bullshit from C, you can conditionaly compile
+
+## one function or another, but in a nice and effecient way
## the ? and None type
+
someType: ?; # is a None, or a real type.
-# Thats what it does. Gives a way to init without a type. Can also be used to identify things. if (someType? == None) {return "Nothing found";}
-# It is our solution to NULL, but it provides a more meaning. Because it can also be a value if (someType?) {return "value found";}
-# This is very straigh forward in its concept. someType? returns the value inside, or it returns the None type.
+
+## Thats what it does. Gives a way to init without a type. Can also be used to identify things. if (someType? == None) {return "Nothing found";}
+
+## It is our solution to NULL, but it provides a more meaning. Because it can also be a value if (someType?) {return "value found";}
+
+## This is very straigh forward in its concept. someType? returns the value inside, or it returns the None type
## the set type
-# A set is a fixed sized array of types
-# a, b, c : (int, float, char);
-# const func1 = fn () (int, float, int) {}
-# The same thing.
-# a, b, c : () = func1;
-# changes the the position for the returning set.
-# a, b, c : (float, int, int) = func1;
-# The () is here to be explicit that we are expecting a set from func1.
-
-## Luma Post-Processing and Build System (LPBS).
-# LPBS is made in Luma and uses a subset (vars, funcs, if (expr)).
+
+## A set is a fixed sized array of types
+
+## a, b, c : (int, float, char)
+
+## const func1 = fn () (int, float, int) {}
+
+## The same thing
+
+## a, b, c : () = func1
+
+## changes the the position for the returning set
+
+## a, b, c : (float, int, int) = func1
+
+## The () is here to be explicit that we are expecting a set from func1
+
+## Luma Post-Processing and Build System (LPBS)
+
+## LPBS is made in Luma and uses a subset (vars, funcs, if (expr))
// -V2 : verbose level 2 for strictness?
// -OB : object files
@@ -195,25 +227,32 @@ clean_all -> (where) {
output("Removed all artefacts\n");
}
-# compile: and clean: are labels, there are the external commands a user can run (lpbs compile, lpbs clean).
-# Lets break it down. Post-Processing is about understanding end context from the src code and a solution.
-# The LPB System should generate bindings for the end result.
-# It should manage ffi for C and providing that compatability.
-# It should provide a way to create, manage, and work with shared libraries or static libraries.
-# Then it should build the program, it should read in a simp
+## compile: and clean: are labels, there are the external commands a user can run (lpbs compile, lpbs clean)
+
+## Lets break it down. Post-Processing is about understanding end context from the src code and a solution
+
+## The LPB System should generate bindings for the end result
+
+## It should manage ffi for C and providing that compatability
+
+## It should provide a way to create, manage, and work with shared libraries or static libraries
+
+## Then it should build the program, it should read in a simp
### Manual Binding Generation
+
```bash
-# Generate bindings from C header
+## Generate bindings from C header
./luma bindgen stdio.h -o std/stdio.lx
-# Build with explicit linking
+## Build with explicit linking
./luma build main.lx -l std/stdio.lx -name main
```
### Automatic Header Detection
+
```bash
-# Build system detects and handles C dependencies
+## Build system detects and handles C dependencies
./luma build main.lx -l std/stdio.lx -link-c stdio -name main
```
@@ -236,17 +275,17 @@ pub const main = fn () int {
## Comprehensive Standard Library Generation
```bash
-# Generate all standard C library bindings at once
+## Generate all standard C library bindings at once
./luma bindgen --std-headers -o std/
-# This creates:
-# std/stdio.lx
-# std/stdlib.lx
-# std/math.lx
-# std/string.lx
-# etc.
+## This creates:
+## std/stdio.lx
+## std/stdlib.lx
+## std/math.lx
+## std/string.lx
+## etc.
-# Build with multiple C libraries
+## Build with multiple C libraries
./luma build main.lx -l std/stdio.lx -l std/math.lx -name main
```
@@ -267,26 +306,32 @@ extern "C" {
```
Build system automatically detects linking requirements:
+
```bash
-# Build system sees @link_lib "c" and automatically adds -lc
+## Build system sees @link_lib "c" and automatically adds -lc
./luma build main.lx -l std/stdio.lx -name main
```
## Key Benefits
### 1. **Module System Consistency**
+
C bindings are treated as regular Luma modules, maintaining language design coherence.
### 2. **Explicit Dependency Management**
+
Developers maintain full control over what gets linked and in what order, respecting the build system's link order requirements.
### 3. **Namespace Protection**
+
The `@use "stdio" as io` pattern prevents namespace pollution while clearly indicating C library usage.
### 4. **Tooling Integration**
+
Automatic binding generation creates proper Luma modules that integrate seamlessly with existing tools.
### 5. **Build System Compatibility**
+
Works within the existing build system architecture without requiring fundamental changes.
## Implementation Strategy
@@ -298,5 +343,6 @@ Works within the existing build system architecture without requiring fundamenta
This approach provides a foundation for C interoperability that feels natural to Luma developers while maintaining the language's design principles.
-## Look into adding in Multithreading
+## Look into adding in Multithreading
+
## Add in multiple return types. ``const createStack = fn (stackCeiling: int) <*Stack, *void>``
diff --git a/install.bat b/install.bat
new file mode 100644
index 00000000..7b75faff
--- /dev/null
+++ b/install.bat
@@ -0,0 +1,165 @@
+@echo off
+setlocal enabledelayedexpansion
+
+:: Luma Compiler v0.1.0 - Windows Installer
+:: This script installs the Luma compiler and standard library
+
+echo.
+echo ================================================
+echo Luma Compiler v0.1.0 - Windows Installer
+echo ================================================
+echo.
+
+:: Check if running as administrator
+net session >nul 2>&1
+if %errorLevel% == 0 (
+ set "ADMIN=1"
+ echo Running with administrator privileges...
+ echo Installing system-wide to: C:\Program Files\luma
+ set "INSTALL_DIR=C:\Program Files\luma"
+) else (
+ set "ADMIN=0"
+ echo Running without administrator privileges...
+ echo Installing for current user to: %USERPROFILE%\.luma
+ set "INSTALL_DIR=%USERPROFILE%\.luma"
+)
+
+echo.
+
+:: Create installation directories
+echo Creating installation directories...
+if not exist "%INSTALL_DIR%\bin" mkdir "%INSTALL_DIR%\bin"
+if not exist "%INSTALL_DIR%\std" mkdir "%INSTALL_DIR%\std"
+
+if %errorLevel% neq 0 (
+ echo ERROR: Failed to create directories!
+ echo Please check permissions and try again.
+ pause
+ exit /b 1
+)
+
+:: Copy luma.exe
+echo Copying luma.exe...
+if exist "luma.exe" (
+ copy /Y "luma.exe" "%INSTALL_DIR%\bin\luma.exe" >nul
+ if %errorLevel% neq 0 (
+ echo ERROR: Failed to copy luma.exe!
+ pause
+ exit /b 1
+ )
+) else (
+ echo ERROR: luma.exe not found in current directory!
+ pause
+ exit /b 1
+)
+
+:: Copy standard library
+echo Copying standard library...
+if exist "std" (
+ xcopy /E /I /Y "std\*" "%INSTALL_DIR%\std\" >nul
+ if %errorLevel% neq 0 (
+ echo ERROR: Failed to copy standard library!
+ pause
+ exit /b 1
+ )
+) else (
+ echo WARNING: std\ directory not found. Standard library not installed.
+)
+
+echo.
+echo Installation completed successfully!
+echo.
+echo Luma installed to: %INSTALL_DIR%
+echo.
+
+:: Check if already in PATH
+echo %PATH% | findstr /C:"%INSTALL_DIR%\bin" >nul
+if %errorLevel% == 0 (
+ echo Path already configured correctly.
+ goto :verify
+)
+
+:: Add to PATH
+echo Configuring PATH...
+if %ADMIN% == 1 (
+ :: System-wide PATH (requires admin)
+ for /f "skip=2 tokens=3*" %%a in ('reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment" /v Path') do set "SYSTEM_PATH=%%a %%b"
+ setx /M PATH "%SYSTEM_PATH%;%INSTALL_DIR%\bin" >nul 2>&1
+ if %errorLevel% == 0 (
+ echo PATH updated successfully (system-wide^).
+ ) else (
+ echo WARNING: Failed to update system PATH automatically.
+ echo Please add manually: %INSTALL_DIR%\bin
+ )
+) else (
+ :: User PATH
+ for /f "skip=2 tokens=3*" %%a in ('reg query "HKCU\Environment" /v Path 2^>nul') do set "USER_PATH=%%a %%b"
+ if "!USER_PATH!" == "" (
+ setx PATH "%INSTALL_DIR%\bin" >nul 2>&1
+ ) else (
+ setx PATH "!USER_PATH!;%INSTALL_DIR%\bin" >nul 2>&1
+ )
+ if %errorLevel% == 0 (
+ echo PATH updated successfully (user-only^).
+ ) else (
+ echo WARNING: Failed to update user PATH automatically.
+ echo Please add manually: %INSTALL_DIR%\bin
+ )
+)
+
+:verify
+echo.
+echo ================================================
+echo Verifying installation...
+echo ================================================
+echo.
+
+:: Test if luma is accessible
+"%INSTALL_DIR%\bin\luma.exe" --version >nul 2>&1
+if %errorLevel% == 0 (
+ echo [OK] Luma compiler found
+ "%INSTALL_DIR%\bin\luma.exe" --version
+) else (
+ echo [WARNING] Could not verify luma installation
+)
+
+echo.
+
+:: Check standard library
+if exist "%INSTALL_DIR%\std\io.luma" (
+ echo [OK] Standard library installed
+) else (
+ echo [WARNING] Standard library may not be complete
+)
+
+echo.
+echo ================================================
+echo Installation Summary
+echo ================================================
+echo.
+echo Installation directory: %INSTALL_DIR%
+echo Compiler binary: %INSTALL_DIR%\bin\luma.exe
+echo Standard library: %INSTALL_DIR%\std\
+echo.
+
+if %ADMIN% == 0 (
+ echo IMPORTANT: You may need to restart your Command Prompt
+ echo for PATH changes to take effect.
+) else (
+ echo IMPORTANT: You may need to restart your Command Prompt
+ echo for PATH changes to take effect.
+)
+
+echo.
+echo To verify installation, open a NEW command prompt and run:
+echo luma --version
+echo.
+echo To uninstall, simply delete: %INSTALL_DIR%
+echo and remove from PATH if necessary.
+echo.
+echo ================================================
+echo Installation complete!
+echo ================================================
+echo.
+
+pause
diff --git a/install.sh b/install.sh
new file mode 100755
index 00000000..c43f3298
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+
+# Luma Compiler Installation Script v0.1.0
+# Installs the Luma compiler and standard library
+
+set -e
+
+VERSION="v0.1.0"
+INSTALL_DIR="/usr/local"
+BIN_DIR="$INSTALL_DIR/bin"
+LIB_DIR="$INSTALL_DIR/lib/luma"
+STD_DIR="$LIB_DIR/std"
+
+echo "======================================"
+echo " Luma Compiler $VERSION Installer"
+echo "======================================"
+echo ""
+
+# Check if running as root for system-wide install
+if [ "$EUID" -ne 0 ]; then
+ echo "Note: Not running as root. Will attempt user-local installation."
+ echo "For system-wide installation, run with sudo."
+ echo ""
+
+ INSTALL_DIR="$HOME/.local"
+ BIN_DIR="$INSTALL_DIR/bin"
+ LIB_DIR="$HOME/.luma"
+ STD_DIR="$LIB_DIR/std"
+ USER_INSTALL=true
+fi
+
+# Create directories
+echo "Creating installation directories..."
+mkdir -p "$BIN_DIR"
+mkdir -p "$STD_DIR"
+
+# Copy binary
+echo "Installing Luma compiler to $BIN_DIR..."
+if [ -f "./luma" ]; then
+ cp ./luma "$BIN_DIR/"
+ chmod +x "$BIN_DIR/luma"
+elif [ -f "./bin/luma" ]; then
+ cp ./bin/luma "$BIN_DIR/"
+ chmod +x "$BIN_DIR/luma"
+else
+ echo "Error: Could not find luma binary!"
+ echo "Please ensure 'luma' is in the current directory or bin/ subdirectory."
+ exit 1
+fi
+
+# Copy standard library
+echo "Installing standard library to $STD_DIR..."
+if [ -d "./std" ]; then
+ cp -r ./std/* "$STD_DIR/"
+elif [ -d "./lib/std" ]; then
+ cp -r ./lib/std/* "$STD_DIR/"
+else
+ echo "Warning: Standard library not found. Skipping..."
+fi
+
+echo ""
+echo "Installation complete!"
+echo ""
+echo "Installed to:"
+echo " Binary: $BIN_DIR/luma"
+echo " Std lib: $STD_DIR"
+echo ""
+
+# Check if bin directory is in PATH
+if [ "$USER_INSTALL" = true ]; then
+ if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
+ echo "IMPORTANT: Add the following to your ~/.bashrc or ~/.zshrc:"
+ echo ""
+ echo " export PATH=\"\$PATH:$BIN_DIR\""
+ echo ""
+ fi
+fi
+
+echo "Verify installation with: luma --version"
+echo ""
diff --git a/mockups/a.out b/mockups/a.out
deleted file mode 100755
index 2de10155..00000000
Binary files a/mockups/a.out and /dev/null differ
diff --git a/mockups/file.c b/mockups/file.c
deleted file mode 100644
index 7804f6d4..00000000
--- a/mockups/file.c
+++ /dev/null
@@ -1,141 +0,0 @@
-#include
-#include
-#include
-
-// used to prototype options
-enum { SUCCESS, ERROR };
-enum {
- FAIL_OPEN = 1,
- FAIL_READ = 2,
- FAIL_WRITE = 3,
- FAIL_ACCESS = 4,
- NONE = 0
-};
-
-typedef struct FILE_STRUCT {
- // store the pointer to the file
- FILE *fileptr;
- char *file_name;
- char *file_flag; // r, w, rw
- // position in the file
- int position;
-
-} File;
-
-// file return should return a file struct or error information
-typedef struct FILE_RET {
- File *file_core;
- int OK_ERR;
- int FAIL_ON;
-} File_Re;
-
-// private used to init the values of the file object
-File_Re *file_init(char *file_name, char *file_flag) {
- File_Re *fre = (File_Re *)malloc(sizeof(File_Re));
- if (!fre)
- return NULL;
-
- File *file = (File *)malloc(sizeof(File));
- if (!file) {
- free(fre);
- return NULL;
- }
-
- // FLAGS r, w, rw, a, ab, r+, w+,
- file->file_name = file_name;
- file->file_flag = file_flag;
- file->position = 0;
- file->fileptr = NULL;
-
- fre->file_core = file;
- fre->OK_ERR = SUCCESS;
- fre->FAIL_ON = NONE;
-
- return fre;
-}
-
-// used to open a specific file. Strict error handling so if a empty buffer was
-// passed in we say NO
-File_Re *file_open(File_Re *fre) {
- if (!fre->file_core) {
- if (!fre)
- return NULL;
- fre->OK_ERR = ERROR;
- fre->FAIL_ON = FAIL_OPEN;
- return fre;
- }
-
- // always try and open file
- fre->file_core->fileptr =
- fopen(fre->file_core->file_name, fre->file_core->file_flag);
- if (!fre->file_core->fileptr) {
- fre->OK_ERR = ERROR;
- fre->FAIL_ON = FAIL_OPEN;
- } else {
- fre->OK_ERR = SUCCESS;
- fre->FAIL_ON = NONE;
- }
- return fre;
-}
-
-// used to close a file object and deinit
-File_Re *file_close(File_Re *fre) {
- if (!fre)
- return NULL;
-
- if (fre->file_core) {
- if (fre->file_core->fileptr) {
- fclose(fre->file_core->fileptr);
- fre->file_core->fileptr = NULL;
- }
- free(fre->file_core);
- fre->file_core = NULL;
- }
- fre->OK_ERR = SUCCESS;
- fre->FAIL_ON = NONE;
- return fre;
-}
-
-char *fail_type(int type) {
- char *to_return;
- switch (type) {
- case FAIL_OPEN:
- to_return = "FAIL_OPEN";
- break;
- case FAIL_READ:
- to_return = "FAIL_READ";
- break;
- case FAIL_WRITE:
- to_return = "FAIL_WRITE";
- break;
- case FAIL_ACCESS:
- to_return = "FAIL_ACCESS";
- break;
- case NONE:
- to_return = "NONE";
- break;
- }
- return to_return;
-}
-
-int main(void) {
-
- File_Re *fre = file_init("text.txt", "r");
- if (!fre) {
- printf("Failed to init file\n");
- }
- printf("after init\n");
-
- fre = file_open(fre);
- if (fre->OK_ERR == ERROR)
- printf("Failed to open file, error: %s\n", fail_type(fre->FAIL_ON));
- else
- printf("After open\n");
-
- fre = file_close(fre);
- printf("after close\n");
-
- free(fre);
-
- return 0;
-}
diff --git a/mockups/text.txt b/mockups/text.txt
deleted file mode 100644
index b5fd817d..00000000
--- a/mockups/text.txt
+++ /dev/null
@@ -1 +0,0 @@
-some stuff
diff --git a/releases/v0-1-0.md b/releases/v0-1-0.md
deleted file mode 100644
index 5e425b1b..00000000
--- a/releases/v0-1-0.md
+++ /dev/null
@@ -1,109 +0,0 @@
-# 🚀 Luma v0.1.0 — Ownership and Static Analysis Update
-
-Luma is a modern, low-level compiled language focused on **manual memory management with compile-time safety**.
-
-This release introduces **ownership annotations**, **pointer aliasing tracking**, and significant improvements to the **static analyzer**.
-It's now capable of verifying most memory operations — before codegen — while keeping full manual control in the developer's hands.
-
----
-
-## ✨ Highlights
-
-### 🧠 Static Memory Analyzer
-Luma's static analyzer now validates heap operations at compile time:
-- Detects **use-after-free**, **double-free**, and **memory leaks**
-- Integrates with `defer` for automatic cleanup
-- Tracks **ownership transfers** between variables
-- Performs analysis at the end of type checking (no runtime cost)
-- Reports exact source locations for memory safety issues
-
-**Example:**
-```lux
-let p = alloc(32);
-free(p);
-use(p); // ❌ use-after-free (caught at compile time)
-```
-
-### 🔗 Pointer Aliasing Support
-
-Ownership and lifetime tracking now correctly handle pointer aliasing:
-
-```lux
-let c: *char = cast<*char>(alloc(6 * sizeof));
-let b: *char = c;
-defer { free(b); }
-```
-
-The analyzer recognizes `b` as an alias of `c` and ensures they share the same ownership record.
-- ✅ No double-free
-- ✅ No leaks
-- ✅ Clean valgrind output
-
-### 🏷️ Ownership Annotations
-
-Added two new annotations to help the analyzer understand memory semantics:
-
-| Annotation | Description |
-|------------|-------------|
-| `#returns_ownership` | Marks functions that return owned memory |
-| `#takes_ownership` | Marks parameters that consume ownership of their arguments |
-
-**Example:**
-
-```lux
-#returns_ownership
-pub const make_buffer = fn(size: int) *char {
- return alloc(size);
-}
-
-#takes_ownership
-pub const consume = fn(ptr: *char) void {
- free(ptr);
-}
-```
-
-These annotations make ownership intent explicit while avoiding a full borrow/lifetime system.
-
-### 🧩 Developer Experience
-
-- `alloc()`, `free()`, and `defer` now integrate directly with the analyzer
-- Improved diagnostic clarity (better source mapping and context)
-- Consistent behavior across alias chains
-- Simplified and cleaner syntax for low-level constructs
-
----
-
-## 🔮 Next Steps
-
-- Cross-function memory tracking
-- Struct and array allocation tracking
-- Codegen for struct (functions) and union
-- Generics and type inference
-
----
-
-## 🧠 Philosophy
-
-Luma's goal is to stay low-level and explicit — no garbage collector, no runtime lifetimes —
-just pure manual memory control with a static verifier ensuring it's safe.
-
-> "You choose when to free — Luma just makes sure you do it right."
-
----
-
-## 🧪 Verified Behavior
-
-- ✅ All test cases pass under Valgrind (no leaks)
-- ✅ Analyzer successfully detects invalid frees and missing frees
-- ✅ Pointer aliasing merges ownership safely
-- ✅ Deferred frees resolve correctly across scopes
-
----
-
-## 📦 Example
-For examples look at the test folder [here](../tests/) or the std folder here [here](../std/).
-
----
-
-Made with ❤️ by Connor Harris
-🔗 GitHub: [TheDevConnor/Luma](https://github.com/TheDevConnor/Luma)
diff --git a/releases/v0-1-0/luma-v0.1.0-linux-x86_64.tar.gz b/releases/v0-1-0/luma-v0.1.0-linux-x86_64.tar.gz
new file mode 100644
index 00000000..00dbf35a
Binary files /dev/null and b/releases/v0-1-0/luma-v0.1.0-linux-x86_64.tar.gz differ
diff --git a/releases/v0-1-0/luma-v0.1.0-windows.zip b/releases/v0-1-0/luma-v0.1.0-windows.zip
new file mode 100644
index 00000000..d60ea537
Binary files /dev/null and b/releases/v0-1-0/luma-v0.1.0-windows.zip differ
diff --git a/releases/v0-1-0/v0-1-0.md b/releases/v0-1-0/v0-1-0.md
new file mode 100644
index 00000000..dfc8601b
--- /dev/null
+++ b/releases/v0-1-0/v0-1-0.md
@@ -0,0 +1,292 @@
+# Luma v0.1.0 - Initial Release
+
+We're excited to announce the first public release of **Luma**, a statically typed, compiled systems programming language designed for simplicity, safety, and performance.
+
+## What is Luma?
+
+Luma combines the low-level control of C with modern safety features and a clean, consistent syntax. It's designed for systems programmers who want predictable performance without sacrificing code clarity or safety.
+
+### Core Principles
+
+- **Simplicity**: Minimal syntax with consistent patterns
+- **Safety**: Strong typing and compile-time memory safety verification
+- **Performance**: Zero-cost abstractions and predictable performance
+
+### Key Features
+
+- **Strong Static Type System** with explicit typing
+- **Manual Memory Management** with compile-time static analysis
+- **Struct Methods** for clean object-oriented patterns
+- **Ownership Annotations** for clear memory semantics
+- **Modern Safety Features** including defer statements and pointer aliasing tracking
+- **Simple Module System** for code organization
+- **Pattern Matching** via exhaustive switch statements
+- **Direct System Calls** for low-level control
+
+## What's New in v0.1.0
+
+### Static Memory Analyzer
+
+The compiler now validates heap operations at compile time:
+- Detects **use-after-free**, **double-free**, and **memory leaks**
+- Integrates with `defer` for automatic cleanup tracking
+- Tracks **ownership transfers** between variables
+- Performs analysis at the end of type checking (no runtime cost)
+- Reports exact source locations for memory safety issues
+
+**Example:**
+```luma
+let p = alloc(32);
+free(p);
+use(p); // Error: use-after-free (caught at compile time)
+```
+
+### Pointer Aliasing Support
+
+Ownership and lifetime tracking now correctly handle pointer aliasing:
+
+```luma
+let c: *char = cast<*char>(alloc(6 * sizeof));
+let b: *char = c;
+defer { free(b); }
+```
+
+The analyzer recognizes `b` as an alias of `c` and ensures they share the same ownership record, preventing double-free errors and memory leaks.
+
+### Ownership Annotations
+
+Two new function attributes help the analyzer understand memory semantics:
+
+**`#returns_ownership`** - Marks functions that return owned memory:
+
+```luma
+#returns_ownership
+pub const make_buffer -> fn(size: int) *char {
+ return alloc(size);
+}
+```
+
+**`#takes_ownership`** - Marks parameters that consume ownership:
+
+```luma
+#takes_ownership
+pub const consume -> fn(ptr: *char) void {
+ free(ptr);
+}
+```
+
+These annotations make ownership intent explicit while avoiding a full borrow/lifetime system.
+
+## What's Included
+
+### Language Features
+
+- Complete type system (primitives, structs, enums, arrays, pointers)
+- Struct methods
+- Pattern matching with switch statements
+- Defer statements for resource cleanup
+- Static memory analysis (leak detection, double-free prevention)
+- Ownership attributes (`#returns_ownership`, `#takes_ownership`)
+- Module system with `@use` and `@module`
+
+### Standard Library
+
+The v0.1.0 release includes several essential modules:
+
+- **`math`** - Mathematical operations, trigonometry, constants
+- **`memory`** - Low-level memory operations (memcpy, memset, calloc, etc.)
+- **`string`** - String manipulation and conversion functions
+- **`sys`** - Linux system call wrappers (x86_64 only)
+- **`terminal`** - Interactive terminal input (getch, getpass, etc.)
+- **`termfx`** - ANSI terminal formatting and colors
+- **`time`** - Timing and sleep functions
+- **`io`** - Formatted I/O operations
+
+### Example Programs
+
+This release includes several fully working example programs:
+
+1. **Chess Engine** - Complete chess implementation with move validation and check detection
+2. **Tetris** - Full terminal-based Tetris game with colors and scoring
+3. **3D Spinning Cube** - Real-time 3D rendering in the terminal
+4. **Bubble Sort** - Classic sorting algorithm demonstration
+5. **Test Suites** - Comprehensive tests for memory, string, and system operations
+
+## Quick Start
+
+```luma
+@module "main"
+
+@use "math" as math
+@use "termfx" as fx
+
+const Point -> struct {
+ x: int,
+ y: int
+};
+
+pub const main -> fn () int {
+ let origin: Point = Point { x: 0, y: 0 };
+ let destination: Point = Point { x: 3, y: 4 };
+
+ let distance: double = math::sqrt(
+ cast((destination.x - origin.x) * (destination.x - origin.x) +
+ (destination.y - origin.y) * (destination.y - origin.y))
+ );
+
+ output(fx::GREEN, "Distance: ", fx::RESET, distance, "\n");
+ return 0;
+}
+```
+
+Compile and run:
+```bash
+luma main.lx -name program -l std/math.lx std/termfx.lx std/string.lx
+./program
+```
+
+## Installation
+
+### Pre-built Binaries
+
+Download the latest release for your platform:
+- [Linux x86_64](https://github.com/Luma-Programming-Language/Luma/releases/download/v0.1.0/luma-linux-x86_64)
+
+### From Source
+
+Requires: GCC/Clang, Make, LLVM
+
+```bash
+git clone https://github.com/Luma-Programming-Language/Luma.git
+cd luma
+make
+sudo ./install.sh
+```
+
+## Platform Support
+
+**Current Release:**
+- Linux x86_64
+
+**Planned:**
+- macOS (ARM64 and x86_64)
+- Windows
+- Linux ARM64
+
+The system call interface (`sys.lx`) is currently Linux-specific. Other modules are platform-agnostic.
+
+## Known Limitations
+
+This is an early release. Please be aware of these current limitations:
+
+### Language
+- No generics yet (planned for future release)
+- No function overloading
+- Limited operator overloading
+- No compile-time evaluation beyond constants
+- No module exports beyond `pub` keyword
+
+### Standard Library
+- `sys.lx` is Linux x86_64 only
+- `terminal.lx` uses shell commands (may not work in all environments)
+- Limited string formatting capabilities
+- No networking or file I/O abstractions (use `sys` module directly)
+
+### Tooling
+- No package manager yet
+- Limited error messages
+- No LSP or IDE support
+- Manual compilation only (no build system beyond makefiles)
+
+### Known Bugs
+- Pipe operations in `sys.lx` may block indefinitely (marked in tests)
+- Some terminal operations might not work on all terminal emulators
+- Memory analyzer may have false positives with complex ownership patterns
+
+## Example Programs
+
+Try these example programs to see Luma in action:
+
+```bash
+# Terminal Tetris
+luma tetris.lx -name tetris -l std/string.lx std/terminal.lx std/termfx.lx std/math.lx std/time.lx && ./tetris
+
+# 3D Spinning Cube
+luma 3d_spinning_cube.lx -name cube -l std/math.lx std/memory.lx std/string.lx std/termfx.lx std/time.lx std/io.lx && ./cube
+
+# Memory Tests (with Valgrind)
+luma mem_test.lx -name mem_test -l std/memory.lx && valgrind --leak-check=full ./mem_test
+
+# Chess Game
+luma main.lx -l board.lx piece.lx std/terminal.lx std/string.lx std/termfx.lx std/memory.lx -name chess && ./chess
+```
+
+## Documentation
+
+Complete language documentation is included in `docs.md`, covering:
+- Language philosophy and design
+- Complete type system reference
+- Memory management guide
+- Standard library API reference
+- Example programs and patterns
+
+## Verified Behavior
+
+- All test cases pass under Valgrind (no leaks)
+- Analyzer successfully detects invalid frees and missing frees
+- Pointer aliasing merges ownership safely
+- Deferred frees resolve correctly across scopes
+
+## Roadmap
+
+Future releases will focus on:
+
+1. **v0.2.0** - Cross-platform support (macOS)
+2. **v0.3.0** - Package manager and build system
+3. **v0.4.0** - Language Server Protocol (LSP) support
+4. **v0.5.0** - Advanced features (traits, interfaces, compile-time functions)
+
+## Contributing
+
+We welcome contributions! This is an early-stage project and there's plenty to do:
+
+- Report bugs via GitHub Issues
+- Suggest features via GitHub Discussions
+- Improve documentation
+- Add test cases
+- Fix known issues
+- Create standard library modules
+
+Please see `CONTRIBUTING.md` for guidelines.
+
+## Philosophy
+
+Luma's goal is to stay low-level and explicit - no garbage collector, no runtime lifetimes - just pure manual memory control with a static verifier ensuring it's safe.
+
+> "You choose when to free - Luma just makes sure you do it right."
+
+## License
+
+Luma is released under the MIT License.
+
+## Acknowledgments
+
+Thank you to everyone who provided feedback during development and to the systems programming community for inspiration from languages like C, Rust, and Zig.
+
+## Getting Help
+
+- **Documentation**: See `docs.md` in the repository
+- **Issues**: https://github.com/Luma-Programming-Language/Luma/issues
+- **Discussions**: https://github.com/Luma-Programming-Language/Luma/discussions
+- **Examples**: Check the `tests/` and `examples/` directories
+
+---
+
+**Note**: This is an alpha-quality release intended for experimentation and feedback. It is **not recommended for production use** at this time. APIs may change between releases.
+
+We're excited to share Luma with the community and look forward to your feedback!
+
+---
+
+Made with care by the Luma Team
+GitHub: [Luma-Programming-Language/Luma](https://github.com/Luma-Programming-Language/Luma)
diff --git a/src/ast/ast.h b/src/ast/ast.h
index 4ab2cbd1..101f5414 100644
--- a/src/ast/ast.h
+++ b/src/ast/ast.h
@@ -11,467 +11,490 @@ typedef struct AstNode AstNode;
// Node type enumeration
typedef enum {
- // Preprocessor nodes
- AST_PREPROCESSOR_MODULE, // Module declaration
- AST_PREPROCESSOR_USE, // Use/import statement
-
- // Expression nodes
- AST_EXPR_LITERAL, // Literal values (numbers, strings, booleans)
- AST_EXPR_IDENTIFIER, // Variable/function names
- AST_EXPR_BINARY, // Binary operations (+, -, *, /, etc.)
- AST_EXPR_UNARY, // Unary operations (!, -, ++, --)
- AST_EXPR_CALL, // Function calls
- AST_EXPR_ASSIGNMENT, // Assignment expressions
- AST_EXPR_TERNARY, // Conditional expressions (? :)
- AST_EXPR_MEMBER, // Member access (obj.field)
- AST_EXPR_INDEX, // Array/object indexing (obj[index])
- AST_EXPR_GROUPING, // Parenthesized expressions
- AST_EXPR_RANGE, // range expressions '..'
- AST_EXPR_ARRAY, // [ ... ] array expressions
- AST_EXPR_DEREF, // *object
- AST_EXPR_ADDR, // &object
- AST_EXPR_ALLOC,
- AST_EXPR_MEMCPY,
- AST_EXPR_FREE,
- AST_EXPR_CAST,
- AST_EXPR_INPUT,
- AST_EXPR_SIZEOF,
- AST_EXPR_SYSTEM, // System Statement
-
- // Statement nodes
- AST_PROGRAM, // Program root node
- AST_STMT_EXPRESSION, // Expression statements
- AST_STMT_VAR_DECL, // Variable declarations
- AST_STMT_CONST_DECL, // Constant declarations
- AST_STMT_FUNCTION, // Function definitions
- AST_STMT_IF, // If statements
- AST_STMT_LOOP, // Loop statements (while, for)
- AST_STMT_BREAK_CONTINUE, // Break and continue statements
- AST_STMT_RETURN, // Return statements
- AST_STMT_BLOCK, // Block statements
- AST_STMT_PRINT, // Print statements
- AST_STMT_MODULE, // Module declarations
- AST_STMT_ENUM, // Enum declarations
- AST_STMT_STRUCT, // Struct declarations
- AST_STMT_FIELD_DECL, // Field declarations (for structs)
- AST_STMT_DEFER, // Defer statements
- AST_STMT_SWITCH, // Switch statement
- AST_STMT_IMPL, // impl statement
- AST_STMT_CASE,
- AST_STMT_DEFAULT,
-
- // Type nodes
- AST_TYPE_BASIC, // Basic types (int, float, string, etc.)
- AST_TYPE_POINTER, // Pointer types
- AST_TYPE_ARRAY, // Array types
- AST_TYPE_FUNCTION, // Function types
- AST_TYPE_STRUCT, // Struct types
- AST_TYPE_ENUM, // Enum types
+ // Preprocessor nodes
+ AST_PREPROCESSOR_MODULE, // Module declaration
+ AST_PREPROCESSOR_USE, // Use/import statement
+
+ // Expression nodes
+ AST_EXPR_LITERAL, // Literal values (numbers, strings, booleans)
+ AST_EXPR_IDENTIFIER, // Variable/function names
+ AST_EXPR_BINARY, // Binary operations (+, -, *, /, etc.)
+ AST_EXPR_UNARY, // Unary operations (!, -, ++, --)
+ AST_EXPR_CALL, // Function calls
+ AST_EXPR_ASSIGNMENT, // Assignment expressions
+ AST_EXPR_TERNARY, // Conditional expressions (? :)
+ AST_EXPR_MEMBER, // Member access (obj.field)
+ AST_EXPR_INDEX, // Array/object indexing (obj[index])
+ AST_EXPR_GROUPING, // Parenthesized expressions
+ AST_EXPR_RANGE, // range expressions '..'
+ AST_EXPR_ARRAY, // [ ... ] array expressions
+ AST_EXPR_DEREF, // *object
+ AST_EXPR_ADDR, // &object
+ AST_EXPR_ALLOC,
+ AST_EXPR_MEMCPY,
+ AST_EXPR_FREE,
+ AST_EXPR_CAST,
+ AST_EXPR_INPUT,
+ AST_EXPR_SIZEOF,
+ AST_EXPR_SYSTEM, // System Statement
+ AST_EXPR_SYSCALL,
+ AST_EXPR_STRUCT,
+
+ // Statement nodes
+ AST_PROGRAM, // Program root node
+ AST_STMT_EXPRESSION, // Expression statements
+ AST_STMT_VAR_DECL, // Variable declarations
+ AST_STMT_CONST_DECL, // Constant declarations
+ AST_STMT_FUNCTION, // Function definitions
+ AST_STMT_IF, // If statements
+ AST_STMT_LOOP, // Loop statements (while, for)
+ AST_STMT_BREAK_CONTINUE, // Break and continue statements
+ AST_STMT_RETURN, // Return statements
+ AST_STMT_BLOCK, // Block statements
+ AST_STMT_PRINT, // Print statements
+ AST_STMT_MODULE, // Module declarations
+ AST_STMT_ENUM, // Enum declarations
+ AST_STMT_STRUCT, // Struct declarations
+ AST_STMT_FIELD_DECL, // Field declarations (for structs)
+ AST_STMT_DEFER, // Defer statements
+ AST_STMT_SWITCH, // Switch statement
+ AST_STMT_IMPL, // impl statement
+ AST_STMT_CASE,
+ AST_STMT_DEFAULT,
+
+ // Type nodes
+ AST_TYPE_RESOLUTION, // Namespace::Type resolution
+ AST_TYPE_BASIC, // Basic types (int, float, string, etc.)
+ AST_TYPE_POINTER, // Pointer types
+ AST_TYPE_ARRAY, // Array types
+ AST_TYPE_FUNCTION, // Function types
+ AST_TYPE_STRUCT, // Struct types
+ AST_TYPE_ENUM, // Enum types
} NodeType;
// Literal types
typedef enum {
- LITERAL_IDENT,
- LITERAL_INT,
- LITERAL_FLOAT,
- LITERAL_DOUBLE,
- LITERAL_STRING,
- LITERAL_CHAR,
- LITERAL_BOOL,
- LITERAL_NULL
+ LITERAL_IDENT,
+ LITERAL_INT,
+ LITERAL_FLOAT,
+ LITERAL_DOUBLE,
+ LITERAL_STRING,
+ LITERAL_CHAR,
+ LITERAL_BOOL,
+ LITERAL_NULL
} LiteralType;
// Binary operators
typedef enum {
- BINOP_ADD, // +
- BINOP_SUB, // -
- BINOP_MUL, // *
- BINOP_DIV, // /
- BINOP_MOD, // %
- BINOP_POW, // **
- BINOP_EQ, // ==
- BINOP_NE, // !=
- BINOP_LT, // <
- BINOP_LE, // <=
- BINOP_GT, // >
- BINOP_GE, // >=
- BINOP_AND, // &&
- BINOP_OR, // ||
- BINOP_BIT_AND, // &
- BINOP_BIT_OR, // |
- BINOP_BIT_XOR, // ^
- BINOP_SHL, // <<
- BINOP_SHR, // >>
- BINOP_RANGE, // ..
+ BINOP_ADD, // +
+ BINOP_SUB, // -
+ BINOP_MUL, // *
+ BINOP_DIV, // /
+ BINOP_MOD, // %
+ BINOP_POW, // **
+ BINOP_EQ, // ==
+ BINOP_NE, // !=
+ BINOP_LT, // <
+ BINOP_LE, // <=
+ BINOP_GT, // >
+ BINOP_GE, // >=
+ BINOP_AND, // &&
+ BINOP_OR, // ||
+ BINOP_BIT_AND, // &
+ BINOP_BIT_OR, // |
+ BINOP_BIT_XOR, // ^
+ BINOP_SHL, // <<
+ BINOP_SHR, // >>
+ BINOP_RANGE, // ..
} BinaryOp;
// Unary operators
typedef enum {
- UNOP_NOT, // !
- UNOP_NEG, // -
- UNOP_POS, // +
- UNOP_BIT_NOT, // ~
- UNOP_PRE_INC, // ++x
- UNOP_PRE_DEC, // --x
- UNOP_POST_INC, // x++
- UNOP_POST_DEC, // x--
- UNOP_DEREF, // *x
- UNOP_ADDR, // &x
+ UNOP_NOT, // !
+ UNOP_NEG, // -
+ UNOP_POS, // +
+ UNOP_BIT_NOT, // ~
+ UNOP_PRE_INC, // ++x
+ UNOP_PRE_DEC, // --x
+ UNOP_POST_INC, // x++
+ UNOP_POST_DEC, // x--
+ UNOP_DEREF, // *x
+ UNOP_ADDR, // &x
} UnaryOp;
typedef enum {
- Node_Category_EXPR,
- Node_Category_STMT,
- Node_Category_TYPE,
- Node_Category_PREPROCESSOR
+ Node_Category_EXPR,
+ Node_Category_STMT,
+ Node_Category_TYPE,
+ Node_Category_PREPROCESSOR
} NodeCategory;
// Base AST node structure
struct AstNode {
- NodeType type;
- size_t line;
- size_t column;
- NodeCategory category; // Category of the node (expression, statement, type)
-
- union {
- struct {
- union {
- // Preprocessor-specific data
- struct {
- char *name;
- int potions;
- AstNode **body;
- size_t body_count;
- const char *file_path;
- Token *tokens;
- size_t token_count;
- void *scope; // NEW: Add this field
- } module;
-
- // @use "module_name" as module;
- struct {
- const char *module_name;
- const char *alias;
- } use;
- };
- } preprocessor;
-
- struct {
- // Expression-specific data
- union {
- // Literal expression
- struct {
- LiteralType lit_type;
- union {
- long long int_val;
- double float_val;
- char *string_val;
- char char_val;
- bool bool_val;
- } value;
- } literal;
-
- // Identifier expression
- struct {
- char *name;
- } identifier;
-
- // Binary expression
- struct {
- BinaryOp op;
- AstNode *left; // Changed from Expr* to AstNode*
- AstNode *right; // Changed from Expr* to AstNode*
- } binary;
-
- // Unary expression
- struct {
- UnaryOp op;
- AstNode *operand; // Changed from Expr* to AstNode*
- } unary;
-
- // Function call expression
- struct {
- AstNode *callee; // Changed from Expr* to AstNode*
- AstNode **args; // Changed from Expr** to AstNode**
- size_t arg_count;
- } call;
-
- // Assignment expression
- struct {
- AstNode *target; // Changed from Expr* to AstNode*
- AstNode *value; // Changed from Expr* to AstNode*
- } assignment;
-
- // Ternary expression
- struct {
- AstNode *condition; // Changed from Expr* to AstNode*
- AstNode *then_expr; // Changed from Expr* to AstNode*
- AstNode *else_expr; // Changed from Expr* to AstNode*
- } ternary;
-
- // Member access expression
- struct {
- bool is_compiletime;
- AstNode *object; // Changed from Expr* to AstNode*
- char *member;
- } member;
-
- // Index expression
- struct {
- AstNode *object; // Changed from Expr* to AstNode*
- AstNode *index; // Changed from Expr* to AstNode*
- } index;
-
- // Grouping expression
- struct {
- AstNode *expr; // Changed from Expr* to AstNode*
- } grouping;
-
- // Array expression
- struct {
- AstNode **elements; // Changed from Expr** to AstNode**
- size_t element_count;
- } array;
-
- // Deref expression
- struct {
- AstNode *object;
- } deref;
-
- // Address experssion
- struct {
- AstNode *object;
- } addr;
-
- // alloc expression
- struct {
- AstNode *size;
- } alloc;
-
- // memcpy expression
- struct {
- AstNode *to;
- AstNode *from;
- AstNode *size;
- } memcpy;
-
- // free expression
- struct {
- AstNode *ptr;
- } free;
-
- // cast expression
- struct {
- AstNode *type;
- AstNode *castee;
- } cast;
-
- // input expression
- struct {
- AstNode *type;
- AstNode *msg;
- } input;
-
- // system expression
- struct {
- AstNode *command;
- } _system;
-
- // sizeof expression
- struct {
- AstNode *object;
- bool is_type;
- } size_of;
- };
- } expr;
-
- struct {
- // Statement-specific data
- union {
- // Program root node
- struct {
- AstNode **modules; // Changed from Stmt** to AstNode**
- size_t module_count;
- } program;
-
- // Expression statement
- struct {
- AstNode *expression; // Changed from Expr* to AstNode*
- } expr_stmt;
-
- // Variable declaration
- struct {
- const char *name;
- AstNode *var_type; // Changed from Type* to AstNode*
- AstNode *initializer; // Changed from Expr* to AstNode*
- bool is_mutable; // Whether the variable is mutable
- bool is_public;
- } var_decl;
-
- // const Persion = struct {
- // pub:
- // name: str;
- // age: int;
- // priv:
- // ssn: str;
- // };
- // Struct declaration
- struct {
- const char *name;
- AstNode *
- *public_members; // Changed from Stmt** to AstNode**
- size_t public_count;
- AstNode *
- *private_members; // Changed from Stmt** to AstNode**
- size_t private_count;
- bool is_public; // Whether the struct is public (which is
- // true by default)
- } struct_decl;
-
- struct {
- const char *name;
- AstNode *type; // Changed from Type* to AstNode*
- AstNode *function; // Changed from Stmt* to AstNode*
- bool is_public; // Whether the field is public
- } field_decl;
-
- // Enumeration declaration
- struct {
- const char *name;
- char **members; // Changed from char** to AstNode**
- size_t member_count;
- bool is_public; // same as struct, enums are public by
- // default
- } enum_decl;
-
- // Function declaration
- struct {
- const char *name;
- char **param_names;
- AstNode **param_types; // Changed from Type** to AstNode**
- size_t param_count;
- AstNode *return_type; // Changed from Type* to AstNode*
- bool is_public;
- AstNode *body; // Changed from Stmt* to AstNode*
- bool returns_ownership;
- bool takes_ownership;
- } func_decl;
-
- // If statement
- struct {
- AstNode *condition; // Changed from Expr* to AstNode*
- AstNode *then_stmt; // Changed from Stmt* to AstNode*
- AstNode **elif_stmts; // Changed from Stmt* to AstNode*
- int elif_count; // Number of elif statements
- AstNode *else_stmt; // Changed from Stmt* to AstNode*
- } if_stmt;
-
- // Loop statement (Combined while and for)
- struct {
- AstNode *condition; // Changed from Expr* to AstNode*
- AstNode *optional; // Optional expression (e.g., for loop
- // initializer)
- AstNode *body; // Changed from Stmt* to AstNode*
- // For loops can be represented as a while loop with an
- // initializer and increment
- AstNode **initializer; // Optional initializer for for loops
- // (Changed from Stmt* to AstNode*)
- size_t init_count; // Number of initializers
- } loop_stmt;
-
- // Return statement
- struct {
- AstNode *value; // Changed from Expr* to AstNode*
- } return_stmt;
-
- // Block statement
- struct {
- AstNode **statements; // Changed from Stmt** to AstNode**
- size_t stmt_count;
- } block;
-
- // Print statement
- struct {
- AstNode **expressions; // Changed from Expr** to AstNode**
- size_t expr_count;
- bool ln; // Whether to print with a newline
- } print_stmt;
-
- struct {
- bool is_continue; // true for continue, false for break
- } break_continue;
-
- struct {
- AstNode *statement;
- } defer_stmt;
-
- struct {
- AstNode *condition; // The switch expression
- struct AstNode **cases; // Array of case clauses
- size_t case_count;
- struct AstNode *default_case; // Optional default case
- } switch_stmt;
-
- // impl [fun1, fun2, ...] -> [struct1, struct2, ...] {}
- struct {
- char **function_name_list;
- AstNode **function_type_list;
- char **struct_name_list;
- size_t function_name_count;
- size_t struct_name_count;
-
- AstNode *body;
- } impl_stmt;
-
- // Case clause node
- struct {
- AstNode **values; // Array of expressions (0, 1, 2, 3, ...)
- size_t value_count; // Number of values in this case
- AstNode *body; // Block statement for this case
- } case_clause;
-
- struct {
- AstNode *body; // Block statement for default case
- } default_clause;
- };
- } stmt;
-
- struct {
- // Type-specific data
- union {
- // Basic type
- struct {
- const char *name;
- } basic;
-
- // Pointer type
- struct {
- AstNode *pointee_type; // Changed from Type* to AstNode*
- } pointer;
-
- // Array type
- struct {
- AstNode *element_type; // Changed from Type* to AstNode*
- AstNode *size; // Changed from Expr* to AstNode*
- } array;
-
- // Function type
- struct {
- AstNode **param_types; // Changed from Type** to AstNode**
- size_t param_count;
- AstNode *return_type; // Changed from Type* to AstNode*
- } function;
-
- // AST_TYPE_STRUCT - ADD THIS STRUCT
- struct {
- const char *name; // Name of the struct type
- AstNode **member_types; // Array of member types
- const char **member_names; // Array of member names
- size_t member_count; // Number of members
- } struct_type;
- };
- } type_data;
- };
+ NodeType type;
+ size_t line;
+ size_t column;
+ NodeCategory category; // Category of the node (expression, statement, type)
+
+ union {
+ struct {
+ union {
+ // Preprocessor-specific data
+ struct {
+ char *name;
+ int potions;
+ AstNode **body;
+ size_t body_count;
+ const char *file_path;
+ Token *tokens;
+ size_t token_count;
+ void *scope; // NEW: Add this field
+ } module;
+
+ // @use "module_name" as module;
+ struct {
+ const char *module_name;
+ const char *alias;
+ } use;
+ };
+ } preprocessor;
+
+ struct {
+ // Expression-specific data
+ union {
+ // Literal expression
+ struct {
+ LiteralType lit_type;
+ union {
+ long long int_val;
+ double float_val;
+ char *string_val;
+ char char_val;
+ bool bool_val;
+ } value;
+ } literal;
+
+ // Identifier expression
+ struct {
+ char *name;
+ } identifier;
+
+ // Binary expression
+ struct {
+ BinaryOp op;
+ AstNode *left; // Changed from Expr* to AstNode*
+ AstNode *right; // Changed from Expr* to AstNode*
+ } binary;
+
+ // Unary expression
+ struct {
+ UnaryOp op;
+ AstNode *operand; // Changed from Expr* to AstNode*
+ } unary;
+
+ // Function call expression
+ struct {
+ AstNode *callee; // Changed from Expr* to AstNode*
+ AstNode **args; // Changed from Expr** to AstNode**
+ size_t arg_count;
+ } call;
+
+ // Assignment expression
+ struct {
+ AstNode *target; // Changed from Expr* to AstNode*
+ AstNode *value; // Changed from Expr* to AstNode*
+ } assignment;
+
+ // Ternary expression
+ struct {
+ AstNode *condition; // Changed from Expr* to AstNode*
+ AstNode *then_expr; // Changed from Expr* to AstNode*
+ AstNode *else_expr; // Changed from Expr* to AstNode*
+ } ternary;
+
+ // Member access expression
+ struct {
+ bool is_compiletime;
+ AstNode *object; // Changed from Expr* to AstNode*
+ char *member;
+ } member;
+
+ // Index expression
+ struct {
+ AstNode *object; // Changed from Expr* to AstNode*
+ AstNode *index; // Changed from Expr* to AstNode*
+ } index;
+
+ // Grouping expression
+ struct {
+ AstNode *expr; // Changed from Expr* to AstNode*
+ } grouping;
+
+ // Array expression
+ struct {
+ AstNode **elements; // Changed from Expr** to AstNode**
+ size_t element_count;
+ size_t target_size;
+ } array;
+
+ // Deref expression
+ struct {
+ AstNode *object;
+ } deref;
+
+ // Address experssion
+ struct {
+ AstNode *object;
+ } addr;
+
+ // alloc expression
+ struct {
+ AstNode *size;
+ } alloc;
+
+ // memcpy expression
+ struct {
+ AstNode *to;
+ AstNode *from;
+ AstNode *size;
+ } memcpy;
+
+ // free expression
+ struct {
+ AstNode *ptr;
+ } free;
+
+ // cast expression
+ struct {
+ AstNode *type;
+ AstNode *castee;
+ } cast;
+
+ // input expression
+ struct {
+ AstNode *type;
+ AstNode *msg;
+ } input;
+
+ // system expression
+ struct {
+ AstNode *command;
+ } _system;
+
+ // syscall expr
+ struct {
+ AstNode **args;
+ size_t count;
+ } syscall;
+
+ // sizeof expression
+ struct {
+ AstNode *object;
+ bool is_type;
+ } size_of;
+
+ struct {
+ char *name; // Struct type name (NULL for anonymous)
+ char **field_names; // Array of field names
+ AstNode **field_value; // Array of field values (expressions)
+ size_t field_count; // Number of fields
+ } struct_expr;
+ };
+ } expr;
+
+ struct {
+ // Statement-specific data
+ union {
+ // Program root node
+ struct {
+ AstNode **modules; // Changed from Stmt** to AstNode**
+ size_t module_count;
+ } program;
+
+ // Expression statement
+ struct {
+ AstNode *expression; // Changed from Expr* to AstNode*
+ } expr_stmt;
+
+ // Variable declaration
+ struct {
+ const char *name;
+ AstNode *var_type; // Changed from Type* to AstNode*
+ AstNode *initializer; // Changed from Expr* to AstNode*
+ bool is_mutable; // Whether the variable is mutable
+ bool is_public;
+ } var_decl;
+
+ // const Persion = struct {
+ // pub:
+ // name: str;
+ // age: int;
+ // priv:
+ // ssn: str;
+ // };
+ // Struct declaration
+ struct {
+ const char *name;
+ AstNode **public_members; // Changed from Stmt** to AstNode**
+ size_t public_count;
+ AstNode **private_members; // Changed from Stmt** to AstNode**
+ size_t private_count;
+ bool is_public; // Whether the struct is public (which is
+ // true by default)
+ } struct_decl;
+
+ struct {
+ const char *name;
+ AstNode *type; // Changed from Type* to AstNode*
+ AstNode *function; // Changed from Stmt* to AstNode*
+ bool is_public; // Whether the field is public
+ } field_decl;
+
+ // Enumeration declaration
+ struct {
+ const char *name;
+ char **members; // Changed from char** to AstNode**
+ size_t member_count;
+ bool is_public; // same as struct, enums are public by
+ // default
+ } enum_decl;
+
+ // Function declaration
+ struct {
+ const char *name;
+ char **param_names;
+ AstNode **param_types; // Changed from Type** to AstNode**
+ size_t param_count;
+ AstNode *return_type; // Changed from Type* to AstNode*
+ bool is_public;
+ AstNode *body; // Changed from Stmt* to AstNode*
+ bool returns_ownership;
+ bool takes_ownership;
+ bool forward_declared;
+ void *scope;
+ } func_decl;
+
+ // If statement
+ struct {
+ AstNode *condition;
+ AstNode *then_stmt;
+ AstNode **elif_stmts;
+ int elif_count;
+ AstNode *else_stmt;
+ void *scope;
+ void *then_scope;
+ void *else_scope;
+ } if_stmt;
+
+ // Loop statement (Combined while and for)
+ struct {
+ AstNode *condition;
+ AstNode *optional;
+ AstNode *body;
+ AstNode **initializer;
+ size_t init_count;
+ void *scope;
+ } loop_stmt;
+
+ // Return statement
+ struct {
+ AstNode *value; // Changed from Expr* to AstNode*
+ } return_stmt;
+
+ struct {
+ AstNode **statements;
+ size_t stmt_count;
+ void *scope;
+ } block;
+
+ // Print statement
+ struct {
+ AstNode **expressions; // Changed from Expr** to AstNode**
+ size_t expr_count;
+ bool ln; // Whether to print with a newline
+ } print_stmt;
+
+ struct {
+ bool is_continue; // true for continue, false for break
+ } break_continue;
+
+ struct {
+ AstNode *statement;
+ } defer_stmt;
+
+ struct {
+ AstNode *condition;
+ struct AstNode **cases;
+ size_t case_count;
+ struct AstNode *default_case;
+ void *scope;
+ } switch_stmt;
+
+ // impl [fun1, fun2, ...] -> [struct1, struct2, ...] {}
+ struct {
+ char **function_name_list;
+ AstNode **function_type_list;
+ char **struct_name_list;
+ size_t function_name_count;
+ size_t struct_name_count;
+
+ AstNode *body;
+ } impl_stmt;
+
+ // Case clause node
+ struct {
+ AstNode **values; // Array of expressions (0, 1, 2, 3, ...)
+ size_t value_count; // Number of values in this case
+ AstNode *body; // Block statement for this case
+ } case_clause;
+
+ struct {
+ AstNode *body; // Block statement for default case
+ } default_clause;
+ };
+ } stmt;
+
+ struct {
+ // Type-specific data
+ union {
+ // Basic type
+ struct {
+ const char *name;
+ } basic;
+
+ // Pointer type
+ struct {
+ AstNode *pointee_type; // Changed from Type* to AstNode*
+ } pointer;
+
+ // Array type
+ struct {
+ AstNode *element_type; // Changed from Type* to AstNode*
+ AstNode *size; // Changed from Expr* to AstNode*
+ } array;
+
+ // Function type
+ struct {
+ AstNode **param_types; // Changed from Type** to AstNode**
+ size_t param_count;
+ AstNode *return_type; // Changed from Type* to AstNode*
+ } function;
+
+ struct {
+ char **parts; // Array of namespace/type parts
+ size_t part_count; // Number of parts
+ } resolution;
+
+ // AST_TYPE_STRUCT - ADD THIS STRUCT
+ struct {
+ const char *name; // Name of the struct type
+ AstNode **member_types; // Array of member types
+ const char **member_names; // Array of member names
+ size_t member_count; // Number of members
+ } struct_type;
+ };
+ } type_data;
+ };
};
// Type aliases for cleaner code (defined AFTER the struct)
@@ -492,14 +515,14 @@ AstNode *create_type_node(ArenaAllocator *arena, NodeType type, size_t line,
// Helper macros for creating nodes
#define create_preprocessor(arena, type, line, column) \
- create_preprocessor_node(arena, type, Node_Category_PREPROCESSOR, line, \
- column)
+ create_preprocessor_node(arena, type, Node_Category_PREPROCESSOR, line, \
+ column)
#define create_expr(arena, type, line, column) \
- create_expr_node(arena, type, line, column)
+ create_expr_node(arena, type, line, column)
#define create_stmt(arena, type, line, column) \
- create_stmt_node(arena, type, line, column)
+ create_stmt_node(arena, type, line, column)
#define create_type(arena, type, line, column) \
- create_type_node(arena, type, line, column)
+ create_type_node(arena, type, line, column)
// Create the AstNode
AstNode *create_ast_node(ArenaAllocator *arena, NodeType type,
@@ -553,8 +576,13 @@ AstNode *create_input_expr(ArenaAllocator *arena, Expr *type, Expr *msg,
size_t line, size_t col);
AstNode *create_system_expr(ArenaAllocator *arena, Expr *command, size_t line,
size_t col);
+AstNode *create_syscall_expr(ArenaAllocator *arena, Expr **args, size_t count,
+ size_t line, size_t col);
AstNode *create_sizeof_expr(ArenaAllocator *arena, Expr *object, bool is_type,
size_t line, size_t col);
+Expr *create_struct_expr(ArenaAllocator *arena, char *name, char **field_names,
+ AstNode **field_values, size_t field_count, int line,
+ int col);
// Statement creation macros
AstNode *create_program_node(ArenaAllocator *arena, AstNode **statements,
@@ -569,8 +597,8 @@ AstNode *create_func_decl_stmt(ArenaAllocator *arena, const char *name,
char **param_names, AstNode **param_types,
size_t param_count, AstNode *return_type,
bool is_public, bool returns_ownership,
- bool takes_ownership, AstNode *body, size_t line,
- size_t column);
+ bool takes_ownership, bool forward_declared,
+ AstNode *body, size_t line, size_t column);
AstNode *create_struct_decl_stmt(ArenaAllocator *arena, const char *name,
AstNode **public_members, size_t public_count,
AstNode **private_members,
@@ -633,3 +661,5 @@ AstNode *create_array_type(ArenaAllocator *arena, AstNode *element_type,
AstNode *create_function_type(ArenaAllocator *arena, AstNode **param_types,
size_t param_count, AstNode *return_type,
size_t line, size_t column);
+AstNode *create_resolution_type(ArenaAllocator *arena, char **parts,
+ size_t part_count, size_t line, size_t column);
diff --git a/src/ast/ast_definistions/expr.c b/src/ast/ast_definistions/expr.c
index 7debca50..299b8843 100644
--- a/src/ast/ast_definistions/expr.c
+++ b/src/ast/ast_definistions/expr.c
@@ -131,6 +131,7 @@ AstNode *create_array_expr(ArenaAllocator *arena, AstNode **elements,
AstNode *node = create_expr(arena, AST_EXPR_ARRAY, line, column);
node->expr.array.elements = elements;
node->expr.array.element_count = element_count;
+ node->expr.array.target_size = 0; // Default to 0 (no explicit size)
return node;
}
@@ -194,6 +195,14 @@ AstNode *create_system_expr(ArenaAllocator *arena, Expr *command, size_t line,
return node;
}
+AstNode *create_syscall_expr(ArenaAllocator *arena, Expr **args, size_t count,
+ size_t line, size_t col) {
+ AstNode *node = create_expr(arena, AST_EXPR_SYSCALL, line, col);
+ node->expr.syscall.args = args;
+ node->expr.syscall.count = count;
+ return node;
+}
+
AstNode *create_sizeof_expr(ArenaAllocator *arena, Expr *object, bool is_type,
size_t line, size_t col) {
AstNode *node = create_expr(arena, AST_EXPR_SIZEOF, line, col);
@@ -201,3 +210,14 @@ AstNode *create_sizeof_expr(ArenaAllocator *arena, Expr *object, bool is_type,
node->expr.size_of.is_type = is_type;
return node;
}
+
+Expr *create_struct_expr(ArenaAllocator *arena, char *name, char **field_names,
+ AstNode **field_values, size_t field_count, int line,
+ int col) {
+ AstNode *node = create_expr(arena, AST_EXPR_STRUCT, line, col);
+ node->expr.struct_expr.name = name;
+ node->expr.struct_expr.field_names = field_names;
+ node->expr.struct_expr.field_value = field_values;
+ node->expr.struct_expr.field_count = field_count;
+ return node;
+}
diff --git a/src/ast/ast_definistions/stmt.c b/src/ast/ast_definistions/stmt.c
index 1c8cf43a..17694fde 100644
--- a/src/ast/ast_definistions/stmt.c
+++ b/src/ast/ast_definistions/stmt.c
@@ -4,49 +4,50 @@
AstNode *create_program_node(ArenaAllocator *arena, AstNode **statements,
size_t stmt_count, size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_PROGRAM, line, column);
- node->stmt.program.modules = statements;
- node->stmt.program.module_count = stmt_count;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_PROGRAM, line, column);
+ node->stmt.program.modules = statements;
+ node->stmt.program.module_count = stmt_count;
+ return node;
}
AstNode *create_expr_stmt(ArenaAllocator *arena, Expr *expression, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_EXPRESSION, line, column);
- node->stmt.expr_stmt.expression = expression;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_EXPRESSION, line, column);
+ node->stmt.expr_stmt.expression = expression;
+ return node;
}
AstNode *create_var_decl_stmt(ArenaAllocator *arena, const char *name,
AstNode *var_type, Expr *initializer,
bool is_mutable, bool is_public, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_VAR_DECL, line, column);
- node->stmt.var_decl.name = name;
- node->stmt.var_decl.var_type = var_type;
- node->stmt.var_decl.initializer = initializer;
- node->stmt.var_decl.is_mutable = is_mutable;
- node->stmt.var_decl.is_public = is_public;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_VAR_DECL, line, column);
+ node->stmt.var_decl.name = name;
+ node->stmt.var_decl.var_type = var_type;
+ node->stmt.var_decl.initializer = initializer;
+ node->stmt.var_decl.is_mutable = is_mutable;
+ node->stmt.var_decl.is_public = is_public;
+ return node;
}
AstNode *create_func_decl_stmt(ArenaAllocator *arena, const char *name,
char **param_names, AstNode **param_types,
size_t param_count, AstNode *return_type,
bool is_public, bool returns_ownership,
- bool takes_ownership, AstNode *body, size_t line,
- size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_FUNCTION, line, column);
- node->stmt.func_decl.name = name;
- node->stmt.func_decl.param_names = param_names;
- node->stmt.func_decl.param_types = param_types;
- node->stmt.func_decl.param_count = param_count;
- node->stmt.func_decl.return_type = return_type;
- node->stmt.func_decl.is_public = is_public;
- node->stmt.func_decl.returns_ownership = returns_ownership;
- node->stmt.func_decl.takes_ownership = takes_ownership;
- node->stmt.func_decl.body = body;
- return node;
+ bool takes_ownership, bool forward_declared,
+ AstNode *body, size_t line, size_t column) {
+ AstNode *node = create_stmt_node(arena, AST_STMT_FUNCTION, line, column);
+ node->stmt.func_decl.name = name;
+ node->stmt.func_decl.param_names = param_names;
+ node->stmt.func_decl.param_types = param_types;
+ node->stmt.func_decl.param_count = param_count;
+ node->stmt.func_decl.return_type = return_type;
+ node->stmt.func_decl.is_public = is_public;
+ node->stmt.func_decl.returns_ownership = returns_ownership;
+ node->stmt.func_decl.takes_ownership = takes_ownership;
+ node->stmt.func_decl.forward_declared = forward_declared;
+ node->stmt.func_decl.body = body;
+ return node;
}
AstNode *create_struct_decl_stmt(ArenaAllocator *arena, const char *name,
@@ -54,137 +55,136 @@ AstNode *create_struct_decl_stmt(ArenaAllocator *arena, const char *name,
AstNode **private_members,
size_t private_count, bool is_public,
size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_STRUCT, line, column);
- node->stmt.struct_decl.name = name;
- node->stmt.struct_decl.public_members = public_members;
- node->stmt.struct_decl.public_count = public_count;
- node->stmt.struct_decl.private_members = private_members;
- node->stmt.struct_decl.private_count = private_count;
- node->stmt.struct_decl.is_public = is_public;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_STRUCT, line, column);
+ node->stmt.struct_decl.name = name;
+ node->stmt.struct_decl.public_members = public_members;
+ node->stmt.struct_decl.public_count = public_count;
+ node->stmt.struct_decl.private_members = private_members;
+ node->stmt.struct_decl.private_count = private_count;
+ node->stmt.struct_decl.is_public = is_public;
+ return node;
}
AstNode *create_field_decl_stmt(ArenaAllocator *arena, const char *name,
AstNode *type, AstNode *function,
bool is_public, size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_FIELD_DECL, line, column);
- node->stmt.field_decl.name = name;
- node->stmt.field_decl.type = type;
- node->stmt.field_decl.function = function;
- node->stmt.field_decl.is_public = is_public;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_FIELD_DECL, line, column);
+ node->stmt.field_decl.name = name;
+ node->stmt.field_decl.type = type;
+ node->stmt.field_decl.function = function;
+ node->stmt.field_decl.is_public = is_public;
+ return node;
}
AstNode *create_enum_decl_stmt(ArenaAllocator *arena, const char *name,
char **members, size_t member_count,
bool is_public, size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_ENUM, line, column);
- node->stmt.enum_decl.name = name;
- node->stmt.enum_decl.members = members;
- node->stmt.enum_decl.member_count = member_count;
- node->stmt.enum_decl.is_public = is_public;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_ENUM, line, column);
+ node->stmt.enum_decl.name = name;
+ node->stmt.enum_decl.members = members;
+ node->stmt.enum_decl.member_count = member_count;
+ node->stmt.enum_decl.is_public = is_public;
+ return node;
}
AstNode *create_if_stmt(ArenaAllocator *arena, Expr *condition,
AstNode *then_stmt, AstNode **elif_stmts,
int elif_count, AstNode *else_stmt, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_IF, line, column);
- node->stmt.if_stmt.condition = condition;
- node->stmt.if_stmt.then_stmt = then_stmt;
- node->stmt.if_stmt.elif_stmts = elif_stmts;
- node->stmt.if_stmt.elif_count = elif_count;
- node->stmt.if_stmt.else_stmt = else_stmt;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_IF, line, column);
+ node->stmt.if_stmt.condition = condition;
+ node->stmt.if_stmt.then_stmt = then_stmt;
+ node->stmt.if_stmt.elif_stmts = elif_stmts;
+ node->stmt.if_stmt.elif_count = elif_count;
+ node->stmt.if_stmt.else_stmt = else_stmt;
+ return node;
}
AstNode *create_infinite_loop_stmt(ArenaAllocator *arena, AstNode *body,
size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_LOOP, line, column);
- node->stmt.loop_stmt.condition = NULL;
- node->stmt.loop_stmt.optional = NULL;
- node->stmt.loop_stmt.initializer = NULL;
- node->stmt.loop_stmt.init_count = 0;
- node->stmt.loop_stmt.body = body;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_LOOP, line, column);
+ node->stmt.loop_stmt.condition = NULL;
+ node->stmt.loop_stmt.optional = NULL;
+ node->stmt.loop_stmt.initializer = NULL;
+ node->stmt.loop_stmt.init_count = 0;
+ node->stmt.loop_stmt.body = body;
+ return node;
}
AstNode *create_for_loop_stmt(ArenaAllocator *arena, AstNode **initializers,
size_t init_count, Expr *condition,
Expr *optional, AstNode *body, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_LOOP, line, column);
- node->stmt.loop_stmt.condition = condition;
- node->stmt.loop_stmt.optional = optional;
- node->stmt.loop_stmt.body = body;
- node->stmt.loop_stmt.initializer = initializers;
- node->stmt.loop_stmt.init_count = init_count;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_LOOP, line, column);
+ node->stmt.loop_stmt.condition = condition;
+ node->stmt.loop_stmt.optional = optional;
+ node->stmt.loop_stmt.body = body;
+ node->stmt.loop_stmt.initializer = initializers;
+ node->stmt.loop_stmt.init_count = init_count;
+ return node;
}
AstNode *create_loop_stmt(ArenaAllocator *arena, Expr *condition,
Expr *optional, AstNode *body, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_LOOP, line, column);
- node->stmt.loop_stmt.condition = condition;
- node->stmt.loop_stmt.optional = optional;
- node->stmt.loop_stmt.initializer =
- NULL; // No initializers for standard loops
- node->stmt.loop_stmt.init_count = 0;
- node->stmt.loop_stmt.body = body;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_LOOP, line, column);
+ node->stmt.loop_stmt.condition = condition;
+ node->stmt.loop_stmt.optional = optional;
+ node->stmt.loop_stmt.initializer = NULL; // No initializers for standard loops
+ node->stmt.loop_stmt.init_count = 0;
+ node->stmt.loop_stmt.body = body;
+ return node;
}
AstNode *create_return_stmt(ArenaAllocator *arena, Expr *value, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_RETURN, line, column);
- node->stmt.return_stmt.value = value;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_RETURN, line, column);
+ node->stmt.return_stmt.value = value;
+ return node;
}
AstNode *create_block_stmt(ArenaAllocator *arena, AstNode **statements,
size_t stmt_count, size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_BLOCK, line, column);
- node->stmt.block.statements = statements;
- node->stmt.block.stmt_count = stmt_count;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_BLOCK, line, column);
+ node->stmt.block.statements = statements;
+ node->stmt.block.stmt_count = stmt_count;
+ return node;
}
AstNode *create_print_stmt(ArenaAllocator *arena, Expr **expressions,
size_t expr_count, bool ln, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_PRINT, line, column);
- node->stmt.print_stmt.expressions = expressions;
- node->stmt.print_stmt.expr_count = expr_count;
- node->stmt.print_stmt.ln = ln;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_PRINT, line, column);
+ node->stmt.print_stmt.expressions = expressions;
+ node->stmt.print_stmt.expr_count = expr_count;
+ node->stmt.print_stmt.ln = ln;
+ return node;
}
AstNode *create_break_continue_stmt(ArenaAllocator *arena, bool is_continue,
size_t line, size_t column) {
- AstNode *node =
- create_stmt_node(arena, AST_STMT_BREAK_CONTINUE, line, column);
- node->stmt.break_continue.is_continue = is_continue;
- return node;
+ AstNode *node =
+ create_stmt_node(arena, AST_STMT_BREAK_CONTINUE, line, column);
+ node->stmt.break_continue.is_continue = is_continue;
+ return node;
}
AstNode *create_defer_stmt(ArenaAllocator *arena, AstNode *statement,
size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_DEFER, line, column);
- node->stmt.defer_stmt.statement = statement;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_DEFER, line, column);
+ node->stmt.defer_stmt.statement = statement;
+ return node;
}
AstNode *create_switch_stmt(ArenaAllocator *arena, AstNode *condition,
AstNode **cases, size_t case_count,
AstNode *default_case, size_t line, size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_SWITCH, line, column);
- node->stmt.switch_stmt.condition = condition;
- node->stmt.switch_stmt.case_count = case_count;
- node->stmt.switch_stmt.cases = cases;
- node->stmt.switch_stmt.default_case = default_case;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_SWITCH, line, column);
+ node->stmt.switch_stmt.condition = condition;
+ node->stmt.switch_stmt.case_count = case_count;
+ node->stmt.switch_stmt.cases = cases;
+ node->stmt.switch_stmt.default_case = default_case;
+ return node;
}
AstNode *create_impl_stmt(ArenaAllocator *arena, char **function_name_list,
@@ -192,29 +192,29 @@ AstNode *create_impl_stmt(ArenaAllocator *arena, char **function_name_list,
char **struct_name_list, size_t function_name_count,
size_t struct_name_count, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_IMPL, line, column);
- node->stmt.impl_stmt.function_name_list = function_name_list;
- node->stmt.impl_stmt.function_type_list = function_type_list;
- node->stmt.impl_stmt.struct_name_list = struct_name_list;
- node->stmt.impl_stmt.function_name_count = function_name_count;
- node->stmt.impl_stmt.struct_name_count = struct_name_count;
- node->stmt.impl_stmt.body = body;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_IMPL, line, column);
+ node->stmt.impl_stmt.function_name_list = function_name_list;
+ node->stmt.impl_stmt.function_type_list = function_type_list;
+ node->stmt.impl_stmt.struct_name_list = struct_name_list;
+ node->stmt.impl_stmt.function_name_count = function_name_count;
+ node->stmt.impl_stmt.struct_name_count = struct_name_count;
+ node->stmt.impl_stmt.body = body;
+ return node;
}
AstNode *create_case_stmt(ArenaAllocator *arena, AstNode **values,
size_t value_count, AstNode *body, size_t line,
size_t column) {
- AstNode *node = create_stmt_node(arena, AST_STMT_CASE, line, column);
- node->stmt.case_clause.values = values;
- node->stmt.case_clause.value_count = value_count;
- node->stmt.case_clause.body = body;
- return node;
+ AstNode *node = create_stmt_node(arena, AST_STMT_CASE, line, column);
+ node->stmt.case_clause.values = values;
+ node->stmt.case_clause.value_count = value_count;
+ node->stmt.case_clause.body = body;
+ return node;
}
AstNode *create_default_stmt(ArenaAllocator *arena, AstNode *body, size_t line,
size_t column) {
- AstNode *node = create_stmt(arena, AST_STMT_DEFAULT, line, column);
- node->stmt.default_clause.body = body;
- return node;
+ AstNode *node = create_stmt(arena, AST_STMT_DEFAULT, line, column);
+ node->stmt.default_clause.body = body;
+ return node;
}
diff --git a/src/ast/ast_definistions/type.c b/src/ast/ast_definistions/type.c
index 50f06ca9..afd30cfc 100644
--- a/src/ast/ast_definistions/type.c
+++ b/src/ast/ast_definistions/type.c
@@ -27,4 +27,11 @@ AstNode *create_function_type(ArenaAllocator *arena, AstNode **param_types, size
node->type_data.function.param_count = param_count;
node->type_data.function.return_type = return_type;
return node;
+}
+
+AstNode *create_resolution_type(ArenaAllocator *arena, char **parts, size_t part_count, size_t line, size_t column) {
+ AstNode *node = create_type_node(arena, AST_TYPE_RESOLUTION, line, column);
+ node->type_data.resolution.parts = parts;
+ node->type_data.resolution.part_count = part_count;
+ return node;
}
\ No newline at end of file
diff --git a/src/c_libs/error/error.c b/src/c_libs/error/error.c
index 9b34b376..fefd6270 100644
--- a/src/c_libs/error/error.c
+++ b/src/c_libs/error/error.c
@@ -210,6 +210,7 @@ static void print_indicator(int col, int length, int line,
const char *line_text, const char *token_text,
int token_length, int max_width,
const char *label) {
+ (void)line;
print_gutter(max_width);
// Calculate actual column position
diff --git a/src/helper/help.c b/src/helper/help.c
index 3a731c50..2f80ec5b 100644
--- a/src/helper/help.c
+++ b/src/helper/help.c
@@ -33,7 +33,7 @@
*/
bool check_argc(int argc, int expected) {
if (argc < expected) {
- fprintf(stderr, "Usage: %s \n", "lux");
+ fprintf(stderr, "Usage: %s \n", "luma");
return false;
}
return true;
@@ -105,7 +105,6 @@ int print_help() {
printf(" This mode is used by editors/IDEs for:\n");
printf(" - Code completion\n");
printf(" - Hover information\n");
- printf(" - Go to definition\n");
printf(" - Real-time diagnostics\n");
printf(" - Document symbols\n");
@@ -118,7 +117,7 @@ int print_help() {
* @return Always returns 0.
*/
int print_version() {
- printf("Lux Compiler v1.0\n");
+ printf("Luma Compiler %s\n", Luma_Compiler_version);
return 0;
}
@@ -128,7 +127,7 @@ int print_version() {
* @return Always returns 0.
*/
int print_license() {
- printf("Lux Compiler is licensed under the MIT License.\n");
+ printf("Luma Compiler is licensed under the MIT License.\n");
return 0;
}
diff --git a/src/helper/help.h b/src/helper/help.h
index 723d8690..9ff11a12 100644
--- a/src/helper/help.h
+++ b/src/helper/help.h
@@ -28,6 +28,8 @@
/** Enable debug logs for arena allocator (comment to disable) */
#define DEBUG_ARENA_ALLOC 1
+#define Luma_Compiler_version "v0.1.0"
+
/** Error codes returned by the compiler */
typedef enum {
ARGC_ERROR = 1,
@@ -55,7 +57,7 @@ typedef struct {
bool lsp_mode; // Run as Language Server
GrowableArray files; // Change from char** to GrowableArray
size_t file_count; // Keep for convenience, or remove and use files.count
- int opt_level; // 0, 1, 2, or
+ int opt_level; // 0, 1, 2, or
GrowableArray tokens;
size_t token_count;
@@ -100,6 +102,7 @@ bool link_with_ld(const char *obj_filename, const char *exe_filename);
bool get_gcc_file_path(const char *filename, char *buffer, size_t buffer_size);
bool get_lib_paths(char *buffer, size_t buffer_size);
bool link_with_ld_simple(const char *obj_filename, const char *exe_filename);
-bool link_object_files(const char *output_dir, const char *executable_name, int opt_level);
+bool link_object_files(const char *output_dir, const char *executable_name,
+ int opt_level);
bool validate_module_system(CodeGenContext *ctx);
void save_module_output_files(CodeGenContext *ctx, const char *output_dir);
diff --git a/src/helper/run.c b/src/helper/run.c
index 686e899e..884ec066 100644
--- a/src/helper/run.c
+++ b/src/helper/run.c
@@ -4,6 +4,7 @@
#include "../parser/parser.h"
#include "../typechecker/type.h"
#include "help.h"
+#include "std_path.h"
#include
#include
@@ -12,17 +13,16 @@
#include
// Helper function to create directory if it doesn't exist
-bool create_directory(const char *path) {
+// NOTE Replaced with macro
#ifdef _WIN32
- return mkdir(path) == 0 || errno == EEXIST;
+#define create_directory(path) (mkdir((path)) == 0 || errno == EEXIST)
#else
- return mkdir(path, 0755) == 0 || errno == EEXIST;
+#define create_directory(path) (mkdir((path), 0755) == 0 || errno == EEXIST)
#endif
-}
void handle_segfault(int sig) {
(void)sig;
- fprintf(stderr, "\nSegmentation fault caught during LLVM operations!\n");
+ fprintf(stderr, "\nSegmentation fault!\n");
fprintf(stderr, "This likely indicates a problem in LLVM IR generation.\n");
exit(1);
}
@@ -73,10 +73,30 @@ void save_module_output_files(CodeGenContext *ctx, const char *output_dir) {
}
}
+// ADD THIS NEW HELPER FUNCTION
+const char *resolve_import_path(const char *path, ArenaAllocator *allocator) {
+ // Check if this is a std/ import
+ if (strncmp(path, "std/", 4) == 0 || strncmp(path, "std\\", 4) == 0) {
+ char resolved_path[1024];
+ if (resolve_std_path(path, resolved_path, sizeof(resolved_path))) {
+ return arena_strdup(allocator, resolved_path);
+ } else {
+ fprintf(stderr, "Error: Could not find standard library file: %s\n",
+ path);
+ fprintf(stderr, "\n");
+ print_std_search_paths();
+ return NULL;
+ }
+ }
+
+ // Not a std/ import, return as-is
+ return path;
+}
+
// Update your generate_llvm_code_modules function:
bool generate_llvm_code_modules(AstNode *root, BuildConfig config,
ArenaAllocator *allocator, int *step,
- CompileTimer *timer) { // ADD TIMER PARAM
+ CompileTimer *timer) {
CodeGenContext *ctx = init_codegen_context(allocator);
if (!ctx) {
return false;
@@ -95,14 +115,14 @@ bool generate_llvm_code_modules(AstNode *root, BuildConfig config,
signal(SIGILL, handle_illegal_instruction);
bool success = generate_program_modules(ctx, root, output_dir);
+
if (!success) {
fprintf(stderr, "Failed to generate LLVM modules\n");
cleanup_codegen_context(ctx);
return false;
}
- print_progress_with_time(++(*step), 9, "LLVM IR Generation",
- timer); // USE TIMER
+ print_progress_with_time(++(*step), 9, "LLVM IR Generation", timer);
if (config.save) {
save_module_output_files(ctx, output_dir);
@@ -117,19 +137,25 @@ bool generate_llvm_code_modules(AstNode *root, BuildConfig config,
return false;
}
- print_progress_with_time(++(*step), 9, "Linking", timer); // USE TIMER
+ print_progress_with_time(++(*step), 9, "Linking", timer);
cleanup_codegen_context(ctx);
return true;
}
// Helper function to link all object files in a directory
-bool link_object_files(const char *output_dir, const char *executable_name, int opt_level) {
+bool link_object_files(const char *output_dir, const char *executable_name,
+ int opt_level) {
char command[2048];
// Build the linking command with PIE-compatible flags
- snprintf(command, sizeof(command), "cc -O%d -pie %s/*.o -o %s",
- opt_level, output_dir, executable_name);
+ if (opt_level > 0) {
+ snprintf(command, sizeof(command), "cc -O%d -pie %s/*.o -o %s", opt_level,
+ output_dir, executable_name);
+ } else {
+ snprintf(command, sizeof(command), "cc -pie %s/*.o -o %s", output_dir,
+ executable_name);
+ }
int result = system(command);
if (result != 0) {
@@ -137,8 +163,8 @@ bool link_object_files(const char *output_dir, const char *executable_name, int
// Try alternative linking approach
printf("Trying alternative linking approach...\n");
- snprintf(command, sizeof(command), "gcc -O%d -no-pie %s/*.o -o %s", opt_level, output_dir,
- executable_name);
+ snprintf(command, sizeof(command), "gcc -O%d -no-pie %s/*.o -o %s",
+ opt_level, output_dir, executable_name);
printf("Alternative linking command: %s\n", command);
result = system(command);
@@ -153,11 +179,18 @@ bool link_object_files(const char *output_dir, const char *executable_name, int
return true;
}
+// UPDATED parse_file_to_module
Stmt *parse_file_to_module(const char *path, size_t position,
ArenaAllocator *allocator, BuildConfig *config) {
- const char *source = read_file(path);
+ // Resolve the path if it's a std/ import
+ const char *resolved_path = resolve_import_path(path, allocator);
+ if (!resolved_path) {
+ return NULL;
+ }
+
+ const char *source = read_file(resolved_path);
if (!source) {
- fprintf(stderr, "Failed to read source file: %s\n", path);
+ fprintf(stderr, "Failed to read source file: %s\n", resolved_path);
return NULL;
}
@@ -166,7 +199,8 @@ Stmt *parse_file_to_module(const char *path, size_t position,
GrowableArray tokens;
if (!growable_array_init(&tokens, allocator, MAX_TOKENS, sizeof(Token))) {
- fprintf(stderr, "Failed to initialize token array for %s.\n", path);
+ fprintf(stderr, "Failed to initialize token array for %s.\n",
+ resolved_path);
free((void *)source);
return NULL;
}
@@ -213,19 +247,18 @@ Stmt *parse_file_to_module(const char *path, size_t position,
}
char abs_path[4096];
- const char *file_path_to_store = path;
+ const char *file_path_to_store = resolved_path;
#ifndef _WIN32
- if (realpath(path, abs_path)) {
+ if (realpath(resolved_path, abs_path)) {
file_path_to_store = arena_strdup(allocator, abs_path);
}
#else
- if (_fullpath(abs_path, path, sizeof(abs_path))) {
+ if (_fullpath(abs_path, resolved_path, sizeof(abs_path))) {
file_path_to_store = arena_strdup(allocator, abs_path);
}
#endif
- // ... rest of the function, using file_path_to_store instead of path
module->preprocessor.module.file_path = file_path_to_store;
// Restore config
@@ -240,11 +273,18 @@ Stmt *parse_file_to_module(const char *path, size_t position,
return NULL;
}
+// UPDATED lex_and_parse_file
AstNode *lex_and_parse_file(const char *path, ArenaAllocator *allocator,
BuildConfig *config) {
- const char *source = read_file(path);
+ // Resolve the path if it's a std/ import
+ const char *resolved_path = resolve_import_path(path, allocator);
+ if (!resolved_path) {
+ return NULL;
+ }
+
+ const char *source = read_file(resolved_path);
if (!source) {
- fprintf(stderr, "Failed to read source file: %s\n", path);
+ fprintf(stderr, "Failed to read source file: %s\n", resolved_path);
return NULL;
}
@@ -253,7 +293,8 @@ AstNode *lex_and_parse_file(const char *path, ArenaAllocator *allocator,
GrowableArray tokens;
if (!growable_array_init(&tokens, allocator, MAX_TOKENS, sizeof(Token))) {
- fprintf(stderr, "Failed to initialize token array for %s.\n", path);
+ fprintf(stderr, "Failed to initialize token array for %s.\n",
+ resolved_path);
free((void *)source);
return NULL;
}
@@ -332,6 +373,8 @@ bool run_build(BuildConfig config, ArenaAllocator *allocator) {
if (!combined_program)
goto cleanup;
+ // print_ast(combined_program, "", false, false);
+
// Stage 4: Typechecking
print_progress_with_time(++step, total_stages, "Typechecking", &timer);
@@ -347,7 +390,10 @@ bool run_build(BuildConfig config, ArenaAllocator *allocator) {
if (tc) {
// Stage 6: LLVM IR
print_progress_with_time(++step, total_stages, "LLVM IR", &timer);
-
+ if (!combined_program || combined_program->type != AST_PROGRAM) {
+ fprintf(stderr, "ERROR: Invalid program node before codegen\n");
+ goto cleanup;
+ }
success = generate_llvm_code_modules(combined_program, config, allocator,
&step, &timer);
}
diff --git a/src/helper/std_path.c b/src/helper/std_path.c
new file mode 100644
index 00000000..ee9912c4
--- /dev/null
+++ b/src/helper/std_path.c
@@ -0,0 +1,145 @@
+#include "std_path.h"
+
+bool file_exists(const char *path) {
+#if defined(__MINGW32__) || defined(_WIN32)
+ DWORD attr = GetFileAttributesA(path);
+ return (attr != INVALID_FILE_ATTRIBUTES &&
+ !(attr & FILE_ATTRIBUTE_DIRECTORY));
+#else
+ return access(path, F_OK) == 0;
+#endif
+}
+
+const char *normalize_std_import(const char *path) {
+ // Remove "std/" or "std\" prefix if present
+ if (strncmp(path, "std/", 4) == 0) {
+ return path + 4;
+ }
+ if (strncmp(path, "std\\", 4) == 0) {
+ return path + 4;
+ }
+ return path;
+}
+
+bool get_system_std_path(char *buffer, size_t buffer_size) {
+#if defined(__MINGW32__) || defined(_WIN32)
+ char program_files[MAX_PATH];
+ // Windows: C:\Program Files\luma\std
+ if (SHGetFolderPathA(NULL, CSIDL_PROGRAM_FILES, NULL, 0, program_files) != S_OK) {
+ return false;
+ }
+ snprintf(buffer, buffer_size, "%s\\luma\\std", program_files);
+#else
+ // Unix: /usr/local/lib/luma/std/
+ snprintf(buffer, buffer_size, "/usr/local/lib/luma/std");
+#endif
+ return true;
+}
+
+bool get_user_std_path(char *buffer, size_t buffer_size) {
+#if defined(__MINGW32__) || defined(_WIN32)
+ char *userprofile = getenv("USERPROFILE");
+ // Windows: %USERPROFILE%\.luma\std
+ if (!userprofile) {
+ return false;
+ }
+ snprintf(buffer, buffer_size, "%s\\.luma\\std", userprofile);
+#else
+ // Unix: ~/.luma/std/
+ const char *home = getenv("HOME");
+ if (!home) {
+ // Fallback to getpwuid if HOME is not set
+ struct passwd *pw = getpwuid(getuid());
+ if (pw) {
+ home = pw->pw_dir;
+ }
+ }
+ if (!home) {
+ return false;
+ }
+ snprintf(buffer, buffer_size, "%s/.luma/std", home);
+#endif
+ return true;
+}
+
+bool resolve_std_path(const char *import_path, char *buffer,
+ size_t buffer_size) {
+ // Normalize the import path (remove "std/" prefix)
+ const char *normalized = normalize_std_import(import_path);
+
+ // Check if file already has an extension (.luma or .lx)
+ char with_extension[512];
+ const char *ext = strrchr(normalized, '.');
+ if (ext && (strcmp(ext, ".luma") == 0 || strcmp(ext, ".lx") == 0)) {
+ // Already has a valid extension, use as-is
+ snprintf(with_extension, sizeof(with_extension), "%s", normalized);
+ } else {
+ // No extension, try .lx first (your current extension), then .luma
+ snprintf(with_extension, sizeof(with_extension), "%s.lx", normalized);
+ }
+
+ char test_path[1024];
+
+ // Try both .lx and .luma extensions
+ const char *extensions[] = {with_extension, NULL};
+ char alt_extension[512];
+ // If we used .lx, also try .luma as fallback
+ if (ext == NULL || (strcmp(ext, ".lx") != 0 && strcmp(ext, ".luma") != 0)) {
+ snprintf(alt_extension, sizeof(alt_extension), "%s.luma", normalized);
+ extensions[1] = alt_extension;
+ }
+
+ for (int ext_idx = 0; ext_idx < 2 && extensions[ext_idx]; ext_idx++) {
+ const char *try_ext = extensions[ext_idx];
+
+ // 1. Check system-wide installation
+ if (get_system_std_path(test_path, sizeof(test_path))) {
+ snprintf(buffer, buffer_size, "%s%c%s", test_path, PATH_SEPARATOR,
+ try_ext);
+ if (file_exists(buffer)) {
+ return true;
+ }
+ }
+
+ // 2. Check user-local installation
+ if (get_user_std_path(test_path, sizeof(test_path))) {
+ snprintf(buffer, buffer_size, "%s%c%s", test_path, PATH_SEPARATOR,
+ try_ext);
+ if (file_exists(buffer)) {
+ return true;
+ }
+ }
+
+ // 3. Check current directory (development fallback)
+ snprintf(buffer, buffer_size, ".%cstd%c%s", PATH_SEPARATOR, PATH_SEPARATOR,
+ try_ext);
+ if (file_exists(buffer)) {
+ return true;
+ }
+ }
+
+ // 4. Check if the import path itself is valid (absolute or relative path)
+ if (file_exists(import_path)) {
+ snprintf(buffer, buffer_size, "%s", import_path);
+ return true;
+ }
+
+ // Not found in any location
+ return false;
+}
+
+// Helper function to print search paths for debugging
+void print_std_search_paths(void) {
+ char path[1024];
+ fprintf(stderr, "Standard library search paths:\n");
+
+ if (get_system_std_path(path, sizeof(path))) {
+ fprintf(stderr, " 1. System: %s\n", path);
+ }
+
+ if (get_user_std_path(path, sizeof(path))) {
+ fprintf(stderr, " 2. User: %s\n", path);
+ }
+
+ fprintf(stderr, " 3. Local: ./std/\n");
+}
\ No newline at end of file
diff --git a/src/helper/std_path.h b/src/helper/std_path.h
new file mode 100644
index 00000000..1c2b0dd4
--- /dev/null
+++ b/src/helper/std_path.h
@@ -0,0 +1,74 @@
+/**
+ * @file std_path.h
+ * @brief Standard library path resolution for Luma compiler
+ *
+ * Resolves std/ imports by checking multiple locations:
+ * 1. System-wide: /usr/local/lib/luma/std/ or C:\Program Files\luma\std\
+ * 2. User-local: ~/.luma/std/ or %USERPROFILE%\.luma\std\
+ * 3. Current directory: ./std/
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#if defined(__MINGW32__) || defined(_WIN32)
+#include
+#include
+#define PATH_SEPARATOR '\\'
+#define PATH_SEPARATOR_STR "\\"
+#else
+#include
+#include
+#define PATH_SEPARATOR '/'
+#define PATH_SEPARATOR_STR "/"
+#endif
+
+/**
+ * @brief Resolve a std/ import path to an actual file path
+ *
+ * @param import_path The import path (e.g., "std/io" or "std/math")
+ * @param buffer Buffer to store the resolved path
+ * @param buffer_size Size of the buffer
+ * @return true if the path was resolved successfully, false otherwise
+ */
+bool resolve_std_path(const char *import_path, char *buffer,
+ size_t buffer_size);
+
+/**
+ * @brief Get the system-wide Luma standard library path
+ *
+ * @param buffer Buffer to store the path
+ * @param buffer_size Size of the buffer
+ * @return true if successful, false otherwise
+ */
+bool get_system_std_path(char *buffer, size_t buffer_size);
+
+/**
+ * @brief Get the user-local Luma standard library path
+ *
+ * @param buffer Buffer to store the path
+ * @param buffer_size Size of the buffer
+ * @return true if successful, false otherwise
+ */
+bool get_user_std_path(char *buffer, size_t buffer_size);
+
+/**
+ * @brief Check if a file exists at the given path
+ *
+ * @param path Path to check
+ * @return true if file exists, false otherwise
+ */
+bool file_exists(const char *path);
+
+/**
+ * @brief Normalize a path by removing "std/" prefix if present
+ *
+ * @param path The input path
+ * @return Pointer to the normalized path (within the input string)
+ */
+const char *normalize_std_import(const char *path);
+void print_std_search_paths(void);
diff --git a/src/lexer/lexer.c b/src/lexer/lexer.c
index 078255fc..ffcd7388 100644
--- a/src/lexer/lexer.c
+++ b/src/lexer/lexer.c
@@ -17,14 +17,14 @@
/** @internal Macro to compare string to a key of known length */
#define STR_EQUALS_LEN(str, key, len) \
- (strncmp(str, key, len) == 0 && key[len] == '\0')
+ (strncmp(str, key, len) == 0 && key[len] == '\0')
/** @internal Macro to check if next two chars match given pair */
#define MATCH_NEXT(lx, a, b) (peek(lx, 0) == (a) && peek(lx, 1) == (b))
/** @internal Macro to construct a token */
#define MAKE_TOKEN(type, start, lx, length, whitespace_len) \
- make_token(type, start, lx->line, lx->col - 1, length, whitespace_len)
+ make_token(type, start, lx->line, lx->col - 1, length, whitespace_len)
/** @internal Symbol to token type mapping */
static const SymbolEntry symbols[] = {
@@ -84,6 +84,8 @@ static const KeywordEntry keywords[] = {
{"impl", TOK_IMPL},
{"input", TOK_INPUT},
{"system", TOK_SYSTEM},
+
+ {"__syscall__", TOK_SYSCALL},
};
static const KeywordEntry preprocessor_directives[] = {
@@ -111,19 +113,19 @@ static const KeywordEntry function_attributes[] = {
void report_lexer_error(Lexer *lx, const char *error_type, const char *file,
const char *msg, const char *line_text, int line,
int col, int tk_length) {
- ErrorInformation err = {
- .error_type = error_type,
- .file_path = file,
- .message = msg,
- .line = line,
- .col = col,
- .line_text = arena_strdup(lx->arena, line_text),
- .token_length = tk_length,
- .label = "Undefined Token",
- .note = NULL,
- .help = NULL,
- };
- error_add(err);
+ ErrorInformation err = {
+ .error_type = error_type,
+ .file_path = file,
+ .message = msg,
+ .line = line,
+ .col = col,
+ .line_text = arena_strdup(lx->arena, line_text),
+ .token_length = tk_length,
+ .label = "Undefined Token",
+ .note = NULL,
+ .help = NULL,
+ };
+ error_add(err);
}
/**
@@ -134,30 +136,29 @@ void report_lexer_error(Lexer *lx, const char *error_type, const char *file,
* @return Pointer to static buffer containing the line text
*/
const char *get_line_text_from_source(const char *source, int target_line) {
- static char line_buffer[1024];
- const char *start = source;
- int current_line = 1;
+ static char line_buffer[1024];
+ const char *start = source;
+ int current_line = 1;
- while (current_line < target_line && *start != '\0') {
- if (*start == '\n') {
- current_line++;
- }
- start++;
+ while (current_line < target_line && *start != '\0') {
+ if (*start == '\n') {
+ current_line++;
}
+ start++;
+ }
- if (current_line != target_line) {
- line_buffer[0] = '\0';
- return line_buffer;
- }
+ if (current_line != target_line) {
+ line_buffer[0] = '\0';
+ return line_buffer;
+ }
- int i = 0;
- while (*start != '\0' && *start != '\n' &&
- i < (int)sizeof(line_buffer) - 1) {
- line_buffer[i++] = *start++;
- }
- line_buffer[i] = '\0';
+ int i = 0;
+ while (*start != '\0' && *start != '\n' && i < (int)sizeof(line_buffer) - 1) {
+ line_buffer[i++] = *start++;
+ }
+ line_buffer[i] = '\0';
- return line_buffer;
+ return line_buffer;
}
/**
@@ -169,12 +170,12 @@ const char *get_line_text_from_source(const char *source, int target_line) {
* @return LumaTokenType keyword token if found, else TOK_IDENTIFIER
*/
static LumaTokenType lookup_keyword(const char *str, int length) {
- for (int i = 0; i < (int)(sizeof(keywords) / sizeof(*keywords)); ++i) {
- if (STR_EQUALS_LEN(str, keywords[i].text, length)) {
- return keywords[i].type;
- }
+ for (int i = 0; i < (int)(sizeof(keywords) / sizeof(*keywords)); ++i) {
+ if (STR_EQUALS_LEN(str, keywords[i].text, length)) {
+ return keywords[i].type;
}
- return TOK_IDENTIFIER;
+ }
+ return TOK_IDENTIFIER;
}
/**
@@ -186,14 +187,14 @@ static LumaTokenType lookup_keyword(const char *str, int length) {
* @return LumaTokenType preprocessor token if found, else TOK_SYMBOL
*/
static LumaTokenType lookup_preprocessor(const char *str, int length) {
- for (int i = 0; i < (int)(sizeof(preprocessor_directives) /
- sizeof(*preprocessor_directives));
- ++i) {
- if (STR_EQUALS_LEN(str, preprocessor_directives[i].text, length)) {
- return preprocessor_directives[i].type;
- }
+ for (int i = 0; i < (int)(sizeof(preprocessor_directives) /
+ sizeof(*preprocessor_directives));
+ ++i) {
+ if (STR_EQUALS_LEN(str, preprocessor_directives[i].text, length)) {
+ return preprocessor_directives[i].type;
}
- return TOK_SYMBOL;
+ }
+ return TOK_SYMBOL;
}
/**
@@ -205,12 +206,12 @@ static LumaTokenType lookup_preprocessor(const char *str, int length) {
* @return LumaTokenType symbol token if found, else TOK_SYMBOL
*/
static LumaTokenType lookup_symbol(const char *str, int length) {
- for (int i = 0; i < (int)(sizeof(symbols) / sizeof(*symbols)); ++i) {
- if (STR_EQUALS_LEN(str, symbols[i].text, length)) {
- return symbols[i].type;
- }
+ for (int i = 0; i < (int)(sizeof(symbols) / sizeof(*symbols)); ++i) {
+ if (STR_EQUALS_LEN(str, symbols[i].text, length)) {
+ return symbols[i].type;
}
- return TOK_SYMBOL;
+ }
+ return TOK_SYMBOL;
}
/**
@@ -221,11 +222,11 @@ static LumaTokenType lookup_symbol(const char *str, int length) {
* @param arena Arena allocator to allocate tokens and strings
*/
void init_lexer(Lexer *lexer, const char *source, ArenaAllocator *arena) {
- lexer->arena = arena;
- lexer->src = source;
- lexer->current = source;
- lexer->line = 1;
- lexer->col = 0;
+ lexer->arena = arena;
+ lexer->src = source;
+ lexer->current = source;
+ lexer->line = 1;
+ lexer->col = 0;
}
/**
@@ -256,14 +257,14 @@ bool is_at_end(Lexer *lx) { return *lx->current == '\0'; }
* @return The character that was advanced past
*/
char advance(Lexer *lx) {
- char c = *lx->current++;
- if (c == '\n') {
- lx->line++;
- lx->col = 0;
- } else if (c != '\0') {
- lx->col++;
- }
- return c;
+ char c = *lx->current++;
+ if (c == '\n') {
+ lx->line++;
+ lx->col = 0;
+ } else if (c != '\0') {
+ lx->col++;
+ }
+ return c;
}
/**
@@ -279,7 +280,7 @@ char advance(Lexer *lx) {
*/
Token make_token(LumaTokenType type, const char *start, int line, int col,
int length, int whitespace_len) {
- return (Token){type, start, line, col, length, whitespace_len};
+ return (Token){type, start, line, col, length, whitespace_len};
}
/**
@@ -290,19 +291,19 @@ Token make_token(LumaTokenType type, const char *start, int line, int col,
* @return Number of characters skipped
*/
int skip_multiline_comment(Lexer *lx) {
- int count = 0;
- advance(lx); // skip '/'
+ int count = 0;
+ advance(lx); // skip '/'
+ advance(lx); // skip '*'
+ count += 2;
+ while (!is_at_end(lx) && !MATCH_NEXT(lx, '*', '/')) {
+ advance(lx);
+ count++;
+ }
+ if (!is_at_end(lx)) {
advance(lx); // skip '*'
- count += 2;
- while (!is_at_end(lx) && !MATCH_NEXT(lx, '*', '/')) {
- advance(lx);
- count++;
- }
- if (!is_at_end(lx)) {
- advance(lx); // skip '*'
- advance(lx); // skip '/'
- }
- return count + 2;
+ advance(lx); // skip '/'
+ }
+ return count + 2;
}
/**
@@ -313,37 +314,37 @@ int skip_multiline_comment(Lexer *lx) {
* @return Total count of skipped characters
*/
int skip_whitespace(Lexer *lx) {
- int whitespace_count = 0;
-
- while (!is_at_end(lx)) {
- char c = peek(lx, 0);
-
- if (c == ' ' || c == '\t') {
- // Count spaces and tabs as whitespace
- advance(lx);
- whitespace_count++;
- } else if (c == '\n' || c == '\r') {
- // Newline resets whitespace count - we only want leading whitespace
- // on the current line
- advance(lx);
- whitespace_count = 0; // Reset because we're on a new line
- } else if (c == '/' && peek(lx, 1) == '/') {
- // Skip single-line comment
- while (!is_at_end(lx) && peek(lx, 0) != '\n') {
- advance(lx);
- }
- // Don't count comment characters as whitespace
- } else if (c == '/' && peek(lx, 1) == '*') {
- // Skip multiline comment
- skip_multiline_comment(lx);
- // Don't count comment characters as whitespace
- } else {
- // Not whitespace or comment, stop skipping
- break;
- }
+ int whitespace_count = 0;
+
+ while (!is_at_end(lx)) {
+ char c = peek(lx, 0);
+
+ if (c == ' ' || c == '\t') {
+ // Count spaces and tabs as whitespace
+ advance(lx);
+ whitespace_count++;
+ } else if (c == '\n' || c == '\r') {
+ // Newline resets whitespace count - we only want leading whitespace
+ // on the current line
+ advance(lx);
+ whitespace_count = 0; // Reset because we're on a new line
+ } else if (c == '/' && peek(lx, 1) == '/') {
+ // Skip single-line comment
+ while (!is_at_end(lx) && peek(lx, 0) != '\n') {
+ advance(lx);
+ }
+ // Don't count comment characters as whitespace
+ } else if (c == '/' && peek(lx, 1) == '*') {
+ // Skip multiline comment
+ skip_multiline_comment(lx);
+ // Don't count comment characters as whitespace
+ } else {
+ // Not whitespace or comment, stop skipping
+ break;
}
+ }
- return whitespace_count;
+ return whitespace_count;
}
/**
@@ -353,255 +354,254 @@ int skip_whitespace(Lexer *lx) {
* @return Next token found in the input
*/
Token next_token(Lexer *lx) {
- int wh_count = skip_whitespace(lx);
- if (is_at_end(lx)) {
- return MAKE_TOKEN(TOK_EOF, lx->current, lx, 0, wh_count);
+ int wh_count = skip_whitespace(lx);
+ if (is_at_end(lx)) {
+ return MAKE_TOKEN(TOK_EOF, lx->current, lx, 0, wh_count);
+ }
+
+ const char *start = lx->current;
+ char c = advance(lx);
+
+ // Preprocessor directives (starting with @)
+ if (c == '@') {
+ if (isalpha(peek(lx, 0))) {
+ // Read the rest of the directive
+ while (isalnum(peek(lx, 0)) || peek(lx, 0) == '_') {
+ advance(lx);
+ }
+ int len = (int)(lx->current - start);
+ LumaTokenType type = lookup_preprocessor(start, len);
+ if (type != TOK_SYMBOL) {
+ return MAKE_TOKEN(type, start, lx, len, wh_count);
+ }
+ // If not a known preprocessor directive, treat as error or symbol
+ static char error_msg[64];
+ snprintf(error_msg, sizeof(error_msg),
+ "Unknown preprocessor directive: '%.*s'", len, start);
+ report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col - len, len);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, len, wh_count);
}
+ // Just @ by itself - treat as symbol
+ LumaTokenType single_type = lookup_symbol(start, 1);
+ return MAKE_TOKEN(single_type, start, lx, 1, wh_count);
+ }
- const char *start = lx->current;
- char c = advance(lx);
-
- // Preprocessor directives (starting with @)
- if (c == '@') {
- if (isalpha(peek(lx, 0))) {
- // Read the rest of the directive
- while (isalnum(peek(lx, 0)) || peek(lx, 0) == '_') {
- advance(lx);
- }
- int len = (int)(lx->current - start);
- LumaTokenType type = lookup_preprocessor(start, len);
- if (type != TOK_SYMBOL) {
- return MAKE_TOKEN(type, start, lx, len, wh_count);
- }
- // If not a known preprocessor directive, treat as error or symbol
- static char error_msg[64];
- snprintf(error_msg, sizeof(error_msg),
- "Unknown preprocessor directive: '%.*s'", len, start);
- report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
- get_line_text_from_source(lx->src, lx->line),
- lx->line, lx->col - len, len);
- return MAKE_TOKEN(TOK_ERROR, start, lx, len, wh_count);
+ if (c == '#') {
+ if (isalpha(peek(lx, 0))) {
+ // Read the rest of the attribute
+ while (isalnum(peek(lx, 0)) || peek(lx, 0) == '_') {
+ advance(lx);
+ }
+ int len = (int)(lx->current - start);
+ for (int i = 0; i < (int)(sizeof(function_attributes) /
+ sizeof(*function_attributes));
+ ++i) {
+ if (STR_EQUALS_LEN(start, function_attributes[i].text, len)) {
+ return MAKE_TOKEN(function_attributes[i].type, start, lx, len,
+ wh_count);
}
- // Just @ by itself - treat as symbol
- LumaTokenType single_type = lookup_symbol(start, 1);
- return MAKE_TOKEN(single_type, start, lx, 1, wh_count);
+ }
+ // If not a known function attribute, treat as error
+ static char error_msg[64];
+ snprintf(error_msg, sizeof(error_msg),
+ "Unknown function attribute: '%.*s'", len, start);
+ report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col - len, len);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, len, wh_count);
}
+ // Just # by itself - treat as symbol
+ LumaTokenType single_type = lookup_symbol(start, 1);
+ return MAKE_TOKEN(single_type, start, lx, 1, wh_count);
+ }
- if (c == '#') {
- if (isalpha(peek(lx, 0))) {
- // Read the rest of the attribute
- while (isalnum(peek(lx, 0)) || peek(lx, 0) == '_') {
- advance(lx);
- }
- int len = (int)(lx->current - start);
- for (int i = 0; i < (int)(sizeof(function_attributes) /
- sizeof(*function_attributes));
- ++i) {
- if (STR_EQUALS_LEN(start, function_attributes[i].text, len)) {
- return MAKE_TOKEN(function_attributes[i].type, start, lx,
- len, wh_count);
- }
- }
- // If not a known function attribute, treat as error
- static char error_msg[64];
- snprintf(error_msg, sizeof(error_msg),
- "Unknown function attribute: '%.*s'", len, start);
- report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
- get_line_text_from_source(lx->src, lx->line),
- lx->line, lx->col - len, len);
- return MAKE_TOKEN(TOK_ERROR, start, lx, len, wh_count);
- }
- // Just # by itself - treat as symbol
- LumaTokenType single_type = lookup_symbol(start, 1);
- return MAKE_TOKEN(single_type, start, lx, 1, wh_count);
+ // Identifiers and keywords
+ if (isalpha(c) || c == '_') {
+ while (isalnum(peek(lx, 0)) || peek(lx, 0) == '_') {
+ advance(lx);
}
-
- // Identifiers and keywords
- if (isalpha(c) || c == '_') {
- while (isalnum(peek(lx, 0)) || peek(lx, 0) == '_') {
- advance(lx);
- }
- int len = (int)(lx->current - start);
- LumaTokenType type = lookup_keyword(start, len);
- return MAKE_TOKEN(type, start, lx, len, wh_count);
+ int len = (int)(lx->current - start);
+ LumaTokenType type = lookup_keyword(start, len);
+ return MAKE_TOKEN(type, start, lx, len, wh_count);
+ }
+
+ // Numbers
+ if (isdigit(c)) {
+ // Read the integer part
+ while (isdigit(peek(lx, 0))) {
+ advance(lx);
}
- // Numbers
- if (isdigit(c)) {
- // Read the integer part
- while (isdigit(peek(lx, 0))) {
- advance(lx);
- }
+ // Check for decimal point
+ if (peek(lx, 0) == '.' && isdigit(peek(lx, 1))) {
+ advance(lx); // consume the '.'
+
+ // Read the fractional part
+ while (isdigit(peek(lx, 0))) {
+ advance(lx);
+ }
- // Check for decimal point
- if (peek(lx, 0) == '.' && isdigit(peek(lx, 1))) {
- advance(lx); // consume the '.'
+ int len = (int)(lx->current - start);
+ return MAKE_TOKEN(TOK_NUM_FLOAT, start, lx, len,
+ wh_count); // Make sure this is TOK_FLOAT_LITERAL
+ }
- // Read the fractional part
- while (isdigit(peek(lx, 0))) {
- advance(lx);
- }
+ int len = (int)(lx->current - start);
+ return MAKE_TOKEN(TOK_NUMBER, start, lx, len,
+ wh_count); // Make sure this is TOK_NUMBER
+ }
- int len = (int)(lx->current - start);
- return MAKE_TOKEN(TOK_NUM_FLOAT, start, lx, len,
- wh_count); // Make sure this is TOK_FLOAT_LITERAL
- }
+ if (c == '\'') {
+ const char *char_start = lx->current; // Start after opening quote
+ char actual_char = 0; // The actual character value we'll store
+ int char_count = 0; // How many characters we consumed
- int len = (int)(lx->current - start);
- return MAKE_TOKEN(TOK_NUMBER, start, lx, len,
- wh_count); // Make sure this is TOK_NUMBER
+ if (is_at_end(lx)) {
+ // Unclosed character literal
+ report_lexer_error(lx, "LexerError", "unknown_file",
+ "Unclosed character literal",
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col - 1, 1);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count);
}
- if (c == '\'') {
- const char *char_start = lx->current; // Start after opening quote
- char actual_char = 0; // The actual character value we'll store
- int char_count = 0; // How many characters we consumed
-
- if (is_at_end(lx)) {
- // Unclosed character literal
- report_lexer_error(lx, "LexerError", "unknown_file",
- "Unclosed character literal",
- get_line_text_from_source(lx->src, lx->line),
- lx->line, lx->col - 1, 1);
- return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count);
- }
-
- char ch = peek(lx, 0);
-
- // Handle escape sequences
- if (ch == '\\') {
- advance(lx); // consume backslash
- char_count++;
-
- if (is_at_end(lx)) {
- report_lexer_error(
- lx, "LexerError", "unknown_file",
- "Incomplete escape sequence in character literal",
- get_line_text_from_source(lx->src, lx->line), lx->line,
- lx->col - 2, 2);
- return MAKE_TOKEN(TOK_ERROR, start, lx, 2, wh_count);
- }
-
- char escaped = peek(lx, 0);
-
- // Convert escape sequence to actual character value
- switch (escaped) {
- case 'n':
- actual_char = '\n';
- break;
- case 't':
- actual_char = '\t';
- break;
- case 'r':
- actual_char = '\r';
- break;
- case '\\':
- actual_char = '\\';
- break;
- case '\'':
- actual_char = '\'';
- break;
- case '\"':
- actual_char = '\"';
- break;
- case '0':
- actual_char = '\0';
- break;
- default: {
- static char error_msg[64];
- snprintf(error_msg, sizeof(error_msg),
- "Invalid escape sequence '\\%c' in character literal",
- escaped);
- report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
- get_line_text_from_source(lx->src, lx->line),
- lx->line, lx->col - 2, 3);
- return MAKE_TOKEN(TOK_ERROR, start, lx, 3, wh_count);
- }
- }
-
- advance(lx); // consume escaped character
- char_count++;
-
- } else if (ch == '\'') {
- // Empty character literal
- report_lexer_error(lx, "LexerError", "unknown_file",
- "Empty character literal",
- get_line_text_from_source(lx->src, lx->line),
- lx->line, lx->col - 1, 2);
- advance(lx); // consume closing quote
- return MAKE_TOKEN(TOK_ERROR, start, lx, 2, wh_count);
-
- } else if (ch == '\n' || ch == '\r') {
- // Newline in character literal
- report_lexer_error(lx, "LexerError", "unknown_file",
- "Newline in character literal",
- get_line_text_from_source(lx->src, lx->line),
- lx->line, lx->col - 1, 1);
- return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count);
-
- } else {
- // Regular character
- actual_char = ch;
- advance(lx); // consume the character
- char_count = 1;
- }
+ char ch = peek(lx, 0);
+
+ // Handle escape sequences
+ if (ch == '\\') {
+ advance(lx); // consume backslash
+ char_count++;
+
+ if (is_at_end(lx)) {
+ report_lexer_error(lx, "LexerError", "unknown_file",
+ "Incomplete escape sequence in character literal",
+ get_line_text_from_source(lx->src, lx->line),
+ lx->line, lx->col - 2, 2);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, 2, wh_count);
+ }
+
+ char escaped = peek(lx, 0);
+
+ // Convert escape sequence to actual character value
+ switch (escaped) {
+ case 'n':
+ actual_char = '\n';
+ break;
+ case 't':
+ actual_char = '\t';
+ break;
+ case 'r':
+ actual_char = '\r';
+ break;
+ case '\\':
+ actual_char = '\\';
+ break;
+ case '\'':
+ actual_char = '\'';
+ break;
+ case '\"':
+ actual_char = '\"';
+ break;
+ case '0':
+ actual_char = '\0';
+ break;
+ default: {
+ static char error_msg[64];
+ snprintf(error_msg, sizeof(error_msg),
+ "Invalid escape sequence '\\%c' in character literal",
+ escaped);
+ report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
+ get_line_text_from_source(lx->src, lx->line),
+ lx->line, lx->col - 2, 3);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, 3, wh_count);
+ }
+ }
+
+ advance(lx); // consume escaped character
+ char_count++;
+
+ } else if (ch == '\'') {
+ // Empty character literal
+ report_lexer_error(lx, "LexerError", "unknown_file",
+ "Empty character literal",
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col - 1, 2);
+ advance(lx); // consume closing quote
+ return MAKE_TOKEN(TOK_ERROR, start, lx, 2, wh_count);
+
+ } else if (ch == '\n' || ch == '\r') {
+ // Newline in character literal
+ report_lexer_error(lx, "LexerError", "unknown_file",
+ "Newline in character literal",
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col - 1, 1);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count);
+
+ } else {
+ // Regular character
+ actual_char = ch;
+ advance(lx); // consume the character
+ char_count = 1;
+ }
- // Check for closing quote
- if (is_at_end(lx) || peek(lx, 0) != '\'') {
- report_lexer_error(
- lx, "LexerError", "unknown_file", "Unclosed character literal",
- get_line_text_from_source(lx->src, lx->line), lx->line,
- lx->col - 1, (int)(lx->current - start));
- return MAKE_TOKEN(TOK_ERROR, start, lx, (int)(lx->current - start),
- wh_count);
- }
+ // Check for closing quote
+ if (is_at_end(lx) || peek(lx, 0) != '\'') {
+ report_lexer_error(lx, "LexerError", "unknown_file",
+ "Unclosed character literal",
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col - 1, (int)(lx->current - start));
+ return MAKE_TOKEN(TOK_ERROR, start, lx, (int)(lx->current - start),
+ wh_count);
+ }
- advance(lx); // consume closing quote
- int total_len = (int)(lx->current - start);
+ advance(lx); // consume closing quote
+ int total_len = (int)(lx->current - start);
- // CRITICAL: Store the actual character value, not the escape sequence
- // We'll create a single-character string with the resolved value
- char *char_storage = (char *)arena_alloc(lx->arena, 2, 1);
- char_storage[0] = actual_char;
- char_storage[1] = '\0';
+ // CRITICAL: Store the actual character value, not the escape sequence
+ // We'll create a single-character string with the resolved value
+ char *char_storage = (char *)arena_alloc(lx->arena, 2, 1);
+ char_storage[0] = actual_char;
+ char_storage[1] = '\0';
- return make_token(TOK_CHAR_LITERAL, char_storage, lx->line,
- lx->col - total_len, 1,
- wh_count); // length = 1 (single char)
- }
+ return make_token(TOK_CHAR_LITERAL, char_storage, lx->line,
+ lx->col - total_len, 1,
+ wh_count); // length = 1 (single char)
+ }
- // Strings
- if (c == '"') {
- while (!is_at_end(lx) && peek(lx, 0) != '"') {
- advance(lx);
- }
- if (!is_at_end(lx)) {
- advance(lx); // Skip closing quote
- }
- int len = (int)(lx->current - start - 2);
- return MAKE_TOKEN(TOK_STRING, start + 1, lx, len, wh_count);
+ // Strings
+ if (c == '"') {
+ while (!is_at_end(lx) && peek(lx, 0) != '"') {
+ advance(lx);
}
-
- // Try to match two-character symbol
if (!is_at_end(lx)) {
- char two[3] = {start[0], peek(lx, 0), '\0'};
- LumaTokenType ttype = lookup_symbol(two, 2);
- if (ttype != TOK_SYMBOL) {
- advance(lx);
- return MAKE_TOKEN(ttype, start, lx, 2, wh_count);
- }
+ advance(lx); // Skip closing quote
}
-
- // Single-character symbol fallback
- LumaTokenType single_type = lookup_symbol(start, 1);
- if (single_type != TOK_SYMBOL)
- return MAKE_TOKEN(single_type, start, lx, 1, wh_count);
-
- // Error token if none matched
- static char error_msg[64];
- snprintf(error_msg, sizeof(error_msg), "Token not found: '%c'", c);
- report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
- get_line_text_from_source(lx->src, lx->line), lx->line,
- lx->col, 1);
- return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count);
+ int len = (int)(lx->current - start - 2);
+ return MAKE_TOKEN(TOK_STRING, start + 1, lx, len, wh_count);
+ }
+
+ // Try to match two-character symbol
+ if (!is_at_end(lx)) {
+ char two[3] = {start[0], peek(lx, 0), '\0'};
+ LumaTokenType ttype = lookup_symbol(two, 2);
+ if (ttype != TOK_SYMBOL) {
+ advance(lx);
+ return MAKE_TOKEN(ttype, start, lx, 2, wh_count);
+ }
+ }
+
+ // Single-character symbol fallback
+ LumaTokenType single_type = lookup_symbol(start, 1);
+ if (single_type != TOK_SYMBOL)
+ return MAKE_TOKEN(single_type, start, lx, 1, wh_count);
+
+ // Error token if none matched
+ static char error_msg[64];
+ snprintf(error_msg, sizeof(error_msg), "Token not found: '%c'", c);
+ report_lexer_error(lx, "LexerError", "unknown_file", error_msg,
+ get_line_text_from_source(lx->src, lx->line), lx->line,
+ lx->col, 1);
+ return MAKE_TOKEN(TOK_ERROR, start, lx, 1, wh_count);
}
diff --git a/src/lexer/lexer.h b/src/lexer/lexer.h
index d14af9f7..f93e73c3 100644
--- a/src/lexer/lexer.h
+++ b/src/lexer/lexer.h
@@ -17,109 +17,110 @@
* @brief Enumeration of all possible token types recognized by the lexer.
*/
typedef enum {
- TOK_EOF, /**< End of file/input */
- TOK_ERROR, /**< Error token */
- TOK_IDENTIFIER, /**< Identifier (variable/function names) */
- TOK_KEYWORD, /**< Reserved keyword */
- TOK_NUMBER, /**< Numeric literal */
- TOK_NUM_FLOAT, /**< Floating point numeric literal */
- TOK_STRING, /**< String literal */
- TOK_CHAR_LITERAL, /**< Character literal */
-
- // Primitive Types
- TOK_INT, /**< int */
- TOK_DOUBLE, /**< double */
- TOK_UINT, /**< unsigned int */
- TOK_FLOAT, /**< float */
- TOK_BOOL, /**< bool */
- TOK_STRINGT, /**< str (string type) */
- TOK_VOID, /**< void */
- TOK_CHAR, /**< char */
-
- // Keywords
- TOK_IF, /**< if keyword */
- TOK_ELIF, /**< elif keyword */
- TOK_ELSE, /**< else keyword */
- TOK_LOOP, /**< loop keyword */
- TOK_RETURN, /**< return keyword */
- TOK_BREAK, /**< break keyword */
- TOK_CONTINUE, /**< continue keyword */
- TOK_STRUCT, /**< struct keyword */
- TOK_ENUM, /**< enum keyword */
- TOK_MOD, /**< mod keyword */
- TOK_IMPORT, /**< import keyword */
- TOK_TRUE, /**< true keyword */
- TOK_FALSE, /**< false keyword */
- TOK_PUBLIC, /**< pub keyword */
- TOK_PRIVATE, /**< private keyword */
- TOK_VAR, /**< let keyword */
- TOK_CONST, /**< const keyword */
- TOK_FN, /**< fn keyword */
- TOK_PRINT, /**< output keyword */
- TOK_PRINTLN, /**< println keyword */
- TOK_INPUT, /**< input keyword */
- TOK_ALLOC, /**< alloc(size_t size) */
- TOK_FREE, /**< free(void *ptr, size_t size) */
- TOK_CAST, /**< cast(value you want to cast too) */
- TOK_SIZE_OF, /**< size_of */
- TOK_AS, /**< as keyword (for use in modules) */
- TOK_DEFER, /**< defer keyword */
- TOK_IN, /**< in keyword */
- TOK_SWITCH, /**< switch keyword */
- TOK_SYSTEM, /**< system keyword */
- TOK_IMPL, /**< implement keyword */
-
- // prepocessor directives
- TOK_MODULE, /**< @module */
- TOK_USE, /**< @use */
-
- // function attibutes
- TOK_RETURNES_OWNERSHIP, /** #returns_ownership */
- TOK_TAKES_OWNERSHIP, /** #takes_ownership */
-
- // Symbols
- TOK_SYMBOL, /**< Fallback symbol */
- TOK_LPAREN, /**< ( */
- TOK_RPAREN, /**< ) */
- TOK_LBRACE, /**< { */
- TOK_RBRACE, /**< } */
- TOK_LBRACKET, /**< [ */
- TOK_RBRACKET, /**< ] */
- TOK_SEMICOLON, /**< ; */
- TOK_COMMA, /**< , */
- TOK_DOT, /**< . */
- TOK_AT, /**< @ */
- TOK_EQUAL, /**< = */
- TOK_PLUS, /**< + */
- TOK_MINUS, /**< - */
- TOK_STAR, /**< * */
- TOK_SLASH, /**< / */
- TOK_LT, /**< < */
- TOK_GT, /**< > */
- TOK_LE, /**< <= */
- TOK_GE, /**< >= */
- TOK_EQEQ, /**< == */
- TOK_NEQ, /**< != */
- TOK_AMP, /**< & */
- TOK_PIPE, /**< | */
- TOK_CARET, /**< ^ */
- TOK_TILDE, /**< ~ */
- TOK_AND, /**< && */
- TOK_OR, /**< || */
- TOK_RESOLVE, /**< :: */
- TOK_COLON, /**< : */
- TOK_BANG, /**< ! */
- TOK_QUESTION, /**< ? */
- TOK_PLUSPLUS, /**< ++ */
- TOK_MINUSMINUS, /**< -- */
- TOK_SHIFT_LEFT, /**< << */
- TOK_SHIFT_RIGHT, /**< >> */
- TOK_RANGE, /**< .. */
- TOK_RIGHT_ARROW, /**< -> */
- TOK_LEFT_ARROW, /**< <- */
- TOK_MODL, /**< % */
- TOK_WHITESPACE, /**< whitespace */
- TOK_COMMENT /**< comment */
+ TOK_EOF, /**< End of file/input */
+ TOK_ERROR, /**< Error token */
+ TOK_IDENTIFIER, /**< Identifier (variable/function names) */
+ TOK_KEYWORD, /**< Reserved keyword */
+ TOK_NUMBER, /**< Numeric literal */
+ TOK_NUM_FLOAT, /**< Floating point numeric literal */
+ TOK_STRING, /**< String literal */
+ TOK_CHAR_LITERAL, /**< Character literal */
+
+ // Primitive Types
+ TOK_INT, /**< int */
+ TOK_DOUBLE, /**< double */
+ TOK_UINT, /**< unsigned int */
+ TOK_FLOAT, /**< float */
+ TOK_BOOL, /**< bool */
+ TOK_STRINGT, /**< str (string type) */
+ TOK_VOID, /**< void */
+ TOK_CHAR, /**< char */
+
+ // Keywords
+ TOK_IF, /**< if keyword */
+ TOK_ELIF, /**< elif keyword */
+ TOK_ELSE, /**< else keyword */
+ TOK_LOOP, /**< loop keyword */
+ TOK_RETURN, /**< return keyword */
+ TOK_BREAK, /**< break keyword */
+ TOK_CONTINUE, /**< continue keyword */
+ TOK_STRUCT, /**< struct keyword */
+ TOK_ENUM, /**< enum keyword */
+ TOK_MOD, /**< mod keyword */
+ TOK_IMPORT, /**< import keyword */
+ TOK_TRUE, /**< true keyword */
+ TOK_FALSE, /**< false keyword */
+ TOK_PUBLIC, /**< pub keyword */
+ TOK_PRIVATE, /**< private keyword */
+ TOK_VAR, /**< let keyword */
+ TOK_CONST, /**< const keyword */
+ TOK_FN, /**< fn keyword */
+ TOK_PRINT, /**< output keyword */
+ TOK_PRINTLN, /**< println keyword */
+ TOK_INPUT, /**< input keyword */
+ TOK_ALLOC, /**< alloc(size_t size) */
+ TOK_FREE, /**< free(void *ptr, size_t size) */
+ TOK_CAST, /**< cast(value you want to cast too) */
+ TOK_SIZE_OF, /**< size_of */
+ TOK_AS, /**< as keyword (for use in modules) */
+ TOK_DEFER, /**< defer keyword */
+ TOK_IN, /**< in keyword */
+ TOK_SWITCH, /**< switch keyword */
+ TOK_SYSTEM, /**< system keyword */
+ TOK_IMPL, /**< implement keyword */
+ TOK_SYSCALL,
+
+ // prepocessor directives
+ TOK_MODULE, /**< @module */
+ TOK_USE, /**< @use */
+
+ // function attibutes
+ TOK_RETURNES_OWNERSHIP, /** #returns_ownership */
+ TOK_TAKES_OWNERSHIP, /** #takes_ownership */
+
+ // Symbols
+ TOK_SYMBOL, /**< Fallback symbol */
+ TOK_LPAREN, /**< ( */
+ TOK_RPAREN, /**< ) */
+ TOK_LBRACE, /**< { */
+ TOK_RBRACE, /**< } */
+ TOK_LBRACKET, /**< [ */
+ TOK_RBRACKET, /**< ] */
+ TOK_SEMICOLON, /**< ; */
+ TOK_COMMA, /**< , */
+ TOK_DOT, /**< . */
+ TOK_AT, /**< @ */
+ TOK_EQUAL, /**< = */
+ TOK_PLUS, /**< + */
+ TOK_MINUS, /**< - */
+ TOK_STAR, /**< * */
+ TOK_SLASH, /**< / */
+ TOK_LT, /**< < */
+ TOK_GT, /**< > */
+ TOK_LE, /**< <= */
+ TOK_GE, /**< >= */
+ TOK_EQEQ, /**< == */
+ TOK_NEQ, /**< != */
+ TOK_AMP, /**< & */
+ TOK_PIPE, /**< | */
+ TOK_CARET, /**< ^ */
+ TOK_TILDE, /**< ~ */
+ TOK_AND, /**< && */
+ TOK_OR, /**< || */
+ TOK_RESOLVE, /**< :: */
+ TOK_COLON, /**< : */
+ TOK_BANG, /**< ! */
+ TOK_QUESTION, /**< ? */
+ TOK_PLUSPLUS, /**< ++ */
+ TOK_MINUSMINUS, /**< -- */
+ TOK_SHIFT_LEFT, /**< << */
+ TOK_SHIFT_RIGHT, /**< >> */
+ TOK_RANGE, /**< .. */
+ TOK_RIGHT_ARROW, /**< -> */
+ TOK_LEFT_ARROW, /**< <- */
+ TOK_MODL, /**< % */
+ TOK_WHITESPACE, /**< whitespace */
+ TOK_COMMENT /**< comment */
} LumaTokenType;
/**
@@ -127,11 +128,11 @@ typedef enum {
* @brief Lexer state object for scanning source code.
*/
typedef struct {
- ArenaAllocator *arena; /**< Arena allocator for token storage */
- const char *src; /**< Pointer to the source code string */
- const char *current; /**< Current scanning position in source */
- int line; /**< Current line number */
- int col; /**< Current column number */
+ ArenaAllocator *arena; /**< Arena allocator for token storage */
+ const char *src; /**< Pointer to the source code string */
+ const char *current; /**< Current scanning position in source */
+ int line; /**< Current line number */
+ int col; /**< Current column number */
} Lexer;
/**
@@ -139,12 +140,12 @@ typedef struct {
* @brief Represents a single token extracted by the lexer.
*/
typedef struct {
- LumaTokenType type_; /**< Token type */
- const char *value; /**< Pointer to token text start */
- int line; /**< Line number of token */
- int col; /**< Column number of token */
- int length; /**< Length of the token text */
- int whitespace_len; /**< Leading whitespace length before token */
+ LumaTokenType type_; /**< Token type */
+ const char *value; /**< Pointer to token text start */
+ int line; /**< Line number of token */
+ int col; /**< Column number of token */
+ int length; /**< Length of the token text */
+ int whitespace_len; /**< Leading whitespace length before token */
} Token;
/**
@@ -152,8 +153,8 @@ typedef struct {
* @brief Maps symbol text to token type for quick lookup.
*/
typedef struct {
- const char *text; /**< Symbol text */
- LumaTokenType type; /**< Corresponding token type */
+ const char *text; /**< Symbol text */
+ LumaTokenType type; /**< Corresponding token type */
} SymbolEntry;
/**
@@ -161,8 +162,8 @@ typedef struct {
* @brief Maps keyword text to token type for quick lookup.
*/
typedef struct {
- const char *text; /**< Keyword text */
- LumaTokenType type; /**< Corresponding token type */
+ const char *text; /**< Keyword text */
+ LumaTokenType type; /**< Corresponding token type */
} KeywordEntry;
/**
diff --git a/src/llvm/arrays.c b/src/llvm/arrays.c
index 32353559..e15cd32c 100644
--- a/src/llvm/arrays.c
+++ b/src/llvm/arrays.c
@@ -51,6 +51,8 @@ LLVMValueRef convert_value_to_type(CodeGenContext *ctx, LLVMValueRef value,
bool check_array_bounds_runtime(CodeGenContext *ctx, LLVMValueRef array_ptr,
LLVMTypeRef array_type, LLVMValueRef index,
const char *var_name) {
+ (void)ctx;
+ (void)array_ptr;
if (LLVMGetTypeKind(array_type) != LLVMArrayTypeKind) {
return true; // Can't check bounds for non-arrays
}
diff --git a/src/llvm/expr.c b/src/llvm/expr.c
index a44f1084..a5248b27 100644
--- a/src/llvm/expr.c
+++ b/src/llvm/expr.c
@@ -1,3 +1,4 @@
+#include
#include
#include "llvm.h"
@@ -68,10 +69,26 @@ LLVMValueRef range_length(CodeGenContext *ctx, LLVMValueRef range_struct) {
}
LLVMValueRef codegen_expr_literal(CodeGenContext *ctx, AstNode *node) {
+ if (!ctx) {
+ fprintf(stderr, "FATAL: ctx is NULL in codegen_expr_literal!\n");
+ abort();
+ }
+ if (!ctx->context) {
+ fprintf(stderr, "FATAL: ctx->context is NULL in codegen_expr_literal!\n");
+ fprintf(stderr, "LLVM context was not initialized properly!\n");
+ abort();
+ }
+ if (!ctx->builder) {
+ fprintf(stderr, "FATAL: ctx->builder is NULL in codegen_expr_literal!\n");
+ fprintf(stderr, "LLVM builder was not initialized properly!\n");
+ abort();
+ }
+
switch (node->expr.literal.lit_type) {
case LITERAL_INT:
return LLVMConstInt(LLVMInt64TypeInContext(ctx->context),
node->expr.literal.value.int_val, false);
+
case LITERAL_FLOAT:
return LLVMConstReal(LLVMDoubleTypeInContext(ctx->context),
node->expr.literal.value.float_val);
@@ -79,10 +96,12 @@ LLVMValueRef codegen_expr_literal(CodeGenContext *ctx, AstNode *node) {
case LITERAL_BOOL:
return LLVMConstInt(LLVMInt1TypeInContext(ctx->context),
node->expr.literal.value.bool_val ? 1 : 0, false);
+
case LITERAL_CHAR:
return LLVMConstInt(LLVMInt8TypeInContext(ctx->context),
(unsigned char)node->expr.literal.value.char_val,
false);
+
case LITERAL_STRING: {
char *processed_str =
process_escape_sequences(node->expr.literal.value.string_val);
@@ -126,7 +145,10 @@ LLVMValueRef codegen_expr_literal(CodeGenContext *ctx, AstNode *node) {
case LITERAL_NULL:
return LLVMConstNull(
LLVMPointerType(LLVMInt8TypeInContext(ctx->context), 0));
+
default:
+ fprintf(stderr, "ERROR: Unknown literal type: %d\n",
+ node->expr.literal.lit_type);
return NULL;
}
}
@@ -333,12 +355,53 @@ LLVMValueRef codegen_expr_binary(CodeGenContext *ctx, AstNode *node) {
// Range operations work with both integers and floats
return create_range_struct(ctx, left, right);
+ // Bitwise operations (only for integers)
+ case BINOP_BIT_AND:
+ if (is_float_op) {
+ fprintf(stderr,
+ "Error: Bitwise AND not supported for floating point values\n");
+ return NULL;
+ }
+ return LLVMBuildAnd(ctx->builder, left, right, "bitand");
+
+ case BINOP_BIT_OR:
+ if (is_float_op) {
+ fprintf(stderr,
+ "Error: Bitwise OR not supported for floating point values\n");
+ return NULL;
+ }
+ return LLVMBuildOr(ctx->builder, left, right, "bitor");
+
+ case BINOP_BIT_XOR:
+ if (is_float_op) {
+ fprintf(stderr,
+ "Error: Bitwise XOR not supported for floating point values\n");
+ return NULL;
+ }
+ return LLVMBuildXor(ctx->builder, left, right, "bitxor");
+
+ case BINOP_SHL:
+ if (is_float_op) {
+ fprintf(stderr,
+ "Error: Left shift not supported for floating point values\n");
+ return NULL;
+ }
+ return LLVMBuildShl(ctx->builder, left, right, "shl");
+
+ case BINOP_SHR:
+ if (is_float_op) {
+ fprintf(stderr,
+ "Error: Right shift not supported for floating point values\n");
+ return NULL;
+ }
+ // Use arithmetic right shift (sign-extending for signed integers)
+ return LLVMBuildAShr(ctx->builder, left, right, "ashr");
+
default:
return NULL;
}
}
-// Enhanced codegen_expr_unary function with float support
LLVMValueRef codegen_expr_unary(CodeGenContext *ctx, AstNode *node) {
LLVMValueRef operand = codegen_expr(ctx, node->expr.unary.operand);
if (!operand)
@@ -365,6 +428,15 @@ LLVMValueRef codegen_expr_unary(CodeGenContext *ctx, AstNode *node) {
}
return LLVMBuildNot(ctx->builder, operand, "not");
+ case UNOP_BIT_NOT:
+ if (is_float) {
+ fprintf(
+ stderr,
+ "Error: Bitwise NOT (~) not supported for floating point values\n");
+ return NULL;
+ }
+ return LLVMBuildNot(ctx->builder, operand, "bitnot");
+
case UNOP_PRE_INC:
case UNOP_POST_INC: {
if (node->expr.unary.operand->type != AST_EXPR_IDENTIFIER) {
@@ -437,55 +509,55 @@ LLVMValueRef codegen_expr_call(CodeGenContext *ctx, AstNode *node) {
LLVMValueRef callee_value = NULL;
LLVMValueRef *args = NULL;
size_t arg_count = node->expr.call.arg_count;
-
+
// Check if this is a method call (obj.method())
if (callee->type == AST_EXPR_MEMBER && !callee->expr.member.is_compiletime) {
// Method call: obj.method(arg1, arg2)
// The typechecker has already injected 'self' as the first argument
// So we just need to codegen all arguments as-is
-
+
// Get the method function
const char *member_name = callee->expr.member.member;
-
+
// Look up the method in the current module
LLVMModuleRef current_llvm_module =
ctx->current_module ? ctx->current_module->module : ctx->module;
- LLVMValueRef method_func = LLVMGetNamedFunction(current_llvm_module, member_name);
-
+ LLVMValueRef method_func =
+ LLVMGetNamedFunction(current_llvm_module, member_name);
+
if (!method_func) {
fprintf(stderr, "Error: Method '%s' not found\n", member_name);
return NULL;
}
-
+
callee_value = method_func;
-
+
// Allocate space for all arguments (including injected self)
args = (LLVMValueRef *)arena_alloc(
- ctx->arena, sizeof(LLVMValueRef) * arg_count,
- alignof(LLVMValueRef));
-
+ ctx->arena, sizeof(LLVMValueRef) * arg_count, alignof(LLVMValueRef));
+
// Codegen all arguments (self is already in args[0] from typechecker)
for (size_t i = 0; i < arg_count; i++) {
args[i] = codegen_expr(ctx, node->expr.call.args[i]);
if (!args[i]) {
- fprintf(stderr, "Error: Failed to generate argument %zu for method '%s'\n",
- i, member_name);
+ fprintf(stderr,
+ "Error: Failed to generate argument %zu for method '%s'\n", i,
+ member_name);
return NULL;
}
}
-
+
} else {
// Regular function call or compile-time member access (module::func)
callee_value = codegen_expr(ctx, callee);
if (!callee_value) {
return NULL;
}
-
+
// Allocate space for arguments
args = (LLVMValueRef *)arena_alloc(
- ctx->arena, sizeof(LLVMValueRef) * arg_count,
- alignof(LLVMValueRef));
-
+ ctx->arena, sizeof(LLVMValueRef) * arg_count, alignof(LLVMValueRef));
+
for (size_t i = 0; i < arg_count; i++) {
args[i] = codegen_expr(ctx, node->expr.call.args[i]);
if (!args[i]) {
@@ -501,8 +573,7 @@ LLVMValueRef codegen_expr_call(CodeGenContext *ctx, AstNode *node) {
// Check if return type is void
if (LLVMGetTypeKind(return_type) == LLVMVoidTypeKind) {
// For void functions, don't assign a name to the call
- LLVMBuildCall2(ctx->builder, func_type, callee_value, args,
- arg_count, "");
+ LLVMBuildCall2(ctx->builder, func_type, callee_value, args, arg_count, "");
// Return a void constant since we can't return NULL
return LLVMConstNull(LLVMVoidTypeInContext(ctx->context));
} else {
@@ -670,8 +741,6 @@ LLVMValueRef codegen_expr_assignment(CodeGenContext *ctx, AstNode *node) {
// Final fallback: use value type
if (!element_type) {
element_type = value_type;
- fprintf(stderr, "Warning: Could not determine pointer element type, "
- "using value type\n");
}
// Convert value to match element type if needed
@@ -805,6 +874,7 @@ LLVMValueRef codegen_expr_array(CodeGenContext *ctx, AstNode *node) {
AstNode **elements = node->expr.array.elements;
size_t element_count = node->expr.array.element_count;
+ size_t target_size = node->expr.array.target_size; // NEW: Get target size for padding
if (element_count == 0) {
fprintf(stderr, "Error: Empty array literals not supported\n");
@@ -819,16 +889,21 @@ LLVMValueRef codegen_expr_array(CodeGenContext *ctx, AstNode *node) {
}
LLVMTypeRef element_type = LLVMTypeOf(first_element);
- LLVMTypeRef array_type = LLVMArrayType(element_type, element_count);
+
+ // NEW: Use target_size if set (for padding), otherwise use actual element_count
+ size_t actual_array_size = (target_size > 0) ? target_size : element_count;
+ LLVMTypeRef array_type = LLVMArrayType(element_type, actual_array_size);
// Check if all elements are constants
bool all_constants = LLVMIsConstant(first_element);
+
+ // NEW: Allocate for the FULL size (including padding)
LLVMValueRef *element_values = (LLVMValueRef *)arena_alloc(
- ctx->arena, sizeof(LLVMValueRef) * element_count, alignof(LLVMValueRef));
+ ctx->arena, sizeof(LLVMValueRef) * actual_array_size, alignof(LLVMValueRef));
element_values[0] = first_element;
- // Generate remaining elements and check for constants
+ // Generate provided elements
for (size_t i = 1; i < element_count; i++) {
element_values[i] = codegen_expr(ctx, elements[i]);
if (!element_values[i]) {
@@ -853,9 +928,21 @@ LLVMValueRef codegen_expr_array(CodeGenContext *ctx, AstNode *node) {
}
}
- // *** ADD THE NEW CODE HERE ***
+ // NEW: Pad remaining elements with zeros if target_size > element_count
+ if (target_size > element_count) {
+ LLVMValueRef zero_value = LLVMConstNull(element_type);
+
+ for (size_t i = element_count; i < target_size; i++) {
+ element_values[i] = zero_value;
+ }
+
+ // If we're padding, we can't be all constants unless zeros count
+ // (which they do, so keep checking)
+ // zero_value is always constant, so all_constants remains unchanged
+ }
+
// Check if any element references a global from another module
- for (size_t i = 0; i < element_count && all_constants; i++) {
+ for (size_t i = 0; i < actual_array_size && all_constants; i++) {
if (LLVMIsConstant(element_values[i]) &&
LLVMIsAGlobalVariable(element_values[i])) {
// Check if it's from a different module
@@ -868,17 +955,16 @@ LLVMValueRef codegen_expr_array(CodeGenContext *ctx, AstNode *node) {
}
}
}
- // *** END NEW CODE ***
if (all_constants) {
- // Create constant array
- return LLVMConstArray(element_type, element_values, element_count);
+ // Create constant array (now with padding)
+ return LLVMConstArray(element_type, element_values, actual_array_size);
} else {
- // Create runtime array
+ // Create runtime array (now with padding)
LLVMValueRef array_alloca =
LLVMBuildAlloca(ctx->builder, array_type, "array_literal");
- for (size_t i = 0; i < element_count; i++) {
+ for (size_t i = 0; i < actual_array_size; i++) {
// Create GEP to element
LLVMValueRef indices[2];
indices[0] = LLVMConstInt(LLVMInt32TypeInContext(ctx->context), 0, false);
@@ -899,6 +985,172 @@ LLVMValueRef codegen_expr_index(CodeGenContext *ctx, AstNode *node) {
return NULL;
}
+ // **NEW: Special handling for indexing member access (struct.field[index])**
+ if (node->expr.index.object->type == AST_EXPR_MEMBER) {
+ AstNode *member_expr = node->expr.index.object;
+ const char *field_name = member_expr->expr.member.member;
+
+ // Generate the member access to get the pointer
+ LLVMValueRef pointer = codegen_expr_struct_access(ctx, member_expr);
+ if (!pointer) {
+ fprintf(stderr, "Error: Failed to resolve member access for indexing\n");
+ return NULL;
+ }
+
+ // Generate the index expression
+ LLVMValueRef index = codegen_expr(ctx, node->expr.index.index);
+ if (!index) {
+ return NULL;
+ }
+
+ LLVMTypeRef pointer_type = LLVMTypeOf(pointer);
+ LLVMTypeKind pointer_kind = LLVMGetTypeKind(pointer_type);
+
+ // The member access should have returned a pointer
+ if (pointer_kind != LLVMPointerTypeKind) {
+ fprintf(stderr, "Error: Member '%s' is not a pointer type for indexing\n",
+ field_name);
+ return NULL;
+ }
+
+ // Find the struct that contains this field to get element type info
+ LLVMTypeRef element_type = NULL;
+
+ // Build a list of field names in the chain (in reverse order)
+ const char *field_chain[32];
+ int chain_length = 0;
+ AstNode *current_node = member_expr;
+
+ while (current_node && current_node->type == AST_EXPR_MEMBER &&
+ chain_length < 32) {
+ field_chain[chain_length++] = current_node->expr.member.member;
+ current_node = current_node->expr.member.object;
+ }
+
+ // Reverse the chain to get the correct order
+ for (int i = 0; i < chain_length / 2; i++) {
+ const char *temp = field_chain[i];
+ field_chain[i] = field_chain[chain_length - 1 - i];
+ field_chain[chain_length - 1 - i] = temp;
+ }
+
+ // Find the base identifier
+ AstNode *base_obj = member_expr->expr.member.object;
+ while (base_obj->type == AST_EXPR_MEMBER) {
+ base_obj = base_obj->expr.member.object;
+ }
+
+ if (base_obj->type == AST_EXPR_IDENTIFIER) {
+ const char *base_name = base_obj->expr.identifier.name;
+ LLVM_Symbol *base_sym = find_symbol(ctx, base_name);
+
+ if (base_sym) {
+ // Find the initial struct type
+ StructInfo *current_struct = NULL;
+ LLVMTypeRef sym_type = base_sym->type;
+ LLVMTypeKind sym_kind = LLVMGetTypeKind(sym_type);
+
+ if (sym_kind == LLVMPointerTypeKind && base_sym->element_type) {
+ // It's a pointer to struct
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ if (info->llvm_type == base_sym->element_type) {
+ current_struct = info;
+ break;
+ }
+ }
+ } else if (sym_kind == LLVMStructTypeKind) {
+ // Direct struct type
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ if (info->llvm_type == sym_type) {
+ current_struct = info;
+ break;
+ }
+ }
+ }
+
+ // Trace through the field chain
+ for (int i = 0; i < chain_length && current_struct; i++) {
+ const char *current_field_name = field_chain[i];
+ int field_idx = get_field_index(current_struct, current_field_name);
+
+ if (field_idx < 0) {
+ fprintf(stderr, "Error: Field '%s' not found in struct '%s'\n",
+ current_field_name, current_struct->name);
+ break;
+ }
+
+ // If this is the last field in the chain, get its element type
+ if (i == chain_length - 1) {
+ LLVMTypeRef field_type = current_struct->field_types[field_idx];
+
+ // Check if this field is an array - if so, get its element type
+ if (LLVMGetTypeKind(field_type) == LLVMArrayTypeKind) {
+ element_type = LLVMGetElementType(field_type);
+ } else {
+ element_type = current_struct->field_element_types[field_idx];
+ }
+ break;
+ }
+
+ // Otherwise, move to the next struct in the chain
+ LLVMTypeRef field_type = current_struct->field_types[field_idx];
+ LLVMTypeKind field_kind = LLVMGetTypeKind(field_type);
+
+ if (field_kind == LLVMStructTypeKind) {
+ // Field is a struct - find its info
+ StructInfo *next_struct = NULL;
+ for (StructInfo *info = ctx->struct_types; info;
+ info = info->next) {
+ if (info->llvm_type == field_type) {
+ next_struct = info;
+ break;
+ }
+ }
+ current_struct = next_struct;
+ } else if (field_kind == LLVMPointerTypeKind) {
+ // Field is a pointer - get what it points to
+ LLVMTypeRef pointee =
+ current_struct->field_element_types[field_idx];
+ if (pointee && LLVMGetTypeKind(pointee) == LLVMStructTypeKind) {
+ // Find the struct info for the pointee
+ StructInfo *next_struct = NULL;
+ for (StructInfo *info = ctx->struct_types; info;
+ info = info->next) {
+ if (info->llvm_type == pointee) {
+ next_struct = info;
+ break;
+ }
+ }
+ current_struct = next_struct;
+ } else {
+ // Not a struct pointer, can't continue
+ break;
+ }
+ } else {
+ // Field is not a struct or pointer, can't continue
+ break;
+ }
+ }
+ }
+ }
+
+ if (!element_type) {
+ fprintf(
+ stderr,
+ "Error: Could not determine pointer element type for indexing '%s'\n",
+ field_name);
+ return NULL;
+ }
+
+ // Build GEP for pointer arithmetic
+ LLVMValueRef element_ptr = LLVMBuildGEP2(
+ ctx->builder, element_type, pointer, &index, 1, "member_ptr_element");
+
+ // Load the actual value
+ return LLVMBuildLoad2(ctx->builder, element_type, element_ptr,
+ "member_element_val");
+ }
+
// Generate the object being indexed
LLVMValueRef object = codegen_expr(ctx, node->expr.index.object);
if (!object) {
@@ -1032,21 +1284,6 @@ LLVMValueRef codegen_expr_index(CodeGenContext *ctx, AstNode *node) {
cast_node->expr.cast.type->type_data.pointer.pointee_type;
pointee_type = codegen_type(ctx, pointee_node);
}
- } else if (node->expr.index.object->type == AST_EXPR_MEMBER) {
- AstNode *member_expr = node->expr.index.object;
- const char *field_name = member_expr->expr.member.member;
-
- // Get the struct info
- StructInfo *struct_info = NULL;
- for (StructInfo *info = ctx->struct_types; info; info = info->next) {
- int field_idx = get_field_index(info, field_name);
- if (field_idx >= 0) {
- struct_info = info;
- // Use the element type stored for this field
- pointee_type = struct_info->field_element_types[field_idx];
- break;
- }
- }
}
// CRITICAL: Don't fall back to i8! This causes the bug.
@@ -1380,6 +1617,170 @@ LLVMValueRef codegen_expr_system(CodeGenContext *ctx, AstNode *node) {
"system_call");
}
+/**
+ * @brief Generate LLVM IR for syscall expression
+ *
+ * Syscall in x86_64 Linux:
+ * - syscall number goes in %rax
+ * - arguments go in %rdi, %rsi, %rdx, %r10, %r8, %r9 (in that order)
+ * - return value comes back in %rax
+ *
+ * We use inline assembly to invoke the syscall instruction.
+ */
+LLVMValueRef codegen_expr_syscall(CodeGenContext *ctx, AstNode *node) {
+ if (!node || node->type != AST_EXPR_SYSCALL) {
+ fprintf(stderr, "Error: Expected syscall expression node\n");
+ return NULL;
+ }
+
+ AstNode **args = node->expr.syscall.args;
+ size_t arg_count = node->expr.syscall.count;
+
+ // Syscall requires at least 1 argument (the syscall number)
+ if (arg_count == 0) {
+ fprintf(
+ stderr,
+ "Error: syscall() requires at least one argument (syscall number)\n");
+ return NULL;
+ }
+
+ // Maximum 7 arguments: syscall_num + 6 syscall args
+ if (arg_count > 7) {
+ fprintf(stderr, "Error: syscall() supports maximum 7 arguments (syscall "
+ "number + 6 parameters)\n");
+ return NULL;
+ }
+
+ // Get current LLVM module
+ LLVMModuleRef current_llvm_module =
+ ctx->current_module ? ctx->current_module->module : ctx->module;
+
+ // Generate all arguments
+ LLVMValueRef *llvm_args = (LLVMValueRef *)arena_alloc(
+ ctx->arena, sizeof(LLVMValueRef) * arg_count, alignof(LLVMValueRef));
+
+ for (size_t i = 0; i < arg_count; i++) {
+ llvm_args[i] = codegen_expr(ctx, args[i]);
+ if (!llvm_args[i]) {
+ fprintf(stderr, "Error: Failed to generate syscall argument %zu\n",
+ i + 1);
+ return NULL;
+ }
+
+ // Ensure all arguments are i64 (syscalls expect 64-bit values)
+ LLVMTypeRef arg_type = LLVMTypeOf(llvm_args[i]);
+ LLVMTypeKind arg_kind = LLVMGetTypeKind(arg_type);
+
+ if (arg_kind == LLVMIntegerTypeKind) {
+ unsigned bits = LLVMGetIntTypeWidth(arg_type);
+ if (bits < 64) {
+ // Zero-extend smaller integers to i64
+ llvm_args[i] = LLVMBuildZExt(ctx->builder, llvm_args[i],
+ LLVMInt64TypeInContext(ctx->context),
+ "syscall_arg_ext");
+ } else if (bits > 64) {
+ // Truncate larger integers to i64
+ llvm_args[i] = LLVMBuildTrunc(ctx->builder, llvm_args[i],
+ LLVMInt64TypeInContext(ctx->context),
+ "syscall_arg_trunc");
+ }
+ } else if (arg_kind == LLVMPointerTypeKind) {
+ // Convert pointer to i64
+ llvm_args[i] = LLVMBuildPtrToInt(ctx->builder, llvm_args[i],
+ LLVMInt64TypeInContext(ctx->context),
+ "syscall_ptr_to_int");
+ } else if (arg_kind == LLVMFloatTypeKind ||
+ arg_kind == LLVMDoubleTypeKind) {
+ // Convert float/double to i64 (reinterpret bits, don't convert value)
+ fprintf(stderr,
+ "Warning: syscall argument %zu is float/double, casting to int\n",
+ i + 1);
+ llvm_args[i] = LLVMBuildFPToSI(ctx->builder, llvm_args[i],
+ LLVMInt64TypeInContext(ctx->context),
+ "syscall_float_to_int");
+ }
+ }
+
+ // Build inline assembly string based on number of arguments
+ // Linux x86_64 syscall convention:
+ // rax = syscall number
+ // rdi, rsi, rdx, r10, r8, r9 = arguments 1-6
+
+ const char *asm_template;
+ const char *constraints;
+
+ switch (arg_count) {
+ case 1: // syscall number only
+ asm_template = "syscall";
+ constraints = "={rax},{rax}";
+ break;
+ case 2: // syscall + 1 arg
+ asm_template = "syscall";
+ constraints = "={rax},{rax},{rdi}";
+ break;
+ case 3: // syscall + 2 args
+ asm_template = "syscall";
+ constraints = "={rax},{rax},{rdi},{rsi}";
+ break;
+ case 4: // syscall + 3 args
+ asm_template = "syscall";
+ constraints = "={rax},{rax},{rdi},{rsi},{rdx}";
+ break;
+ case 5: // syscall + 4 args
+ asm_template = "syscall";
+ constraints = "={rax},{rax},{rdi},{rsi},{rdx},{r10}";
+ break;
+ case 6: // syscall + 5 args
+ asm_template = "syscall";
+ constraints = "={rax},{rax},{rdi},{rsi},{rdx},{r10},{r8}";
+ break;
+ case 7: // syscall + 6 args (maximum)
+ asm_template = "syscall";
+ constraints = "={rax},{rax},{rdi},{rsi},{rdx},{r10},{r8},{r9}";
+ break;
+ default:
+ fprintf(stderr, "Error: Invalid syscall argument count\n");
+ return NULL;
+ }
+
+ // Create parameter types array (all i64)
+ LLVMTypeRef *param_types = (LLVMTypeRef *)arena_alloc(
+ ctx->arena, sizeof(LLVMTypeRef) * arg_count, alignof(LLVMTypeRef));
+
+ for (size_t i = 0; i < arg_count; i++) {
+ param_types[i] = LLVMInt64TypeInContext(ctx->context);
+ }
+
+ // Create inline assembly function type: i64 (args...)
+ LLVMTypeRef asm_func_type = LLVMFunctionType(
+ LLVMInt64TypeInContext(ctx->context), // return type (syscall result)
+ param_types, arg_count,
+ false // not vararg
+ );
+
+ // Create the inline assembly call
+ LLVMValueRef asm_func =
+ LLVMGetInlineAsm(asm_func_type,
+ (char *)asm_template, // assembly template
+ strlen(asm_template),
+ (char *)constraints, // constraints
+ strlen(constraints),
+ true, // has side effects
+ false, // align stack
+ LLVMInlineAsmDialectATT, // AT&T syntax
+ false // can throw
+ );
+
+ // Call the inline assembly
+ LLVMValueRef result = LLVMBuildCall2(ctx->builder, asm_func_type, asm_func,
+ llvm_args, arg_count, "syscall_result");
+
+ // Mark as volatile to prevent optimization
+ LLVMSetVolatile(result, true);
+
+ return result;
+}
+
// sizeof
LLVMValueRef codegen_expr_sizeof(CodeGenContext *ctx, AstNode *node) {
LLVMTypeRef type;
@@ -1408,37 +1809,61 @@ LLVMValueRef codegen_expr_sizeof(CodeGenContext *ctx, AstNode *node) {
case LLVMPointerTypeKind:
return LLVMConstInt(LLVMInt64TypeInContext(ctx->context), 8, false);
case LLVMStructTypeKind: {
- // Calculate struct size by summing field sizes
unsigned field_count = LLVMCountStructElementTypes(type);
LLVMTypeRef *field_types = malloc(field_count * sizeof(LLVMTypeRef));
LLVMGetStructElementTypes(type, field_types);
uint64_t total_size = 0;
+ uint64_t max_align = 1;
+
for (unsigned i = 0; i < field_count; i++) {
- LLVMTypeKind field_kind = LLVMGetTypeKind(field_types[i]);
- switch (field_kind) {
+ LLVMTypeRef ftype = field_types[i];
+ uint64_t fsize = 0;
+ uint64_t falign = 1;
+
+ LLVMTypeKind fkind = LLVMGetTypeKind(ftype);
+ switch (fkind) {
case LLVMIntegerTypeKind:
- total_size += LLVMGetIntTypeWidth(field_types[i]) / 8;
+ fsize = LLVMGetIntTypeWidth(ftype) / 8;
+ falign = fsize > 8 ? 8 : fsize; // int64 align=8
break;
case LLVMFloatTypeKind:
- total_size += 4;
+ fsize = 4;
+ falign = 4;
break;
case LLVMDoubleTypeKind:
- total_size += 8;
+ fsize = 8;
+ falign = 8;
break;
case LLVMPointerTypeKind:
- total_size += 8;
+ fsize = 8;
+ falign = 8;
break;
default:
- total_size += 8; // fallback
+ fsize = 8;
+ falign = 8;
break;
}
+
+ // align current offset
+ if (total_size % falign != 0)
+ total_size += falign - (total_size % falign);
+
+ total_size += fsize;
+ if (falign > max_align)
+ max_align = falign;
}
+ // align final struct size to max_align
+ if (total_size % max_align != 0)
+ total_size += max_align - (total_size % max_align);
+
free(field_types);
+
return LLVMConstInt(LLVMInt64TypeInContext(ctx->context), total_size,
false);
}
+
default:
return LLVMConstInt(LLVMInt64TypeInContext(ctx->context), 8, false);
}
@@ -1564,7 +1989,8 @@ LLVMValueRef codegen_expr_deref(CodeGenContext *ctx, AstNode *node) {
// Final fallback if we couldn't determine the type
if (!element_type) {
- fprintf(stderr, "Warning: Could not determine pointer element type for dereference, defaulting to i64\n");
+ fprintf(stderr, "Warning: Could not determine pointer element type for "
+ "dereference, defaulting to i64\n");
element_type = LLVMInt64TypeInContext(ctx->context);
}
diff --git a/src/llvm/llvm.c b/src/llvm/llvm.c
index ded1f548..4ed486bc 100644
--- a/src/llvm/llvm.c
+++ b/src/llvm/llvm.c
@@ -232,6 +232,13 @@ CodeGenContext *init_codegen_context(ArenaAllocator *arena) {
ctx->loop_break_block = NULL;
ctx->struct_types = NULL;
ctx->arena = arena;
+
+ // ADD THIS LINE:
+ ctx->module = NULL; // Initialize legacy module field
+
+ // OR initialize deferred statements if they exist:
+ ctx->deferred_statements = NULL;
+ ctx->deferred_count = 0;
return ctx;
}
diff --git a/src/llvm/llvm.h b/src/llvm/llvm.h
index 47679748..2f0839e4 100644
--- a/src/llvm/llvm.h
+++ b/src/llvm/llvm.h
@@ -200,9 +200,9 @@ void add_struct_type(CodeGenContext *ctx, StructInfo *struct_info);
int get_field_index(StructInfo *struct_info, const char *field_name);
bool is_field_access_allowed(CodeGenContext *ctx, StructInfo *struct_info,
int field_index);
-LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
- StructInfo *struct_info, const char *method_name,
- bool is_public);
+LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
+ StructInfo *struct_info,
+ const char *method_name, bool is_public);
// Enhanced member access (handles both struct.field and module.symbol)
LLVMValueRef codegen_expr_member_access_enhanced(CodeGenContext *ctx,
AstNode *node);
@@ -286,18 +286,19 @@ LLVMValueRef codegen_expr_identifier(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_binary(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_unary(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_call(CodeGenContext *ctx, AstNode *node);
-// LLVMValueRef codegen_method_call(CodeGenContext *ctx, AstNode *call_node);
LLVMValueRef codegen_expr_assignment(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_array(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_index(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_cast(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_input(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_system(CodeGenContext *ctx, AstNode *node);
+LLVMValueRef codegen_expr_syscall(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_sizeof(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_alloc(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_free(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_deref(CodeGenContext *ctx, AstNode *node);
LLVMValueRef codegen_expr_addr(CodeGenContext *ctx, AstNode *node);
+LLVMValueRef codegen_expr_struct_literal(CodeGenContext *ctx, AstNode *node);
// =============================================================================
// AST NODE HANDLERS - STATEMENT TYPES
diff --git a/src/llvm/lookup.c b/src/llvm/lookup.c
index f79377b7..09e3a9d1 100644
--- a/src/llvm/lookup.c
+++ b/src/llvm/lookup.c
@@ -2,6 +2,8 @@
LLVMValueRef codegen_expr(CodeGenContext *ctx, AstNode *node) {
if (!node || node->category != Node_Category_EXPR) {
+ fprintf(stderr, "ERROR: codegen_expr - invalid node (node=%p, category=%d)\n",
+ (void*)node, node ? (signed int)node->category : -1);
return NULL;
}
@@ -30,6 +32,8 @@ LLVMValueRef codegen_expr(CodeGenContext *ctx, AstNode *node) {
return codegen_expr_input(ctx, node);
case AST_EXPR_SYSTEM:
return codegen_expr_system(ctx, node);
+ case AST_EXPR_SYSCALL:
+ return codegen_expr_syscall(ctx, node);
case AST_EXPR_SIZEOF:
return codegen_expr_sizeof(ctx, node);
case AST_EXPR_ALLOC:
@@ -41,8 +45,9 @@ LLVMValueRef codegen_expr(CodeGenContext *ctx, AstNode *node) {
case AST_EXPR_ADDR:
return codegen_expr_addr(ctx, node);
case AST_EXPR_MEMBER:
- // Enhanced member access that handles both module.symbol and struct.field
return codegen_expr_member_access_enhanced(ctx, node);
+ case AST_EXPR_STRUCT:
+ return codegen_expr_struct_literal(ctx, node);
default:
fprintf(stderr, "Error: Unknown expression type: %d\n", node->type);
return NULL;
diff --git a/src/llvm/module_handles.c b/src/llvm/module_handles.c
index bf39d48a..d746b38e 100644
--- a/src/llvm/module_handles.c
+++ b/src/llvm/module_handles.c
@@ -361,71 +361,90 @@ LLVMValueRef codegen_expr_member_access_enhanced(CodeGenContext *ctx,
const char *member = node->expr.member.member;
bool is_compiletime = node->expr.member.is_compiletime;
- if (object->type != AST_EXPR_IDENTIFIER) {
+ if (object->type != AST_EXPR_IDENTIFIER &&
+ !(object->type == AST_EXPR_MEMBER && is_compiletime)) {
// Handle complex expressions like function_call().field or (*ptr).field
return codegen_expr_struct_access(ctx, node);
}
- const char *object_name = object->expr.identifier.name;
-
- // NEW: First check if this is actually a local variable/parameter with struct type
- // This prevents false positives when parameter names match module names
- LLVM_Symbol *local_sym = find_symbol(ctx, object_name);
- if (local_sym && !local_sym->is_function) {
- // Check if this local variable has a struct type
- LLVMTypeRef sym_type = local_sym->type;
- LLVMTypeKind sym_kind = LLVMGetTypeKind(sym_type);
-
- // If it's a struct or pointer to struct, this is struct field access
- bool is_struct_access = false;
-
- if (sym_kind == LLVMStructTypeKind) {
- is_struct_access = true;
- } else if (sym_kind == LLVMPointerTypeKind && local_sym->element_type) {
- if (LLVMGetTypeKind(local_sym->element_type) == LLVMStructTypeKind) {
- is_struct_access = true;
- }
- }
-
- if (is_struct_access) {
- // This is definitely struct field access, not module access
- if (is_compiletime) {
- fprintf(stderr, "Error: Cannot use compile-time access '::' for struct field.\n"
- " Variable '%s' is a struct, use runtime access '.' instead: %s.%s\n",
- object_name, object_name, member);
+ // **FIX: For compile-time access (::), check module access FIRST**
+ if (is_compiletime) {
+ // **NEW: Handle chained compile-time access (ast::ExprKind::EXPR_NUMBER)**
+ // Check if object itself is a member expression (chained ::)
+ if (object->type == AST_EXPR_MEMBER && object->expr.member.is_compiletime) {
+ // This is chained: module::Type::Member
+ // Example: ast::ExprKind::EXPR_NUMBER
+ // object = ast::ExprKind (another member expr)
+ // member = EXPR_NUMBER
+
+ if (object->expr.member.object->type != AST_EXPR_IDENTIFIER) {
+ fprintf(stderr, "Error: Expected identifier in chained compile-time access\n");
return NULL;
}
- return codegen_expr_struct_access(ctx, node);
+
+ const char *module_name = object->expr.member.object->expr.identifier.name;
+ const char *type_name = object->expr.member.member;
+
+ // Build the fully qualified name: TypeName.Member
+ char type_qualified_name[256];
+ snprintf(type_qualified_name, sizeof(type_qualified_name), "%s.%s", type_name, member);
+
+ // Look in the specified module
+ ModuleCompilationUnit *source_module = find_module(ctx, module_name);
+ if (source_module) {
+ LLVM_Symbol *enum_member = find_symbol_in_module(source_module, type_qualified_name);
+ if (enum_member && is_enum_constant(enum_member)) {
+ return LLVMGetInitializer(enum_member->value);
+ }
+ }
+
+ // If not found in the named module, try current module (in case it was imported)
+ LLVM_Symbol *enum_member = find_symbol_in_module(ctx->current_module, type_qualified_name);
+ if (enum_member && is_enum_constant(enum_member)) {
+ return LLVMGetInitializer(enum_member->value);
+ }
+
+ // Try all imported modules as fallback
+ for (ModuleCompilationUnit *unit = ctx->modules; unit; unit = unit->next) {
+ if (unit == ctx->current_module)
+ continue;
+
+ LLVM_Symbol *sym = find_symbol_in_module(unit, type_qualified_name);
+ if (sym && is_enum_constant(sym)) {
+ return LLVMGetInitializer(sym->value);
+ }
+ }
+
+ fprintf(stderr, "Error: Enum member '%s::%s::%s' not found\n",
+ module_name, type_name, member);
+ return NULL;
}
- }
- // Check if this is compile-time access (::)
- if (is_compiletime) {
- // This is compile-time access - handle module functions and enum members
+ // Handle simple compile-time access (module::symbol or Type::member)
+ const char *object_name = NULL;
+ if (object->type == AST_EXPR_IDENTIFIER) {
+ object_name = object->expr.identifier.name;
+ } else {
+ fprintf(stderr, "Error: Expected identifier for compile-time access\n");
+ return NULL;
+ }
- // 1. First check if it's a direct qualified symbol (module.function or
- // enum.member)
+ // 1. First check if it's a direct qualified symbol (module.function or enum.member)
char qualified_name[256];
- snprintf(qualified_name, sizeof(qualified_name), "%s.%s", object_name,
- member);
+ snprintf(qualified_name, sizeof(qualified_name), "%s.%s", object_name, member);
- LLVM_Symbol *qualified_sym =
- find_symbol_in_module(ctx->current_module, qualified_name);
+ LLVM_Symbol *qualified_sym = find_symbol_in_module(ctx->current_module, qualified_name);
if (qualified_sym) {
if (qualified_sym->is_function) {
return qualified_sym->value;
} else if (is_enum_constant(qualified_sym)) {
- // Enum constant - return the constant value directly
return LLVMGetInitializer(qualified_sym->value);
} else {
- // Regular variable - load its value
- return LLVMBuildLoad2(ctx->builder, qualified_sym->type,
- qualified_sym->value, "load");
+ return LLVMBuildLoad2(ctx->builder, qualified_sym->type, qualified_sym->value, "load");
}
}
- // 1.5 Search all modules to find where this symbol exists
- // (handles both direct module names and aliases)
+ // 2. Search all modules to find where this symbol exists
for (ModuleCompilationUnit *search_module = ctx->modules; search_module;
search_module = search_module->next) {
if (search_module == ctx->current_module)
@@ -444,34 +463,28 @@ LLVMValueRef codegen_expr_member_access_enhanced(CodeGenContext *ctx,
}
// Now try to find the imported symbol again
- qualified_sym =
- find_symbol_in_module(ctx->current_module, qualified_name);
+ qualified_sym = find_symbol_in_module(ctx->current_module, qualified_name);
if (qualified_sym) {
if (qualified_sym->is_function) {
return qualified_sym->value;
} else {
- return LLVMBuildLoad2(ctx->builder, qualified_sym->type,
- qualified_sym->value, "load");
+ return LLVMBuildLoad2(ctx->builder, qualified_sym->type, qualified_sym->value, "load");
}
}
break; // Found and imported, stop searching
}
}
- // 2. Check if this is an imported module function
- // Look for module import with this alias
+ // 3. Check if this is an imported module function
for (ModuleCompilationUnit *unit = ctx->modules; unit; unit = unit->next) {
if (unit == ctx->current_module)
continue;
- // Check if we have any imported symbols from this module with the alias
- // pattern
+ // Check if we have any imported symbols from this module with the alias pattern
char test_qualified_name[256];
- snprintf(test_qualified_name, sizeof(test_qualified_name), "%s.%s",
- object_name, member);
+ snprintf(test_qualified_name, sizeof(test_qualified_name), "%s.%s", object_name, member);
- LLVM_Symbol *imported_sym =
- find_symbol_in_module(ctx->current_module, test_qualified_name);
+ LLVM_Symbol *imported_sym = find_symbol_in_module(ctx->current_module, test_qualified_name);
if (imported_sym && imported_sym->is_function) {
return imported_sym->value;
}
@@ -485,28 +498,50 @@ LLVMValueRef codegen_expr_member_access_enhanced(CodeGenContext *ctx,
LLVMModuleRef current_llvm_module =
ctx->current_module ? ctx->current_module->module : ctx->module;
- LLVMValueRef existing =
- LLVMGetNamedFunction(current_llvm_module, member);
+ LLVMValueRef existing = LLVMGetNamedFunction(current_llvm_module, member);
if (!existing) {
LLVMTypeRef func_type = LLVMGlobalGetValueType(original_sym->value);
existing = LLVMAddFunction(current_llvm_module, member, func_type);
LLVMSetLinkage(existing, LLVMExternalLinkage);
// Add to current module's symbol table
- add_symbol_to_module(ctx->current_module, test_qualified_name,
- existing, func_type, true);
+ add_symbol_to_module(ctx->current_module, test_qualified_name, existing, func_type, true);
}
return original_sym->value;
}
}
- // 3. Error for compile-time access that wasn't found
- fprintf(stderr, "Error: No compile-time symbol '%s::%s' found\n",
- object_name, member);
+ // 4. Error for compile-time access that wasn't found
+ fprintf(stderr, "Error: No compile-time symbol '%s::%s' found\n", object_name, member);
return NULL;
}
- // This is runtime access (.) - handle struct field access
+ // **NOW check for runtime access (.)**
+ const char *object_name = object->expr.identifier.name;
+
+ // Only NOW do we check if this is a local struct variable
+ LLVM_Symbol *local_sym = find_symbol(ctx, object_name);
+ if (local_sym && !local_sym->is_function) {
+ // Check if this local variable has a struct type
+ LLVMTypeRef sym_type = local_sym->type;
+ LLVMTypeKind sym_kind = LLVMGetTypeKind(sym_type);
+
+ // If it's a struct or pointer to struct, this is struct field access
+ bool is_struct_access = false;
+
+ if (sym_kind == LLVMStructTypeKind) {
+ is_struct_access = true;
+ } else if (sym_kind == LLVMPointerTypeKind && local_sym->element_type) {
+ if (LLVMGetTypeKind(local_sym->element_type) == LLVMStructTypeKind) {
+ is_struct_access = true;
+ }
+ }
+
+ if (is_struct_access) {
+ // This is struct field/method access
+ return codegen_expr_struct_access(ctx, node);
+ }
+ }
// 1. Check if base_name is a known module (give helpful error)
bool is_module_access = false;
@@ -518,10 +553,8 @@ LLVMValueRef codegen_expr_member_access_enhanced(CodeGenContext *ctx,
// Also check for alias pattern
char test_qualified_name[256];
- snprintf(test_qualified_name, sizeof(test_qualified_name), "%s.%s",
- object_name, member);
- LLVM_Symbol *test_sym =
- find_symbol_in_module(ctx->current_module, test_qualified_name);
+ snprintf(test_qualified_name, sizeof(test_qualified_name), "%s.%s", object_name, member);
+ LLVM_Symbol *test_sym = find_symbol_in_module(ctx->current_module, test_qualified_name);
if (test_sym && test_sym->is_function) {
is_module_access = true;
break;
@@ -546,8 +579,7 @@ LLVMValueRef codegen_expr_member_access_enhanced(CodeGenContext *ctx,
}
if (obj_sym->is_function) {
- fprintf(stderr, "Error: Cannot use member access on function '%s'\n",
- object_name);
+ fprintf(stderr, "Error: Cannot use member access on function '%s'\n", object_name);
return NULL;
}
@@ -674,7 +706,6 @@ void print_module_info(CodeGenContext *ctx) {
// Also add this debug function to help diagnose linking issues:
void debug_object_files(const char *output_dir) {
printf("\n=== OBJECT FILE DEBUG INFO ===\n");
-
char command[512];
snprintf(command, sizeof(command), "ls -la %s/*.o", output_dir);
printf("Object files in %s:\n", output_dir);
diff --git a/src/llvm/stmt.c b/src/llvm/stmt.c
index 6a3f8d62..348d3fd7 100644
--- a/src/llvm/stmt.c
+++ b/src/llvm/stmt.c
@@ -13,19 +13,18 @@ LLVMValueRef codegen_stmt_expression(CodeGenContext *ctx, AstNode *node) {
return codegen_expr(ctx, node->stmt.expr_stmt.expression);
}
-void add_symbol_with_element_type(CodeGenContext *ctx, const char *name,
- LLVMValueRef value, LLVMTypeRef type,
+void add_symbol_with_element_type(CodeGenContext *ctx, const char *name,
+ LLVMValueRef value, LLVMTypeRef type,
LLVMTypeRef element_type, bool is_function) {
if (ctx->current_module) {
- add_symbol_to_module_with_element_type(ctx->current_module, name, value,
- type, element_type, is_function);
+ add_symbol_to_module_with_element_type(ctx->current_module, name, value,
+ type, element_type, is_function);
}
}
-void add_symbol_to_module_with_element_type(ModuleCompilationUnit *module,
- const char *name, LLVMValueRef value,
- LLVMTypeRef type, LLVMTypeRef element_type,
- bool is_function) {
+void add_symbol_to_module_with_element_type(
+ ModuleCompilationUnit *module, const char *name, LLVMValueRef value,
+ LLVMTypeRef type, LLVMTypeRef element_type, bool is_function) {
LLVM_Symbol *sym = (LLVM_Symbol *)malloc(sizeof(LLVM_Symbol));
sym->name = strdup(name);
sym->value = value;
@@ -36,18 +35,20 @@ void add_symbol_to_module_with_element_type(ModuleCompilationUnit *module,
module->symbols = sym;
}
-LLVMTypeRef extract_element_type_from_ast(CodeGenContext *ctx, AstNode *type_node) {
- if (!type_node) return NULL;
-
+LLVMTypeRef extract_element_type_from_ast(CodeGenContext *ctx,
+ AstNode *type_node) {
+ if (!type_node)
+ return NULL;
+
if (type_node->type == AST_TYPE_POINTER) {
// This is a pointer type, get what it points to
AstNode *pointee = type_node->type_data.pointer.pointee_type;
-
+
// Generate the full type for what this pointer points to
// This correctly handles **char -> *char, ***int -> **int, etc.
return codegen_type(ctx, pointee);
}
-
+
return NULL; // Not a pointer type
}
@@ -58,14 +59,16 @@ LLVMValueRef codegen_stmt_var_decl(CodeGenContext *ctx, AstNode *node) {
return NULL;
// Extract element type if this is a pointer
- LLVMTypeRef element_type = extract_element_type_from_ast(ctx, node->stmt.var_decl.var_type);
+ LLVMTypeRef element_type =
+ extract_element_type_from_ast(ctx, node->stmt.var_decl.var_type);
LLVMValueRef var_ref;
LLVMModuleRef current_llvm_module =
ctx->current_module ? ctx->current_module->module : ctx->module;
if (ctx->current_function == NULL) {
- var_ref = LLVMAddGlobal(current_llvm_module, var_type, node->stmt.var_decl.name);
+ var_ref =
+ LLVMAddGlobal(current_llvm_module, var_type, node->stmt.var_decl.name);
if (node->stmt.var_decl.is_public) {
LLVMSetLinkage(var_ref, LLVMExternalLinkage);
} else {
@@ -80,30 +83,38 @@ LLVMValueRef codegen_stmt_var_decl(CodeGenContext *ctx, AstNode *node) {
LLVMValueRef init_val = codegen_expr(ctx, node->stmt.var_decl.initializer);
if (init_val) {
LLVMTypeRef init_type = LLVMTypeOf(init_val);
-
+
if (var_type != init_type) {
// Handle type conversions
LLVMTypeKind var_kind = LLVMGetTypeKind(var_type);
LLVMTypeKind init_kind = LLVMGetTypeKind(init_type);
-
+
if (var_kind == LLVMDoubleTypeKind && init_kind == LLVMFloatTypeKind) {
- init_val = LLVMBuildFPExt(ctx->builder, init_val, var_type, "float_to_double");
- } else if (var_kind == LLVMFloatTypeKind && init_kind == LLVMDoubleTypeKind) {
- init_val = LLVMBuildFPTrunc(ctx->builder, init_val, var_type, "double_to_float");
- } else if (var_kind == LLVMIntegerTypeKind &&
- (init_kind == LLVMFloatTypeKind || init_kind == LLVMDoubleTypeKind)) {
- init_val = LLVMBuildFPToSI(ctx->builder, init_val, var_type, "float_to_int");
- } else if ((var_kind == LLVMFloatTypeKind || var_kind == LLVMDoubleTypeKind) &&
+ init_val = LLVMBuildFPExt(ctx->builder, init_val, var_type,
+ "float_to_double");
+ } else if (var_kind == LLVMFloatTypeKind &&
+ init_kind == LLVMDoubleTypeKind) {
+ init_val = LLVMBuildFPTrunc(ctx->builder, init_val, var_type,
+ "double_to_float");
+ } else if (var_kind == LLVMIntegerTypeKind &&
+ (init_kind == LLVMFloatTypeKind ||
+ init_kind == LLVMDoubleTypeKind)) {
+ init_val =
+ LLVMBuildFPToSI(ctx->builder, init_val, var_type, "float_to_int");
+ } else if ((var_kind == LLVMFloatTypeKind ||
+ var_kind == LLVMDoubleTypeKind) &&
init_kind == LLVMIntegerTypeKind) {
- init_val = LLVMBuildSIToFP(ctx->builder, init_val, var_type, "int_to_float");
+ init_val =
+ LLVMBuildSIToFP(ctx->builder, init_val, var_type, "int_to_float");
}
}
-
+
if (ctx->current_function == NULL) {
if (LLVMIsConstant(init_val)) {
LLVMSetInitializer(var_ref, init_val);
} else {
- fprintf(stderr, "Error: Global variable initializer must be constant\n");
+ fprintf(stderr,
+ "Error: Global variable initializer must be constant\n");
LLVMSetInitializer(var_ref, LLVMConstNull(var_type));
}
} else {
@@ -121,42 +132,157 @@ LLVMValueRef codegen_stmt_var_decl(CodeGenContext *ctx, AstNode *node) {
}
// Add symbol with element type information
- add_symbol_with_element_type(ctx, node->stmt.var_decl.name, var_ref, var_type, element_type, false);
+ add_symbol_with_element_type(ctx, node->stmt.var_decl.name, var_ref, var_type,
+ element_type, false);
return var_ref;
}
LLVMValueRef codegen_stmt_function(CodeGenContext *ctx, AstNode *node) {
+ const char *func_name = node->stmt.func_decl.name;
+ bool forward_declared = node->stmt.func_decl.forward_declared;
+
+ // Generate parameter types
LLVMTypeRef *param_types = (LLVMTypeRef *)arena_alloc(
ctx->arena, sizeof(LLVMTypeRef) * node->stmt.func_decl.param_count,
alignof(LLVMTypeRef));
for (size_t i = 0; i < node->stmt.func_decl.param_count; i++) {
param_types[i] = codegen_type(ctx, node->stmt.func_decl.param_types[i]);
- if (!param_types[i])
+ if (!param_types[i]) {
+ fprintf(
+ stderr,
+ "Error: Failed to generate parameter type %zu for function '%s'\n", i,
+ func_name);
return NULL;
+ }
}
+ // Generate return type
LLVMTypeRef return_type = codegen_type(ctx, node->stmt.func_decl.return_type);
- if (!return_type)
+ if (!return_type) {
+ fprintf(stderr, "Error: Failed to generate return type for function '%s'\n",
+ func_name);
return NULL;
+ }
+ // Create function type
LLVMTypeRef func_type = LLVMFunctionType(
return_type, param_types, node->stmt.func_decl.param_count, false);
LLVMModuleRef current_llvm_module =
ctx->current_module ? ctx->current_module->module : ctx->module;
- LLVMValueRef function = LLVMAddFunction(current_llvm_module,
- node->stmt.func_decl.name, func_type);
+ // Check if function already exists
+ LLVMValueRef existing_function =
+ LLVMGetNamedFunction(current_llvm_module, func_name);
- LLVMSetLinkage(function, get_function_linkage(node));
- add_symbol(ctx, node->stmt.func_decl.name, function, func_type, true);
+ if (existing_function) {
+ // Function already declared - validate signature matches
+ LLVMTypeRef existing_type = LLVMGlobalGetValueType(existing_function);
- // Set parameter names
- for (size_t i = 0; i < node->stmt.func_decl.param_count; i++) {
- LLVMValueRef param = LLVMGetParam(function, i);
- LLVMSetValueName2(param, node->stmt.func_decl.param_names[i],
- strlen(node->stmt.func_decl.param_names[i]));
+ // Compare return types
+ if (LLVMGetReturnType(existing_type) != return_type) {
+ fprintf(stderr,
+ "Error: Function '%s' redeclared with different return type\n",
+ func_name);
+ return NULL;
+ }
+
+ // Compare parameter counts
+ if (LLVMCountParamTypes(existing_type) !=
+ node->stmt.func_decl.param_count) {
+ fprintf(
+ stderr,
+ "Error: Function '%s' redeclared with different parameter count\n",
+ func_name);
+ return NULL;
+ }
+
+ // Compare parameter types
+ LLVMTypeRef *existing_param_types = (LLVMTypeRef *)arena_alloc(
+ ctx->arena, sizeof(LLVMTypeRef) * node->stmt.func_decl.param_count,
+ alignof(LLVMTypeRef));
+ LLVMGetParamTypes(existing_type, existing_param_types);
+
+ for (size_t i = 0; i < node->stmt.func_decl.param_count; i++) {
+ if (existing_param_types[i] != param_types[i]) {
+ fprintf(stderr,
+ "Error: Function '%s' redeclared with different parameter %zu "
+ "type\n",
+ func_name, i);
+ return NULL;
+ }
+ }
+
+ // Signatures match
+ if (forward_declared) {
+ // This is another prototype - already exists, just return it
+ return existing_function;
+ }
+
+ // This is the implementation - check if it already has a body
+ if (LLVMCountBasicBlocks(existing_function) > 0) {
+ fprintf(stderr, "Error: Function '%s' already has an implementation\n",
+ func_name);
+ return NULL;
+ }
+
+ // Use the existing declaration for the implementation
+ LLVMValueRef function = existing_function;
+
+ // Update symbol table if needed (it should already be there)
+ // Just proceed to generate the body
+ goto generate_body;
+
+ } else {
+ // First declaration - create new function
+ LLVMValueRef function =
+ LLVMAddFunction(current_llvm_module, func_name, func_type);
+
+ if (!function) {
+ fprintf(stderr, "Error: Failed to create LLVM function '%s'\n",
+ func_name);
+ return NULL;
+ }
+
+ // Set linkage
+ LLVMSetLinkage(function, get_function_linkage(node));
+
+ // Add to symbol table
+ add_symbol(ctx, func_name, function, func_type, true);
+
+ // Set parameter names
+ for (size_t i = 0; i < node->stmt.func_decl.param_count; i++) {
+ LLVMValueRef param = LLVMGetParam(function, i);
+ LLVMSetValueName2(param, node->stmt.func_decl.param_names[i],
+ strlen(node->stmt.func_decl.param_names[i]));
+ }
+
+ // If this is just a forward declaration (prototype), we're done
+ if (forward_declared) {
+ return function;
+ }
+
+ // Otherwise, proceed to generate the body
+ goto generate_body;
+ }
+
+generate_body: {
+ // At this point, 'function' or 'existing_function' is set
+ LLVMValueRef function =
+ existing_function ? existing_function
+ : LLVMGetNamedFunction(current_llvm_module, func_name);
+
+ if (!function) {
+ fprintf(stderr, "Error: Function reference lost for '%s'\n", func_name);
+ return NULL;
+ }
+
+ // Function must have a body at this point
+ if (!node->stmt.func_decl.body) {
+ fprintf(stderr, "Error: Function '%s' implementation missing body\n",
+ func_name);
+ return NULL;
}
// Create entry block
@@ -180,9 +306,11 @@ LLVMValueRef codegen_stmt_function(CodeGenContext *ctx, AstNode *node) {
node->stmt.func_decl.param_names[i]);
LLVMBuildStore(ctx->builder, param, alloca);
- // Extract element type for the pointer
- LLVMTypeRef element_type = extract_element_type_from_ast(ctx, node->stmt.func_decl.param_types[i]);
- add_symbol_with_element_type(ctx, node->stmt.func_decl.param_names[i], alloca, param_types[i], element_type, false);
+ // Extract element type for the pointer
+ LLVMTypeRef element_type =
+ extract_element_type_from_ast(ctx, node->stmt.func_decl.param_types[i]);
+ add_symbol_with_element_type(ctx, node->stmt.func_decl.param_names[i],
+ alloca, param_types[i], element_type, false);
}
// Create blocks for normal return and cleanup
@@ -231,6 +359,7 @@ LLVMValueRef codegen_stmt_function(CodeGenContext *ctx, AstNode *node) {
return function;
}
+}
bool is_enum_constant(LLVM_Symbol *sym) {
if (!sym || sym->is_function) {
@@ -387,11 +516,20 @@ LLVMValueRef codegen_stmt_block(CodeGenContext *ctx, AstNode *node) {
// Process all statements in the block
for (size_t i = 0; i < node->stmt.block.stmt_count; i++) {
+ AstNode *stmt = node->stmt.block.statements[i];
+
+ // ADD THESE CHECKS:
+ if (!stmt) {
+ fprintf(stderr, "ERROR: Statement %zu is NULL!\n", i);
+ continue;
+ }
+
// Stop processing if we hit a terminator
if (LLVMGetBasicBlockTerminator(LLVMGetInsertBlock(ctx->builder))) {
break;
}
- codegen_stmt(ctx, node->stmt.block.statements[i]);
+
+ codegen_stmt(ctx, stmt);
}
// Execute any deferred statements from this block scope (in reverse order)
@@ -780,7 +918,6 @@ LLVMValueRef codegen_infinite_loop(CodeGenContext *ctx, AstNode *node) {
// loop (i < 10) { ... }
// loop (i < 10) : (i++) { ... }
LLVMValueRef codegen_while_loop(CodeGenContext *ctx, AstNode *node) {
- // Create basic blocks for the loop structure
LLVMBasicBlockRef cond_block = LLVMAppendBasicBlockInContext(
ctx->context, ctx->current_function, "while_cond");
LLVMBasicBlockRef body_block = LLVMAppendBasicBlockInContext(
@@ -788,42 +925,43 @@ LLVMValueRef codegen_while_loop(CodeGenContext *ctx, AstNode *node) {
LLVMBasicBlockRef after_block = LLVMAppendBasicBlockInContext(
ctx->context, ctx->current_function, "while_end");
- // Jump to condition check
+ // ADD THIS: Save and set loop context
+ LLVMBasicBlockRef old_continue = ctx->loop_continue_block;
+ LLVMBasicBlockRef old_break = ctx->loop_break_block;
+ ctx->loop_continue_block = cond_block; // Continue jumps back to condition
+ ctx->loop_break_block = after_block; // Break jumps to after loop
+
LLVMBuildBr(ctx->builder, cond_block);
- // Generate condition block
LLVMPositionBuilderAtEnd(ctx->builder, cond_block);
if (node->stmt.loop_stmt.condition) {
LLVMValueRef cond = codegen_expr(ctx, node->stmt.loop_stmt.condition);
if (!cond) {
- fprintf(
- stderr,
- "Error: Failed to generate condition for while loop at line %zu\n",
- node->line);
+ // ADD THIS: Restore on error
+ ctx->loop_continue_block = old_continue;
+ ctx->loop_break_block = old_break;
return NULL;
}
LLVMBuildCondBr(ctx->builder, cond, body_block, after_block);
} else {
- // Infinite loop if no condition
LLVMBuildBr(ctx->builder, body_block);
}
- // Generate loop body
LLVMPositionBuilderAtEnd(ctx->builder, body_block);
codegen_stmt(ctx, node->stmt.loop_stmt.body);
- // Generate increment expressions if they exist
if (node->stmt.loop_stmt.optional) {
codegen_expr(ctx, node->stmt.loop_stmt.optional);
}
- // If body doesn't have a terminator, jump back to condition check
if (!LLVMGetBasicBlockTerminator(LLVMGetInsertBlock(ctx->builder))) {
- LLVMBuildBr(ctx->builder,
- cond_block); // Jump back to condition, not after_block
+ LLVMBuildBr(ctx->builder, cond_block);
}
- // Continue with after loop block
+ // ADD THIS: Restore old loop context
+ ctx->loop_continue_block = old_continue;
+ ctx->loop_break_block = old_break;
+
LLVMPositionBuilderAtEnd(ctx->builder, after_block);
return NULL;
}
@@ -1061,9 +1199,73 @@ LLVMValueRef codegen_enum_member_case(CodeGenContext *ctx,
return NULL;
}
- // Get the enum name and member name
- const char *enum_name = member_expr->expr.member.object->expr.identifier.name;
const char *member_name = member_expr->expr.member.member;
+ AstNode *object = member_expr->expr.member.object;
+
+ // **NEW: Handle chained compile-time access (ast::ExprKind::EXPR_NUMBER)**
+ if (object->type == AST_EXPR_MEMBER &&
+ member_expr->expr.member.is_compiletime) {
+ // This is chained: module::Type::Member
+ // Example: ast::ExprKind::EXPR_NUMBER
+ // object = ast::ExprKind (another member expr)
+ // member = EXPR_NUMBER
+
+ if (object->expr.member.object->type != AST_EXPR_IDENTIFIER) {
+ fprintf(stderr, "Error: Expected identifier in chained compile-time "
+ "access for case value\n");
+ return NULL;
+ }
+
+ const char *module_name = object->expr.member.object->expr.identifier.name;
+ const char *type_name = object->expr.member.member;
+
+ // Build the fully qualified name: TypeName.Member
+ char type_qualified_name[256];
+ snprintf(type_qualified_name, sizeof(type_qualified_name), "%s.%s",
+ type_name, member_name);
+
+ // Look in the specified module
+ ModuleCompilationUnit *source_module = find_module(ctx, module_name);
+ if (source_module) {
+ LLVM_Symbol *enum_member =
+ find_symbol_in_module(source_module, type_qualified_name);
+ if (enum_member && is_enum_constant(enum_member)) {
+ return LLVMGetInitializer(enum_member->value);
+ }
+ }
+
+ // If not found in the named module, try current module (in case it was
+ // imported)
+ LLVM_Symbol *enum_member =
+ find_symbol_in_module(ctx->current_module, type_qualified_name);
+ if (enum_member && is_enum_constant(enum_member)) {
+ return LLVMGetInitializer(enum_member->value);
+ }
+
+ // Try all modules as fallback
+ for (ModuleCompilationUnit *unit = ctx->modules; unit; unit = unit->next) {
+ if (unit == ctx->current_module)
+ continue;
+
+ LLVM_Symbol *sym = find_symbol_in_module(unit, type_qualified_name);
+ if (sym && is_enum_constant(sym)) {
+ return LLVMGetInitializer(sym->value);
+ }
+ }
+
+ fprintf(stderr,
+ "Error: Enum member '%s::%s::%s' not found for switch case\n",
+ module_name, type_name, member_name);
+ return NULL;
+ }
+
+ // Handle simple case: EnumType::Member
+ if (object->type != AST_EXPR_IDENTIFIER) {
+ fprintf(stderr, "Error: Expected identifier for enum case value\n");
+ return NULL;
+ }
+
+ const char *enum_name = object->expr.identifier.name;
// Create qualified member name
char qualified_name[256];
@@ -1073,7 +1275,8 @@ LLVMValueRef codegen_enum_member_case(CodeGenContext *ctx,
// Look up the enum member symbol
LLVM_Symbol *enum_member = find_symbol(ctx, qualified_name);
if (!enum_member) {
- fprintf(stderr, "Error: Enum member '%s' not found\n", qualified_name);
+ fprintf(stderr, "Error: Enum member '%s' not found for switch case\n",
+ qualified_name);
return NULL;
}
diff --git a/src/llvm/struct.c b/src/llvm/struct.c
index 0f234b57..2652076c 100644
--- a/src/llvm/struct.c
+++ b/src/llvm/struct.c
@@ -49,19 +49,22 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
size_t data_field_count = 0;
for (size_t i = 0; i < public_count; i++) {
AstNode *member = node->stmt.struct_decl.public_members[i];
- if (member->type == AST_STMT_FIELD_DECL && !member->stmt.field_decl.function) {
+ if (member->type == AST_STMT_FIELD_DECL &&
+ !member->stmt.field_decl.function) {
data_field_count++;
}
}
for (size_t i = 0; i < private_count; i++) {
AstNode *member = node->stmt.struct_decl.private_members[i];
- if (member->type == AST_STMT_FIELD_DECL && !member->stmt.field_decl.function) {
+ if (member->type == AST_STMT_FIELD_DECL &&
+ !member->stmt.field_decl.function) {
data_field_count++;
}
}
if (data_field_count == 0) {
- fprintf(stderr, "Error: Struct %s must have at least one data field\n", struct_name);
+ fprintf(stderr, "Error: Struct %s must have at least one data field\n",
+ struct_name);
return NULL;
}
@@ -89,14 +92,24 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
struct_info->field_is_public = (bool *)arena_alloc(
ctx->arena, sizeof(bool) * data_field_count, alignof(bool));
+ // CRITICAL FIX: Create an OPAQUE struct type FIRST (forward declaration)
+ // This allows self-referential structs like: struct Node { next: *Node; }
+ struct_info->llvm_type = LLVMStructCreateNamed(ctx->context, struct_name);
+
+ // Add to context IMMEDIATELY so it can be found during field type resolution
+ add_struct_type(ctx, struct_info);
+ add_symbol(ctx, struct_name, NULL, struct_info->llvm_type, false);
+
// Process public data fields
size_t field_index = 0;
for (size_t i = 0; i < public_count; i++) {
AstNode *member = node->stmt.struct_decl.public_members[i];
- if (member->type != AST_STMT_FIELD_DECL) continue;
-
+ if (member->type != AST_STMT_FIELD_DECL)
+ continue;
+
// Skip methods for now, we'll process them after the struct type is created
- if (member->stmt.field_decl.function) continue;
+ if (member->stmt.field_decl.function)
+ continue;
const char *field_name = member->stmt.field_decl.name;
@@ -109,13 +122,17 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
}
}
- struct_info->field_names[field_index] = arena_strdup(ctx->arena, field_name);
- struct_info->field_types[field_index] = codegen_type(ctx, member->stmt.field_decl.type);
- struct_info->field_element_types[field_index] = extract_element_type_from_ast(ctx, member->stmt.field_decl.type);
+ struct_info->field_names[field_index] =
+ arena_strdup(ctx->arena, field_name);
+ struct_info->field_types[field_index] =
+ codegen_type(ctx, member->stmt.field_decl.type);
+ struct_info->field_element_types[field_index] =
+ extract_element_type_from_ast(ctx, member->stmt.field_decl.type);
struct_info->field_is_public[field_index] = true;
if (!struct_info->field_types[field_index]) {
- fprintf(stderr, "Error: Failed to resolve type for field %s in struct %s\n",
+ fprintf(stderr,
+ "Error: Failed to resolve type for field %s in struct %s\n",
field_name, struct_name);
return NULL;
}
@@ -125,9 +142,11 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
// Process private data fields
for (size_t i = 0; i < private_count; i++) {
AstNode *member = node->stmt.struct_decl.private_members[i];
- if (member->type != AST_STMT_FIELD_DECL) continue;
-
- if (member->stmt.field_decl.function) continue;
+ if (member->type != AST_STMT_FIELD_DECL)
+ continue;
+
+ if (member->stmt.field_decl.function)
+ continue;
const char *field_name = member->stmt.field_decl.name;
@@ -139,40 +158,45 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
}
}
- struct_info->field_names[field_index] = arena_strdup(ctx->arena, field_name);
- struct_info->field_types[field_index] = codegen_type(ctx, member->stmt.field_decl.type);
- struct_info->field_element_types[field_index] = extract_element_type_from_ast(ctx, member->stmt.field_decl.type);
- struct_info->field_is_public[field_index] = member->stmt.field_decl.is_public;
+ struct_info->field_names[field_index] =
+ arena_strdup(ctx->arena, field_name);
+ struct_info->field_types[field_index] =
+ codegen_type(ctx, member->stmt.field_decl.type);
+ struct_info->field_element_types[field_index] =
+ extract_element_type_from_ast(ctx, member->stmt.field_decl.type);
+ struct_info->field_is_public[field_index] =
+ member->stmt.field_decl.is_public;
if (!struct_info->field_types[field_index]) {
- fprintf(stderr, "Error: Failed to resolve type for field %s in struct %s\n",
+ fprintf(stderr,
+ "Error: Failed to resolve type for field %s in struct %s\n",
field_name, struct_name);
return NULL;
}
field_index++;
}
- // Create LLVM struct type
- struct_info->llvm_type = LLVMStructTypeInContext(
- ctx->context, struct_info->field_types, data_field_count, false);
-
- // Add to context BEFORE processing methods
- add_struct_type(ctx, struct_info);
- add_symbol(ctx, struct_name, NULL, struct_info->llvm_type, false);
+ // CRITICAL: Set the struct body AFTER all field types are resolved
+ // This completes the opaque struct declaration with its actual fields
+ LLVMStructSetBody(struct_info->llvm_type, struct_info->field_types,
+ data_field_count, false);
- // NOW process methods with access to the struct type
+ // NOW process methods with access to the complete struct type
for (size_t i = 0; i < public_count; i++) {
AstNode *member = node->stmt.struct_decl.public_members[i];
- if (member->type != AST_STMT_FIELD_DECL) continue;
-
+ if (member->type != AST_STMT_FIELD_DECL)
+ continue;
+
// Only process methods
- if (!member->stmt.field_decl.function) continue;
+ if (!member->stmt.field_decl.function)
+ continue;
AstNode *func_node = member->stmt.field_decl.function;
const char *method_name = member->stmt.field_decl.name;
// Generate the method with implicit 'self' parameter
- if (!codegen_struct_method(ctx, func_node, struct_info, method_name, true)) {
+ if (!codegen_struct_method(ctx, func_node, struct_info, method_name,
+ true)) {
fprintf(stderr, "Error: Failed to generate method '%s' for struct '%s'\n",
method_name, struct_name);
return NULL;
@@ -182,15 +206,19 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
// Process private methods
for (size_t i = 0; i < private_count; i++) {
AstNode *member = node->stmt.struct_decl.private_members[i];
- if (member->type != AST_STMT_FIELD_DECL) continue;
-
- if (!member->stmt.field_decl.function) continue;
+ if (member->type != AST_STMT_FIELD_DECL)
+ continue;
+
+ if (!member->stmt.field_decl.function)
+ continue;
AstNode *func_node = member->stmt.field_decl.function;
const char *method_name = member->stmt.field_decl.name;
- if (!codegen_struct_method(ctx, func_node, struct_info, method_name, false)) {
- fprintf(stderr, "Error: Failed to generate private method '%s' for struct '%s'\n",
+ if (!codegen_struct_method(ctx, func_node, struct_info, method_name,
+ false)) {
+ fprintf(stderr,
+ "Error: Failed to generate private method '%s' for struct '%s'\n",
method_name, struct_name);
return NULL;
}
@@ -199,11 +227,12 @@ LLVMValueRef codegen_stmt_struct(CodeGenContext *ctx, AstNode *node) {
return NULL;
}
-LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
- StructInfo *struct_info, const char *method_name,
- bool is_public) {
+LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
+ StructInfo *struct_info,
+ const char *method_name, bool is_public) {
if (!func_node || func_node->type != AST_STMT_FUNCTION) {
- fprintf(stderr, "Error: Invalid function node for method '%s'\n", method_name);
+ fprintf(stderr, "Error: Invalid function node for method '%s'\n",
+ method_name);
return NULL;
}
@@ -214,29 +243,32 @@ LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
char **original_param_names = func_node->stmt.func_decl.param_names;
// CRITICAL: Methods need an implicit 'self' parameter as the FIRST parameter
- // The typechecker injects 'self' when calling methods, so the method definition must match
+ // The typechecker injects 'self' when calling methods, so the method
+ // definition must match
size_t param_count = original_param_count + 1; // +1 for 'self'
-
+
// Allocate arrays for ALL parameters (including self)
LLVMTypeRef *llvm_param_types = (LLVMTypeRef *)arena_alloc(
ctx->arena, sizeof(LLVMTypeRef) * param_count, alignof(LLVMTypeRef));
-
+
char **param_names = (char **)arena_alloc(
ctx->arena, sizeof(char *) * param_count, alignof(char *));
-
+
AstNode **param_type_nodes = (AstNode **)arena_alloc(
ctx->arena, sizeof(AstNode *) * param_count, alignof(AstNode *));
// First parameter is 'self' - a pointer to the struct
llvm_param_types[0] = LLVMPointerType(struct_info->llvm_type, 0);
param_names[0] = "self";
- param_type_nodes[0] = NULL; // We'll handle this specially for element type extraction
+ param_type_nodes[0] =
+ NULL; // We'll handle this specially for element type extraction
// Copy the rest of the original parameters (shifted by 1)
for (size_t i = 0; i < original_param_count; i++) {
llvm_param_types[i + 1] = codegen_type(ctx, original_param_type_nodes[i]);
if (!llvm_param_types[i + 1]) {
- fprintf(stderr, "Error: Failed to resolve parameter type %zu for method '%s'\n",
+ fprintf(stderr,
+ "Error: Failed to resolve parameter type %zu for method '%s'\n",
i, method_name);
return NULL;
}
@@ -247,22 +279,25 @@ LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
// Create function type
LLVMTypeRef llvm_return_type = codegen_type(ctx, return_type_node);
if (!llvm_return_type) {
- fprintf(stderr, "Error: Failed to resolve return type for method '%s'\n", method_name);
+ fprintf(stderr, "Error: Failed to resolve return type for method '%s'\n",
+ method_name);
return NULL;
}
- LLVMTypeRef func_type = LLVMFunctionType(llvm_return_type, llvm_param_types,
- param_count, 0);
+ LLVMTypeRef func_type =
+ LLVMFunctionType(llvm_return_type, llvm_param_types, param_count, 0);
// Get the current LLVM module
LLVMModuleRef current_llvm_module =
ctx->current_module ? ctx->current_module->module : ctx->module;
// Create the function in the current module
- LLVMValueRef func = LLVMAddFunction(current_llvm_module, method_name, func_type);
-
+ LLVMValueRef func =
+ LLVMAddFunction(current_llvm_module, method_name, func_type);
+
if (!func) {
- fprintf(stderr, "Error: Failed to create LLVM function for method '%s'\n", method_name);
+ fprintf(stderr, "Error: Failed to create LLVM function for method '%s'\n",
+ method_name);
return NULL;
}
@@ -275,31 +310,34 @@ LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
// CRITICAL: Save the old function context before starting method generation
LLVMValueRef old_function = ctx->current_function;
-
+
// Set current function context
ctx->current_function = func;
// Create entry basic block
- LLVMBasicBlockRef entry = LLVMAppendBasicBlockInContext(
- ctx->context, func, "entry");
+ LLVMBasicBlockRef entry =
+ LLVMAppendBasicBlockInContext(ctx->context, func, "entry");
LLVMPositionBuilderAtEnd(ctx->builder, entry);
// Add all parameters to symbol table (including self at index 0)
for (size_t i = 0; i < param_count; i++) {
LLVMValueRef param = LLVMGetParam(func, i);
const char *param_name = param_names[i];
-
+
LLVMSetValueName2(param, param_name, strlen(param_name));
-
+
// Allocate stack space and store parameter
- LLVMValueRef alloca = LLVMBuildAlloca(ctx->builder, llvm_param_types[i], param_name);
+ LLVMValueRef alloca =
+ LLVMBuildAlloca(ctx->builder, llvm_param_types[i], param_name);
LLVMBuildStore(ctx->builder, param, alloca);
-
- // Extract element type for pointer parameters (needed for self which is *Person)
- LLVMTypeRef element_type = extract_element_type_from_ast(ctx, param_type_nodes[i]);
-
+
+ // Extract element type for pointer parameters (needed for self which is
+ // *Person)
+ LLVMTypeRef element_type =
+ extract_element_type_from_ast(ctx, param_type_nodes[i]);
+
// Add to symbol table with element type information
- add_symbol_with_element_type(ctx, param_name, alloca, llvm_param_types[i],
+ add_symbol_with_element_type(ctx, param_name, alloca, llvm_param_types[i],
element_type, false);
}
@@ -318,7 +356,8 @@ LLVMValueRef codegen_struct_method(CodeGenContext *ctx, AstNode *func_node,
// Verify the function
if (LLVMVerifyFunction(func, LLVMReturnStatusAction)) {
- fprintf(stderr, "Error: Function verification failed for method '%s'\n", method_name);
+ fprintf(stderr, "Error: Function verification failed for method '%s'\n",
+ method_name);
LLVMDumpValue(func);
// Restore context even on error
ctx->current_function = old_function;
@@ -359,7 +398,7 @@ LLVMValueRef codegen_expr_struct_access(CodeGenContext *ctx, AstNode *node) {
// Handle different object types
if (object->type == AST_EXPR_IDENTIFIER) {
- // Access like: ptr->field or ptr.field
+ // Access like: ptr->field or ptr.field or struct_var.field
LLVM_Symbol *sym = find_symbol(ctx, object->expr.identifier.name);
if (!sym || sym->is_function) {
fprintf(stderr, "Error: Variable %s not found or is a function\n",
@@ -368,10 +407,11 @@ LLVMValueRef codegen_expr_struct_access(CodeGenContext *ctx, AstNode *node) {
}
LLVMTypeRef symbol_type = sym->type;
+ LLVMTypeKind symbol_kind = LLVMGetTypeKind(symbol_type);
- // Check if this is a pointer to struct (like *Drop)
- if (LLVMGetTypeKind(symbol_type) == LLVMPointerTypeKind) {
- // CRITICAL FIX: Use the element_type from symbol if available
+ // Check if this is a pointer to struct (like *Token)
+ if (symbol_kind == LLVMPointerTypeKind) {
+ // Use the element_type from symbol if available
if (sym->element_type) {
// Find struct info by LLVM type
for (StructInfo *info = ctx->struct_types; info; info = info->next) {
@@ -414,15 +454,87 @@ LLVMValueRef codegen_expr_struct_access(CodeGenContext *ctx, AstNode *node) {
}
}
- } else {
+ } else if (symbol_kind == LLVMStructTypeKind) {
// Direct struct type (stored by value)
for (StructInfo *info = ctx->struct_types; info; info = info->next) {
if (info->llvm_type == symbol_type) {
struct_info = info;
- struct_ptr = sym->value; // This is already the struct address
+ struct_ptr =
+ sym->value; // This is already the struct address (alloca)
break;
}
}
+
+ if (!struct_info) {
+ // Try finding by field name
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ int field_idx = get_field_index(info, field_name);
+ if (field_idx >= 0) {
+ struct_info = info;
+ struct_ptr = sym->value;
+ break;
+ }
+ }
+ }
+ }
+
+ } else if (object->type == AST_EXPR_MEMBER) {
+ // **Handle chained member access like psr.tks.list**
+ // First, recursively resolve the base member access
+ LLVMValueRef base_value = codegen_expr_struct_access(ctx, object);
+ if (!base_value) {
+ fprintf(stderr, "Error: Failed to resolve chained member access\n");
+ return NULL;
+ }
+
+ LLVMTypeRef base_type = LLVMTypeOf(base_value);
+ LLVMTypeKind base_kind = LLVMGetTypeKind(base_type);
+
+ // Check if the result is a struct
+ if (base_kind == LLVMStructTypeKind) {
+ // Find which struct type this is
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ if (info->llvm_type == base_type) {
+ struct_info = info;
+ break;
+ }
+ }
+
+ if (!struct_info) {
+ // Try to find by field name
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ int field_idx = get_field_index(info, field_name);
+ if (field_idx >= 0) {
+ struct_info = info;
+ break;
+ }
+ }
+ }
+
+ if (struct_info) {
+ // Allocate space to store the struct value so we can GEP into it
+ struct_ptr =
+ LLVMBuildAlloca(ctx->builder, base_type, "chained_struct_temp");
+ LLVMBuildStore(ctx->builder, base_value, struct_ptr);
+ }
+ } else if (base_kind == LLVMPointerTypeKind) {
+ // The chained access returned a pointer
+ struct_ptr = base_value;
+
+ // Try to find struct info by field name
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ int field_idx = get_field_index(info, field_name);
+ if (field_idx >= 0) {
+ struct_info = info;
+ break;
+ }
+ }
+ } else {
+ fprintf(stderr,
+ "Error: Chained member access does not produce a struct (kind: "
+ "%d)\n",
+ base_kind);
+ return NULL;
}
} else if (object->type == AST_EXPR_DEREF) {
@@ -443,9 +555,111 @@ LLVMValueRef codegen_expr_struct_access(CodeGenContext *ctx, AstNode *node) {
}
}
+ } else if (object->type == AST_EXPR_INDEX) {
+ // Handle indexed struct access like lex.list[i].value
+
+ // First, generate the indexed expression (e.g., lex.list[i])
+ LLVMValueRef indexed_value = codegen_expr_index(ctx, object);
+ if (!indexed_value) {
+ fprintf(
+ stderr,
+ "Error: Failed to generate indexed expression for struct access\n");
+ return NULL;
+ }
+
+ LLVMTypeRef indexed_type = LLVMTypeOf(indexed_value);
+ LLVMTypeKind indexed_kind = LLVMGetTypeKind(indexed_type);
+
+ // Check if the indexed result is a struct value
+ if (indexed_kind == LLVMStructTypeKind) {
+ // Find which struct type this is
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ if (info->llvm_type == indexed_type) {
+ struct_info = info;
+ break;
+ }
+ }
+
+ if (!struct_info) {
+ // Try to find by field name
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ int field_idx = get_field_index(info, field_name);
+ if (field_idx >= 0) {
+ struct_info = info;
+ break;
+ }
+ }
+ }
+
+ if (struct_info) {
+ // Allocate space to store the struct value so we can GEP into it
+ struct_ptr =
+ LLVMBuildAlloca(ctx->builder, indexed_type, "indexed_struct_temp");
+ LLVMBuildStore(ctx->builder, indexed_value, struct_ptr);
+ }
+ } else if (indexed_kind == LLVMPointerTypeKind) {
+ // The indexed expression returned a pointer (shouldn't happen for
+ // array[i] but handle it)
+ struct_ptr = indexed_value;
+
+ // Try to find struct info by field name
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ int field_idx = get_field_index(info, field_name);
+ if (field_idx >= 0) {
+ struct_info = info;
+ break;
+ }
+ }
+ } else {
+ fprintf(
+ stderr,
+ "Error: Indexed expression does not produce a struct (kind: %d)\n",
+ indexed_kind);
+ return NULL;
+ }
+
+ } else if (object->type == AST_EXPR_CALL) {
+ // Function call returning a struct: get_token().field
+ LLVMValueRef call_result = codegen_expr(ctx, object);
+ if (!call_result) {
+ return NULL;
+ }
+
+ LLVMTypeRef result_type = LLVMTypeOf(call_result);
+
+ if (LLVMGetTypeKind(result_type) == LLVMStructTypeKind) {
+ // Find struct info
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ if (info->llvm_type == result_type) {
+ struct_info = info;
+ break;
+ }
+ }
+
+ if (!struct_info) {
+ // Try by field name
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ int field_idx = get_field_index(info, field_name);
+ if (field_idx >= 0) {
+ struct_info = info;
+ break;
+ }
+ }
+ }
+
+ if (struct_info) {
+ // Store the result so we can GEP into it
+ struct_ptr =
+ LLVMBuildAlloca(ctx->builder, result_type, "call_result_temp");
+ LLVMBuildStore(ctx->builder, call_result, struct_ptr);
+ }
+ }
+
} else {
- // Handle other cases (function calls, etc.)
- fprintf(stderr, "Error: Unsupported struct access pattern\n");
+ // Handle other cases
+ fprintf(stderr,
+ "Error: Unsupported struct access pattern (object type: %d)\n",
+ object->type);
return NULL;
}
@@ -479,9 +693,25 @@ LLVMValueRef codegen_expr_struct_access(CodeGenContext *ctx, AstNode *node) {
LLVMBuildStructGEP2(ctx->builder, struct_info->llvm_type, struct_ptr,
field_index, "field_ptr");
- // Load the field value
- return LLVMBuildLoad2(ctx->builder, struct_info->field_types[field_index],
- field_ptr, "field_val");
+ // **CRITICAL FIX: Check if this field is an array**
+ LLVMTypeRef field_type = struct_info->field_types[field_index];
+ if (LLVMGetTypeKind(field_type) == LLVMArrayTypeKind) {
+ // For array fields, return a pointer to the first element
+ // This allows subsequent indexing operations to work correctly
+ // Example: rule.move_offsets[i] where move_offsets is [int; 16]
+ LLVMValueRef zero =
+ LLVMConstInt(LLVMInt32TypeInContext(ctx->context), 0, false);
+ LLVMValueRef indices[2] = {zero, zero};
+ return LLVMBuildGEP2(ctx->builder, field_type, field_ptr, indices, 2,
+ "array_field_ptr");
+ }
+
+ // Load the element value (for non-array fields)
+ LLVMValueRef result =
+ LLVMBuildLoad2(ctx->builder, struct_info->field_types[field_index],
+ field_ptr, "field_val");
+
+ return result;
}
// Handle struct member assignment (obj.field = value)
diff --git a/src/llvm/struct_expr.c b/src/llvm/struct_expr.c
new file mode 100644
index 00000000..fdfc7cee
--- /dev/null
+++ b/src/llvm/struct_expr.c
@@ -0,0 +1,228 @@
+#include "llvm.h"
+
+// Helper function to infer struct type from context
+static StructInfo *infer_struct_type_from_context(CodeGenContext *ctx,
+ char **field_names,
+ size_t field_count) {
+ // Try to find a struct that has all these field names
+ for (StructInfo *info = ctx->struct_types; info; info = info->next) {
+ if (info->field_count != field_count) {
+ continue;
+ }
+
+ // Check if all field names match (order-independent)
+ bool all_match = true;
+ for (size_t i = 0; i < field_count; i++) {
+ bool found = false;
+ for (size_t j = 0; j < info->field_count; j++) {
+ if (strcmp(field_names[i], info->field_names[j]) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ all_match = false;
+ break;
+ }
+ }
+
+ if (all_match) {
+ return info;
+ }
+ }
+
+ return NULL;
+}
+
+// Helper function to map user-provided field order to struct definition order
+static bool map_field_order(StructInfo *struct_info, char **provided_names,
+ AstNode **provided_values, size_t provided_count,
+ LLVMValueRef *ordered_values) {
+
+ // Check that all provided fields exist in struct
+ for (size_t i = 0; i < provided_count; i++) {
+ int field_idx = get_field_index(struct_info, provided_names[i]);
+ if (field_idx < 0) {
+ fprintf(stderr, "Error: Field '%s' not found in struct '%s'\n",
+ provided_names[i], struct_info->name);
+ return false;
+ }
+ }
+
+ // Map provided values to struct field order
+ for (size_t i = 0; i < struct_info->field_count; i++) {
+ bool found = false;
+
+ for (size_t j = 0; j < provided_count; j++) {
+ if (strcmp(struct_info->field_names[i], provided_names[j]) == 0) {
+ ordered_values[i] =
+ (LLVMValueRef)provided_values[j]; // Store AST node temporarily
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ fprintf(stderr,
+ "Error: Missing field '%s' in struct initialization for '%s'\n",
+ struct_info->field_names[i], struct_info->name);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+LLVMValueRef codegen_expr_struct_literal(CodeGenContext *ctx, AstNode *node) {
+ if (!node || node->type != AST_EXPR_STRUCT) {
+ fprintf(stderr, "Error: Expected struct expression node\n");
+ return NULL;
+ }
+
+ const char *struct_name = node->expr.struct_expr.name;
+ char **field_names = node->expr.struct_expr.field_names;
+ AstNode **field_values = node->expr.struct_expr.field_value;
+ size_t field_count = node->expr.struct_expr.field_count;
+
+ StructInfo *struct_info = NULL;
+
+ // Case 1: Explicit struct type (Point { x: 20, y: 40 })
+ if (struct_name) {
+ struct_info = find_struct_type(ctx, struct_name);
+ if (!struct_info) {
+ fprintf(stderr, "Error: Struct type '%s' not found\n", struct_name);
+ return NULL;
+ }
+ }
+ // Case 2: Inferred struct type ({ x: 50, y: 10 })
+ else {
+ struct_info = infer_struct_type_from_context(ctx, field_names, field_count);
+ if (!struct_info) {
+ fprintf(stderr, "Error: Could not infer struct type from field names. ");
+ fprintf(stderr, "Provided fields: ");
+ for (size_t i = 0; i < field_count; i++) {
+ fprintf(stderr, "%s%s", field_names[i],
+ i < field_count - 1 ? ", " : "\n");
+ }
+ return NULL;
+ }
+ }
+
+ // Verify field count matches
+ if (field_count != struct_info->field_count) {
+ fprintf(stderr, "Error: Struct '%s' expects %zu fields, got %zu\n",
+ struct_info->name, struct_info->field_count, field_count);
+ return NULL;
+ }
+
+ // Allocate array for ordered field values (in struct definition order)
+ LLVMValueRef *ordered_ast_nodes = (LLVMValueRef *)arena_alloc(
+ ctx->arena, sizeof(LLVMValueRef) * field_count, alignof(LLVMValueRef));
+
+ // Map user-provided field order to struct definition order
+ if (!map_field_order(struct_info, field_names, field_values, field_count,
+ ordered_ast_nodes)) {
+ return NULL;
+ }
+
+ // Now generate code for each field value in the correct order
+ LLVMValueRef *llvm_field_values = (LLVMValueRef *)arena_alloc(
+ ctx->arena, sizeof(LLVMValueRef) * field_count, alignof(LLVMValueRef));
+
+ bool all_constant = true;
+
+ for (size_t i = 0; i < field_count; i++) {
+ // Generate the field value expression
+ AstNode *field_value_node = (AstNode *)ordered_ast_nodes[i];
+ llvm_field_values[i] = codegen_expr(ctx, field_value_node);
+
+ if (!llvm_field_values[i]) {
+ fprintf(stderr,
+ "Error: Failed to generate value for field '%s' in struct '%s'\n",
+ struct_info->field_names[i], struct_info->name);
+ return NULL;
+ }
+
+ // Check type compatibility
+ LLVMTypeRef expected_type = struct_info->field_types[i];
+ LLVMTypeRef actual_type = LLVMTypeOf(llvm_field_values[i]);
+
+ if (expected_type != actual_type) {
+ // Try type conversion
+ LLVMTypeKind expected_kind = LLVMGetTypeKind(expected_type);
+ LLVMTypeKind actual_kind = LLVMGetTypeKind(actual_type);
+
+ // Integer conversions
+ if (expected_kind == LLVMIntegerTypeKind &&
+ actual_kind == LLVMIntegerTypeKind) {
+ unsigned expected_bits = LLVMGetIntTypeWidth(expected_type);
+ unsigned actual_bits = LLVMGetIntTypeWidth(actual_type);
+
+ if (expected_bits > actual_bits) {
+ llvm_field_values[i] = LLVMBuildSExt(
+ ctx->builder, llvm_field_values[i], expected_type, "sext_field");
+ } else if (expected_bits < actual_bits) {
+ llvm_field_values[i] = LLVMBuildTrunc(
+ ctx->builder, llvm_field_values[i], expected_type, "trunc_field");
+ }
+ }
+ // Float conversions
+ else if (expected_kind == LLVMDoubleTypeKind &&
+ actual_kind == LLVMFloatTypeKind) {
+ llvm_field_values[i] = LLVMBuildFPExt(
+ ctx->builder, llvm_field_values[i], expected_type, "fpext_field");
+ } else if (expected_kind == LLVMFloatTypeKind &&
+ actual_kind == LLVMDoubleTypeKind) {
+ llvm_field_values[i] = LLVMBuildFPTrunc(
+ ctx->builder, llvm_field_values[i], expected_type, "fptrunc_field");
+ }
+ // Int to float
+ else if ((expected_kind == LLVMFloatTypeKind ||
+ expected_kind == LLVMDoubleTypeKind) &&
+ actual_kind == LLVMIntegerTypeKind) {
+ llvm_field_values[i] = LLVMBuildSIToFP(
+ ctx->builder, llvm_field_values[i], expected_type, "sitofp_field");
+ }
+ // Float to int
+ else if (expected_kind == LLVMIntegerTypeKind &&
+ (actual_kind == LLVMFloatTypeKind ||
+ actual_kind == LLVMDoubleTypeKind)) {
+ llvm_field_values[i] = LLVMBuildFPToSI(
+ ctx->builder, llvm_field_values[i], expected_type, "fptosi_field");
+ } else {
+ fprintf(stderr, "Error: Type mismatch for field '%s' in struct '%s'\n",
+ struct_info->field_names[i], struct_info->name);
+ fprintf(stderr, " Expected type kind: %d, got type kind: %d\n",
+ expected_kind, actual_kind);
+ return NULL;
+ }
+ }
+
+ // Check if all values are constants
+ if (all_constant && !LLVMIsConstant(llvm_field_values[i])) {
+ all_constant = false;
+ }
+ }
+
+ // Create the struct value
+ if (all_constant) {
+ // Create constant struct
+ return LLVMConstNamedStruct(struct_info->llvm_type, llvm_field_values,
+ field_count);
+ } else {
+ // Create runtime struct
+ LLVMValueRef struct_alloca =
+ LLVMBuildAlloca(ctx->builder, struct_info->llvm_type, "struct_literal");
+
+ // Store each field value
+ for (size_t i = 0; i < field_count; i++) {
+ LLVMValueRef field_ptr = LLVMBuildStructGEP2(
+ ctx->builder, struct_info->llvm_type, struct_alloca, i, "field_ptr");
+ LLVMBuildStore(ctx->builder, llvm_field_values[i], field_ptr);
+ }
+
+ // Load and return the complete struct
+ return LLVMBuildLoad2(ctx->builder, struct_info->llvm_type, struct_alloca,
+ "struct_val");
+ }
+}
diff --git a/src/lsp/formatter/expr.c b/src/lsp/formatter/expr.c
index e8a11993..2c2c8bbb 100644
--- a/src/lsp/formatter/expr.c
+++ b/src/lsp/formatter/expr.c
@@ -102,6 +102,11 @@ void format_unary_expression(FormatterContext *ctx, Expr *expr) {
case UNOP_DEREF:
write_string(ctx, "*");
break;
+ case UNOP_POST_INC:
+ case UNOP_POST_DEC:
+ // UNIMPLEMENTED!!
+ fprintf(stderr, "Postfix unary operators not implemented yet\n");
+ break;
case UNOP_ADDR:
write_string(ctx, "&");
break;
@@ -128,7 +133,7 @@ void format_function_call(FormatterContext *ctx, Expr *expr) {
write_string(ctx, "(");
// Format arguments
- for (int i = 0; i < expr->expr.call.arg_count; i++) {
+ for (size_t i = 0; i < expr->expr.call.arg_count; i++) {
if (i > 0) {
write_string(ctx, ",");
if (ctx->config.space_after_comma) {
@@ -143,6 +148,11 @@ void format_function_call(FormatterContext *ctx, Expr *expr) {
void format_literal_expression(FormatterContext *ctx, Expr *expr) {
switch (expr->expr.literal.lit_type) {
+ case LITERAL_IDENT:
+ case LITERAL_DOUBLE:
+ case LITERAL_NULL:
+ fprintf(stderr, "Literal type not implemented in formatter yet check expr.c\n");
+ break;
case LITERAL_INT: {
char buffer[64];
snprintf(buffer, sizeof(buffer), "%lld", expr->expr.literal.value.int_val);
diff --git a/src/lsp/formatter/stmt.c b/src/lsp/formatter/stmt.c
index b37ed504..924893d7 100644
--- a/src/lsp/formatter/stmt.c
+++ b/src/lsp/formatter/stmt.c
@@ -65,7 +65,7 @@ void format_function_definition(FormatterContext *ctx, Stmt *stmt) {
// Format parameters
write_string(ctx, "(");
if (stmt->stmt.func_decl.param_count > 0) {
- for (int i = 0; i < stmt->stmt.func_decl.param_count; i++) {
+ for (size_t i = 0; i < stmt->stmt.func_decl.param_count; i++) {
if (i > 0) {
write_string(ctx, ",");
if (ctx->config.space_after_comma) {
@@ -159,7 +159,7 @@ void format_struct_definition(FormatterContext *ctx, Stmt *stmt) {
increase_indent(ctx);
}
- for (int i = 0; i < stmt->stmt.struct_decl.public_count; i++) {
+ for (size_t i = 0; i < stmt->stmt.struct_decl.public_count; i++) {
format_stmt(ctx, stmt->stmt.struct_decl.public_members[i]);
}
@@ -169,7 +169,7 @@ void format_struct_definition(FormatterContext *ctx, Stmt *stmt) {
increase_indent(ctx);
}
- for (int i = 0; i < stmt->stmt.struct_decl.private_count; i++) {
+ for (size_t i = 0; i < stmt->stmt.struct_decl.private_count; i++) {
format_stmt(ctx, stmt->stmt.struct_decl.private_members[i]);
}
@@ -188,7 +188,7 @@ void format_enum_definition(FormatterContext *ctx, Stmt *stmt) {
increase_indent(ctx);
- for (int i = 0; i < stmt->stmt.enum_decl.member_count; i++) {
+ for (size_t i = 0; i < stmt->stmt.enum_decl.member_count; i++) {
write_string(ctx, stmt->stmt.enum_decl.members[i]);
if (i < stmt->stmt.enum_decl.member_count - 1) {
write_string(ctx, ",");
diff --git a/src/lsp/language-support/client/src/extension.ts b/src/lsp/language-support/client/src/extension.ts
index d0964b6a..4d266e1f 100644
--- a/src/lsp/language-support/client/src/extension.ts
+++ b/src/lsp/language-support/client/src/extension.ts
@@ -14,7 +14,8 @@ export function activate(context: ExtensionContext) {
// Otherwise the run options are used
const serverOptions: ServerOptions = {
//? the name of the project is "luma" in the Makefile so this will be the name of exe for users
- command: "/home/thedevconnor/Projects/lux/luma",
+ // command: "luma",
+ command: "/home/sovietpancakes/Desktop/Code/luma/luma",
transport: TransportKind.stdio,
args: ["-lsp"]
};
diff --git a/src/lsp/language-support/package.json b/src/lsp/language-support/package.json
index 38c00940..517d3c1c 100644
--- a/src/lsp/language-support/package.json
+++ b/src/lsp/language-support/package.json
@@ -1,7 +1,7 @@
{
- "name": "language-support",
- "displayName": "Lux Language Support",
- "description": "Syntax highlighting and language support for Lux programming language",
+ "name": "luma-language-support",
+ "displayName": "Luma Language Support",
+ "description": "Syntax highlighting and language support for Luma programming language",
"version": "0.1.0",
"publisher": "TheDevConnor",
"engines": {
@@ -13,9 +13,9 @@
"contributes": {
"languages": [
{
- "id": "lux",
+ "id": "luma",
"aliases": [
- "Lux",
+ "luma",
"lux"
],
"extensions": [
@@ -26,21 +26,21 @@
],
"grammars": [
{
- "language": "lux",
- "scopeName": "source.lux",
- "path": "./syntaxes/lux.tmLanguage.json"
+ "language": "luma",
+ "scopeName": "source.luma",
+ "path": "./syntaxes/luma.tmLanguage.json"
}
],
"themes": [
{
- "label": "Lux Tokyo Night",
+ "label": "Luma Tokyo Night",
"uiTheme": "vs-dark",
- "path": "./themes/lux-tokyo-night.json"
+ "path": "./themes/luma-tokyo-night.json"
},
{
- "label": "Lux Horizon Reimagined",
+ "label": "Luma Horizon Reimagined",
"uiTheme": "vs-dark",
- "path": "./themes/lux-horizon-reimagined.json"
+ "path": "./themes/luma-horizon-reimagined.json"
}
]
},
diff --git a/src/lsp/language-support/syntaxes/lux.tmLanguage.json b/src/lsp/language-support/syntaxes/luma.tmLanguage.json
similarity index 67%
rename from src/lsp/language-support/syntaxes/lux.tmLanguage.json
rename to src/lsp/language-support/syntaxes/luma.tmLanguage.json
index c53b58e2..a4d3b7c1 100644
--- a/src/lsp/language-support/syntaxes/lux.tmLanguage.json
+++ b/src/lsp/language-support/syntaxes/luma.tmLanguage.json
@@ -1,7 +1,7 @@
{
"$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json",
"name": "Luma",
- "scopeName": "source.lux",
+ "scopeName": "source.luma",
"patterns": [
{
"include": "#comments"
@@ -38,12 +38,12 @@
"comments": {
"patterns": [
{
- "name": "comment.line.double-colon.lux",
+ "name": "comment.line.double-colon.luma",
"begin": "//",
"end": "$"
},
{
- "name": "comment.block.lux",
+ "name": "comment.block.luma",
"begin": "/\\*",
"end": "\\*/",
"patterns": [
@@ -57,7 +57,7 @@
"attributes": {
"patterns": [
{
- "name": "storage.modifier.attribute.lux",
+ "name": "storage.modifier.attribute.luma",
"match": "#(returns_ownership|takes_ownership)\\b"
}
]
@@ -65,23 +65,23 @@
"preprocessor": {
"patterns": [
{
- "name": "meta.preprocessor.lux",
+ "name": "meta.preprocessor.luma",
"match": "(@)(module|use)\\s+(\"[^\"]*\")(\\s+as\\s+([a-zA-Z_][a-zA-Z0-9_]*))?",
"captures": {
"1": {
- "name": "punctuation.definition.directive.lux"
+ "name": "punctuation.definition.directive.luma"
},
"2": {
- "name": "keyword.control.directive.lux"
+ "name": "keyword.control.directive.luma"
},
"3": {
- "name": "string.quoted.double.lux"
+ "name": "string.quoted.double.luma"
},
"4": {
- "name": "keyword.control.as.lux"
+ "name": "keyword.control.as.luma"
},
"5": {
- "name": "entity.name.namespace.lux"
+ "name": "entity.name.namespace.luma"
}
}
}
@@ -90,23 +90,23 @@
"strings": {
"patterns": [
{
- "name": "string.quoted.double.lux",
+ "name": "string.quoted.double.luma",
"begin": "\"",
"end": "\"",
"patterns": [
{
- "name": "constant.character.escape.lux",
+ "name": "constant.character.escape.luma",
"match": "\\\\."
}
]
},
{
- "name": "string.quoted.single.lux",
+ "name": "string.quoted.single.luma",
"begin": "'",
"end": "'",
"patterns": [
{
- "name": "constant.character.escape.lux",
+ "name": "constant.character.escape.luma",
"match": "\\\\."
}
]
@@ -116,11 +116,11 @@
"numbers": {
"patterns": [
{
- "name": "constant.numeric.float.lux",
+ "name": "constant.numeric.float.luma",
"match": "\\b\\d+\\.\\d+([eE][+-]?\\d+)?\\b"
},
{
- "name": "constant.numeric.integer.lux",
+ "name": "constant.numeric.integer.luma",
"match": "\\b\\d+\\b"
}
]
@@ -128,19 +128,19 @@
"keywords": {
"patterns": [
{
- "name": "keyword.control.lux",
+ "name": "keyword.control.luma",
"match": "\\b(if|elif|else|loop|break|continue|return|defer)\\b"
},
{
- "name": "storage.type.lux",
+ "name": "storage.type.luma",
"match": "\\b(const|let)\\b"
},
{
- "name": "keyword.other.lux",
+ "name": "keyword.other.luma",
"match": "\\b(fn|struct|enum)\\b"
},
{
- "name": "storage.modifier.lux",
+ "name": "storage.modifier.luma",
"match": "\\b(pub|priv|public|private)\\b"
}
]
@@ -148,11 +148,11 @@
"types": {
"patterns": [
{
- "name": "support.type.primitive.lux",
+ "name": "support.type.primitive.luma",
"match": "\\b(int|uint|float|double|bool|str|void|char|short|long|nil)\\b"
},
{
- "name": "entity.name.type.lux",
+ "name": "entity.name.type.luma",
"match": "\\b[A-Z][a-zA-Z0-9_]*\\b"
}
]
@@ -160,29 +160,29 @@
"functions": {
"patterns": [
{
- "name": "meta.function.definition.lux",
+ "name": "meta.function.definition.luma",
"begin": "\\b(const)\\s+([a-zA-Z_][a-zA-Z0-9_]*)\\s*(=)\\s*(fn)\\s*(\\()",
"beginCaptures": {
"1": {
- "name": "storage.type.lux"
+ "name": "storage.type.luma"
},
"2": {
- "name": "entity.name.function.lux"
+ "name": "entity.name.function.luma"
},
"3": {
- "name": "keyword.operator.assignment.lux"
+ "name": "keyword.operator.assignment.luma"
},
"4": {
- "name": "keyword.other.lux"
+ "name": "keyword.other.luma"
},
"5": {
- "name": "punctuation.definition.parameters.begin.lux"
+ "name": "punctuation.definition.parameters.begin.luma"
}
},
"end": "\\)",
"endCaptures": {
"0": {
- "name": "punctuation.definition.parameters.end.lux"
+ "name": "punctuation.definition.parameters.end.luma"
}
},
"patterns": [
@@ -192,11 +192,11 @@
]
},
{
- "name": "meta.function.call.lux",
+ "name": "meta.function.call.luma",
"match": "\\b([a-zA-Z_][a-zA-Z0-9_]*)\\s*(?=\\()",
"captures": {
"1": {
- "name": "entity.name.function.lux"
+ "name": "entity.name.function.luma"
}
}
}
@@ -205,7 +205,7 @@
"function-parameters": {
"patterns": [
{
- "name": "variable.parameter.lux",
+ "name": "variable.parameter.luma",
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b(?=\\s*:)"
},
{
@@ -219,35 +219,35 @@
"operators": {
"patterns": [
{
- "name": "keyword.operator.arithmetic.lux",
+ "name": "keyword.operator.arithmetic.luma",
"match": "\\+\\+|--|\\+|\\-|\\*|/|%"
},
{
- "name": "keyword.operator.comparison.lux",
+ "name": "keyword.operator.comparison.luma",
"match": "==|!=|<|>|<=|>="
},
{
- "name": "keyword.operator.logical.lux",
+ "name": "keyword.operator.logical.luma",
"match": "&&|\\|\\||!"
},
{
- "name": "keyword.operator.assignment.lux",
+ "name": "keyword.operator.assignment.luma",
"match": "="
},
{
- "name": "keyword.operator.bitwise.lux",
+ "name": "keyword.operator.bitwise.luma",
"match": "&|\\||\\^|~|<<|>>"
},
{
- "name": "keyword.operator.pointer.lux",
+ "name": "keyword.operator.pointer.luma",
"match": "\\*|&"
},
{
- "name": "keyword.operator.runtime-member.lux",
+ "name": "keyword.operator.runtime-member.luma",
"match": "\\."
},
{
- "name": "keyword.operator.compiletime-const.lux",
+ "name": "keyword.operator.compiletime-const.luma",
"match": "::"
}
]
@@ -255,7 +255,7 @@
"identifiers": {
"patterns": [
{
- "name": "variable.other.lux",
+ "name": "variable.other.luma",
"match": "\\b[a-zA-Z_][a-zA-Z0-9_]*\\b"
}
]
diff --git a/src/lsp/language-support/themes/lux-horizon-reimagined.json b/src/lsp/language-support/themes/luma-horizon-reimagined.json
similarity index 60%
rename from src/lsp/language-support/themes/lux-horizon-reimagined.json
rename to src/lsp/language-support/themes/luma-horizon-reimagined.json
index e0cee61c..913d6eef 100644
--- a/src/lsp/language-support/themes/lux-horizon-reimagined.json
+++ b/src/lsp/language-support/themes/luma-horizon-reimagined.json
@@ -1,6 +1,6 @@
{
"$schema": "vscode://schemas/color-theme",
- "name": "Lux Horizon Reimagined",
+ "name": "Luma Horizon Reimagined",
"type": "dark",
"colors": {
"editor.background": "#1C1E26",
@@ -20,85 +20,85 @@
"tokenColors": [
{
"scope": [
- "comment.line.double-colon.lux",
- "comment.block.lux",
+ "comment.line.double-colon.luma",
+ "comment.block.luma",
"punctuation.definition.comment"
],
"settings": { "foreground": "#6C6F93", "fontStyle": "italic" }
},
{
"scope": [
- "keyword.control.lux",
- "keyword.other.lux",
- "storage.type.lux",
- "storage.modifier.lux"
+ "keyword.control.luma",
+ "keyword.other.luma",
+ "storage.type.luma",
+ "storage.modifier.luma"
],
"settings": { "foreground": "#FAB795", "fontStyle": "bold" }
},
{
- "scope": "storage.modifier.attribute.lux",
+ "scope": "storage.modifier.attribute.luma",
"settings": { "foreground": "#25B2BC", "fontStyle": "bold italic" }
},
{
"scope": [
- "variable.other.lux",
- "variable.parameter.lux",
+ "variable.other.luma",
+ "variable.parameter.luma",
"entity.name.variable"
],
"settings": { "foreground": "#E0DEF4" }
},
{
"scope": [
- "entity.name.function.lux",
+ "entity.name.function.luma",
"support.function"
],
"settings": { "foreground": "#25B2BC" }
},
{
"scope": [
- "string.quoted.double.lux",
- "string.quoted.single.lux",
- "constant.character.escape.lux"
+ "string.quoted.double.luma",
+ "string.quoted.single.luma",
+ "constant.character.escape.luma"
],
"settings": { "foreground": "#B877DB" }
},
{
"scope": [
- "constant.numeric.integer.lux",
- "constant.numeric.float.lux",
+ "constant.numeric.integer.luma",
+ "constant.numeric.float.luma",
"constant.language"
],
"settings": { "foreground": "#F09383" }
},
{
"scope": [
- "entity.name.type.lux",
- "support.type.primitive.lux"
+ "entity.name.type.luma",
+ "support.type.primitive.luma"
],
"settings": { "foreground": "#F6C177" }
},
{
"scope": [
- "keyword.operator.arithmetic.lux",
- "keyword.operator.comparison.lux",
- "keyword.operator.logical.lux",
- "keyword.operator.assignment.lux",
- "keyword.operator.bitwise.lux",
- "keyword.operator.pointer.lux",
- "keyword.operator.member.lux"
+ "keyword.operator.arithmetic.luma",
+ "keyword.operator.comparison.luma",
+ "keyword.operator.logical.luma",
+ "keyword.operator.assignment.luma",
+ "keyword.operator.bitwise.luma",
+ "keyword.operator.pointer.luma",
+ "keyword.operator.member.luma"
],
"settings": { "foreground": "#89ddff" }
},
{
"scope": [
- "meta.preprocessor.lux",
- "punctuation.definition.directive.lux",
- "keyword.control.directive.lux"
+ "meta.preprocessor.luma",
+ "punctuation.definition.directive.luma",
+ "keyword.control.directive.luma"
],
"settings": { "foreground": "#bb9af7" }
},
{
- "scope": "entity.name.namespace.lux",
+ "scope": "entity.name.namespace.luma",
"settings": { "foreground": "#73daca" }
}
]
diff --git a/src/lsp/language-support/themes/lux-tokyo-night.json b/src/lsp/language-support/themes/luma-tokyo-night.json
similarity index 63%
rename from src/lsp/language-support/themes/lux-tokyo-night.json
rename to src/lsp/language-support/themes/luma-tokyo-night.json
index 8dfd6760..506bd6f8 100644
--- a/src/lsp/language-support/themes/lux-tokyo-night.json
+++ b/src/lsp/language-support/themes/luma-tokyo-night.json
@@ -1,5 +1,5 @@
{
- "name": "Lux Tokyo Night",
+ "name": "Luma Tokyo Night",
"type": "dark",
"colors": {
"editor.background": "#1a1b26",
@@ -12,8 +12,8 @@
{
"name": "Comment",
"scope": [
- "comment.line.double-slash.lux",
- "comment.block.lux"
+ "comment.line.double-slash.luma",
+ "comment.block.luma"
],
"settings": {
"foreground": "#565f89",
@@ -23,9 +23,9 @@
{
"name": "Preprocessor Directives",
"scope": [
- "meta.preprocessor.lux",
- "punctuation.definition.directive.lux",
- "keyword.control.directive.lux"
+ "meta.preprocessor.luma",
+ "punctuation.definition.directive.luma",
+ "keyword.control.directive.luma"
],
"settings": {
"foreground": "#bb9af7"
@@ -33,7 +33,7 @@
},
{
"name": "Function Attributes",
- "scope": "storage.modifier.attribute.lux",
+ "scope": "storage.modifier.attribute.luma",
"settings": {
"foreground": "#7aa2f7",
"fontStyle": "bold italic"
@@ -42,9 +42,9 @@
{
"name": "Keywords",
"scope": [
- "keyword.control.lux",
- "storage.type.lux",
- "keyword.other.lux"
+ "keyword.control.luma",
+ "storage.type.luma",
+ "keyword.other.luma"
],
"settings": {
"foreground": "#bb9af7",
@@ -53,35 +53,35 @@
},
{
"name": "Storage Modifiers",
- "scope": "storage.modifier.lux",
+ "scope": "storage.modifier.luma",
"settings": {
"foreground": "#f7768e"
}
},
{
"name": "Primitive Types",
- "scope": "support.type.primitive.lux",
+ "scope": "support.type.primitive.luma",
"settings": {
"foreground": "#2ac3de"
}
},
{
"name": "User-defined Types",
- "scope": "entity.name.type.lux",
+ "scope": "entity.name.type.luma",
"settings": {
"foreground": "#9ece6a"
}
},
{
"name": "Function Names",
- "scope": "entity.name.function.lux",
+ "scope": "entity.name.function.luma",
"settings": {
"foreground": "#7aa2f7"
}
},
{
"name": "Function Parameters",
- "scope": "variable.parameter.lux",
+ "scope": "variable.parameter.luma",
"settings": {
"foreground": "#e0af68"
}
@@ -89,8 +89,8 @@
{
"name": "Strings",
"scope": [
- "string.quoted.double.lux",
- "string.quoted.single.lux"
+ "string.quoted.double.luma",
+ "string.quoted.single.luma"
],
"settings": {
"foreground": "#9ece6a"
@@ -98,7 +98,7 @@
},
{
"name": "String Escape Characters",
- "scope": "constant.character.escape.lux",
+ "scope": "constant.character.escape.luma",
"settings": {
"foreground": "#ff9e64"
}
@@ -106,8 +106,8 @@
{
"name": "Numbers",
"scope": [
- "constant.numeric.integer.lux",
- "constant.numeric.float.lux"
+ "constant.numeric.integer.luma",
+ "constant.numeric.float.luma"
],
"settings": {
"foreground": "#ff9e64"
@@ -116,13 +116,13 @@
{
"name": "Operators",
"scope": [
- "keyword.operator.arithmetic.lux",
- "keyword.operator.comparison.lux",
- "keyword.operator.logical.lux",
- "keyword.operator.assignment.lux",
- "keyword.operator.bitwise.lux",
- "keyword.operator.pointer.lux",
- "keyword.operator.member.lux"
+ "keyword.operator.arithmetic.luma",
+ "keyword.operator.comparison.luma",
+ "keyword.operator.logical.luma",
+ "keyword.operator.assignment.luma",
+ "keyword.operator.bitwise.luma",
+ "keyword.operator.pointer.luma",
+ "keyword.operator.member.luma"
],
"settings": {
"foreground": "#89ddff"
@@ -130,14 +130,14 @@
},
{
"name": "Variables",
- "scope": "variable.other.lux",
+ "scope": "variable.other.luma",
"settings": {
"foreground": "#a9b1d6"
}
},
{
"name": "Namespaces",
- "scope": "entity.name.namespace.lux",
+ "scope": "entity.name.namespace.luma",
"settings": {
"foreground": "#73daca"
}
@@ -145,8 +145,8 @@
{
"name": "Punctuation",
"scope": [
- "punctuation.definition.parameters.begin.lux",
- "punctuation.definition.parameters.end.lux"
+ "punctuation.definition.parameters.begin.luma",
+ "punctuation.definition.parameters.end.luma"
],
"settings": {
"foreground": "#a9b1d6"
diff --git a/src/lsp/lsp_document.c b/src/lsp/lsp_document.c
index d31e8667..b5ebb039 100644
--- a/src/lsp/lsp_document.c
+++ b/src/lsp/lsp_document.c
@@ -90,17 +90,105 @@ LSPDocument *lsp_document_find(LSPServer *server, const char *uri) {
return NULL;
}
+// NEW: Helper to recursively collect all module dependencies
+static void collect_all_module_deps(LSPServer *server, const char *module_uri,
+ BuildConfig *config, ArenaAllocator *arena,
+ GrowableArray *all_modules,
+ GrowableArray *visited_uris) {
+ // Check if already visited (prevent cycles)
+ for (size_t i = 0; i < visited_uris->count; i++) {
+ const char *visited = ((const char **)visited_uris->data)[i];
+ if (strcmp(visited, module_uri) == 0) {
+ fprintf(stderr, "[LSP] Skipping already visited module: %s\n",
+ module_uri);
+ return; // Already processed
+ }
+ }
+
+ // Mark as visited
+ const char **visited_slot = (const char **)growable_array_push(visited_uris);
+ if (visited_slot) {
+ *visited_slot = arena_strdup(arena, module_uri);
+ }
+
+ fprintf(stderr, "[LSP] Collecting dependencies for: %s\n", module_uri);
+
+ // Parse the module
+ AstNode *module_ast =
+ parse_imported_module_ast(server, module_uri, config, arena);
+
+ if (!module_ast) {
+ fprintf(stderr, "[LSP] Failed to parse module: %s\n", module_uri);
+ return;
+ }
+
+ // Extract imports from this module's content
+ const char *file_path = lsp_uri_to_path(module_uri, arena);
+ if (!file_path) {
+ fprintf(stderr, "[LSP] Failed to convert URI to path: %s\n", module_uri);
+ return;
+ }
+
+ FILE *f = fopen(file_path, "r");
+ if (!f) {
+ fprintf(stderr, "[LSP] Failed to open file: %s\n", file_path);
+ return;
+ }
+
+ fseek(f, 0, SEEK_END);
+ long size = ftell(f);
+ fseek(f, 0, SEEK_SET);
+
+ char *content = arena_alloc(arena, size + 1, 1);
+ fread(content, 1, size, f);
+ content[size] = '\0';
+ fclose(f);
+
+ // Create temporary document to extract imports
+ LSPDocument temp_doc = {0};
+ temp_doc.content = content;
+ temp_doc.arena = arena;
+
+ extract_imports(&temp_doc, arena);
+
+ fprintf(stderr, "[LSP] Module %s has %zu imports\n", module_uri,
+ temp_doc.import_count);
+
+ // Recursively process each import
+ for (size_t i = 0; i < temp_doc.import_count; i++) {
+ const char *imported_module_path = temp_doc.imports[i].module_path;
+ const char *resolved_uri = lookup_module(server, imported_module_path);
+
+ if (resolved_uri) {
+ fprintf(stderr, "[LSP] Recursively processing import: %s -> %s\n",
+ imported_module_path, resolved_uri);
+ // Recurse!
+ collect_all_module_deps(server, resolved_uri, config, arena, all_modules,
+ visited_uris);
+ } else {
+ fprintf(stderr, "[LSP] Warning: Could not resolve import '%s' in %s\n",
+ imported_module_path, module_uri);
+ }
+ }
+
+ // Add this module to the list (after processing its dependencies)
+ AstNode **slot = (AstNode **)growable_array_push(all_modules);
+ if (slot) {
+ *slot = module_ast;
+ fprintf(stderr, "[LSP] Added module to list: %s (total: %zu)\n", module_uri,
+ all_modules->count);
+ }
+}
+
bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
BuildConfig *config) {
if (!doc || !doc->needs_reanalysis)
return true;
- // NEW: Save import scopes BEFORE destroying arena (they live in server arena
- // after successful typecheck)
+ // Save import scopes BEFORE destroying arena
Scope **saved_scopes = NULL;
size_t saved_import_count = doc->import_count;
if (saved_import_count > 0 && doc->imports) {
- // Allocate in server arena (persistent)
saved_scopes = arena_alloc(
server->arena, saved_import_count * sizeof(Scope *), alignof(Scope *));
for (size_t i = 0; i < saved_import_count; i++) {
@@ -108,6 +196,9 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
}
}
+ // ALSO save the last successful scope
+ Scope *last_successful_scope = doc->scope;
+
arena_destroy(doc->arena);
arena_allocator_init(doc->arena, 1024 * 1024);
@@ -122,12 +213,10 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
extract_imports(doc, doc->arena);
- // NEW: Restore saved scopes to the newly created imports
+ // Restore saved scopes
if (saved_scopes && doc->import_count == saved_import_count) {
for (size_t i = 0; i < doc->import_count; i++) {
doc->imports[i].scope = saved_scopes[i];
- fprintf(stderr, "[LSP] Restored scope for import '%s'\n",
- doc->imports[i].module_path);
}
}
@@ -157,8 +246,14 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
fprintf(stderr, "[LSP] Parse has %d errors, skipping typecheck\n",
error_get_count());
- // CRITICAL FIX: Clear scope when parse fails
- doc->scope = NULL;
+ // PRESERVE the last successful scope for completions
+ if (last_successful_scope) {
+ fprintf(stderr,
+ "[LSP] Preserving last successful scope for completions\n");
+ doc->scope = last_successful_scope;
+ } else {
+ doc->scope = NULL;
+ }
doc->diagnostics =
convert_errors_to_diagnostics(&doc->diagnostic_count, doc->arena);
@@ -166,35 +261,25 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
return false;
}
+ // NEW: Recursively collect ALL module dependencies (transitive closure)
GrowableArray all_modules;
- growable_array_init(&all_modules, doc->arena, 8, sizeof(AstNode *));
+ growable_array_init(&all_modules, doc->arena, 16, sizeof(AstNode *));
+
+ GrowableArray visited_uris;
+ growable_array_init(&visited_uris, doc->arena, 16, sizeof(const char *));
- // Resolve and collect imported modules
+ // Collect all transitive dependencies
for (size_t i = 0; i < doc->import_count; i++) {
ImportedModule *import = &doc->imports[i];
const char *resolved_uri = lookup_module(server, import->module_path);
- if (!resolved_uri) {
- fprintf(stderr, "[LSP] Module '%s' not found in registry\n",
- import->module_path);
- continue;
- }
-
- fprintf(stderr, "[LSP] Resolved '%s' -> %s\n", import->module_path,
- resolved_uri);
-
- AstNode *module_ast =
- parse_imported_module_ast(server, resolved_uri, config, doc->arena);
-
- if (module_ast) {
- // Add to all_modules
- AstNode **slot = (AstNode **)growable_array_push(&all_modules);
- if (slot) {
- *slot = module_ast;
- }
+ if (resolved_uri) {
+ collect_all_module_deps(server, resolved_uri, config, doc->arena,
+ &all_modules, &visited_uris);
}
}
+ // Add the main module last (so it can see all dependencies)
AstNode *main_module = doc->ast;
if (doc->ast->type == AST_PROGRAM &&
doc->ast->stmt.program.module_count > 0) {
@@ -206,25 +291,31 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
*main_slot = main_module;
}
- fprintf(stderr, "[LSP] Combined %zu modules for typechecking\n",
- all_modules.count);
-
+ // Create combined program with all modules
AstNode *combined_program = create_program_node(
doc->arena, (AstNode **)all_modules.data, all_modules.count, 0, 0);
if (!combined_program) {
- fprintf(stderr, "[LSP] Failed to create combined program\n");
- doc->scope = NULL; // Clear scope on failure
+ // Preserve last scope on error
+ if (last_successful_scope) {
+ doc->scope = last_successful_scope;
+ } else {
+ doc->scope = NULL;
+ }
doc->needs_reanalysis = false;
return false;
}
- // Use SERVER arena for global scope so it persists across document analyses
+ // Use SERVER arena for global scope (persists across analyses)
Scope *global_scope =
arena_alloc(server->arena, sizeof(Scope), alignof(Scope));
if (!global_scope) {
- fprintf(stderr, "[LSP] Failed to allocate global scope\n");
- doc->scope = NULL;
+ // Preserve last scope on error
+ if (last_successful_scope) {
+ doc->scope = last_successful_scope;
+ } else {
+ doc->scope = NULL;
+ }
doc->needs_reanalysis = false;
return false;
}
@@ -247,10 +338,15 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
fprintf(stderr, "[LSP] Typecheck result: %s, errors: %d\n",
success ? "success" : "failed", error_get_count());
- // If typecheck failed, clear the scope (it may be partially initialized)
if (!success) {
- fprintf(stderr, "[LSP] Typecheck failed, clearing scope\n");
- doc->scope = NULL;
+ fprintf(stderr,
+ "[LSP] Typecheck failed, preserving last successful scope\n");
+ // On typecheck failure, preserve the last successful scope if we have one
+ if (last_successful_scope) {
+ doc->scope = last_successful_scope;
+ } else {
+ doc->scope = NULL;
+ }
}
// Link module scopes ONLY IF typecheck succeeded
@@ -258,30 +354,20 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
for (size_t i = 0; i < doc->import_count; i++) {
ImportedModule *import = &doc->imports[i];
- // Find the corresponding module AST node from imported_modules
- for (size_t j = 0; j < all_modules.count - 1;
- j++) { // -1 to skip main module
+ // Find the corresponding module AST node
+ for (size_t j = 0; j < all_modules.count - 1; j++) { // -1 to skip main
AstNode *module_ast = ((AstNode **)all_modules.data)[j];
if (module_ast->type == AST_PREPROCESSOR_MODULE) {
const char *module_file_name = module_ast->preprocessor.module.name;
if (strcmp(module_file_name, import->module_path) == 0) {
- // Found the matching module - extract its scope
Scope *module_scope =
(Scope *)module_ast->preprocessor.module.scope;
- // Only update if we got a valid scope
if (module_scope) {
import->scope = module_scope;
}
- // If module_scope is NULL, keep the saved scope
-
- fprintf(stderr,
- "[LSP] Linked import '%s' (alias: %s) to scope with %zu "
- "symbols\n",
- import->module_path, import->alias ? import->alias : "none",
- import->scope ? import->scope->symbols.count : 0);
break;
}
}
@@ -292,8 +378,6 @@ bool lsp_document_analyze(LSPDocument *doc, LSPServer *server,
doc->diagnostics =
convert_errors_to_diagnostics(&doc->diagnostic_count, doc->arena);
- fprintf(stderr, "[LSP] Generated %zu diagnostics\n", doc->diagnostic_count);
-
doc->needs_reanalysis = false;
return success;
diff --git a/src/lsp/lsp_features.c b/src/lsp/lsp_features.c
index 4256b6d8..37439918 100644
--- a/src/lsp/lsp_features.c
+++ b/src/lsp/lsp_features.c
@@ -43,107 +43,62 @@ LSPLocation *lsp_definition(LSPDocument *doc, LSPPosition position,
LSPCompletionItem *lsp_completion(LSPDocument *doc, LSPPosition position,
size_t *completion_count,
ArenaAllocator *arena) {
- fprintf(stderr,
- "[LSP] lsp_completion called: doc=%p, position=(%d,%d), arena=%p\n",
- (void *)doc, position.line, position.character, (void *)arena);
-
+ (void)position;
if (!doc || !completion_count) {
- fprintf(stderr,
- "[LSP] lsp_completion early return: doc=%p, completion_count=%p\n",
- (void *)doc, (void *)completion_count);
return NULL;
}
- Token *token = lsp_token_at_position(doc, position);
- fprintf(stderr, "[LSP] Token at position: %p\n", (void *)token);
-
GrowableArray completions;
growable_array_init(&completions, arena, 32, sizeof(LSPCompletionItem));
- fprintf(stderr, "[LSP] Initialized completions array\n");
- // Add keyword snippets based on Luma syntax
+ // Add keyword snippets
const struct {
const char *label;
const char *snippet;
const char *detail;
} keywords[] = {
- // Top-level declarations
- {"const fn", "const ${1:name} = fn (${2:params}) ${3:Type} {\n\t$0\n}",
- "Function declaration (Private by default)"},
+ {"const fn",
+ "const ${1:name} -> fn (${2:params}) ${3:Type} {\\n\\t$0\\n}",
+ "Function declaration"},
{"pub const fn",
- "pub const ${1:name} = fn (${2:params}) ${3:Type} {\n\t$0\n}",
- "Public Function declaration"},
- {"priv const fn",
- "priv const ${1:name} = fn (${2:params}) ${3:Type} {\n\t$0\n}",
- "Private Function declaration"},
+ "pub const ${1:name} -> fn (${2:params}) ${3:Type} {\\n\\t$0\\n}",
+ "Public function"},
{"const struct",
- "const ${1:Name} = struct {\n\t${2:field}: ${3:Type}$0,\n};",
+ "const ${1:Name} -> struct {\\n\\t${2:field}: ${3:Type}$0,\\n};",
"Struct definition"},
- {"const enum", "const ${1:Name} = enum {\n\t${2:Variant}$0,\n};",
+ {"const enum", "const ${1:Name} -> enum {\\n\\t${2:Variant}$0,\\n};",
"Enum definition"},
{"const var", "const ${1:name}: ${2:Type} = ${3:value};$0",
"Top-level constant"},
- // Function attributes for memory management
- {"#returns_ownership",
- "#returns_ownership\nconst ${1:name} = fn (${2:params}) *${3:Type} "
- "{\n\tlet ${4:ptr}: *${3:Type} = "
- "cast<*${3:Type}>(alloc(sizeof<${3:Type}>));\n\t$0\n\treturn "
- "${4:ptr};\n};",
- "Function that returns owned pointer"},
- {"#takes_ownership",
- "#takes_ownership\nconst ${1:name} = fn (${2:ptr}: *${3:Type}) "
- "${4:void} {\n\t$0\n\tfree(${2:ptr});\n};",
- "Function that takes ownership and frees"},
-
- // Control flow
- {"if", "if ${1:condition} {\n\t$0\n}", "If statement"},
- {"if else", "if ${1:condition} {\n\t${2}\n} else {\n\t$0\n}",
+ {"if", "if ${1:condition} {\\n\\t$0\\n}", "If statement"},
+ {"if else", "if ${1:condition} {\\n\\t${2}\\n} else {\\n\\t$0\\n}",
"If-else statement"},
- {"if elif",
- "if ${1:condition} {\n\t${2}\n} elif ${3:condition} {\n\t${4}\n} else "
- "{\n\t$0\n}",
- "If-elif-else statement"},
- // Loop constructs
- {"loop", "loop {\n\t$0\n}", "Infinite loop"},
+ {"loop", "loop {\\n\\t$0\\n}", "Infinite loop"},
{"loop for",
- "loop [${1:i}: int = 0](${1:i} < ${2:10}) : (++${1:i}) {\n\t$0\n}",
+ "loop [${1:i}: int = 0](${1:i} < ${2:10}) : (++${1:i}) {\\n\\t$0\\n}",
"For-style loop"},
- {"loop while", "loop (${1:condition}) {\n\t$0\n}", "While-style loop"},
- // Switch statement
- {"switch", "switch (${1:value}) {\n\t${2:case} => ${3:result};$0\n}",
+ {"switch", "switch (${1:value}) {\\n\\t${2:case} => ${3:result};$0\\n}",
"Switch statement"},
- // Variables
{"let", "let ${1:name}: ${2:Type} = ${3:value};$0",
"Variable declaration"},
- // Memory management
- {"alloc", "cast<*${1:Type}>(alloc(sizeof<${1:Type}>))",
- "Allocate memory"},
{"defer", "defer free(${1:ptr});$0", "Defer statement"},
- {"defer block", "defer {\n\t${1:cleanup()};\n\t$0\n}", "Defer block"},
+ {"defer block", "defer {\\n\\t${1:cleanup()};\\n\\t$0\\n}",
+ "Defer block"},
- // Module system
- {"@module", "@module \"${1:name}\"$0", "Module declaration"},
- {"@use", "@use \"${1:module}\" as ${2:alias}$0", "Import module"},
+ {"@module", "@module \\\"${1:name}\\\"$0", "Module declaration"},
+ {"@use", "@use \\\"${1:module}\\\" as ${2:alias}$0", "Import module"},
- // Other statements
{"return", "return ${1:value};$0", "Return statement"},
{"break", "break;$0", "Break statement"},
{"continue", "continue;$0", "Continue statement"},
- // Struct with access modifiers
- {"struct pub/priv",
- "const ${1:Name} = struct {\npub:\n\t${2:public_field}: "
- "${3:Type},\npriv:\n\t${4:private_field}: ${5:Type}$0\n};",
- "Struct with access modifiers"},
-
- // Common patterns
- {"main", "const main = fn () int {\n\t$0\n\treturn 0;\n};",
+ {"main", "const main -> fn () int {\\n\\t$0\\n\\treturn 0;\\n};",
"Main function"},
{"outputln", "outputln(${1:message});$0", "Output with newline"},
{"cast", "cast<${1:Type}>(${2:value})$0", "Type cast"},
@@ -165,25 +120,23 @@ LSPCompletionItem *lsp_completion(LSPDocument *doc, LSPPosition position,
}
}
- fprintf(stderr, "[LSP] Added %zu keyword completions\n", completions.count);
+ // Find the most specific scope at the cursor position
+ Scope *local_scope = doc->scope;
+ // if (doc->ast) {
+ // local_scope = find_scope_at_position(doc->ast, position, doc->scope);
+ // }
- // Add symbols from scope (variables, functions, etc.)
- fprintf(stderr, "[LSP] Checking document scope: %p\n", (void *)doc->scope);
- if (doc->scope) {
- Scope *current_scope = doc->scope;
+ // Add symbols from local scope and all parent scopes
+ if (local_scope) {
+ Scope *current_scope = local_scope;
int scope_depth = 0;
- while (current_scope) {
- fprintf(stderr, "[LSP] Scope depth %d: symbols.data=%p, count=%zu\n",
- scope_depth, (void *)current_scope->symbols.data,
- current_scope->symbols.count);
- // SAFETY CHECK: Validate scope has valid data
+ while (current_scope) {
if (current_scope->symbols.data && current_scope->symbols.count > 0) {
for (size_t i = 0; i < current_scope->symbols.count; i++) {
Symbol *sym = (Symbol *)((char *)current_scope->symbols.data +
i * sizeof(Symbol));
- // SAFETY CHECK: Validate symbol has valid name and type
if (!sym || !sym->name || !sym->type) {
continue;
}
@@ -193,90 +146,77 @@ LSPCompletionItem *lsp_completion(LSPDocument *doc, LSPPosition position,
if (item) {
item->label = arena_strdup(arena, sym->name);
- // Determine kind based on symbol type
if (sym->type->type == AST_TYPE_FUNCTION) {
item->kind = LSP_COMPLETION_FUNCTION;
- // Create function call snippet
+
+ // Create function call snippet with placeholders for params
char snippet[512];
- snprintf(snippet, sizeof(snippet), "%s($0)", sym->name);
+ snprintf(snippet, sizeof(snippet), "%s()$0", sym->name);
item->insert_text = arena_strdup(arena, snippet);
item->format = LSP_INSERT_FORMAT_SNIPPET;
+
+ // Show function signature in detail (type_to_string handles this
+ // now)
+ item->detail = type_to_string(sym->type, arena);
} else if (sym->type->type == AST_TYPE_STRUCT) {
item->kind = LSP_COMPLETION_STRUCT;
item->insert_text = arena_strdup(arena, sym->name);
item->format = LSP_INSERT_FORMAT_PLAIN_TEXT;
+ item->detail = type_to_string(sym->type, arena);
} else {
item->kind = LSP_COMPLETION_VARIABLE;
item->insert_text = arena_strdup(arena, sym->name);
item->format = LSP_INSERT_FORMAT_PLAIN_TEXT;
+ item->detail = type_to_string(sym->type, arena);
+ }
+
+ // Add scope depth info for sorting (prefer local variables)
+ if (scope_depth == 0) {
+ item->sort_text = arena_strdup(arena, "0");
+ } else {
+ char sort[8];
+ snprintf(sort, sizeof(sort), "%d", scope_depth);
+ item->sort_text = arena_strdup(arena, sort);
}
- item->detail = type_to_string(sym->type, arena);
item->documentation = NULL;
- item->sort_text = NULL;
item->filter_text = NULL;
}
}
}
- scope_depth++;
+
current_scope = current_scope->parent;
+ scope_depth++;
}
- fprintf(stderr, "[LSP] Finished adding scope symbols, total depth: %d\n",
- scope_depth);
}
- fprintf(stderr, "[LSP] Checking %zu imports for completions\n",
- doc->import_count);
-
// Add imported module symbols
if (doc->imports && doc->import_count > 0) {
for (size_t i = 0; i < doc->import_count; i++) {
ImportedModule *import = &doc->imports[i];
- fprintf(stderr,
- "[LSP] Import %zu: module_path='%s', alias='%s', scope=%p\n", i,
- import->module_path ? import->module_path : "NULL",
- import->alias ? import->alias : "NULL", (void *)import->scope);
-
- // CRITICAL SAFETY CHECKS
- if (!import->scope) {
- fprintf(stderr, "[LSP] Skipping import - no scope\n");
- continue;
- }
-
- // Validate scope structure
- if (!import->scope->symbols.data) {
- fprintf(stderr, "[LSP] Skipping import - scope has no symbol data\n");
+ if (!import->scope || !import->scope->symbols.data) {
continue;
}
- fprintf(stderr, "[LSP] Import scope has %zu symbols\n",
- import->scope->symbols.count);
-
- // Add symbols with prefix (e.g., "string::strlen")
const char *prefix = import->alias ? import->alias : "module";
for (size_t j = 0; j < import->scope->symbols.count; j++) {
Symbol *sym = (Symbol *)((char *)import->scope->symbols.data +
j * sizeof(Symbol));
- // SAFETY CHECK: Validate symbol
- if (!sym || !sym->name || !sym->type) {
- fprintf(stderr, "[LSP] Symbol %zu: INVALID (skipping)\n", j);
+ if (!sym || !sym->name || !sym->type || !sym->is_public) {
continue;
}
- fprintf(stderr, "[LSP] Symbol %zu: '%s', is_public=%d\n", j,
- sym->name, sym->is_public);
-
- // Only include public symbols
- if (!sym->is_public)
+ // FILTER OUT INTERNAL SYMBOLS (those starting with __)
+ if (strncmp(sym->name, "__", 2) == 0) {
continue;
+ }
LSPCompletionItem *item =
(LSPCompletionItem *)growable_array_push(&completions);
if (item) {
- // Format: "alias::name"
size_t label_len = strlen(prefix) + strlen(sym->name) + 3;
char *label = arena_alloc(arena, label_len, 1);
snprintf(label, label_len, "%s::%s", prefix, sym->name);
@@ -287,9 +227,12 @@ LSPCompletionItem *lsp_completion(LSPDocument *doc, LSPPosition position,
: LSP_COMPLETION_VARIABLE;
item->insert_text = arena_strdup(arena, label);
item->format = LSP_INSERT_FORMAT_PLAIN_TEXT;
+
+ // Show function signature or type
item->detail = type_to_string(sym->type, arena);
+
item->documentation = NULL;
- item->sort_text = NULL;
+ item->sort_text = arena_strdup(arena, "9");
item->filter_text = NULL;
}
}
@@ -297,7 +240,5 @@ LSPCompletionItem *lsp_completion(LSPDocument *doc, LSPPosition position,
}
*completion_count = completions.count;
- fprintf(stderr, "[LSP] Returning %zu completion items\n", *completion_count);
- fflush(stderr); // CRITICAL: Ensure logs are written before return
return (LSPCompletionItem *)completions.data;
}
diff --git a/src/lsp/lsp_json.c b/src/lsp/lsp_json.c
index d1f9096c..caf2c809 100644
--- a/src/lsp/lsp_json.c
+++ b/src/lsp/lsp_json.c
@@ -152,30 +152,44 @@ LSPPosition extract_position(const char *json) {
}
const char *find_json_value(const char *json, const char *key) {
- char search[256];
- snprintf(search, sizeof(search), "\"%s\"", key);
-
- const char *found = strstr(json, search);
- if (!found) {
+ if (!json || !key) {
return NULL;
}
- // Skip to colon
- const char *colon = found + strlen(search);
- while (*colon && *colon != ':') {
- colon++;
- }
-
- if (!*colon)
- return NULL;
- colon++; // Skip the colon
+ // Build the search pattern: "key":
+ char search[256];
+ snprintf(search, sizeof(search), "\"%s\"", key);
- // Skip whitespace
- while (*colon && isspace(*colon)) {
- colon++;
+ const char *current = json;
+
+ // Search for all occurrences of the key until we find a valid one
+ while ((current = strstr(current, search)) != NULL) {
+ // Move past the key
+ const char *after_key = current + strlen(search);
+
+ // Skip whitespace
+ while (*after_key && isspace(*after_key)) {
+ after_key++;
+ }
+
+ // Check if followed by colon (indicates this is a key, not a value)
+ if (*after_key == ':') {
+ // Skip the colon
+ after_key++;
+
+ // Skip whitespace after colon
+ while (*after_key && isspace(*after_key)) {
+ after_key++;
+ }
+
+ return after_key;
+ }
+
+ // Not a valid key-value pair, continue searching
+ current = after_key;
}
- return colon;
+ return NULL;
}
LSPMethod lsp_parse_method(const char *json) {
@@ -185,10 +199,9 @@ LSPMethod lsp_parse_method(const char *json) {
}
// Log the first part of the JSON for debugging
- fprintf(stderr, "[LSP] parse_method: checking first 200 chars: %.200s\n",
- json);
+ fprintf(stderr, "[LSP] parse_method: checking message: %.200s\n", json);
- // Find the "method" value
+ // Find the "method" value - it could be anywhere in the JSON
const char *method_value = find_json_value(json, "method");
if (!method_value) {
fprintf(stderr, "[LSP] parse_method: no 'method' field found\n");
diff --git a/src/lsp/lsp_module.c b/src/lsp/lsp_module.c
index 8cf5858a..413563cd 100644
--- a/src/lsp/lsp_module.c
+++ b/src/lsp/lsp_module.c
@@ -55,7 +55,6 @@ const char *extract_module_name(const char *content, ArenaAllocator *arena) {
memcpy(module_name, name_start, name_len);
module_name[name_len] = '\0';
- fprintf(stderr, "[LSP] Found @module \"%s\"\n", module_name);
return module_name;
}
}
@@ -100,8 +99,6 @@ void scan_file_for_module(LSPServer *server, const char *file_uri,
// Already registered, update URI
server->module_registry.entries[i].file_uri =
arena_strdup(server->arena, file_uri);
- fprintf(stderr, "[LSP] Updated module '%s' -> %s\n", module_name,
- file_uri);
return;
}
}
@@ -130,9 +127,6 @@ void scan_file_for_module(LSPServer *server, const char *file_uri,
&server->module_registry.entries[server->module_registry.count++];
entry->module_name = arena_strdup(server->arena, module_name);
entry->file_uri = arena_strdup(server->arena, file_uri);
-
- fprintf(stderr, "[LSP] Registered module '%s' -> %s\n", module_name,
- file_uri);
}
// Recursively scan directory for .lx files
@@ -210,9 +204,6 @@ void scan_directory_recursive(LSPServer *server, const char *dir_path,
// Build module registry by scanning workspace
void build_module_registry(LSPServer *server, const char *workspace_uri) {
- fprintf(stderr, "[LSP] Building module registry for workspace: %s\n",
- workspace_uri);
-
ArenaAllocator temp_arena;
arena_allocator_init(&temp_arena, 64 * 1024);
@@ -223,10 +214,6 @@ void build_module_registry(LSPServer *server, const char *workspace_uri) {
}
scan_directory_recursive(server, workspace_path, &temp_arena);
-
- fprintf(stderr, "[LSP] Module registry built: %zu modules\n",
- server->module_registry.count);
-
arena_destroy(&temp_arena);
}
@@ -235,13 +222,10 @@ const char *lookup_module(LSPServer *server, const char *module_name) {
for (size_t i = 0; i < server->module_registry.count; i++) {
if (strcmp(server->module_registry.entries[i].module_name, module_name) ==
0) {
- fprintf(stderr, "[LSP] Lookup: '%s' -> %s\n", module_name,
- server->module_registry.entries[i].file_uri);
return server->module_registry.entries[i].file_uri;
}
}
- fprintf(stderr, "[LSP] Lookup: '%s' not found\n", module_name);
return NULL;
}
@@ -365,7 +349,6 @@ AstNode *parse_imported_module_ast(LSPServer *server, const char *module_uri,
FILE *f = fopen(file_path, "r");
if (!f) {
- fprintf(stderr, "[LSP] Failed to open module: %s\n", file_path);
return NULL;
}
@@ -396,7 +379,6 @@ AstNode *parse_imported_module_ast(LSPServer *server, const char *module_uri,
AstNode *module_ast = parse(&tokens, arena, config);
if (!module_ast) {
- fprintf(stderr, "[LSP] Failed to parse imported module: %s\n", file_path);
return NULL;
}
@@ -415,9 +397,6 @@ void resolve_imports(LSPServer *server, LSPDocument *doc, BuildConfig *config,
if (!doc || !doc->imports || doc->import_count == 0)
return;
- fprintf(stderr, "[LSP] Resolving %zu imports for %s\n", doc->import_count,
- doc->uri);
-
for (size_t i = 0; i < doc->import_count; i++) {
ImportedModule *import = &doc->imports[i];
@@ -425,14 +404,9 @@ void resolve_imports(LSPServer *server, LSPDocument *doc, BuildConfig *config,
const char *resolved_uri = lookup_module(server, import->module_path);
if (!resolved_uri) {
- fprintf(stderr, "[LSP] Module '%s' not found in registry\n",
- import->module_path);
continue;
}
- fprintf(stderr, "[LSP] Resolved '%s' -> %s\n", import->module_path,
- resolved_uri);
-
// Parse module and get its AST
AstNode *module_ast =
parse_imported_module_ast(server, resolved_uri, config, doc->arena);
@@ -442,8 +416,6 @@ void resolve_imports(LSPServer *server, LSPDocument *doc, BuildConfig *config,
AstNode **slot = (AstNode **)growable_array_push(imported_modules);
if (slot) {
*slot = module_ast;
- fprintf(stderr, "[LSP] Successfully loaded module: %s (alias: %s)\n",
- import->module_path, import->alias ? import->alias : "none");
}
}
}
diff --git a/src/main.c b/src/main.c
index 24467261..8a3cc924 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,6 +1,6 @@
/**
* @file main.c
- * @brief Entry point for the Lux compiler/interpreter build process.
+ * @brief Entry point for the Luma compiler/interpreter build process.
*
* This program parses command-line arguments, sets up a memory arena,
* and runs the build process on a provided source file.
@@ -13,12 +13,12 @@
*
* ## Usage
* ```bash
- * lux build
+ * luma build
* ```
*
* Example:
* ```bash
- * lux build hello.lx
+ * luma build hello.lx
* ```
*/
diff --git a/src/parser/expr.c b/src/parser/expr.c
index e4227fee..a07f8f76 100644
--- a/src/parser/expr.c
+++ b/src/parser/expr.c
@@ -273,6 +273,188 @@ Expr *array_expr(Parser *parser) {
elements.count, line, col);
}
+// Anonymous struct initialization: { x: 20, y: 50 }
+Expr *struct_expr(Parser *parser) {
+ // When we get here from nud(), we're AT the '{' token
+ int line = p_current(parser).line;
+ int col = p_current(parser).col;
+
+ p_consume(parser, TOK_LBRACE, "Expected '{' for struct expression");
+
+ GrowableArray field_names, field_values;
+ if (!growable_array_init(&field_names, parser->arena, 4, sizeof(char *)) ||
+ !growable_array_init(&field_values, parser->arena, 4, sizeof(Expr *))) {
+ fprintf(stderr, "Failed to initialize struct field arrays\n");
+ return NULL;
+ }
+
+ // Parse field initializers: field_name: value, ...
+ while (p_has_tokens(parser) && p_current(parser).type_ != TOK_RBRACE) {
+ // Parse field name
+ if (p_current(parser).type_ != TOK_IDENTIFIER) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected field name in struct expression",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+
+ char *field_name = get_name(parser);
+ p_advance(parser); // Consume the field name
+
+ p_consume(parser, TOK_COLON, "Expected ':' after field name");
+
+ // Parse field value
+ Expr *field_value = parse_expr(parser, BP_LOWEST);
+ if (!field_value) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected expression for field value",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+
+ // Store field name and value
+ char **name_slot = (char **)growable_array_push(&field_names);
+ Expr **value_slot = (Expr **)growable_array_push(&field_values);
+
+ if (!name_slot || !value_slot) {
+ fprintf(stderr, "Out of memory while growing struct field arrays\n");
+ return NULL;
+ }
+
+ *name_slot = field_name;
+ *value_slot = field_value;
+
+ // Handle comma separator
+ if (p_current(parser).type_ == TOK_COMMA) {
+ p_advance(parser); // Consume the comma
+ } else if (p_current(parser).type_ != TOK_RBRACE) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected ',' or '}' after field value",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+ }
+
+ p_consume(parser, TOK_RBRACE, "Expected '}' to close struct expression");
+
+ // Anonymous struct (name is NULL)
+ return create_struct_expr(parser->arena, NULL, (char **)field_names.data,
+ (AstNode **)field_values.data, field_names.count,
+ line, col);
+}
+
+// Named struct initialization: Point { x: 20, y: 50 }
+// OR namespace::Point { x: 20, y: 50 }
+// This is called from led() when we see '{' after an identifier or member expr
+Expr *named_struct_expr(Parser *parser, Expr *left, BindingPower bp) {
+ (void)bp; // Unused parameter
+
+ char *struct_name = NULL;
+
+ // Handle both identifier and member expressions
+ if (left->type == AST_EXPR_IDENTIFIER) {
+ // Simple case: Point { ... }
+ struct_name = left->expr.identifier.name;
+ } else if (left->type == AST_EXPR_MEMBER) {
+ // Namespace resolution: namespace::Point { ... }
+ // Build the full qualified name from the member expression
+
+ // For now, we'll extract the name from the member expression
+ // This assumes the member expression represents something like
+ // "namespace::Point" You might need to adjust this based on how your AST
+ // represents member expressions
+
+ // Get the member name (the rightmost part, e.g., "Point" in
+ // "namespace::Point")
+ struct_name = left->expr.member.member;
+
+ // Note: If you need the full qualified name, you'll need to traverse
+ // the member expression tree and build the complete path
+ // For example: if left->expr.member.object is also a member expr,
+ // you'd need to recursively build the name
+ } else {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected identifier or namespace resolution before '{' for "
+ "named struct",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+
+ int line = p_current(parser).line;
+ int col = p_current(parser).col;
+
+ p_consume(parser, TOK_LBRACE, "Expected '{' for struct expression");
+
+ GrowableArray field_names, field_values;
+ if (!growable_array_init(&field_names, parser->arena, 4, sizeof(char *)) ||
+ !growable_array_init(&field_values, parser->arena, 4, sizeof(Expr *))) {
+ fprintf(stderr, "Failed to initialize struct field arrays\n");
+ return NULL;
+ }
+
+ // Parse field initializers: field_name: value, ...
+ while (p_has_tokens(parser) && p_current(parser).type_ != TOK_RBRACE) {
+ // Parse field name
+ if (p_current(parser).type_ != TOK_IDENTIFIER) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected field name in struct expression",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+
+ char *field_name = get_name(parser);
+ p_advance(parser); // Consume the field name
+
+ p_consume(parser, TOK_COLON, "Expected ':' after field name");
+
+ // Parse field value
+ Expr *field_value = parse_expr(parser, BP_LOWEST);
+ if (!field_value) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected expression for field value",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+
+ // Store field name and value
+ char **name_slot = (char **)growable_array_push(&field_names);
+ Expr **value_slot = (Expr **)growable_array_push(&field_values);
+
+ if (!name_slot || !value_slot) {
+ fprintf(stderr, "Out of memory while growing struct field arrays\n");
+ return NULL;
+ }
+
+ *name_slot = field_name;
+ *value_slot = field_value;
+
+ // Handle comma separator
+ if (p_current(parser).type_ == TOK_COMMA) {
+ p_advance(parser); // Consume the comma
+ } else if (p_current(parser).type_ != TOK_RBRACE) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected ',' or '}' after field value",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+ }
+
+ p_consume(parser, TOK_RBRACE, "Expected '}' to close struct expression");
+
+ // Store the full expression (identifier or member) for later use
+ // This allows the semantic analyzer to resolve the namespace properly
+ return create_struct_expr(
+ parser->arena, struct_name, (char **)field_names.data,
+ (AstNode **)field_values.data, field_names.count, line, col);
+}
+
Expr *deref_expr(Parser *parser) {
p_advance(parser); // Advance past the *
int line = p_current(parser).line;
@@ -325,20 +507,26 @@ Expr *free_expr(Parser *parser) {
// cast(value);
Expr *cast_expr(Parser *parser) {
- p_advance(parser); // Advance past the cast
int line = p_current(parser).line;
int col = p_current(parser).col;
+
+ p_advance(parser); // Advance past 'cast'
p_consume(parser, TOK_LT,
- "Expected a '<' before you declare the type you want to cast too.");
+ "Expected a '<' before you declare the type you want to cast to.");
+
Type *cast_type = parse_type(parser);
- p_advance(parser);
+ // parse_type() has already advanced past the type
+
p_consume(parser, TOK_GT,
- "Expected a '>' after defining the type you want to cast too, but "
+ "Expected a '>' after defining the type you want to cast to, but "
"before defining what you are casting");
+
p_consume(parser, TOK_LPAREN,
"Expected a '(' before defining what you are casting");
+
Expr *castee = parse_expr(parser, BP_NONE);
+
p_consume(parser, TOK_RPAREN,
"Expected a ')' after defining what you are casting");
@@ -347,22 +535,27 @@ Expr *cast_expr(Parser *parser) {
// input(msg);
Expr *input_expr(Parser *parser) {
- p_advance(parser); // Advance past the cast
int line = p_current(parser).line;
int col = p_current(parser).col;
+
+ p_advance(parser); // Advance past 'input'
p_consume(parser, TOK_LT,
- "Expected a '<' before you declare the type you want to cast too.");
+ "Expected a '<' before you declare the type you want to input.");
+
Type *type = parse_type(parser);
- p_advance(parser);
+ // parse_type() has already advanced past the type
+
p_consume(parser, TOK_GT,
- "Expected a '>' after defining the type you want to cast too, but "
- "before defining what you are casting");
+ "Expected a '>' after defining the type you want to input");
+
p_consume(parser, TOK_LPAREN,
- "Expected a '(' before defining what you are casting");
+ "Expected a '(' before defining the input message");
+
Expr *msg = parse_expr(parser, BP_NONE);
+
p_consume(parser, TOK_RPAREN,
- "Expected a ')' after defining what you are casting");
+ "Expected a ')' after defining the input message");
return create_input_expr(parser->arena, type, msg, line, col);
}
@@ -381,29 +574,76 @@ Expr *system_expr(Parser *parser) {
return create_system_expr(parser->arena, command, line, col);
}
+Expr *syscall_expr(Parser *parser) {
+ p_advance(parser);
+ int line = p_current(parser).line;
+ int col = p_current(parser).col;
+
+ p_consume(
+ parser, TOK_LPAREN,
+ "Expected a '(' before you give the params for the syscall command");
+
+ GrowableArray args;
+ if (!growable_array_init(&args, parser->arena, 2, sizeof(Expr *))) {
+ fprintf(stderr, "Failed to initialize array elements\n");
+ return NULL;
+ }
+
+ while (p_current(parser).type_ != TOK_RPAREN) {
+ Expr *arg = parse_expr(parser, BP_NONE);
+ if (!arg) {
+ fprintf(stderr, "Expected expression inside array\n");
+ return NULL;
+ }
+
+ Expr **slot = (Expr **)growable_array_push(&args);
+ if (!slot) {
+ fprintf(stderr, "Out of memory while growing array elements\n");
+ return NULL;
+ }
+
+ *slot = arg;
+
+ if (p_current(parser).type_ == TOK_COMMA)
+ p_advance(parser);
+ }
+ p_consume(parser, TOK_RPAREN,
+ "Expected a ')' after you give your arguments for syscall.");
+
+ return create_syscall_expr(parser->arena, (Expr **)args.data, args.count,
+ line, col);
+}
+
// size_t sizeof(TYPE);
// sizeof Compile-time constant
// sizeof<[10]int> Compile-time constant
// sizeof<[n]int> Runtime when n is variable
// sizeof Compile-time constant
Expr *sizeof_expr(Parser *parser) {
- p_advance(parser); // Advance past the sizeof
int line = p_current(parser).line;
int col = p_current(parser).col;
- AstNode *object = NULL;
- bool is_type = false;
-
+
+ p_advance(parser); // Advance past 'sizeof'
+
p_consume(parser, TOK_LT,
"Expected a '<' before defining the var or type you want to get "
"the size of.");
- if (parse_type(parser) != NULL) {
- ;
- object = parse_type(parser);
- p_advance(parser);
+
+ AstNode *object = NULL;
+ bool is_type = false;
+
+ // Try to parse as a type first
+ Type *type_result = parse_type(parser);
+ if (type_result != NULL) {
+ object = (AstNode *)type_result;
is_type = true;
+ // parse_type() has already advanced past the type
} else {
- object = parse_expr(parser, BP_NONE);
+ // Not a type, parse as an expression
+ object = (AstNode *)parse_expr(parser, BP_NONE);
+ is_type = false;
}
+
p_consume(parser, TOK_GT,
"Expected a '>' after defining the var or type you want to get the "
"size of.");
diff --git a/src/parser/parser.c b/src/parser/parser.c
index 9ec7a5ab..6fdf60e8 100644
--- a/src/parser/parser.c
+++ b/src/parser/parser.c
@@ -20,7 +20,6 @@
#include
#include
-#include
#include "../ast/ast.h"
#include "../c_libs/error/error.h"
@@ -48,21 +47,20 @@
*/
void parser_error(Parser *psr, const char *error_type, const char *file,
const char *msg, int line, int col, int tk_length) {
- (void)file;
- ErrorInformation err = {
- .error_type = error_type,
- .file_path = psr->file_path,
- .message = msg,
- .line = line,
- .col = col,
- .line_text =
- generate_line(psr->arena, psr->tks, psr->tk_count, err.line),
- .token_length = tk_length,
- .label = "Parser Error",
- .note = NULL,
- .help = NULL,
- };
- error_add(err);
+ (void)file;
+ ErrorInformation err = {
+ .error_type = error_type,
+ .file_path = psr->file_path,
+ .message = msg,
+ .line = line,
+ .col = col,
+ .line_text = generate_line(psr->arena, psr->tks, psr->tk_count, err.line),
+ .token_length = tk_length,
+ .label = "Parser Error",
+ .note = NULL,
+ .help = NULL,
+ };
+ error_add(err);
}
/**
@@ -86,69 +84,69 @@ void parser_error(Parser *psr, const char *error_type, const char *file,
*/
Stmt *parse(GrowableArray *tks, ArenaAllocator *arena, BuildConfig *config) {
- // Initialize parser
- Parser parser = {
- .file_path = config->filepath,
- .arena = arena,
- .tks = (Token *)tks->data,
- .tk_count = tks->count,
- .capacity = (tks->count / 4) + 10,
- .pos = 0,
- };
-
- if (!parser.tks) {
- fprintf(stderr, "Failed to get tokens from GrowableArray\n");
- return NULL;
+ // Initialize parser
+ Parser parser = {
+ .file_path = config->filepath,
+ .arena = arena,
+ .tks = (Token *)tks->data,
+ .tk_count = tks->count,
+ .capacity = (tks->count / 4) + 10,
+ .pos = 0,
+ };
+
+ if (!parser.tks) {
+ fprintf(stderr, "Failed to get tokens from GrowableArray\n");
+ return NULL;
+ }
+
+ // Initialize arrays
+ GrowableArray stmts, modules;
+ if (!init_parser_arrays(&parser, &stmts, &modules)) {
+ return NULL;
+ }
+
+ // Parse module declaration
+ Token module_tok = p_current(&parser);
+ const char *module_name = parse_module_declaration(&parser);
+ if (!module_name) {
+ return NULL;
+ }
+
+ // Create initial module node and add to modules array
+ Stmt *module_stmt = create_module_node(parser.arena, module_name, 0, NULL, 0,
+ module_tok.line, module_tok.col);
+
+ Stmt **module_slot = (Stmt **)growable_array_push(&modules);
+ if (!module_slot) {
+ fprintf(stderr, "Out of memory while growing modules array\n");
+ return NULL;
+ }
+ *module_slot = module_stmt;
+
+ // Parse all statements
+ while (p_current(&parser).type_ != TOK_EOF) {
+ Stmt *stmt = parse_stmt(&parser);
+ if (!stmt) {
+ // Error already reported in parse_stmt
+ return NULL;
}
- // Initialize arrays
- GrowableArray stmts, modules;
- if (!init_parser_arrays(&parser, &stmts, &modules)) {
- return NULL;
+ Stmt **slot = (Stmt **)growable_array_push(&stmts);
+ if (!slot) {
+ fprintf(stderr, "Out of memory while growing statements array\n");
+ return NULL;
}
+ *slot = stmt;
+ }
- // Parse module declaration
- Token module_tok = p_current(&parser);
- const char *module_name = parse_module_declaration(&parser);
- if (!module_name) {
- return NULL;
- }
-
- // Create initial module node and add to modules array
- Stmt *module_stmt = create_module_node(parser.arena, module_name, 0, NULL,
- 0, module_tok.line, module_tok.col);
-
- Stmt **module_slot = (Stmt **)growable_array_push(&modules);
- if (!module_slot) {
- fprintf(stderr, "Out of memory while growing modules array\n");
- return NULL;
- }
- *module_slot = module_stmt;
-
- // Parse all statements
- while (p_current(&parser).type_ != TOK_EOF) {
- Stmt *stmt = parse_stmt(&parser);
- if (!stmt) {
- // Error already reported in parse_stmt
- return NULL;
- }
-
- Stmt **slot = (Stmt **)growable_array_push(&stmts);
- if (!slot) {
- fprintf(stderr, "Out of memory while growing statements array\n");
- return NULL;
- }
- *slot = stmt;
- }
+ // Update module with parsed statements
+ *module_slot =
+ create_module_node(parser.arena, module_name, 0, (Stmt **)stmts.data,
+ stmts.count, module_tok.line, module_tok.col);
- // Update module with parsed statements
- *module_slot =
- create_module_node(parser.arena, module_name, 0, (Stmt **)stmts.data,
- stmts.count, module_tok.line, module_tok.col);
-
- // Create and return program node
- return create_program_node(parser.arena, (AstNode **)modules.data,
- modules.count, 0, 0);
+ // Create and return program node
+ return create_program_node(parser.arena, (AstNode **)modules.data,
+ modules.count, 0, 0);
}
/**
* @brief Gets the binding power (precedence) for a given token type
@@ -179,68 +177,74 @@ Stmt *parse(GrowableArray *tks, ArenaAllocator *arena, BuildConfig *config) {
* @see BindingPower, LumaTokenType
*/
BindingPower get_bp(LumaTokenType kind) {
- switch (kind) {
- // Assignment
- case TOK_EQUAL:
- return BP_ASSIGN;
-
- // Ternary
- case TOK_QUESTION:
- return BP_TERNARY;
-
- // Logical
- case TOK_OR:
- return BP_LOGICAL_OR;
- case TOK_AND:
- return BP_LOGICAL_AND;
-
- // Bitwise
- case TOK_PIPE:
- return BP_BITWISE_OR;
- case TOK_CARET:
- return BP_BITWISE_XOR;
- case TOK_AMP:
- return BP_BITWISE_AND;
-
- // Equality
- case TOK_EQEQ:
- case TOK_NEQ:
- return BP_EQUALITY;
-
- // Relational
- case TOK_LT:
- case TOK_LE:
- case TOK_GT:
- case TOK_GE:
- return BP_RELATIONAL;
-
- // Arithmetic
- case TOK_PLUS:
- case TOK_MINUS:
- return BP_SUM;
- case TOK_STAR:
- case TOK_SLASH:
- case TOK_MODL:
- return BP_PRODUCT;
-
- // Postfix
- case TOK_PLUSPLUS:
- case TOK_MINUSMINUS:
- return BP_POSTFIX;
-
- // Call/indexing/member access
- case TOK_LPAREN:
- case TOK_LBRACKET:
- case TOK_DOT:
- case TOK_RESOLVE:
- return BP_CALL;
-
- case TOK_RANGE:
- return BP_RANGE;
-
- default:
- return BP_NONE;
- }
+ switch (kind) {
+ // Assignment
+ case TOK_EQUAL:
+ return BP_ASSIGN;
+
+ // Ternary
+ case TOK_QUESTION:
+ return BP_TERNARY;
+
+ // Logical
+ case TOK_OR:
+ return BP_LOGICAL_OR;
+ case TOK_AND:
+ return BP_LOGICAL_AND;
+
+ // Bitwise
+ case TOK_PIPE:
+ return BP_BITWISE_OR;
+ case TOK_CARET:
+ return BP_BITWISE_XOR;
+ case TOK_AMP:
+ return BP_BITWISE_AND;
+
+ // Equality
+ case TOK_EQEQ:
+ case TOK_NEQ:
+ return BP_EQUALITY;
+
+ // Relational
+ case TOK_LT:
+ case TOK_LE:
+ case TOK_GT:
+ case TOK_GE:
+ return BP_RELATIONAL;
+
+ // Shift operators (ADD THIS SECTION)
+ case TOK_SHIFT_LEFT:
+ case TOK_SHIFT_RIGHT:
+ return BP_SHIFT;
+
+ // Arithmetic
+ case TOK_PLUS:
+ case TOK_MINUS:
+ return BP_SUM;
+ case TOK_STAR:
+ case TOK_SLASH:
+ case TOK_MODL:
+ return BP_PRODUCT;
+
+ // Postfix
+ case TOK_PLUSPLUS:
+ case TOK_MINUSMINUS:
+ return BP_POSTFIX;
+
+ // Call/indexing/member access
+ case TOK_LPAREN:
+ case TOK_LBRACKET:
+ case TOK_DOT:
+ case TOK_RESOLVE:
+ case TOK_LBRACE:
+ return BP_CALL;
+
+ case TOK_RANGE:
+ return BP_RANGE;
+
+ default:
+ return BP_NONE;
+ }
}
/**
@@ -263,45 +267,50 @@ BindingPower get_bp(LumaTokenType kind) {
* @see led(), parse_expr(), primary(), unary(), grouping(), array_expr()
*/
Expr *nud(Parser *parser) {
- switch (p_current(parser).type_) {
- case TOK_NUMBER:
- case TOK_NUM_FLOAT:
- case TOK_STRING:
- case TOK_CHAR_LITERAL:
- case TOK_IDENTIFIER:
- return primary(parser);
- case TOK_MINUS:
- case TOK_PLUS:
- case TOK_BANG:
- case TOK_PLUSPLUS:
- case TOK_MINUSMINUS:
- return unary(parser);
- case TOK_LPAREN:
- return grouping(parser);
- case TOK_LBRACKET:
- return array_expr(parser);
- case TOK_STAR:
- return deref_expr(parser);
- case TOK_AMP:
- return addr_expr(parser);
- case TOK_ALLOC:
- return alloc_expr(parser);
- case TOK_FREE:
- return free_expr(parser);
- case TOK_CAST:
- return cast_expr(parser);
- case TOK_INPUT:
- return input_expr(parser);
- case TOK_SYSTEM:
- return system_expr(parser);
-
- // Compile time
- case TOK_SIZE_OF:
- return sizeof_expr(parser);
- default:
- p_advance(parser);
- return NULL;
- }
+ switch (p_current(parser).type_) {
+ case TOK_NUMBER:
+ case TOK_NUM_FLOAT:
+ case TOK_STRING:
+ case TOK_CHAR_LITERAL:
+ case TOK_IDENTIFIER:
+ return primary(parser);
+ case TOK_MINUS:
+ case TOK_PLUS:
+ case TOK_BANG:
+ case TOK_TILDE:
+ case TOK_PLUSPLUS:
+ case TOK_MINUSMINUS:
+ return unary(parser);
+ case TOK_LPAREN:
+ return grouping(parser);
+ case TOK_LBRACKET:
+ return array_expr(parser);
+ case TOK_LBRACE:
+ return struct_expr(parser);
+ case TOK_STAR:
+ return deref_expr(parser);
+ case TOK_AMP:
+ return addr_expr(parser);
+ case TOK_ALLOC:
+ return alloc_expr(parser);
+ case TOK_FREE:
+ return free_expr(parser);
+ case TOK_CAST:
+ return cast_expr(parser);
+ case TOK_INPUT:
+ return input_expr(parser);
+ case TOK_SYSTEM:
+ return system_expr(parser);
+ case TOK_SYSCALL:
+ return syscall_expr(parser);
+
+ // Compile time
+ case TOK_SIZE_OF:
+ return sizeof_expr(parser);
+ default:
+ p_advance(parser);
+ return NULL;
+ }
}
/**
@@ -329,40 +338,43 @@ Expr *nud(Parser *parser) {
* @see nud(), parse_expr(), binary(), call_expr(), assign_expr(), prefix_expr()
*/
Expr *led(Parser *parser, Expr *left, BindingPower bp) {
- switch (p_current(parser).type_) {
- case TOK_PLUS:
- case TOK_MINUS:
- case TOK_STAR:
- case TOK_SLASH:
- case TOK_MODL:
- case TOK_EQEQ:
- case TOK_NEQ:
- case TOK_LT:
- case TOK_LE:
- case TOK_GT:
- case TOK_GE:
- case TOK_AMP:
- case TOK_PIPE:
- case TOK_CARET:
- case TOK_AND: // Add logical AND
- case TOK_OR: // Add logical OR
- case TOK_RANGE:
- return binary(parser, left, bp);
- case TOK_LPAREN:
- return call_expr(parser, left, bp);
- case TOK_EQUAL:
- return assign_expr(parser, left, bp);
- case TOK_DOT:
- case TOK_RESOLVE:
- case TOK_PLUSPLUS:
- case TOK_MINUSMINUS:
- case TOK_LBRACKET:
- // Handle member access, postfix increment/decrement, and indexing
- return prefix_expr(parser, left, bp);
- default:
- p_advance(parser);
- return left; // No valid LED found, return left expression
- }
+ switch (p_current(parser).type_) {
+ case TOK_PLUS:
+ case TOK_MINUS:
+ case TOK_STAR:
+ case TOK_SLASH:
+ case TOK_MODL:
+ case TOK_EQEQ:
+ case TOK_NEQ:
+ case TOK_LT:
+ case TOK_LE:
+ case TOK_GT:
+ case TOK_GE:
+ case TOK_AMP:
+ case TOK_PIPE:
+ case TOK_CARET:
+ case TOK_AND:
+ case TOK_OR:
+ case TOK_RANGE:
+ case TOK_SHIFT_LEFT:
+ case TOK_SHIFT_RIGHT:
+ return binary(parser, left, bp);
+ case TOK_LPAREN:
+ return call_expr(parser, left, bp);
+ case TOK_EQUAL:
+ return assign_expr(parser, left, bp);
+ case TOK_DOT:
+ case TOK_RESOLVE:
+ case TOK_PLUSPLUS:
+ case TOK_MINUSMINUS:
+ case TOK_LBRACKET:
+ return prefix_expr(parser, left, bp);
+ case TOK_LBRACE:
+ return named_struct_expr(parser, left, bp);
+ default:
+ p_advance(parser);
+ return left;
+ }
}
/**
@@ -387,13 +399,14 @@ Expr *led(Parser *parser, Expr *left, BindingPower bp) {
* @see nud(), led(), get_bp(), BindingPower
*/
Expr *parse_expr(Parser *parser, BindingPower bp) {
- Expr *left = nud(parser);
+ Expr *left = nud(parser);
- while (p_has_tokens(parser) && get_bp(p_current(parser).type_) > bp) {
- left = led(parser, left, get_bp(p_current(parser).type_));
- }
+ while (p_has_tokens(parser) && get_bp(p_current(parser).type_) > bp) {
+ BindingPower current_bp = get_bp(p_current(parser).type_);
+ left = led(parser, left, current_bp);
+ }
- return left;
+ return left;
}
/**
@@ -419,65 +432,63 @@ Expr *parse_expr(Parser *parser, BindingPower bp) {
* loop_stmt(), print_stmt(), break_continue_stmt(), expr_stmt()
*/
Stmt *parse_stmt(Parser *parser) {
- bool returns_ownership = false;
- bool takes_ownership = false;
- bool is_public = false;
-
- // Check for ownership modifiers
- while (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP ||
- p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
- if (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP) {
- returns_ownership = true;
- p_advance(parser);
- } else if (p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
- takes_ownership = true;
- p_advance(parser);
- }
- }
-
- // Check for visibility modifiers
- if (p_current(parser).type_ == TOK_PUBLIC) {
- is_public = true;
- p_advance(parser); // Advance past the public token
- } else if (p_current(parser).type_ == TOK_PRIVATE) {
- is_public = false;
- p_advance(parser); // Advance past the private token
- }
-
- switch (p_current(parser).type_) {
- case TOK_USE:
- return use_stmt(parser);
- case TOK_CONST:
- return const_stmt(parser, is_public, returns_ownership,
- takes_ownership);
- case TOK_VAR:
- return var_stmt(parser, is_public);
- case TOK_RETURN:
- return return_stmt(parser);
- case TOK_LBRACE:
- return block_stmt(parser);
- case TOK_IF:
- return if_stmt(parser);
- case TOK_LOOP:
- return loop_stmt(parser);
- case TOK_PRINT:
- return print_stmt(parser, false);
- case TOK_PRINTLN:
- return print_stmt(parser, true);
- case TOK_CONTINUE:
- case TOK_BREAK:
- return break_continue_stmt(parser,
- p_current(parser).type_ == TOK_CONTINUE);
- case TOK_DEFER:
- return defer_stmt(parser);
- case TOK_SWITCH:
- return switch_stmt(parser);
- case TOK_IMPL:
- return impl_stmt(parser);
- default:
- return expr_stmt(
- parser); // expression statements handle their own semicolon
+ bool returns_ownership = false;
+ bool takes_ownership = false;
+ bool is_public = false;
+
+ // Check for ownership modifiers
+ while (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP ||
+ p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
+ if (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP) {
+ returns_ownership = true;
+ p_advance(parser);
+ } else if (p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
+ takes_ownership = true;
+ p_advance(parser);
}
+ }
+
+ // Check for visibility modifiers
+ if (p_current(parser).type_ == TOK_PUBLIC) {
+ is_public = true;
+ p_advance(parser); // Advance past the public token
+ } else if (p_current(parser).type_ == TOK_PRIVATE) {
+ is_public = false;
+ p_advance(parser); // Advance past the private token
+ }
+
+ switch (p_current(parser).type_) {
+ case TOK_USE:
+ return use_stmt(parser);
+ case TOK_CONST:
+ return const_stmt(parser, is_public, returns_ownership, takes_ownership);
+ case TOK_VAR:
+ return var_stmt(parser, is_public);
+ case TOK_RETURN:
+ return return_stmt(parser);
+ case TOK_LBRACE:
+ return block_stmt(parser);
+ case TOK_IF:
+ return if_stmt(parser);
+ case TOK_LOOP:
+ return loop_stmt(parser);
+ case TOK_PRINT:
+ return print_stmt(parser, false);
+ case TOK_PRINTLN:
+ return print_stmt(parser, true);
+ case TOK_CONTINUE:
+ case TOK_BREAK:
+ return break_continue_stmt(parser, p_current(parser).type_ == TOK_CONTINUE);
+ case TOK_DEFER:
+ return defer_stmt(parser);
+ case TOK_SWITCH:
+ return switch_stmt(parser);
+ case TOK_IMPL:
+ return impl_stmt(parser);
+ default:
+ return expr_stmt(
+ parser); // expression statements handle their own semicolon
+ }
}
/**
@@ -502,28 +513,24 @@ Stmt *parse_stmt(Parser *parser) {
* @see tnud(), tled(), LumaTokenType
*/
Type *parse_type(Parser *parser) {
- LumaTokenType tok = p_current(parser).type_;
-
- switch (tok) {
- case TOK_INT:
- case TOK_UINT:
- case TOK_DOUBLE:
- case TOK_FLOAT:
- case TOK_BOOL:
- case TOK_STRINGT:
- case TOK_VOID:
- case TOK_CHAR:
- case TOK_STAR: // Pointer type
- case TOK_LBRACKET: // Array type
- return tnud(parser);
-
- // Optionally: handle identifiers like 'MyStruct' or user-defined types
- case TOK_IDENTIFIER:
- return create_basic_type(parser->arena, get_name(parser),
- parser->tks->line, parser->tks->col);
-
- default:
- fprintf(stderr, "[parse_type] Unexpected token for type: %d\n", tok);
- return NULL;
- }
+ LumaTokenType tok = p_current(parser).type_;
+
+ switch (tok) {
+ case TOK_INT:
+ case TOK_UINT:
+ case TOK_DOUBLE:
+ case TOK_FLOAT:
+ case TOK_BOOL:
+ case TOK_STRINGT:
+ case TOK_VOID:
+ case TOK_CHAR:
+ case TOK_STAR: // Pointer type
+ case TOK_LBRACKET: // Array type
+ case TOK_IDENTIFIER: // Could be simple type or namespace::Type
+ return tnud(parser);
+
+ default:
+ fprintf(stderr, "[parse_type] Unexpected token for type: %d\n", tok);
+ return NULL;
+ }
}
diff --git a/src/parser/parser.h b/src/parser/parser.h
index 47dfaa93..a5a33f02 100644
--- a/src/parser/parser.h
+++ b/src/parser/parser.h
@@ -51,26 +51,26 @@
* Used to control operator precedence and associativity in Pratt parsing.
*/
typedef enum {
- BP_NONE = 0, /**< No binding power */
- BP_LOWEST, /**< Lowest binding power */
- BP_ASSIGN, /**< Assignment operators (=, +=, etc.) */
- BP_TERNARY, /**< Ternary conditional operator (? :) */
- BP_LOGICAL_OR, /**< Logical OR operator (||) */
- BP_LOGICAL_AND, /**< Logical AND operator (&&) */
- BP_BITWISE_OR, /**< Bitwise OR operator (|) */
- BP_BITWISE_XOR, /**< Bitwise XOR operator (^) */
- BP_BITWISE_AND, /**< Bitwise AND operator (&) */
- BP_EQUALITY, /**< Equality operators (==, !=) */
- BP_RELATIONAL, /**< Relational operators (<, >, <=, >=) */
- BP_RANGE, /**< Range operations (..) */
- BP_SHIFT, /**< Shift operators (<<, >>) */
- BP_SUM, /**< Addition and subtraction (+, -) */
- BP_PRODUCT, /**< Multiplication, division, modulo (*, /, %) */
- BP_EXPONENT, /**< Exponentiation operator (**) */
- BP_UNARY, /**< Unary operators (!, ~, +, -, prefix ++/--) */
- BP_POSTFIX, /**< Postfix operators (++/-- postfix) */
- BP_CALL, /**< Function call or indexing */
- BP_PRIMARY /**< Primary expressions (literals, variables) */
+ BP_NONE = 0, /**< No binding power */
+ BP_LOWEST, /**< Lowest binding power */
+ BP_ASSIGN, /**< Assignment operators (=, +=, etc.) */
+ BP_TERNARY, /**< Ternary conditional operator (? :) */
+ BP_LOGICAL_OR, /**< Logical OR operator (||) */
+ BP_LOGICAL_AND, /**< Logical AND operator (&&) */
+ BP_BITWISE_OR, /**< Bitwise OR operator (|) */
+ BP_BITWISE_XOR, /**< Bitwise XOR operator (^) */
+ BP_BITWISE_AND, /**< Bitwise AND operator (&) */
+ BP_EQUALITY, /**< Equality operators (==, !=) */
+ BP_RELATIONAL, /**< Relational operators (<, >, <=, >=) */
+ BP_RANGE, /**< Range operations (..) */
+ BP_SHIFT, /**< Shift operators (<<, >>) */
+ BP_SUM, /**< Addition and subtraction (+, -) */
+ BP_PRODUCT, /**< Multiplication, division, modulo (*, /, %) */
+ BP_EXPONENT, /**< Exponentiation operator (**) */
+ BP_UNARY, /**< Unary operators (!, ~, +, -, prefix ++/--) */
+ BP_POSTFIX, /**< Postfix operators (++/-- postfix) */
+ BP_CALL, /**< Function call or indexing */
+ BP_PRIMARY /**< Primary expressions (literals, variables) */
} BindingPower;
/**
@@ -88,15 +88,16 @@ static const LiteralType PRIMARY_LITERAL_TYPE_MAP[] = {
* @brief Maps token types to their corresponding binary operators.
*/
static const BinaryOp TOKEN_TO_BINOP_MAP[] = {
- [TOK_PLUS] = BINOP_ADD, [TOK_MINUS] = BINOP_SUB,
- [TOK_STAR] = BINOP_MUL, [TOK_SLASH] = BINOP_DIV,
- [TOK_EQEQ] = BINOP_EQ, [TOK_NEQ] = BINOP_NE,
- [TOK_LT] = BINOP_LT, [TOK_LE] = BINOP_LE,
- [TOK_GT] = BINOP_GT, [TOK_GE] = BINOP_GE,
- [TOK_AND] = BINOP_AND, [TOK_OR] = BINOP_OR,
- [TOK_AMP] = BINOP_BIT_AND, [TOK_PIPE] = BINOP_BIT_OR,
- [TOK_CARET] = BINOP_BIT_XOR, [TOK_RANGE] = BINOP_RANGE,
- [TOK_MODL] = BINOP_MOD,
+ [TOK_PLUS] = BINOP_ADD, [TOK_MINUS] = BINOP_SUB,
+ [TOK_STAR] = BINOP_MUL, [TOK_SLASH] = BINOP_DIV,
+ [TOK_EQEQ] = BINOP_EQ, [TOK_NEQ] = BINOP_NE,
+ [TOK_LT] = BINOP_LT, [TOK_LE] = BINOP_LE,
+ [TOK_GT] = BINOP_GT, [TOK_GE] = BINOP_GE,
+ [TOK_AND] = BINOP_AND, [TOK_OR] = BINOP_OR,
+ [TOK_AMP] = BINOP_BIT_AND, [TOK_PIPE] = BINOP_BIT_OR,
+ [TOK_CARET] = BINOP_BIT_XOR, [TOK_RANGE] = BINOP_RANGE,
+ [TOK_MODL] = BINOP_MOD, [TOK_SHIFT_LEFT] = BINOP_SHL,
+ [TOK_SHIFT_RIGHT] = BINOP_SHR,
};
/**
@@ -113,12 +114,12 @@ static const UnaryOp TOKEN_TO_UNOP_MAP[] = {
* @brief Parser state holding token stream and current position.
*/
typedef struct {
- const char *file_path;
- ArenaAllocator *arena; /**< Memory arena for AST node allocations */
- Token *tks; /**< Array of tokens to parse */
- size_t tk_count; /**< Number of tokens in the array */
- size_t capacity; /**< Capacity for statements and expressions */
- size_t pos; /**< Current token position */
+ const char *file_path;
+ ArenaAllocator *arena; /**< Memory arena for AST node allocations */
+ Token *tks; /**< Array of tokens to parse */
+ size_t tk_count; /**< Number of tokens in the array */
+ size_t capacity; /**< Capacity for statements and expressions */
+ size_t pos; /**< Current token position */
} Parser;
/**
@@ -189,7 +190,10 @@ Expr *free_expr(Parser *parser);
Expr *cast_expr(Parser *parser);
Expr *input_expr(Parser *parser);
Expr *system_expr(Parser *parser);
+Expr *syscall_expr(Parser *parser);
Expr *sizeof_expr(Parser *parser);
+Expr *struct_expr(Parser *parser);
+Expr *named_struct_expr(Parser *parser, Expr *left, BindingPower bp);
Type *tnud(Parser *parser);
Type *tled(Parser *parser, Type *left, BindingPower bp);
diff --git a/src/parser/stmt.c b/src/parser/stmt.c
index 09ba8fe1..3629b399 100644
--- a/src/parser/stmt.c
+++ b/src/parser/stmt.c
@@ -112,7 +112,6 @@ Stmt *const_stmt(Parser *parser, bool is_public, bool returns_ownership,
p_consume(parser, TOK_COLON, "Expected ':' after const name");
Type *type = parse_type(parser);
- p_advance(parser); // Advance past the type token
p_consume(parser, TOK_EQUAL, "Expected '=' after const type");
Expr *value = parse_expr(parser, BP_LOWEST);
@@ -192,7 +191,6 @@ Stmt *fn_stmt(Parser *parser, const char *name, bool is_public,
fprintf(stderr, "Failed to parse type for parameter '%s'\n", param_name);
return NULL;
}
- p_advance(parser); // Advance past the type token
// Store parameter name and type
char **name_slot = (char **)growable_array_push(¶m_names);
@@ -213,14 +211,21 @@ Stmt *fn_stmt(Parser *parser, const char *name, bool is_public,
p_consume(parser, TOK_RPAREN, "Expected ')' after function parameters");
Type *return_type = parse_type(parser);
- p_advance(parser); // Advance past the return type token
+
+ if (p_current(parser).type_ == TOK_SEMICOLON) {
+ p_consume(parser, TOK_SEMICOLON, "Expected semicolon after function prototype");
+ return create_func_decl_stmt(parser->arena, name, (char **)param_names.data,
+ (AstNode **)param_types.data, param_names.count,
+ return_type, is_public, returns_ownership,
+ takes_ownership, true, NULL, line, col);
+ }
Stmt *body = block_stmt(parser);
return create_func_decl_stmt(parser->arena, name, (char **)param_names.data,
(AstNode **)param_types.data, param_names.count,
return_type, is_public, returns_ownership,
- takes_ownership, body, line, col);
+ takes_ownership, false, body, line, col);
}
/**
@@ -342,35 +347,50 @@ Stmt *struct_stmt(Parser *parser, const char *name, bool is_public) {
int field_line = p_current(parser).line;
int field_col = p_current(parser).col;
+ // CRITICAL FIX: Check for ownership modifiers BEFORE parsing the field name
+ bool takes_ownership = false;
+ bool returns_ownership = false;
+
+ if (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP) {
+ returns_ownership = true;
+ p_advance(parser);
+ } else if (p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
+ takes_ownership = true;
+ p_advance(parser);
+ }
+
+ // Now parse the field name (after consuming any ownership modifiers)
char *field_name = get_name(parser);
- Stmt *field_function = NULL;
- Type *field_type = NULL;
+ if (!field_name) {
+ parser_error(parser, "Parse Error", __FILE__,
+ "Expected field or method name", field_line, field_col, 1);
+ return NULL;
+ }
+
p_advance(parser);
- // TODO: Add in a check to see if we have any function modifiers like
- // returns_ownership or takes_ownership
-
- bool takes_ownership, returns_ownership = false;
- while (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP ||
- p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
- if (p_current(parser).type_ == TOK_RETURNES_OWNERSHIP) {
- returns_ownership = true;
- p_advance(parser);
- } else if (p_current(parser).type_ == TOK_TAKES_OWNERSHIP) {
- takes_ownership = true;
- p_advance(parser);
- }
- }
+ Stmt *field_function = NULL;
+ Type *field_type = NULL;
// Method: field_name -> fn(...)
if (p_current(parser).type_ == TOK_RIGHT_ARROW) {
p_consume(parser, TOK_RIGHT_ARROW, "Expected '->' after field name");
- field_function = fn_stmt(parser, field_name, public_member, returns_ownership, takes_ownership);
+ field_function = fn_stmt(parser, field_name, public_member,
+ returns_ownership, takes_ownership);
} else {
// Data field: field_name: Type
p_consume(parser, TOK_COLON, "Expected ':' after field name");
field_type = parse_type(parser);
- p_advance(parser);
+
+ // Data fields shouldn't have ownership modifiers
+ if (takes_ownership || returns_ownership) {
+ parser_error(
+ parser, "Invalid Modifier", __FILE__,
+ "Ownership modifiers (#takes_ownership, #returns_ownership) "
+ "are only valid for methods, not data fields",
+ field_line, field_col, 1);
+ return NULL;
+ }
}
// Handle field separators
@@ -387,9 +407,15 @@ Stmt *struct_stmt(Parser *parser, const char *name, bool is_public) {
Stmt *field_decl = create_field_decl_stmt(
parser->arena, field_name, field_type, field_function, public_member,
field_line, field_col);
+
Stmt **slot = public_member ? (Stmt **)growable_array_push(&public_fields)
: (Stmt **)growable_array_push(&private_fields);
+ if (!slot) {
+ fprintf(stderr, "Failed to add field to struct.\n");
+ return NULL;
+ }
+
*slot = field_decl;
}
@@ -430,7 +456,6 @@ Stmt *var_stmt(Parser *parser, bool is_public) {
p_consume(parser, TOK_COLON, "Expected ':' after variable name");
Type *type = parse_type(parser);
- p_advance(parser); // Advance past the type token
if (p_current(parser).type_ != TOK_EQUAL) {
p_consume(parser, TOK_SEMICOLON,
@@ -662,7 +687,6 @@ Stmt *loop_init(Parser *parser, int line, int col) {
p_consume(parser, TOK_COLON, "Expected ':' after loop initializer");
Type *type = parse_type(parser);
- p_advance(parser); // Advance past the type token
p_consume(parser, TOK_EQUAL, "Expected '=' after loop initializer");
Expr *initializer = parse_expr(parser, BP_LOWEST);
@@ -1072,7 +1096,6 @@ Stmt *impl_stmt(Parser *parser) {
function_list_name);
return NULL;
}
- p_advance(parser);
char **name_identifier = (char **)growable_array_push(&function_name_list);
Type **type_specifier = (Type **)growable_array_push(&function_name_types);
diff --git a/src/parser/type.c b/src/parser/type.c
index eedd53a8..4021932a 100644
--- a/src/parser/type.c
+++ b/src/parser/type.c
@@ -5,7 +5,7 @@
// *Type
Type *pointer(Parser *parser) {
- // We already advanced past the '*' token
+ // parse_type() will handle advancing through the pointee type
Type *pointee_type = parse_type(parser);
if (!pointee_type) {
fprintf(stderr, "Expected type after '*'\n");
@@ -18,56 +18,117 @@ Type *pointer(Parser *parser) {
// [Type; Size]
Type *array_type(Parser *parser) {
+ int line = p_current(parser).line;
+ int col = p_current(parser).col;
+
Type *element_type = parse_type(parser);
- p_advance(parser); // Consume the element type
p_consume(parser, TOK_SEMICOLON, "Expected ';' after array element type");
Expr *size_expr = parse_expr(parser, BP_LOWEST);
- if (p_current(parser).type_ != TOK_RBRACKET) {
- parser_error(parser, "SyntaxError", "Unknown",
- "Expected ']' to close array type declaration",
- p_current(parser).line, p_current(parser).col,
- CURRENT_TOKEN_LENGTH(parser));
- return NULL; // Error, return NULL
- }
+ p_consume(parser, TOK_RBRACKET, "Expected ']' to close array type declaration");
+
+ return create_array_type(parser->arena, element_type, size_expr, line, col);
+}
- return create_array_type(parser->arena, element_type, size_expr,
- p_current(parser).line, p_current(parser).col);
+// Handle namespace::Type resolution
+// Returns a type and DOES advance past all consumed tokens
+Type *resolution_type(Parser *parser) {
+ int line = p_current(parser).line;
+ int col = p_current(parser).col;
+
+ // We start with an identifier (the namespace or first part)
+ char *first_name = get_name(parser);
+ p_advance(parser); // Consume the identifier
+
+ // Check if we have a resolution operator
+ if (p_current(parser).type_ != TOK_RESOLVE) {
+ // Just a simple identifier type, not a resolution
+ // We already advanced, so we're done
+ return create_basic_type(parser->arena, first_name, line, col);
+ }
+
+ // Build the resolution chain: namespace::name or namespace::sub::name
+ GrowableArray parts;
+ if (!growable_array_init(&parts, parser->arena, 4, sizeof(char *))) {
+ fprintf(stderr, "Failed to initialize resolution parts array\n");
+ return NULL;
+ }
+
+ // Add the first part (namespace)
+ char **slot = (char **)growable_array_push(&parts);
+ if (!slot) {
+ fprintf(stderr, "Out of memory while growing resolution parts\n");
+ return NULL;
+ }
+ *slot = first_name;
+
+ // Parse remaining parts separated by '::'
+ while (p_current(parser).type_ == TOK_RESOLVE) {
+ p_advance(parser); // Consume '::'
+
+ if (p_current(parser).type_ != TOK_IDENTIFIER) {
+ parser_error(parser, "SyntaxError", parser->file_path,
+ "Expected identifier after '::'",
+ p_current(parser).line, p_current(parser).col,
+ p_current(parser).length);
+ return NULL;
+ }
+
+ char *part = get_name(parser);
+ p_advance(parser); // Consume the identifier
+
+ slot = (char **)growable_array_push(&parts);
+ if (!slot) {
+ fprintf(stderr, "Out of memory while growing resolution parts\n");
+ return NULL;
+ }
+ *slot = part;
+ }
+
+ // Create a resolution type with the collected parts
+ return create_resolution_type(parser->arena, (char **)parts.data,
+ parts.count, line, col);
}
+// tnud() is responsible for parsing a type and ADVANCING past it
Type *tnud(Parser *parser) {
+ int line = p_current(parser).line;
+ int col = p_current(parser).col;
+
switch (p_current(parser).type_) {
case TOK_INT:
- return create_basic_type(parser->arena, "int", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "int", line, col);
case TOK_UINT:
- return create_basic_type(parser->arena, "uint", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "uint", line, col);
case TOK_DOUBLE:
- return create_basic_type(parser->arena, "double", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "double", line, col);
case TOK_FLOAT:
- return create_basic_type(parser->arena, "float", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "float", line, col);
case TOK_BOOL:
- return create_basic_type(parser->arena, "bool", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "bool", line, col);
case TOK_STRINGT:
- return create_basic_type(parser->arena, "str", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "str", line, col);
case TOK_VOID:
- return create_basic_type(parser->arena, "void", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "void", line, col);
case TOK_CHAR:
- return create_basic_type(parser->arena, "char", p_current(parser).line,
- p_current(parser).col);
+ p_advance(parser);
+ return create_basic_type(parser->arena, "char", line, col);
case TOK_STAR: // Pointer type
p_advance(parser); // Consume the '*' token
return pointer(parser);
case TOK_LBRACKET: // Array type
p_advance(parser); // Consume the '[' token
return array_type(parser);
+ case TOK_IDENTIFIER: // Could be simple type or namespace::Type
+ return resolution_type(parser); // This handles its own advancing
default:
fprintf(stderr, "Unexpected token in type: %d\n", p_current(parser).type_);
return NULL;
@@ -80,4 +141,4 @@ Type *tled(Parser *parser, Type *left, BindingPower bp) {
fprintf(stderr, "Parsing type led: %.*s\n", CURRENT_TOKEN_LENGTH(parser),
CURRENT_TOKEN_VALUE(parser));
return NULL; // No valid type found
-}
+}
\ No newline at end of file
diff --git a/src/typechecker/array.c b/src/typechecker/array.c
index c0064932..4879a34c 100644
--- a/src/typechecker/array.c
+++ b/src/typechecker/array.c
@@ -66,6 +66,7 @@ bool validate_array_type(AstNode *array_type, Scope *scope,
// Enhanced array bounds checking for constant indices
bool check_array_bounds(AstNode *array_type, AstNode *index_expr,
ArenaAllocator *arena) {
+ (void)arena;
if (!array_type || array_type->type != AST_TYPE_ARRAY || !index_expr) {
return true; // Can't check - assume valid
}
diff --git a/src/typechecker/expr.c b/src/typechecker/expr.c
index 8115c1df..e0f44923 100644
--- a/src/typechecker/expr.c
+++ b/src/typechecker/expr.c
@@ -105,6 +105,29 @@ AstNode *typecheck_binary_expr(AstNode *expr, Scope *scope,
return array;
}
+ // Bitwise operators (&, |, ^, <<, >>)
+ if (op == BINOP_BIT_AND || op == BINOP_BIT_OR || op == BINOP_BIT_XOR ||
+ op == BINOP_SHL || op == BINOP_SHR) {
+ if (!is_numeric_type(left_type)) {
+ tc_error_help(expr, "Type Error",
+ "Bitwise operations require integer operands",
+ "Left operand has non-integer type '%s'",
+ type_to_string(left_type, arena));
+ return NULL;
+ }
+
+ if (!is_numeric_type(right_type)) {
+ tc_error_help(expr, "Type Error",
+ "Bitwise operations require integer operands",
+ "Right operand has non-integer type '%s'",
+ type_to_string(right_type, arena));
+ return NULL;
+ }
+
+ // Bitwise operations return int
+ return create_basic_type(arena, "int", expr->line, expr->column);
+ }
+
tc_error(expr, "Unsupported Operation", "Unsupported binary operation");
return NULL;
}
@@ -140,6 +163,14 @@ AstNode *typecheck_unary_expr(AstNode *expr, Scope *scope,
return create_basic_type(arena, "bool", expr->line, expr->column);
}
+ if (op == UNOP_BIT_NOT) {
+ if (!is_numeric_type(operand_type)) {
+ tc_error(expr, "Type Error", "Bitwise NOT on non-integer type");
+ return NULL;
+ }
+ return operand_type; // Bitwise NOT does not change type
+ }
+
tc_error(expr, "Type Error", "Unsupported unary operation");
return NULL;
}
@@ -156,24 +187,20 @@ AstNode *typecheck_assignment_expr(AstNode *expr, Scope *scope,
// Check for pointer assignment that might transfer ownership
if (is_pointer_type(target_type) && is_pointer_type(value_type)) {
- // fprintf(stderr, "DEBUG: Detected pointer assignment\n");
-
// Extract variable names from both sides
const char *target_var =
extract_variable_name(expr->expr.assignment.target);
const char *source_var = extract_variable_name(expr->expr.assignment.value);
- // fprintf(stderr, "DEBUG: target_var='%s', source_var='%s'\n",
- // target_var ? target_var : "NULL", source_var ? source_var :
- // "NULL");
+ // CRITICAL FIX: Only track aliasing for direct variable-to-variable
+ // assignments NOT for struct member assignments like node1.next = node2
+ // Check if the target is a simple identifier (not a member access)
+ bool is_direct_assignment =
+ (expr->expr.assignment.target->type == AST_EXPR_IDENTIFIER);
- if (target_var && source_var) {
+ if (target_var && source_var && is_direct_assignment) {
StaticMemoryAnalyzer *analyzer = get_static_analyzer(scope);
- // fprintf(stderr, "DEBUG: Got analyzer: %p\n", (void *)analyzer);
if (analyzer) {
- // fprintf(stderr, "DEBUG: Calling track_alias('%s', '%s')\n",
- // target_var,
- // source_var);
static_memory_track_alias(analyzer, target_var, source_var);
}
}
@@ -192,6 +219,9 @@ AstNode *typecheck_assignment_expr(AstNode *expr, Scope *scope,
return target_type;
}
+// Complete typecheck_call_expr function with ownership tracking
+// Place this entire function in expr.c, replacing the existing one
+
AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
ArenaAllocator *arena) {
AstNode *callee = expr->expr.call.callee;
@@ -200,39 +230,26 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
Symbol *func_symbol = NULL;
const char *func_name = NULL;
- bool is_method_call = false; // Track if this is a method call with injected self
+ bool is_method_call = false;
if (callee->type == AST_EXPR_IDENTIFIER) {
- // Simple function call: func()
func_name = callee->expr.identifier.name;
func_symbol = scope_lookup(scope, func_name);
-
} else if (callee->type == AST_EXPR_MEMBER) {
-
- // Member function call: could be module::func() or obj.method()
const char *base_name = callee->expr.member.object->expr.identifier.name;
const char *member_name = callee->expr.member.member;
bool is_compiletime = callee->expr.member.is_compiletime;
if (is_compiletime) {
- // Compile-time member function call: module::func() or
- // Type::static_method()
func_symbol = lookup_qualified_symbol(scope, base_name, member_name);
func_name = member_name;
-
if (!func_symbol) {
tc_error(expr, "Compile-time Call Error",
"No compile-time callable '%s::%s' found", base_name,
member_name);
return NULL;
}
-
} else {
- // Runtime member function call: obj.method()
- // This should be for instance methods on struct objects
-
- // First, check if base_name is a known module (without calling
- // lookup_qualified_symbol)
bool is_module_access = false;
Scope *current = scope;
while (current) {
@@ -259,7 +276,6 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
return NULL;
}
- // Check if base_name is a variable with a struct type
Symbol *base_symbol = scope_lookup(scope, base_name);
if (!base_symbol) {
tc_error(expr, "Runtime Access Error", "Undefined identifier '%s'",
@@ -274,50 +290,42 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
}
AstNode *base_type = base_symbol->type;
- // Handle pointer dereference: if we have a pointer to struct,
- // automatically dereference it
if (base_type->type == AST_TYPE_POINTER) {
AstNode *pointee = base_type->type_data.pointer.pointee_type;
if (pointee && pointee->type == AST_TYPE_BASIC) {
- // Check if the pointee is a struct type name
Symbol *struct_symbol =
scope_lookup(scope, pointee->type_data.basic.name);
if (struct_symbol && struct_symbol->type &&
struct_symbol->type->type == AST_TYPE_STRUCT) {
- base_type = struct_symbol->type; // Use the struct type
+ base_type = struct_symbol->type;
}
} else if (pointee && pointee->type == AST_TYPE_STRUCT) {
base_type = pointee;
}
}
- // Handle case where base_type is a basic type that references a struct
if (base_type->type == AST_TYPE_BASIC) {
- // Look up the struct type by name
Symbol *struct_symbol =
scope_lookup(scope, base_type->type_data.basic.name);
if (struct_symbol && struct_symbol->type &&
struct_symbol->type->type == AST_TYPE_STRUCT) {
- base_type = struct_symbol->type; // Use the actual struct type
+ base_type = struct_symbol->type;
}
}
- // Now check if it's a struct type and get the member
if (base_type->type == AST_TYPE_STRUCT) {
AstNode *member_type = get_struct_member_type(base_type, member_name);
if (member_type && member_type->type == AST_TYPE_FUNCTION) {
- // This is a method call on a struct instance
func_symbol = arena_alloc(arena, sizeof(Symbol), alignof(Symbol));
if (!func_symbol) {
- tc_error(expr, "Memory Error", "Failed to allocate symbol for method '%s'", member_name);
+ tc_error(expr, "Memory Error",
+ "Failed to allocate symbol for method '%s'", member_name);
return NULL;
}
-
+
func_symbol->name = member_name;
-
func_symbol->type = member_type;
-
func_symbol->is_public = true;
func_symbol->is_mutable = false;
func_symbol->scope_depth = 0;
@@ -325,54 +333,44 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
func_symbol->takes_ownership = false;
func_name = member_name;
- // CRITICAL: For method calls, we need to inject 'self' as the first argument
- // Verify we have a valid object before injectin
if (!callee->expr.member.object) {
tc_error(expr, "Internal Error", "Method call has no object");
return NULL;
}
- // Create a new arguments array with 'self' prepended
size_t new_arg_count = arg_count + 1;
- AstNode **new_arguments = arena_alloc(arena, new_arg_count * sizeof(AstNode *),
- alignof(AstNode *));
-
+ AstNode **new_arguments = arena_alloc(
+ arena, new_arg_count * sizeof(AstNode *), alignof(AstNode *));
if (!new_arguments) {
- tc_error(expr, "Memory Error", "Failed to allocate arguments array for method call");
+ tc_error(expr, "Memory Error",
+ "Failed to allocate arguments array for method call");
return NULL;
}
-
- // CRITICAL: Check if we need to take the address of the object
- // The method expects a pointer to the struct (self is Person*)
- // But obj.method() gives us the struct value (Person)
- // We need to automatically inject &obj instead of just obj
-
- // Get the method's parameter types to check what self expects
+
if (!member_type || member_type->type != AST_TYPE_FUNCTION) {
tc_error(expr, "Internal Error", "Method type is not a function");
return NULL;
}
-
- AstNode **method_param_types = member_type->type_data.function.param_types;
- if (!method_param_types || member_type->type_data.function.param_count == 0) {
- tc_error(expr, "Internal Error", "Method has no parameters (missing self?)");
+
+ AstNode **method_param_types =
+ member_type->type_data.function.param_types;
+ if (!method_param_types ||
+ member_type->type_data.function.param_count == 0) {
+ tc_error(expr, "Internal Error",
+ "Method has no parameters (missing self?)");
return NULL;
}
-
- AstNode *self_param_type = method_param_types[0]; // First param is always self
+
+ AstNode *self_param_type = method_param_types[0];
AstNode *object_node = callee->expr.member.object;
-
- // Check what the method expects for self
bool expects_pointer = (self_param_type->type == AST_TYPE_POINTER);
-
- // Check what we have
Symbol *obj_symbol = scope_lookup(scope, base_name);
- bool have_pointer = (obj_symbol && obj_symbol->type &&
+ bool have_pointer = (obj_symbol && obj_symbol->type &&
obj_symbol->type->type == AST_TYPE_POINTER);
-
+
if (expects_pointer && !have_pointer) {
- // Method expects pointer but we have value - take address
- AstNode *addr_expr = arena_alloc(arena, sizeof(AstNode), alignof(AstNode));
+ AstNode *addr_expr =
+ arena_alloc(arena, sizeof(AstNode), alignof(AstNode));
addr_expr->type = AST_EXPR_ADDR;
addr_expr->category = Node_Category_EXPR;
addr_expr->line = object_node->line;
@@ -380,23 +378,18 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
addr_expr->expr.addr.object = object_node;
new_arguments[0] = addr_expr;
} else {
- // Either method expects value, or we already have pointer
new_arguments[0] = object_node;
}
-
- // Copy the rest of the user-provided arguments
+
for (size_t i = 0; i < arg_count; i++) {
new_arguments[i + 1] = arguments[i];
}
-
- // Update the arguments and count for the rest of the function
+
arguments = new_arguments;
arg_count = new_arg_count;
- is_method_call = true; // Mark that we injected self
-
+ is_method_call = true;
expr->expr.call.args = new_arguments;
expr->expr.call.arg_count = new_arg_count;
-
} else if (member_type) {
tc_error(expr, "Runtime Call Error",
"Cannot call non-function member '%s' on struct '%s'",
@@ -408,19 +401,17 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
return NULL;
}
} else {
- // Base is not a struct - runtime access is invalid
tc_error(expr, "Runtime Call Error",
"Cannot use runtime access '.' on non-struct type '%s'",
type_to_string(base_type, arena));
return NULL;
}
}
-
} else {
tc_error(expr, "Call Error", "Unsupported callee type for function call");
return NULL;
}
-
+
if (!func_symbol) {
tc_error(expr, "Call Error", "Undefined function '%s'",
func_name ? func_name : "unknown");
@@ -428,7 +419,6 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
}
AstNode *func_type = func_symbol->type;
-
if (func_type->type != AST_TYPE_FUNCTION) {
tc_error(expr, "Call Error", "'%s' is not a function", func_name);
return NULL;
@@ -444,51 +434,86 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
return NULL;
}
- // For method calls, start from index 1 to skip the injected 'self' parameter
size_t start_index = is_method_call ? 1 : 0;
-
for (size_t i = start_index; i < arg_count; i++) {
- // Validate the argument pointer before dereferencing
- if (!arguments) {
- tc_error(expr, "Call Error",
- "Arguments array is NULL for call to '%s'", func_name);
+ if (!arguments || !arguments[i]) {
+ tc_error(expr, "Call Error", "Argument %zu in call to '%s' is NULL",
+ i + 1, func_name);
return NULL;
}
-
- if (!arguments[i]) {
- tc_error(expr, "Call Error",
- "Argument %zu in call to '%s' is NULL", i + 1, func_name);
- return NULL;
- }
-
+
AstNode *arg_type = typecheck_expression(arguments[i], scope, arena);
-
if (!arg_type) {
tc_error(expr, "Call Error",
"Failed to type-check argument %zu in call to '%s'", i + 1,
func_name);
return NULL;
}
-
- // Validate param_types array
+
if (!param_types || !param_types[i]) {
tc_error(expr, "Call Error",
"Parameter %zu type in function '%s' is NULL", i + 1, func_name);
return NULL;
}
-
+
TypeMatchResult match = types_match(param_types[i], arg_type);
- if (match == TYPE_MATCH_NONE) {
- fprintf(stderr, "DEBUG: Type mismatch - NONE - about to report error\n");
+ // NEW: Special handling for array argument padding
+ // If the parameter expects a larger array than provided, allow it with padding
+ if (match == TYPE_MATCH_NONE &&
+ param_types[i]->type == AST_TYPE_ARRAY &&
+ arg_type->type == AST_TYPE_ARRAY) {
+
+ // Check if element types match
+ TypeMatchResult element_match =
+ types_match(param_types[i]->type_data.array.element_type,
+ arg_type->type_data.array.element_type);
+ if (element_match != TYPE_MATCH_NONE) {
+ // Element types match, check sizes
+ AstNode *param_size = param_types[i]->type_data.array.size;
+ AstNode *arg_size = arg_type->type_data.array.size;
+
+ if (param_size && arg_size &&
+ param_size->type == AST_EXPR_LITERAL &&
+ arg_size->type == AST_EXPR_LITERAL &&
+ param_size->expr.literal.lit_type == LITERAL_INT &&
+ arg_size->expr.literal.lit_type == LITERAL_INT) {
+
+ long long param_size_val = param_size->expr.literal.value.int_val;
+ long long arg_size_val = arg_size->expr.literal.value.int_val;
+
+ // If argument array is smaller than parameter, allow it with padding
+ if (arg_size_val <= param_size_val) {
+ // Set a flag on the argument node to indicate it needs padding
+ // This will be handled by the code generator
+ if (arguments[i]->type == AST_EXPR_ARRAY) {
+ // Mark this array for padding to param_size_val elements
+ // Store the target size for the code generator to use
+ arguments[i]->expr.array.target_size = (size_t)param_size_val;
+ }
+
+ // Continue to next argument - this is valid
+ continue;
+ } else {
+ // Argument array is LARGER than parameter - this is an error
+ tc_error_help(
+ expr, "Array Size Mismatch",
+ "Cannot pass larger array to function expecting smaller array",
+ "Argument %zu: parameter expects array of size %lld, got size %lld",
+ i + 1, param_size_val, arg_size_val);
+ return NULL;
+ }
+ }
+ }
+ }
+
+ if (match == TYPE_MATCH_NONE) {
const char *param_str = type_to_string(param_types[i], arena);
const char *arg_str = type_to_string(arg_type, arena);
-
- tc_error_help(expr, "Call Error",
- "Argument %zu to function '%s' has wrong type.",
- "Expected '%s', got '%s'", i + 1, func_name,
- param_str, arg_str);
+ tc_error_help(
+ expr, "Call Error", "Argument %zu to function '%s' has wrong type.",
+ "Expected '%s', got '%s'", i + 1, func_name, param_str, arg_str);
return NULL;
}
}
@@ -508,7 +533,33 @@ AstNode *typecheck_call_expr(AstNode *expr, Scope *scope,
}
}
}
-
+
+ // ADDITION: For method calls with #takes_ownership, track ownership transfer
+ // of 'self'
+ if (is_method_call && func_symbol->takes_ownership) {
+ if (arguments[0]) {
+ const char *self_var = NULL;
+
+ // Check if we injected &obj for the self parameter
+ if (arguments[0]->type == AST_EXPR_ADDR) {
+ // Extract the object from &obj
+ self_var = extract_variable_name(arguments[0]->expr.addr.object);
+ } else {
+ // Direct parameter (less common, but handle it)
+ self_var = extract_variable_name(arguments[0]);
+ }
+
+ if (self_var) {
+ StaticMemoryAnalyzer *analyzer = get_static_analyzer(scope);
+ if (analyzer) {
+ const char *current_func = get_current_function_name(scope);
+ // Method takes ownership of self - mark as freed
+ static_memory_track_free(analyzer, self_var, current_func);
+ }
+ }
+ }
+ }
+
return return_type;
}
@@ -598,130 +649,266 @@ AstNode *typecheck_index_expr(AstNode *expr, Scope *scope,
AstNode *typecheck_member_expr(AstNode *expr, Scope *scope,
ArenaAllocator *arena) {
- const char *base_name = expr->expr.member.object->expr.identifier.name;
+ // Get the base object and member name
+ AstNode *base_object = expr->expr.member.object;
const char *member_name = expr->expr.member.member;
bool is_compiletime = expr->expr.member.is_compiletime;
if (is_compiletime) {
// Compile-time access (::) - for modules, enums, static members
- // First try module-qualified symbol lookup
- Symbol *module_symbol =
- lookup_qualified_symbol(scope, base_name, member_name);
- if (module_symbol) {
- return module_symbol->type;
- }
+ // CRITICAL FIX: Handle chained access (e.g., ast::ExprKind::EXPR_NUMBER)
+ // First, resolve the base to its type
+ AstNode *base_type = NULL;
+ const char *base_name = NULL;
- // Try enum-style lookup (EnumName::Member)
- size_t qualified_len = strlen(base_name) + strlen(member_name) + 2;
- char *qualified_name = arena_alloc(arena, qualified_len, 1);
- snprintf(qualified_name, qualified_len, "%s.%s", base_name, member_name);
+ if (base_object->type == AST_EXPR_IDENTIFIER) {
+ // Simple case: module::symbol or EnumType::Member
+ base_name = base_object->expr.identifier.name;
- Symbol *member_symbol = scope_lookup(scope, qualified_name);
- if (member_symbol) {
- return member_symbol->type;
- }
+ // First try module-qualified symbol lookup
+ Symbol *module_symbol =
+ lookup_qualified_symbol(scope, base_name, member_name);
+ if (module_symbol) {
+ return module_symbol->type;
+ }
+
+ // Try direct symbol lookup (for enum types in current scope)
+ Symbol *base_symbol = scope_lookup(scope, base_name);
+ if (base_symbol && base_symbol->type) {
+ base_type = base_symbol->type;
+ // base_name already set above
+ } else {
+ // Check if it's an undefined module
+ bool is_module = false;
+ Scope *current = scope;
+ while (current && !is_module) {
+ for (size_t i = 0; i < current->imported_modules.count; i++) {
+ ModuleImport *import =
+ (ModuleImport *)((char *)current->imported_modules.data +
+ i * sizeof(ModuleImport));
+ if (strcmp(import->alias, base_name) == 0) {
+ is_module = true;
+ break;
+ }
+ }
+ current = current->parent;
+ }
- // Error handling for compile-time access
- Symbol *base_symbol = scope_lookup(scope, base_name);
- if (!base_symbol) {
- // Check if it's an unknown module
- for (size_t i = 0; i < scope->imported_modules.count; i++) {
- ModuleImport *import =
- (ModuleImport *)((char *)scope->imported_modules.data +
- i * sizeof(ModuleImport));
- if (strcmp(import->alias, base_name) == 0) {
+ if (is_module) {
tc_error(expr, "Compile-time Access Error",
"Module '%s' has no exported symbol '%s'", base_name,
member_name);
- return NULL;
+ } else {
+ tc_error(expr, "Compile-time Access Error",
+ "Undefined identifier '%s' in compile-time access",
+ base_name);
}
+ return NULL;
}
- tc_error(expr, "Compile-time Access Error",
- "Undefined identifier '%s' in compile-time access", base_name);
- } else {
- // Base exists but member doesn't - check what kind of symbol it is
- if (base_symbol->type && base_symbol->type->type == AST_TYPE_BASIC) {
- // Could be an enum type
+
+ } else if (base_object->type == AST_EXPR_MEMBER) {
+ // Chained case: ast::ExprKind::EXPR_NUMBER
+ // First resolve the inner member expression recursively
+ base_type = typecheck_member_expr(base_object, scope, arena);
+ if (!base_type) {
tc_error(expr, "Compile-time Access Error",
- "Type '%s' has no compile-time member '%s'", base_name,
- member_name);
+ "Failed to resolve base expression in chained access");
+ return NULL;
+ }
+
+ // CRITICAL FIX: Extract the name from the resolved type
+ // The resolved type should be a basic type (enum type name)
+ if (base_type->type == AST_TYPE_BASIC) {
+ base_name = base_type->type_data.basic.name;
+ } else if (base_type->type == AST_TYPE_STRUCT) {
+ base_name = base_type->type_data.struct_type.name;
} else {
tc_error(expr, "Compile-time Access Error",
- "Cannot use compile-time access '::' on runtime value '%s'",
- base_name);
+ "Cannot use compile-time access '::' on complex type '%s'",
+ type_to_string(base_type, arena));
+ return NULL;
+ }
+
+ // IMPORTANT: Now that we have base_name, we need to look up the qualified
+ // symbol Build the qualified name: base_name.member_name
+ size_t qualified_len = strlen(base_name) + strlen(member_name) + 2;
+ char *qualified_name = arena_alloc(arena, qualified_len, 1);
+ snprintf(qualified_name, qualified_len, "%s.%s", base_name, member_name);
+
+ // Look up this qualified symbol (e.g., "ExprKind.EXPR_NUMBER")
+ Symbol *member_symbol = scope_lookup(scope, qualified_name);
+
+ if (!member_symbol) {
+ // Try looking in imported modules
+ Scope *current = scope;
+ while (current && !member_symbol) {
+ for (size_t i = 0; i < current->imported_modules.count; i++) {
+ ModuleImport *import =
+ (ModuleImport *)((char *)current->imported_modules.data +
+ i * sizeof(ModuleImport));
+
+ member_symbol = scope_lookup_current_only_with_visibility(
+ import->module_scope, qualified_name, scope);
+
+ if (member_symbol) {
+ break;
+ }
+ }
+ current = current->parent;
+ }
+ }
+
+ if (member_symbol) {
+ return member_symbol->type;
+ }
+
+ tc_error(expr, "Compile-time Access Error",
+ "Type '%s' has no compile-time member '%s'", base_name,
+ member_name);
+ return NULL;
+
+ } else {
+ tc_error(expr, "Compile-time Access Error",
+ "Compile-time access '::' requires an identifier or member "
+ "expression on the left");
+ return NULL;
+ }
+
+ // Now we have base_name and member_name - look up the qualified symbol
+ // Try enum-style lookup (EnumName::Member or Module::Type)
+ size_t qualified_len = strlen(base_name) + strlen(member_name) + 2;
+ char *qualified_name = arena_alloc(arena, qualified_len, 1);
+ snprintf(qualified_name, qualified_len, "%s.%s", base_name, member_name);
+
+ // Look up the qualified symbol with visibility rules
+ Symbol *member_symbol = scope_lookup(scope, qualified_name);
+
+ if (!member_symbol) {
+ // Try looking in imported modules explicitly
+ Scope *current = scope;
+ while (current && !member_symbol) {
+ for (size_t i = 0; i < current->imported_modules.count; i++) {
+ ModuleImport *import =
+ (ModuleImport *)((char *)current->imported_modules.data +
+ i * sizeof(ModuleImport));
+
+ // Look in the imported module's scope
+ member_symbol = scope_lookup_current_only_with_visibility(
+ import->module_scope, qualified_name, scope);
+
+ if (member_symbol) {
+ break;
+ }
+ }
+ current = current->parent;
}
}
+
+ if (member_symbol) {
+ return member_symbol->type;
+ }
+
+ // Error handling - symbol not found
+ tc_error(expr, "Compile-time Access Error",
+ "Type '%s' has no compile-time member '%s'", base_name,
+ member_name);
return NULL;
} else {
// Runtime access (.) - for struct members, instance data
- Symbol *base_symbol = scope_lookup(scope, base_name);
- if (!base_symbol) {
- tc_error(expr, "Runtime Access Error", "Undefined identifier '%s'",
- base_name);
- return NULL;
- }
-
- if (!base_symbol->type) {
+ // CRITICAL FIX: Typecheck the base expression first
+ // This handles complex expressions like lex.list[i]
+ AstNode *base_type = typecheck_expression(base_object, scope, arena);
+ if (!base_type) {
tc_error(expr, "Runtime Access Error",
- "Symbol '%s' has no type information", base_name);
+ "Failed to determine type of base expression");
return NULL;
}
- AstNode *base_type = base_symbol->type;
-
- // printf("DEBUG: Runtime member access - base '%s' has type %p (category
- // %d, "
- // "type %d)\n",
- // base_name, (void *)base_type, base_type->category,
- // base_type->type);
-
// Handle pointer dereference: if we have a pointer to struct, automatically
// dereference it
if (base_type->type == AST_TYPE_POINTER) {
- // printf("DEBUG: Base type is pointer, checking pointee\n");
AstNode *pointee = base_type->type_data.pointer.pointee_type;
if (pointee && pointee->type == AST_TYPE_BASIC) {
- // printf("DEBUG: Pointee is basic type: '%s'\n",
- // pointee->type_data.basic.name);
- // Check if the pointee is a struct type name
- Symbol *struct_symbol =
- scope_lookup(scope, pointee->type_data.basic.name);
+ const char *type_name = pointee->type_data.basic.name;
+
+ // First try direct lookup
+ Symbol *struct_symbol = scope_lookup(scope, type_name);
+
+ // If not found, try looking in imported modules
+ if (!struct_symbol) {
+ // Check each imported module for this type
+ Scope *current = scope;
+ while (current && !struct_symbol) {
+ for (size_t i = 0; i < current->imported_modules.count; i++) {
+ ModuleImport *import =
+ (ModuleImport *)((char *)current->imported_modules.data +
+ i * sizeof(ModuleImport));
+
+ // Try to find the type in the imported module's scope
+ struct_symbol = scope_lookup_current_only_with_visibility(
+ import->module_scope, type_name, scope);
+
+ if (struct_symbol && struct_symbol->type &&
+ struct_symbol->type->type == AST_TYPE_STRUCT) {
+ break;
+ }
+ struct_symbol = NULL;
+ }
+ current = current->parent;
+ }
+ }
+
if (struct_symbol && struct_symbol->type &&
struct_symbol->type->type == AST_TYPE_STRUCT) {
- // printf("DEBUG: Found struct type through pointer\n");
base_type = struct_symbol->type; // Use the struct type
}
} else if (pointee && pointee->type == AST_TYPE_STRUCT) {
- // printf("DEBUG: Pointee is direct struct type\n");
base_type = pointee;
}
}
// Handle case where base_type is a basic type that references a struct
if (base_type->type == AST_TYPE_BASIC) {
- // printf("DEBUG: Base type is basic: '%s'\n",
- // base_type->type_data.basic.name);
- // Look up the struct type by name
- Symbol *struct_symbol =
- scope_lookup(scope, base_type->type_data.basic.name);
+ const char *type_name = base_type->type_data.basic.name;
+
+ // First try direct lookup
+ Symbol *struct_symbol = scope_lookup(scope, type_name);
+
+ // If not found, try looking in imported modules
+ if (!struct_symbol) {
+ Scope *current = scope;
+ while (current && !struct_symbol) {
+ for (size_t i = 0; i < current->imported_modules.count; i++) {
+ ModuleImport *import =
+ (ModuleImport *)((char *)current->imported_modules.data +
+ i * sizeof(ModuleImport));
+
+ struct_symbol = scope_lookup_current_only_with_visibility(
+ import->module_scope, type_name, scope);
+
+ if (struct_symbol && struct_symbol->type &&
+ struct_symbol->type->type == AST_TYPE_STRUCT) {
+ break;
+ }
+ struct_symbol = NULL;
+ }
+ current = current->parent;
+ }
+ }
+
if (struct_symbol && struct_symbol->type &&
struct_symbol->type->type == AST_TYPE_STRUCT) {
- // printf("DEBUG: Found struct type by name lookup\n");
base_type = struct_symbol->type; // Use the actual struct type
}
}
// Now check if it's a struct type and get the member
if (base_type->type == AST_TYPE_STRUCT) {
- // printf("DEBUG: Checking struct '%s' for member '%s'\n",
- // base_type->type_data.struct_type.name, member_name);
-
AstNode *member_type = get_struct_member_type(base_type, member_name);
if (member_type) {
- // printf("DEBUG: Found member type: %p\n", (void *)member_type);
return member_type;
}
@@ -732,29 +919,9 @@ AstNode *typecheck_member_expr(AstNode *expr, Scope *scope,
} else {
// Base is not a struct - runtime access is invalid
- // printf("DEBUG: Base type is not a struct (type=%d)\n",
- // base_type->type);
-
- // Check if user should have used compile-time access instead
- if (base_symbol->type && base_symbol->type->type == AST_TYPE_BASIC) {
- // Could be an enum or module
- Symbol *potential_enum =
- scope_lookup(scope, base_symbol->type->type_data.basic.name);
- if (potential_enum) {
- tc_error_help(expr, "Access Method Error",
- "Use '::' for compile-time access to enum members",
- "Cannot use runtime access '.' on type '%s' - did you "
- "mean '%s::%s'?",
- base_name, base_name, member_name);
- } else {
- tc_error(expr, "Runtime Access Error",
- "Cannot use runtime access '.' on non-struct type '%s'",
- base_name);
- }
- } else {
- tc_error(expr, "Runtime Access Error",
- "Type '%s' does not support member access", base_name);
- }
+ tc_error(expr, "Runtime Access Error",
+ "Cannot use runtime access '.' on non-struct type '%s'",
+ type_to_string(base_type, arena));
return NULL;
}
}
@@ -847,38 +1014,49 @@ AstNode *typecheck_free_expr(AstNode *expr, Scope *scope,
}
StaticMemoryAnalyzer *analyzer = get_static_analyzer(scope);
- if (analyzer->skip_memory_tracking) {
- // We're in a defer - add to the FUNCTION scope's deferred list
+
+ // Get the variable name early
+ const char *var_name = NULL;
+ if (expr->expr.free.ptr->type == AST_EXPR_IDENTIFIER) {
+ var_name = expr->expr.free.ptr->expr.identifier.name;
+ }
+
+ if (analyzer && analyzer->skip_memory_tracking && var_name) {
+ // We're in a defer block - find the containing FUNCTION scope
Scope *func_scope = scope;
while (func_scope && !func_scope->is_function_scope) {
func_scope = func_scope->parent;
}
if (func_scope) {
- const char **slot =
- (const char **)growable_array_push(&func_scope->deferred_frees);
- if (slot) {
- if (expr->expr.free.ptr->type == AST_EXPR_IDENTIFIER) {
- const char *var_name = expr->expr.free.ptr->expr.identifier.name;
+ // Check if already in deferred list (avoid duplicates)
+ bool already_deferred = false;
+ for (size_t i = 0; i < func_scope->deferred_frees.count; i++) {
+ const char **existing =
+ (const char **)((char *)func_scope->deferred_frees.data +
+ i * sizeof(const char *));
+ if (*existing && strcmp(*existing, var_name) == 0) {
+ already_deferred = true;
+ break;
+ }
+ }
+
+ if (!already_deferred) {
+ const char **slot =
+ (const char **)growable_array_push(&func_scope->deferred_frees);
+ if (slot) {
*slot = var_name;
- } else {
- *slot = NULL; // Non-identifier, cannot track
}
}
}
- } else {
- // Normal free - track in static analyzer
- if (expr->expr.free.ptr->type == AST_EXPR_IDENTIFIER) {
- const char *var_name = expr->expr.free.ptr->expr.identifier.name;
- static_memory_track_free(analyzer, var_name, NULL);
- // Error already reported if use-after-free detected, continue
- // typechecking
- }
+ } else if (analyzer && var_name) {
+ // Normal free - always track it
+ const char *func_name = get_current_function_name(scope);
+ static_memory_track_free(analyzer, var_name, func_name);
}
return create_basic_type(arena, "void", expr->line, expr->column);
}
-
AstNode *typecheck_memcpy_expr(AstNode *expr, Scope *scope,
ArenaAllocator *arena) {
(void)expr;
@@ -1015,6 +1193,85 @@ AstNode *typecheck_system_expr(AstNode *expr, Scope *scope,
return create_basic_type(arena, "int", expr->line, expr->column);
}
+/**
+ * @brief Type check a syscall expression
+ *
+ * Syscall requires:
+ * - First argument: syscall number (int)
+ * - Remaining arguments: syscall parameters (typically int or pointer types)
+ *
+ * Returns: int (the return value from the syscall)
+ */
+AstNode *typecheck_syscall_expr(AstNode *expr, Scope *scope,
+ ArenaAllocator *arena) {
+ if (expr->type != AST_EXPR_SYSCALL) {
+ tc_error(expr, "Internal Error", "Expected syscall expression node");
+ return NULL;
+ }
+
+ AstNode **args = expr->expr.syscall.args;
+ size_t arg_count = expr->expr.syscall.count;
+
+ // Syscall requires at least one argument (the syscall number)
+ if (arg_count == 0) {
+ tc_error(expr, "Syscall Error",
+ "syscall() requires at least one argument (syscall number)");
+ return NULL;
+ }
+
+ // Type check the syscall number (first argument)
+ AstNode *syscall_num_type = typecheck_expression(args[0], scope, arena);
+ if (!syscall_num_type) {
+ tc_error(expr, "Syscall Error",
+ "Failed to determine type of syscall number");
+ return NULL;
+ }
+
+ // Verify syscall number is numeric (typically int)
+ if (!is_numeric_type(syscall_num_type)) {
+ tc_error_help(expr, "Syscall Number Type Error",
+ "The syscall number must be a numeric type (typically int)",
+ "Syscall number has type '%s', expected numeric type",
+ type_to_string(syscall_num_type, arena));
+ return NULL;
+ }
+
+ // Type check all remaining arguments
+ for (size_t i = 1; i < arg_count; i++) {
+ if (!args[i]) {
+ tc_error(expr, "Syscall Error", "Argument %zu is NULL", i + 1);
+ return NULL;
+ }
+
+ AstNode *arg_type = typecheck_expression(args[i], scope, arena);
+ if (!arg_type) {
+ tc_error(expr, "Syscall Error", "Failed to type-check argument %zu",
+ i + 1);
+ return NULL;
+ }
+
+ // Syscall arguments should typically be numeric or pointer types
+ // We allow any type but warn about non-standard types
+ bool is_valid_syscall_arg =
+ is_numeric_type(arg_type) || is_pointer_type(arg_type) ||
+ (arg_type->type == AST_TYPE_BASIC &&
+ strcmp(arg_type->type_data.basic.name, "void") == 0);
+
+ if (!is_valid_syscall_arg) {
+ tc_error_help(
+ expr, "Syscall Argument Type Warning",
+ "Syscall arguments are typically numeric or pointer types",
+ "Argument %zu has type '%s' which may not be valid for syscalls",
+ i + 1, type_to_string(arg_type, arena));
+ // Don't return false - this is a warning, not an error
+ }
+ }
+
+ // syscall() returns a long or int (platform dependent)
+ // For simplicity, we return int here
+ return create_basic_type(arena, "int", expr->line, expr->column);
+}
+
AstNode *typecheck_sizeof_expr(AstNode *expr, Scope *scope,
ArenaAllocator *arena) {
// sizeof always returns size_t (or int in simplified systems)
@@ -1063,7 +1320,20 @@ AstNode *typecheck_array_expr(AstNode *expr, Scope *scope,
// Check all remaining elements match the first element's type
for (size_t i = 1; i < element_count; i++) {
- AstNode *element_type = typecheck_expression(elements[i], scope, arena);
+ AstNode *element_type = NULL;
+
+ // CRITICAL FIX: If this element is an anonymous struct expression,
+ // pass the first element's type as expected_type to ensure they match
+ if (elements[i]->type == AST_EXPR_STRUCT &&
+ !elements[i]->expr.struct_expr.name) {
+ // Anonymous struct - use internal function with expected type
+ element_type = typecheck_struct_expr_internal(elements[i], scope, arena,
+ first_element_type);
+ } else {
+ // Regular expression - typecheck normally
+ element_type = typecheck_expression(elements[i], scope, arena);
+ }
+
if (!element_type) {
tc_error(expr, "Array Type Error",
"Failed to determine type of array element %zu", i);
@@ -1082,7 +1352,12 @@ AstNode *typecheck_array_expr(AstNode *expr, Scope *scope,
}
}
- // Create array size literal
+ // NEW: Check if this array is being used in a context with an expected size
+ // This happens during function call argument checking
+ // We'll set a flag on the array expression to indicate it should be padded
+
+ // For now, just create the array type with the actual element count
+ // The padding will be handled during code generation or in a separate pass
long long size_val = (long long)element_count;
AstNode *size_expr = create_literal_expr(arena, LITERAL_INT, &size_val,
expr->line, expr->column);
@@ -1092,3 +1367,239 @@ AstNode *typecheck_array_expr(AstNode *expr, Scope *scope,
expr->line, expr->column);
return array_type;
}
+
+AstNode *typecheck_struct_expr_internal(AstNode *expr, Scope *scope,
+ ArenaAllocator *arena,
+ AstNode *expected_type) {
+ if (expr->type != AST_EXPR_STRUCT) {
+ tc_error(expr, "Internal Error", "Expected struct expression node");
+ return NULL;
+ }
+
+ const char *struct_name = expr->expr.struct_expr.name;
+ char **field_names = expr->expr.struct_expr.field_names;
+ AstNode **field_values = expr->expr.struct_expr.field_value;
+ size_t field_count = expr->expr.struct_expr.field_count;
+
+ // Check for duplicate field names in the initializer
+ for (size_t i = 0; i < field_count; i++) {
+ for (size_t j = i + 1; j < field_count; j++) {
+ if (strcmp(field_names[i], field_names[j]) == 0) {
+ tc_error_help(expr, "Duplicate Field",
+ "Each field can only be initialized once",
+ "Field '%s' appears multiple times in struct initializer",
+ field_names[i]);
+ return NULL;
+ }
+ }
+ }
+
+ if (struct_name) {
+ // Named struct initialization: Point { x: 10, y: 20 }
+
+ // Look up the struct type directly by name
+ Symbol *struct_symbol = scope_lookup(scope, struct_name);
+ if (!struct_symbol) {
+ tc_error_id(expr, struct_name, "Undefined Type",
+ "Struct type '%s' not found", struct_name);
+ return NULL;
+ }
+
+ AstNode *struct_type = struct_symbol->type;
+ if (!struct_type) {
+ tc_error_id(expr, struct_name, "Type Error",
+ "Type '%s' has no type information", struct_name);
+ return NULL;
+ }
+
+ if (struct_type->type != AST_TYPE_STRUCT) {
+ tc_error_id(expr, struct_name, "Type Error",
+ "'%s' is not a struct type (it's a %s)", struct_name,
+ struct_type->type == AST_TYPE_BASIC ? "basic type"
+ : "other type");
+ return NULL;
+ }
+
+ // Validate each initialized field
+ for (size_t i = 0; i < field_count; i++) {
+ const char *field_name = field_names[i];
+ AstNode *field_value = field_values[i];
+
+ // Check if the field exists in the struct definition
+ AstNode *expected_field_type =
+ get_struct_member_type(struct_type, field_name);
+ if (!expected_field_type) {
+ tc_error_help(expr, "Unknown Field",
+ "Check the struct definition for valid field names",
+ "Struct '%s' has no field named '%s'", struct_name,
+ field_name);
+ return NULL;
+ }
+
+ // Don't allow initializing methods
+ if (expected_field_type->type == AST_TYPE_FUNCTION) {
+ tc_error_help(expr, "Invalid Field Initialization",
+ "Methods cannot be initialized in struct literals",
+ "Cannot initialize method '%s' in struct '%s'",
+ field_name, struct_name);
+ return NULL;
+ }
+
+ // Type check the field value
+ AstNode *actual_type = typecheck_expression(field_value, scope, arena);
+ if (!actual_type) {
+ tc_error(expr, "Type Error",
+ "Failed to determine type of value for field '%s'",
+ field_name);
+ return NULL;
+ }
+
+ // Check type compatibility
+ TypeMatchResult match = types_match(expected_field_type, actual_type);
+ if (match == TYPE_MATCH_NONE) {
+ tc_error_help(expr, "Field Type Mismatch",
+ "The value type must match the struct field type",
+ "Field '%s' expects type '%s', got '%s'", field_name,
+ type_to_string(expected_field_type, arena),
+ type_to_string(actual_type, arena));
+ return NULL;
+ }
+ }
+
+ // CRITICAL FIX: Return a basic type that references the struct name
+ // This matches how struct types are used in variable declarations
+ return create_basic_type(arena, struct_name, expr->line, expr->column);
+
+ } else {
+ // Anonymous struct initialization: { x: 10, y: 20 }
+
+ // If we have an expected type, try to match against it
+ if (expected_type) {
+ // Resolve the expected type to an actual struct type
+ AstNode *target_struct_type = expected_type;
+ const char *target_struct_name = NULL;
+
+ // If expected type is a basic type, resolve it to the struct
+ if (expected_type->type == AST_TYPE_BASIC) {
+ target_struct_name = expected_type->type_data.basic.name;
+ Symbol *struct_symbol = scope_lookup(scope, target_struct_name);
+
+ if (struct_symbol && struct_symbol->type &&
+ struct_symbol->type->type == AST_TYPE_STRUCT) {
+ target_struct_type = struct_symbol->type;
+ } else {
+ // Expected type is not a struct, fall through to create anonymous
+ target_struct_type = NULL;
+ }
+ } else if (expected_type->type != AST_TYPE_STRUCT) {
+ target_struct_type = NULL;
+ }
+
+ // If we successfully resolved to a struct type, validate against it
+ if (target_struct_type && target_struct_type->type == AST_TYPE_STRUCT) {
+ // Validate each field in the anonymous struct
+ for (size_t i = 0; i < field_count; i++) {
+ const char *field_name = field_names[i];
+ AstNode *field_value = field_values[i];
+
+ // Check if the field exists in the expected struct
+ AstNode *expected_field_type =
+ get_struct_member_type(target_struct_type, field_name);
+ if (!expected_field_type) {
+ tc_error_help(expr, "Unknown Field",
+ "Anonymous struct field does not match expected type",
+ "Type '%s' has no field named '%s'",
+ target_struct_name ? target_struct_name : "struct",
+ field_name);
+ return NULL;
+ }
+
+ // Don't allow initializing methods
+ if (expected_field_type->type == AST_TYPE_FUNCTION) {
+ tc_error_help(expr, "Invalid Field Initialization",
+ "Methods cannot be initialized in struct literals",
+ "Cannot initialize method '%s'", field_name);
+ return NULL;
+ }
+
+ // Type check the field value
+ AstNode *actual_type =
+ typecheck_expression(field_value, scope, arena);
+ if (!actual_type) {
+ tc_error(expr, "Type Error",
+ "Failed to determine type of value for field '%s'",
+ field_name);
+ return NULL;
+ }
+
+ // Check type compatibility
+ TypeMatchResult match = types_match(expected_field_type, actual_type);
+ if (match == TYPE_MATCH_NONE) {
+ tc_error_help(expr, "Field Type Mismatch",
+ "The value type must match the struct field type",
+ "Field '%s' expects type '%s', got '%s'", field_name,
+ type_to_string(expected_field_type, arena),
+ type_to_string(actual_type, arena));
+ return NULL;
+ }
+ }
+
+ // Return the expected type (as basic type reference)
+ if (target_struct_name) {
+ return create_basic_type(arena, target_struct_name, expr->line,
+ expr->column);
+ } else {
+ return expected_type;
+ }
+ }
+ }
+
+ // No expected type or couldn't resolve - create true anonymous struct
+ // Type check all field values
+ AstNode **field_types =
+ arena_alloc(arena, field_count * sizeof(AstNode *), alignof(AstNode *));
+ if (!field_types) {
+ tc_error(expr, "Memory Error",
+ "Failed to allocate memory for field types");
+ return NULL;
+ }
+
+ for (size_t i = 0; i < field_count; i++) {
+ AstNode *field_value = field_values[i];
+
+ AstNode *field_type = typecheck_expression(field_value, scope, arena);
+ if (!field_type) {
+ tc_error(expr, "Type Error",
+ "Failed to determine type of value for field '%s'",
+ field_names[i]);
+ return NULL;
+ }
+
+ field_types[i] = field_type;
+ }
+
+ // Create an anonymous struct type with the inferred field types
+ size_t anon_name_len = 50;
+ char *anon_name = arena_alloc(arena, anon_name_len, alignof(char));
+ snprintf(anon_name, anon_name_len, "__anon_struct_%zu_%zu", expr->line,
+ expr->column);
+
+ AstNode *anon_struct_type = create_struct_type(
+ arena, anon_name, field_types, (const char **)field_names, field_count,
+ expr->line, expr->column);
+
+ if (!anon_struct_type) {
+ tc_error(expr, "Type Creation Error",
+ "Failed to create anonymous struct type");
+ return NULL;
+ }
+
+ return anon_struct_type;
+ }
+}
+
+// Public wrapper that doesn't expose expected_type
+AstNode *typecheck_struct_expr(AstNode *expr, Scope *scope,
+ ArenaAllocator *arena) {
+ return typecheck_struct_expr_internal(expr, scope, arena, NULL);
+}
diff --git a/src/typechecker/lookup.c b/src/typechecker/lookup.c
index 042d0270..0b5bf97e 100644
--- a/src/typechecker/lookup.c
+++ b/src/typechecker/lookup.c
@@ -1,3 +1,4 @@
+#include
#include
#include "type.h"
@@ -22,6 +23,7 @@ bool typecheck_statement(AstNode *stmt, Scope *scope, ArenaAllocator *arena) {
return typecheck_if_decl(stmt, scope, arena);
case AST_STMT_BLOCK: {
Scope *block_scope = create_child_scope(scope, "block", arena);
+ stmt->stmt.block.scope = (void *)block_scope;
for (size_t i = 0; i < stmt->stmt.block.stmt_count; i++) {
if (!typecheck(stmt->stmt.block.statements[i], block_scope, arena,
@@ -30,6 +32,23 @@ bool typecheck_statement(AstNode *stmt, Scope *scope, ArenaAllocator *arena) {
}
}
+ if (block_scope->deferred_frees.count > 0) {
+ StaticMemoryAnalyzer *analyzer = get_static_analyzer(block_scope);
+ if (analyzer) {
+ const char *func_name = get_current_function_name(scope);
+
+ for (size_t i = 0; i < block_scope->deferred_frees.count; i++) {
+ const char **var_ptr =
+ (const char **)((char *)block_scope->deferred_frees.data +
+ i * sizeof(const char *));
+ if (*var_ptr) {
+ // Mark as freed when exiting this block
+ static_memory_track_free(analyzer, *var_ptr, func_name);
+ }
+ }
+ }
+ }
+
return true;
}
case AST_STMT_PRINT: {
@@ -48,14 +67,16 @@ bool typecheck_statement(AstNode *stmt, Scope *scope, ArenaAllocator *arena) {
if (analyzer) {
old_skip_state = analyzer->skip_memory_tracking;
- analyzer->skip_memory_tracking = true;
+ analyzer->skip_memory_tracking = true; // Enable defer mode
}
+ // IMPORTANT: Don't create a new scope for defer - use the SAME scope
+ // so that deferred_frees goes to the correct function scope
bool result =
typecheck_statement(stmt->stmt.defer_stmt.statement, scope, arena);
if (analyzer) {
- analyzer->skip_memory_tracking = old_skip_state;
+ analyzer->skip_memory_tracking = old_skip_state; // Restore state
}
return result;
@@ -131,6 +152,8 @@ AstNode *typecheck_expression(AstNode *expr, Scope *scope,
return typecheck_input_expr(expr, scope, arena);
case AST_EXPR_SYSTEM:
return typecheck_system_expr(expr, scope, arena);
+ case AST_EXPR_SYSCALL:
+ return typecheck_syscall_expr(expr, scope, arena);
case AST_EXPR_ALLOC:
return typecheck_alloc_expr(expr, scope, arena);
case AST_EXPR_FREE:
@@ -139,6 +162,8 @@ AstNode *typecheck_expression(AstNode *expr, Scope *scope,
return typecheck_memcpy_expr(expr, scope, arena);
case AST_EXPR_SIZEOF:
return typecheck_sizeof_expr(expr, scope, arena);
+ case AST_EXPR_STRUCT:
+ return typecheck_struct_expr(expr, scope, arena);
default:
tc_error(expr, "Unsupported Expression", "Unsupported expression type %d",
expr->type);
diff --git a/src/typechecker/module.c b/src/typechecker/module.c
index 3e64e58c..5d9b1912 100644
--- a/src/typechecker/module.c
+++ b/src/typechecker/module.c
@@ -178,7 +178,7 @@ bool process_module_in_order(const char *module_name, GrowableArray *dep_graph,
return false;
}
- // UPDATE ERROR CONTEXT FOR THIS MODULE - ADD THIS BLOCK
+ // UPDATE ERROR CONTEXT FOR THIS MODULE
g_tokens = module->preprocessor.module.tokens;
g_token_count = module->preprocessor.module.token_count;
g_file_path = module->preprocessor.module.file_path;
@@ -194,13 +194,39 @@ bool process_module_in_order(const char *module_name, GrowableArray *dep_graph,
AstNode **body = module->preprocessor.module.body;
int body_count = module->preprocessor.module.body_count;
- // Process all non-@use statements
+ // ===== PASS 1: Process forward declarations (prototypes) =====
for (int j = 0; j < body_count; j++) {
if (!body[j])
continue;
if (body[j]->type == AST_PREPROCESSOR_USE)
+ continue; // Already processed
+
+ // Process function prototypes
+ if (body[j]->type == AST_STMT_FUNCTION &&
+ body[j]->stmt.func_decl.forward_declared) {
+ if (!typecheck_func_decl(body[j], module_scope, arena)) {
+ tc_error(body[j], "Prototype Error",
+ "Failed to typecheck function prototype in module '%s'",
+ module_name);
+ return false;
+ }
+ }
+ }
+
+ // ===== PASS 2: Process all other declarations =====
+ for (int j = 0; j < body_count; j++) {
+ if (!body[j])
continue;
+ if (body[j]->type == AST_PREPROCESSOR_USE)
+ continue; // Already processed
+ // Skip prototypes (already processed)
+ if (body[j]->type == AST_STMT_FUNCTION &&
+ body[j]->stmt.func_decl.forward_declared) {
+ continue;
+ }
+
+ // Process everything else (including function implementations)
if (!typecheck(body[j], module_scope, arena, global_scope->config)) {
tc_error(body[j], "Module Error",
"Failed to typecheck statement in module '%s'", module_name);
@@ -208,17 +234,16 @@ bool process_module_in_order(const char *module_name, GrowableArray *dep_graph,
}
}
- // NEW: Store the module scope in the AST node so LSP can access it later
- module->preprocessor.module.scope = (void *)module_scope; // Cast to void*
-
- // fprintf(stderr, "[LSP] Stored scope with %zu symbols in module '%s'\n",
- // module_scope->symbols.count, module_name);
+ // Store the module scope in the AST node for LSP
+ module->preprocessor.module.scope = (void *)module_scope;
+ // Run memory analysis if enabled
StaticMemoryAnalyzer *analyzer = get_static_analyzer(module_scope);
- if (analyzer && g_tokens && g_token_count > 0 && g_file_path && global_scope->config->check_mem) {
+ if (analyzer && g_tokens && g_token_count > 0 && g_file_path &&
+ global_scope->config->check_mem) {
static_memory_check_and_report(analyzer, arena);
}
current_dep->processed = true;
return true;
-}
+}
\ No newline at end of file
diff --git a/src/typechecker/static_mem_tracker.c b/src/typechecker/static_mem_tracker.c
index 86fee745..bd6a0049 100644
--- a/src/typechecker/static_mem_tracker.c
+++ b/src/typechecker/static_mem_tracker.c
@@ -13,9 +13,10 @@ void static_memory_analyzer_init(StaticMemoryAnalyzer *analyzer,
void static_memory_track_alloc(StaticMemoryAnalyzer *analyzer, size_t line,
size_t column, const char *var_name,
- const char *function_name,
- Token *tokens, size_t token_count,
- const char *file_path) {
+ const char *function_name, Token *tokens,
+ size_t token_count, const char *file_path) {
+ (void)tokens;
+ (void)token_count;
if (!var_name || strcmp(var_name, "anonymous") == 0) {
return;
}
@@ -30,12 +31,12 @@ void static_memory_track_alloc(StaticMemoryAnalyzer *analyzer, size_t line,
alloc->free_count = 0;
alloc->use_after_free_count = 0;
alloc->reported = false;
- alloc->function_name = function_name
- ? arena_strdup(analyzer->arena, function_name)
- : NULL;
-
- alloc->file_path = file_path ? arena_strdup(analyzer->arena, file_path) : NULL;
-
+ alloc->function_name =
+ function_name ? arena_strdup(analyzer->arena, function_name) : NULL;
+
+ alloc->file_path =
+ file_path ? arena_strdup(analyzer->arena, file_path) : NULL;
+
growable_array_init(&alloc->aliases, analyzer->arena, 4, sizeof(char *));
}
}
@@ -57,11 +58,10 @@ static StaticAllocation *find_allocation_by_name(StaticMemoryAnalyzer *analyzer,
// If we're tracking function names, ensure they match
if (alloc->function_name && current_function) {
if (strcmp(alloc->function_name, current_function) == 0) {
- return alloc;
+ return alloc; // Return first match in this function
}
- } else {
- // Legacy behavior: match by name only
- return alloc;
+ } else if (!alloc->function_name && !current_function) {
+ return alloc; // Global scope match
}
}
@@ -75,7 +75,7 @@ static StaticAllocation *find_allocation_by_name(StaticMemoryAnalyzer *analyzer,
if (strcmp(alloc->function_name, current_function) == 0) {
return alloc;
}
- } else {
+ } else if (!alloc->function_name && !current_function) {
return alloc;
}
}
@@ -115,6 +115,8 @@ bool static_memory_check_use_after_free(StaticMemoryAnalyzer *analyzer,
Token *tokens, int token_count,
const char *file_path,
const char *function_name) {
+ (void)tokens;
+ (void)token_count;
if (!var_name)
return true;
@@ -219,20 +221,22 @@ int static_memory_check_and_report(StaticMemoryAnalyzer *analyzer,
if (alloc->free_count > 1) {
// Double free - need to re-read the file
const char *source = read_file(alloc->file_path);
- if (!source) continue;
-
+ if (!source)
+ continue;
+
Lexer temp_lexer;
init_lexer(&temp_lexer, source, arena);
-
+
GrowableArray temp_tokens;
growable_array_init(&temp_tokens, arena, 100, sizeof(Token));
-
+
Token tk;
while ((tk = next_token(&temp_lexer)).type_ != TOK_EOF) {
Token *slot = (Token *)growable_array_push(&temp_tokens);
- if (slot) *slot = tk;
+ if (slot)
+ *slot = tk;
}
-
+
ErrorInformation error = {0};
error.error_type = "Double Free";
error.file_path = alloc->file_path;
@@ -247,30 +251,32 @@ int static_memory_check_and_report(StaticMemoryAnalyzer *analyzer,
"Variable '%s' freed %d times (should only be freed once)",
alloc->variable_name, alloc->free_count);
error.message = message;
- error.line_text = generate_line(arena, (Token *)temp_tokens.data,
- temp_tokens.count, error.line);
-
+ error.line_text = generate_line(arena, (Token *)temp_tokens.data,
+ temp_tokens.count, error.line);
+
free((void *)source);
error_add(error);
issues_found++;
-
+
} else if (!alloc->has_matching_free) {
// Memory leak - re-read the file
const char *source = read_file(alloc->file_path);
- if (!source) continue;
-
+ if (!source)
+ continue;
+
Lexer temp_lexer;
init_lexer(&temp_lexer, source, arena);
-
+
GrowableArray temp_tokens;
growable_array_init(&temp_tokens, arena, 100, sizeof(Token));
-
+
Token tk;
while ((tk = next_token(&temp_lexer)).type_ != TOK_EOF) {
Token *slot = (Token *)growable_array_push(&temp_tokens);
- if (slot) *slot = tk;
+ if (slot)
+ *slot = tk;
}
-
+
ErrorInformation error = {0};
error.error_type = "Memory Leak";
error.file_path = alloc->file_path;
@@ -303,8 +309,8 @@ int static_memory_check_and_report(StaticMemoryAnalyzer *analyzer,
error.message = message;
error.line_text = generate_line(arena, (Token *)temp_tokens.data,
- temp_tokens.count, error.line);
-
+ temp_tokens.count, error.line);
+
free((void *)source);
error_add(error);
issues_found++;
@@ -314,4 +320,4 @@ int static_memory_check_and_report(StaticMemoryAnalyzer *analyzer,
}
return issues_found;
-}
\ No newline at end of file
+}
diff --git a/src/typechecker/stmt.c b/src/typechecker/stmt.c
index 8fec9082..a9f18640 100644
--- a/src/typechecker/stmt.c
+++ b/src/typechecker/stmt.c
@@ -87,6 +87,44 @@ bool is_pointer_assignment(AstNode *assignment) {
value->type == AST_EXPR_IDENTIFIER);
}
+static bool function_signatures_match(AstNode *proto_type, AstNode *impl_type,
+ ArenaAllocator *arena) {
+ if (!proto_type || !impl_type)
+ return false;
+ if (proto_type->type != AST_TYPE_FUNCTION ||
+ impl_type->type != AST_TYPE_FUNCTION)
+ return false;
+
+ // Check return types match
+ TypeMatchResult return_match =
+ types_match(proto_type->type_data.function.return_type,
+ impl_type->type_data.function.return_type);
+
+ if (return_match == TYPE_MATCH_NONE) {
+ return false;
+ }
+
+ // Check parameter counts match
+ if (proto_type->type_data.function.param_count !=
+ impl_type->type_data.function.param_count) {
+ return false;
+ }
+
+ // Check each parameter type matches
+ size_t param_count = proto_type->type_data.function.param_count;
+ for (size_t i = 0; i < param_count; i++) {
+ TypeMatchResult param_match =
+ types_match(proto_type->type_data.function.param_types[i],
+ impl_type->type_data.function.param_types[i]);
+
+ if (param_match == TYPE_MATCH_NONE) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
bool typecheck_var_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
const char *name = node->stmt.var_decl.name;
AstNode *declared_type = node->stmt.var_decl.var_type;
@@ -168,7 +206,19 @@ bool typecheck_var_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
}
if (initializer) {
- AstNode *init_type = typecheck_expression(initializer, scope, arena);
+ AstNode *init_type = NULL;
+
+ // SPECIAL HANDLING: If initializer is an anonymous struct expression
+ // and we have a declared type, pass it for validation
+ if (initializer->type == AST_EXPR_STRUCT &&
+ !initializer->expr.struct_expr.name && declared_type) {
+ // Call the internal version with expected type for anonymous structs
+ init_type = typecheck_struct_expr_internal(initializer, scope, arena,
+ declared_type);
+ } else {
+ init_type = typecheck_expression(initializer, scope, arena);
+ }
+
if (!init_type) {
tc_error(node, "Type Error",
"Cannot determine type of initializer for variable '%s'", name);
@@ -221,7 +271,6 @@ bool typecheck_var_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
return true;
}
-// Stub implementations for remaining functions
bool typecheck_func_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
const char *name = node->stmt.func_decl.name;
AstNode *return_type = node->stmt.func_decl.return_type;
@@ -232,6 +281,7 @@ bool typecheck_func_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
bool is_public = node->stmt.func_decl.is_public;
bool returns_ownership = node->stmt.func_decl.returns_ownership;
bool takes_ownership = node->stmt.func_decl.takes_ownership;
+ bool forward_declared = node->stmt.func_decl.forward_declared;
// Validate return type
if (!return_type || return_type->category != Node_Category_TYPE) {
@@ -258,6 +308,13 @@ bool typecheck_func_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
node->stmt.func_decl.is_public = true;
is_public = true;
}
+
+ // Main cannot be forward declared
+ if (forward_declared) {
+ tc_error(node, "Main Function Error",
+ "The 'main' function cannot be forward declared");
+ return false;
+ }
}
// Validate parameters
@@ -274,12 +331,75 @@ bool typecheck_func_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
AstNode *func_type = create_function_type(
arena, param_types, param_count, return_type, node->line, node->column);
- // Add function to scope WITH ownership flags
- if (!scope_add_symbol_with_ownership(scope, name, func_type, is_public, false,
- returns_ownership, takes_ownership,
- arena)) {
- tc_error_id(node, name, "Duplicate Symbol",
- "Function '%s' is already declared in this scope", name);
+ // Check if function already exists in scope
+ Symbol *existing = scope_lookup_current_only(scope, name);
+
+ if (existing) {
+ // Function already declared - check if this is valid
+
+ if (forward_declared) {
+ // Trying to add another prototype - error
+ tc_error_id(node, name, "Duplicate Prototype",
+ "Function '%s' already has a prototype in this scope", name);
+ return false;
+ }
+
+ // This is an implementation - check if it matches the prototype
+ if (!existing->type || existing->type->type != AST_TYPE_FUNCTION) {
+ tc_error_id(node, name, "Symbol Conflict",
+ "Symbol '%s' already exists but is not a function", name);
+ return false;
+ }
+
+ // Validate signature matches prototype
+ if (!function_signatures_match(existing->type, func_type, arena)) {
+ tc_error_help(node, "Signature Mismatch",
+ "Function implementation must match its prototype",
+ "Function '%s' implementation signature does not match "
+ "prototype declaration",
+ name);
+ return false;
+ }
+
+ // Check ownership flags match
+ if (existing->returns_ownership != returns_ownership ||
+ existing->takes_ownership != takes_ownership) {
+ tc_error_help(node, "Ownership Mismatch",
+ "Function implementation ownership flags must match "
+ "prototype",
+ "Function '%s' has mismatched ownership annotations", name);
+ return false;
+ }
+
+ // Signature matches - this is a valid implementation
+ // Update the symbol to mark it as implemented (not forward declared)
+ // We'll update the symbol's type node if needed
+
+ } else {
+ // First declaration of this function
+ if (!scope_add_symbol_with_ownership(scope, name, func_type, is_public,
+ false, returns_ownership,
+ takes_ownership, arena)) {
+ tc_error_id(node, name, "Symbol Error",
+ "Failed to add function '%s' to scope", name);
+ return false;
+ }
+ }
+
+ // If this is just a forward declaration (prototype), we're done
+ if (forward_declared) {
+ if (body) {
+ tc_error(node, "Forward Declaration Error",
+ "Forward declared function '%s' should not have a body", name);
+ return false;
+ }
+ return true;
+ }
+
+ // This is an implementation - must have a body
+ if (!body) {
+ tc_error(node, "Function Implementation Error",
+ "Function '%s' implementation must have a body", name);
return false;
}
@@ -288,6 +408,8 @@ bool typecheck_func_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
func_scope->is_function_scope = true;
func_scope->associated_node = node;
+ node->stmt.func_decl.scope = (void *)func_scope;
+
// Add parameters to function scope (parameters are always local)
for (size_t i = 0; i < param_count; i++) {
if (!scope_add_symbol(func_scope, param_names[i], param_types[i], false,
@@ -311,30 +433,23 @@ bool typecheck_func_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
}
// Typecheck function body
- if (body) {
- if (!typecheck_statement(body, func_scope, arena)) {
- tc_error(node, "Function Body Error",
- "Function '%s' body failed typechecking", name);
- return false;
- }
+ if (!typecheck_statement(body, func_scope, arena)) {
+ tc_error(node, "Function Body Error",
+ "Function '%s' body failed typechecking", name);
+ return false;
+ }
- // Process all deferred frees at function exit
- StaticMemoryAnalyzer *analyzer = get_static_analyzer(func_scope);
- if (analyzer && func_scope->deferred_frees.count > 0) {
- bool old_skip = analyzer->skip_memory_tracking;
- analyzer->skip_memory_tracking = false;
-
- for (size_t i = 0; i < func_scope->deferred_frees.count; i++) {
- const char **var =
- (const char **)((char *)func_scope->deferred_frees.data +
- i * sizeof(const char *));
- static_memory_track_free(analyzer, *var, name);
+ // Process deferred frees - these represent cleanup at function exit
+ StaticMemoryAnalyzer *analyzer = get_static_analyzer(func_scope);
+ if (analyzer && func_scope->deferred_frees.count > 0) {
+ for (size_t i = 0; i < func_scope->deferred_frees.count; i++) {
+ const char **var_ptr =
+ (const char **)((char *)func_scope->deferred_frees.data +
+ i * sizeof(const char *));
+ if (*var_ptr) {
+ // Mark as freed at function exit
+ static_memory_track_free(analyzer, *var_ptr, name);
}
-
- // CRITICAL: Clear the deferred frees after processing
- func_scope->deferred_frees.count = 0;
-
- analyzer->skip_memory_tracking = old_skip;
}
}
@@ -384,7 +499,7 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
for (size_t i = 0; i < public_count; i++) {
AstNode *member = public_members[i];
if (!member || member->type != AST_STMT_FIELD_DECL) {
- tc_error(node, "Struct Error", "Invalid public member %zu in struct '%s'",
+ tc_error(node, "Struct Error", "Invalid public member %zu in struct '%s'",
i, struct_name);
return false;
}
@@ -433,8 +548,8 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
for (size_t i = 0; i < private_count; i++) {
AstNode *member = private_members[i];
if (!member || member->type != AST_STMT_FIELD_DECL) {
- tc_error(node, "Struct Error", "Invalid private member %zu in struct '%s'",
- i, struct_name);
+ tc_error(node, "Struct Error",
+ "Invalid private member %zu in struct '%s'", i, struct_name);
return false;
}
@@ -490,14 +605,15 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
}
// Add struct type to scope BEFORE processing methods
- if (!scope_add_symbol(scope, struct_name, struct_type, is_public, false, arena)) {
+ if (!scope_add_symbol(scope, struct_name, struct_type, is_public, false,
+ arena)) {
tc_error_id(node, struct_name, "Symbol Error",
"Failed to add struct '%s' to scope", struct_name);
return false;
}
- // Now we need to reserve space for methods in the arrays BEFORE processing them
- // Count total fields (data + methods)
+ // Now we need to reserve space for methods in the arrays BEFORE processing
+ // them Count total fields (data + methods)
size_t method_count = 0;
for (size_t i = 0; i < public_count; i++) {
if (public_members[i]->stmt.field_decl.function) {
@@ -511,24 +627,25 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
}
size_t total_member_count = data_field_count + method_count;
-
+
// Reallocate the arrays to hold all members (data + methods)
- AstNode **full_member_types = arena_alloc(arena, total_member_count * sizeof(AstNode *),
- alignof(AstNode *));
- const char **full_member_names = arena_alloc(arena, total_member_count * sizeof(char *),
- alignof(char *));
-
+ AstNode **full_member_types = arena_alloc(
+ arena, total_member_count * sizeof(AstNode *), alignof(AstNode *));
+ const char **full_member_names =
+ arena_alloc(arena, total_member_count * sizeof(char *), alignof(char *));
+
// Copy existing data fields
for (size_t i = 0; i < data_field_count; i++) {
full_member_types[i] = all_member_types[i];
full_member_names[i] = all_member_names[i];
}
-
+
// Update struct type to use new arrays
struct_type->type_data.struct_type.member_types = full_member_types;
struct_type->type_data.struct_type.member_names = full_member_names;
- struct_type->type_data.struct_type.member_count = data_field_count; // Start with data fields
-
+ struct_type->type_data.struct_type.member_count =
+ data_field_count; // Start with data fields
+
member_index = data_field_count; // Continue from where we left off
// SECOND PASS: Process methods with explicit 'self' parameter
@@ -539,7 +656,7 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
if (field_function) {
// This is a method - typecheck it with 'self' parameter
-
+
AstNode *func_node = field_function;
if (func_node->type != AST_STMT_FUNCTION) {
tc_error(node, "Internal Error", "Expected function node for method");
@@ -551,13 +668,14 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
method_scope->is_function_scope = true;
method_scope->associated_node = func_node;
- // **KEY ADDITION**: Add 'self' as the first parameter
- // 'self' is a reference to the struct instance (could be pointer or value)
- // For simplicity, let's make it a pointer to the struct
- AstNode *self_type = create_pointer_type(arena, struct_type,
- func_node->line, func_node->column);
-
- if (!scope_add_symbol(method_scope, "self", self_type, false, true, arena)) {
+ // Add 'self' as the first parameter
+ // 'self' is a reference to the struct instance (could be pointer or
+ // value) For simplicity, let's make it a pointer to the struct
+ AstNode *self_type = create_pointer_type(
+ arena, struct_type, func_node->line, func_node->column);
+
+ if (!scope_add_symbol(method_scope, "self", self_type, false, true,
+ arena)) {
tc_error(func_node, "Method Error",
"Failed to add 'self' parameter to method '%s'", field_name);
return false;
@@ -569,8 +687,8 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
AstNode **param_types = func_node->stmt.func_decl.param_types;
for (size_t j = 0; j < param_count; j++) {
- if (!scope_add_symbol(method_scope, param_names[j], param_types[j],
- false, true, arena)) {
+ if (!scope_add_symbol(method_scope, param_names[j], param_types[j],
+ false, true, arena)) {
tc_error(func_node, "Method Parameter Error",
"Could not add parameter '%s' to method '%s' scope",
param_names[j], field_name);
@@ -589,27 +707,34 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
}
}
- // Register the method in the parent scope
+ // Register the method in the parent scope with QUALIFIED NAME
// CRITICAL: Include 'self' as the first parameter in the method type
AstNode *return_type = func_node->stmt.func_decl.return_type;
-
+
size_t method_param_count = param_count + 1; // +1 for self
- AstNode **method_param_types = arena_alloc(arena, method_param_count * sizeof(AstNode *),
- alignof(AstNode *));
-
+ AstNode **method_param_types = arena_alloc(
+ arena, method_param_count * sizeof(AstNode *), alignof(AstNode *));
+
// First parameter is self
method_param_types[0] = self_type;
-
+
// Copy the rest of the parameters
for (size_t j = 0; j < param_count; j++) {
method_param_types[j + 1] = param_types[j];
}
-
- AstNode *method_type = create_function_type(
- arena, method_param_types, method_param_count, return_type,
- func_node->line, func_node->column);
- if (!scope_add_symbol(scope, field_name, method_type, is_public, false, arena)) {
+ AstNode *method_type =
+ create_function_type(arena, method_param_types, method_param_count,
+ return_type, func_node->line, func_node->column);
+
+ // Create qualified method name: StructName.MethodName
+ size_t qualified_len = strlen(struct_name) + strlen(field_name) + 2;
+ char *qualified_method_name = arena_alloc(arena, qualified_len, 1);
+ snprintf(qualified_method_name, qualified_len, "%s.%s", struct_name,
+ field_name);
+
+ if (!scope_add_symbol(scope, qualified_method_name, method_type,
+ is_public, false, arena)) {
tc_error(func_node, "Method Registration Error",
"Failed to register method '%s' in scope", field_name);
return false;
@@ -619,7 +744,7 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
full_member_types[member_index] = method_type;
full_member_names[member_index] = field_name;
member_index++;
-
+
// Update the struct's member count to include this method
struct_type->type_data.struct_type.member_count = member_index;
}
@@ -643,10 +768,11 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
method_scope->associated_node = func_node;
// Add 'self' parameter
- AstNode *self_type = create_pointer_type(arena, struct_type,
- func_node->line, func_node->column);
-
- if (!scope_add_symbol(method_scope, "self", self_type, false, true, arena)) {
+ AstNode *self_type = create_pointer_type(
+ arena, struct_type, func_node->line, func_node->column);
+
+ if (!scope_add_symbol(method_scope, "self", self_type, false, true,
+ arena)) {
tc_error(func_node, "Method Error",
"Failed to add 'self' parameter to method '%s'", field_name);
return false;
@@ -658,7 +784,7 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
for (size_t j = 0; j < param_count; j++) {
if (!scope_add_symbol(method_scope, param_names[j], param_types[j],
- false, true, arena)) {
+ false, true, arena)) {
tc_error(func_node, "Method Parameter Error",
"Could not add parameter '%s' to method '%s' scope",
param_names[j], field_name);
@@ -677,25 +803,32 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
}
AstNode *return_type = func_node->stmt.func_decl.return_type;
-
+
// CRITICAL: Include 'self' as the first parameter in the method type
size_t method_param_count = param_count + 1; // +1 for self
- AstNode **method_param_types = arena_alloc(arena, method_param_count * sizeof(AstNode *),
- alignof(AstNode *));
-
+ AstNode **method_param_types = arena_alloc(
+ arena, method_param_count * sizeof(AstNode *), alignof(AstNode *));
+
// First parameter is self
method_param_types[0] = self_type;
-
+
// Copy the rest of the parameters
for (size_t j = 0; j < param_count; j++) {
method_param_types[j + 1] = param_types[j];
}
-
- AstNode *method_type = create_function_type(
- arena, method_param_types, method_param_count, return_type,
- func_node->line, func_node->column);
- if (!scope_add_symbol(scope, field_name, method_type, false, false, arena)) {
+ AstNode *method_type =
+ create_function_type(arena, method_param_types, method_param_count,
+ return_type, func_node->line, func_node->column);
+
+ // Create qualified method name: StructName.MethodName
+ size_t qualified_len = strlen(struct_name) + strlen(field_name) + 2;
+ char *qualified_method_name = arena_alloc(arena, qualified_len, 1);
+ snprintf(qualified_method_name, qualified_len, "%s.%s", struct_name,
+ field_name);
+
+ if (!scope_add_symbol(scope, qualified_method_name, method_type, false,
+ false, arena)) {
tc_error(func_node, "Method Registration Error",
"Failed to register method '%s' in scope", field_name);
return false;
@@ -705,7 +838,7 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
full_member_types[member_index] = method_type;
full_member_names[member_index] = field_name;
member_index++;
-
+
// Update the struct's member count to include this method
struct_type->type_data.struct_type.member_count = member_index;
}
@@ -713,6 +846,7 @@ bool typecheck_struct_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
return true;
}
+
bool typecheck_enum_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
const char *enum_name = node->stmt.enum_decl.name;
char **member_names = node->stmt.enum_decl.members;
@@ -840,6 +974,9 @@ bool typecheck_return_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
bool typecheck_if_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
Scope *then_branch = create_child_scope(scope, "then_branch", arena);
Scope *else_branch = create_child_scope(scope, "else_branch", arena);
+ node->stmt.if_stmt.scope = (void *)scope;
+ node->stmt.if_stmt.then_scope = (void *)then_branch;
+ node->stmt.if_stmt.else_scope = (void *)else_branch;
// Typecheck main if condition
Type *expected =
@@ -862,6 +999,21 @@ bool typecheck_if_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
arena)) {
return false;
}
+
+ if (then_branch->deferred_frees.count > 0) {
+ StaticMemoryAnalyzer *analyzer = get_static_analyzer(then_branch);
+ if (analyzer) {
+ const char *func_name = get_current_function_name(scope);
+ for (size_t i = 0; i < then_branch->deferred_frees.count; i++) {
+ const char **var_ptr =
+ (const char **)((char *)then_branch->deferred_frees.data +
+ i * sizeof(const char *));
+ if (*var_ptr) {
+ static_memory_track_free(analyzer, *var_ptr, func_name);
+ }
+ }
+ }
+ }
}
// Typecheck elif branches
@@ -904,6 +1056,21 @@ bool typecheck_if_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
arena)) {
return false;
}
+
+ if (elif_scope->deferred_frees.count > 0) {
+ StaticMemoryAnalyzer *analyzer = get_static_analyzer(elif_scope);
+ if (analyzer) {
+ const char *func_name = get_current_function_name(scope);
+ for (size_t i = 0; i < elif_scope->deferred_frees.count; i++) {
+ const char **var_ptr =
+ (const char **)((char *)elif_scope->deferred_frees.data +
+ i * sizeof(const char *));
+ if (*var_ptr) {
+ static_memory_track_free(analyzer, *var_ptr, func_name);
+ }
+ }
+ }
+ }
}
}
}
@@ -914,6 +1081,21 @@ bool typecheck_if_decl(AstNode *node, Scope *scope, ArenaAllocator *arena) {
arena)) {
return false;
}
+
+ if (else_branch->deferred_frees.count > 0) {
+ StaticMemoryAnalyzer *analyzer = get_static_analyzer(else_branch);
+ if (analyzer) {
+ const char *func_name = get_current_function_name(scope);
+ for (size_t i = 0; i < else_branch->deferred_frees.count; i++) {
+ const char **var_ptr =
+ (const char **)((char *)else_branch->deferred_frees.data +
+ i * sizeof(const char *));
+ if (*var_ptr) {
+ static_memory_track_free(analyzer, *var_ptr, func_name);
+ }
+ }
+ }
+ }
}
return true;
@@ -1003,6 +1185,7 @@ bool typecheck_use_stmt(AstNode *node, Scope *current_scope,
bool typecheck_infinite_loop_decl(AstNode *node, Scope *scope,
ArenaAllocator *arena) {
Scope *loop_scope = create_child_scope(scope, "infinite_loop", arena);
+ node->stmt.loop_stmt.scope = (void *)loop_scope;
if (node->stmt.loop_stmt.body == NULL) {
fprintf(stderr, "Error: Loop body cannot be null at line %zu\n",
@@ -1021,6 +1204,7 @@ bool typecheck_infinite_loop_decl(AstNode *node, Scope *scope,
bool typecheck_while_loop_decl(AstNode *node, Scope *scope,
ArenaAllocator *arena) {
Scope *while_loop = create_child_scope(scope, "while_loop", arena);
+ node->stmt.loop_stmt.scope = (void *)while_loop;
if (!typecheck_expression(node->stmt.loop_stmt.condition, while_loop,
arena)) {
@@ -1051,6 +1235,7 @@ bool typecheck_while_loop_decl(AstNode *node, Scope *scope,
bool typecheck_for_loop_decl(AstNode *node, Scope *scope,
ArenaAllocator *arena) {
Scope *lookup_scope = create_child_scope(scope, "for_loop", arena);
+ node->stmt.loop_stmt.scope = (void *)lookup_scope;
// Define the initializer
for (size_t i = 0; i < node->stmt.loop_stmt.init_count; i++) {
@@ -1166,6 +1351,7 @@ bool typecheck_switch_stmt(AstNode *node, Scope *scope, ArenaAllocator *arena) {
// Create a new scope for the switch body
Scope *switch_scope = create_child_scope(scope, "switch", arena);
+ node->stmt.switch_stmt.scope = (void *)switch_scope;
// Track if we've seen a default case
bool has_default = false;
@@ -1328,9 +1514,6 @@ bool typecheck_case_stmt(AstNode *node, Scope *scope, ArenaAllocator *arena,
return false;
}
- // printf("DEBUG: Typechecking case with %zu values\n",
- // node->stmt.case_clause.value_count);
-
// Typecheck each case value
for (size_t i = 0; i < node->stmt.case_clause.value_count; i++) {
AstNode *case_value = node->stmt.case_clause.values[i];
@@ -1339,17 +1522,24 @@ bool typecheck_case_stmt(AstNode *node, Scope *scope, ArenaAllocator *arena,
return false;
}
+ // CRITICAL FIX: Add null check before typechecking
AstNode *value_type = typecheck_expression(case_value, scope, arena);
if (!value_type) {
- tc_error(node, "Case Error", "Failed to typecheck case value %zu", i);
+ tc_error(node, "Case Error",
+ "Failed to typecheck case value %zu - returned NULL type", i);
return false;
}
- // printf("DEBUG: Case value %zu type: %s\n", i, type_to_string(value_type,
- // arena));
-
// If we know the expected type, check compatibility
if (expected_type) {
+ // CRITICAL FIX: Validate expected_type before using it
+ if (expected_type->category != Node_Category_TYPE) {
+ tc_error(node, "Internal Error",
+ "Expected type for switch is not a type node (category %d)",
+ expected_type->category);
+ return false;
+ }
+
TypeMatchResult match = types_match(expected_type, value_type);
if (match == TYPE_MATCH_NONE) {
tc_error_help(
@@ -1361,7 +1551,12 @@ bool typecheck_case_stmt(AstNode *node, Scope *scope, ArenaAllocator *arena,
return false;
}
- // Special validation for enum cases
+ // NEW: If types are compatible (enum <-> int), skip strict enum checking
+ if (match == TYPE_MATCH_COMPATIBLE) {
+ continue; // Skip to next case value
+ }
+
+ // Special validation for enum cases (only for EXACT matches)
if (expected_type->type == AST_TYPE_BASIC &&
value_type->type == AST_TYPE_BASIC) {
const char *expected_name = expected_type->type_data.basic.name;
@@ -1369,11 +1564,35 @@ bool typecheck_case_stmt(AstNode *node, Scope *scope, ArenaAllocator *arena,
// Check if they're the same enum type
if (strcmp(expected_name, value_name) != 0) {
- // Check if this is an enum member access pattern (EnumName.Member)
+ // Check if this is an enum member access pattern (EnumName::Member)
if (case_value->type == AST_EXPR_MEMBER) {
- const char *base_name =
- case_value->expr.member.object->expr.identifier.name;
- if (strcmp(base_name, expected_name) != 0) {
+ // CRITICAL FIX: Add null checks for member expression parts
+ if (!case_value->expr.member.object) {
+ tc_error(node, "Internal Error",
+ "Case member expression has NULL object");
+ return false;
+ }
+
+ if (case_value->expr.member.object->type != AST_EXPR_IDENTIFIER &&
+ case_value->expr.member.object->type != AST_EXPR_MEMBER) {
+ tc_error(
+ node, "Case Error",
+ "Invalid case value - expected identifier or member access");
+ return false;
+ }
+
+ // For simple case (EnumType::Member), extract base name
+ const char *base_name = NULL;
+ if (case_value->expr.member.object->type == AST_EXPR_IDENTIFIER) {
+ base_name = case_value->expr.member.object->expr.identifier.name;
+ } else {
+ // For chained case (module::EnumType::Member), the value_name
+ // should already be correct because typecheck_member_expr
+ // resolved it
+ base_name = value_name;
+ }
+
+ if (base_name && strcmp(base_name, expected_name) != 0) {
tc_error_help(node, "Enum Case Mismatch",
"Case values must belong to the same enum as the "
"switch condition",
diff --git a/src/typechecker/type.c b/src/typechecker/type.c
index d1213c60..225d2552 100644
--- a/src/typechecker/type.c
+++ b/src/typechecker/type.c
@@ -448,4 +448,4 @@ void debug_print_scope(Scope *scope, int indent_level) {
debug_print_scope(child, indent_level + 2);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/typechecker/type.h b/src/typechecker/type.h
index e699f02e..03c4e2c7 100644
--- a/src/typechecker/type.h
+++ b/src/typechecker/type.h
@@ -309,6 +309,14 @@ AstNode *typecheck_assignment_expr(AstNode *expr, Scope *scope,
ArenaAllocator *arena);
AstNode *typecheck_array_expr(AstNode *expr, Scope *scope,
ArenaAllocator *arena);
+AstNode *typecheck_syscall_expr(AstNode *expr, Scope *scope,
+ ArenaAllocator *arena);
+
+AstNode *typecheck_struct_expr_internal(AstNode *expr, Scope *scope,
+ ArenaAllocator *arena,
+ AstNode *expected_type);
+AstNode *typecheck_struct_expr(AstNode *expr, Scope *scope,
+ ArenaAllocator *arena);
AstNode *get_enclosing_function_return_type(Scope *scope);
diff --git a/std/arena.lx b/std/arena.lx
index e69de29b..16c7978a 100644
--- a/std/arena.lx
+++ b/std/arena.lx
@@ -0,0 +1,9 @@
+@module "arena"
+
+@use "memory" as memory
+
+pub const Arena -> struct {
+ memory: *char,
+ offset: int,
+ capacity: int,
+};
diff --git a/std/io.lx b/std/io.lx
new file mode 100644
index 00000000..22b06798
--- /dev/null
+++ b/std/io.lx
@@ -0,0 +1,159 @@
+@module "io"
+
+@use "string" as string
+@use "memory" as mem
+@use "sys" as sys
+
+const INITIAL_BUFFER_SIZE: int = 4096; // Start with 4KB
+const BUFFER_GROWTH_FACTOR: int = 2; // Double size when growing
+
+pub const print_int -> fn (s: *char, args: [int; 256]) int {
+ // Simple implementation: only supports %d for integers
+ let buffer: *char = cast<*char>(alloc(256 * sizeof));
+ let buf_index: int = 0; let arg_index: int = 0;
+ defer { free(buffer); }
+
+ loop [i: int = 0](s[i] != '\0') : (++i) {
+ if (s[i] == '%' && s[i + 1] == 'd' && arg_index < 256) {
+ let num_str: *char = string::from_int(args[arg_index]);
+ defer { free(num_str); }
+
+ loop [j: int = 0](num_str[j] != '\0') : (++j) {
+ buffer[buf_index] = num_str[j];
+ buf_index = buf_index + 1;
+ }
+ arg_index = arg_index + 1;
+ i = i + 1; // Skip 'd'
+ } else {
+ buffer[buf_index] = s[i];
+ buf_index = buf_index + 1;
+ }
+ }
+ buffer[buf_index] = '\0'; // Null-terminate
+
+ let written: int = sys::write_str(sys::STDOUT, buffer);
+ return written;
+}
+
+pub const print_str -> fn (s: *char, args: [*char; 256]) int {
+ // Simple implementation: only supports %s for strings
+ let buffer: *char = cast<*char>(alloc(256 * sizeof));
+ let buf_index: int = 0; let arg_index: int = 0;
+ defer { free(buffer); }
+
+ loop [i: int = 0](s[i] != '\0') : (++i) {
+ if (s[i] == '%' && s[i + 1] == 's' && arg_index < 256) {
+ let str_arg: *char = args[arg_index];
+ loop [j: int = 0](str_arg[j] != '\0') : (++j) {
+ buffer[buf_index] = str_arg[j];
+ buf_index = buf_index + 1;
+ }
+ arg_index = arg_index + 1;
+ i = i + 1; // Skip 's'
+ } else {
+ buffer[buf_index] = s[i];
+ buf_index = buf_index + 1;
+ }
+ }
+ buffer[buf_index] = '\0'; // Null-terminate
+
+ let written: int = sys::write_str(sys::STDOUT, buffer);
+ return written;
+}
+
+pub const print_char -> fn (s: *char, args: [char; 256]) int {
+ // Simple implementation: only supports %c for characters
+ let buffer: *char = cast<*char>(alloc(256 * sizeof));
+ let buf_index: int = 0; let arg_index: int = 0;
+ defer { free(buffer); }
+
+ let i: int = 0;
+ loop (s[i] != '\0') : (++i) {
+ if (s[i] == '%' && s[i + 1] == 'c' && arg_index < 256) {
+ buffer[buf_index] = args[arg_index];
+ buf_index = buf_index + 1;
+ arg_index = arg_index + 1;
+ i = i + 1; // Skip 'c'
+ } else {
+ buffer[buf_index] = s[i];
+ buf_index = buf_index + 1;
+ }
+ }
+ buffer[buf_index] = '\0'; // Null-terminate
+
+ let written: int = sys::write_str(sys::STDOUT, buffer);
+ return written;
+}
+
+#returns_ownership
+pub const read_file -> fn (path: *char) *char {
+ let fd: int = sys::open(path, sys::O_RDONLY, 0);
+ if (sys::is_error(fd)) {
+ sys::eprint("Error: Failed to open file for reading\n");
+ return cast<*char>(0);
+ }
+
+ // Start with initial buffer
+ let buffer_size: int = INITIAL_BUFFER_SIZE;
+ let buffer: *char = cast<*char>(alloc(buffer_size * sizeof));
+ if (buffer == cast<*char>(0)) {
+ sys::eprint("Error: Failed to allocate initial buffer\n");
+ sys::close(fd);
+ return cast<*char>(0);
+ }
+
+ let total_read: int = 0;
+ let bytes_read: int = 0;
+
+ loop {
+ // Read into buffer at current position
+ let read_size: int = buffer_size - total_read - 1; // Leave room for null terminator
+ bytes_read = sys::read(fd, cast<*void>(cast(buffer) + total_read), read_size);
+
+ if (sys::is_error(bytes_read)) {
+ sys::eprint("Error: Failed to read from file\n");
+ sys::close(fd);
+ free(buffer);
+ return cast<*char>(0);
+ }
+
+ // If we read 0 bytes, we've reached EOF
+ if (bytes_read == 0) {
+ break;
+ }
+
+ total_read = total_read + bytes_read;
+
+ // If buffer is full (minus null terminator space), grow it
+ if (total_read >= buffer_size - 1) {
+ let new_size: int = buffer_size * BUFFER_GROWTH_FACTOR;
+ let new_buffer: *char = cast<*char>(mem::realloc(cast<*void>(buffer), new_size * sizeof));
+
+ if (new_buffer == cast<*char>(0)) {
+ sys::eprint("Error: Failed to grow buffer\n");
+ sys::close(fd);
+ free(buffer);
+ return cast<*char>(0);
+ }
+
+ buffer = new_buffer;
+ buffer_size = new_size;
+ }
+ }
+
+ // Null terminate the string
+ buffer[total_read] = cast(0);
+
+ // shrink buffer to exact size to save memory
+ if (total_read < buffer_size - 1) {
+ let final_buffer: *char = cast<*char>(alloc((total_read + 1) * sizeof));
+ if (final_buffer != cast<*char>(0)) {
+ mem::memcpy(cast<*void>(final_buffer), cast<*void>(buffer), total_read + 1);
+ free(buffer);
+ buffer = final_buffer;
+ }
+ }
+
+ sys::close(fd);
+ return buffer;
+}
diff --git a/std/math.lx b/std/math.lx
index 6928e3c8..b2cde785 100644
--- a/std/math.lx
+++ b/std/math.lx
@@ -9,23 +9,39 @@ pub const DT: double = 0.005525661; // 0.000034339 - 0.0055 - 0.00007;
pub const SIN_TABLE_COUNT: int = 128;
pub const SIN_TABLE: [double; 129] = [
- 0.000000, 0.012272, 0.024541, 0.036807, 0.049068, 0.061321, 0.073565, 0.085797,
- 0.098017, 0.110222, 0.122411, 0.134581, 0.146730, 0.158858, 0.170962, 0.183040,
- 0.195090, 0.207111, 0.219101, 0.231058, 0.242980, 0.254866, 0.266713, 0.278520,
- 0.290285, 0.302006, 0.313682, 0.325310, 0.336890, 0.348419, 0.359895, 0.371317,
- 0.382683, 0.393992, 0.405241, 0.416430, 0.427555, 0.438616, 0.449611, 0.460539,
- 0.471397, 0.482184, 0.492898, 0.503538, 0.514103, 0.524590, 0.534998, 0.545325,
- 0.555570, 0.565732, 0.575808, 0.585798, 0.595699, 0.605511, 0.615232, 0.624859,
- 0.634393, 0.643832, 0.653173, 0.662416, 0.671559, 0.680601, 0.689541, 0.698376,
- 0.707107, 0.715731, 0.724247, 0.732654, 0.740951, 0.749136, 0.757209, 0.765167,
- 0.773010, 0.780737, 0.788346, 0.795837, 0.803208, 0.810457, 0.817585, 0.824589,
- 0.831470, 0.838225, 0.844854, 0.851355, 0.857729, 0.863973, 0.870087, 0.876070,
- 0.881921, 0.887640, 0.893224, 0.898674, 0.903989, 0.909168, 0.914210, 0.919114,
- 0.923880, 0.928506, 0.932993, 0.937339, 0.941544, 0.945607, 0.949528, 0.953306,
- 0.956940, 0.960431, 0.963776, 0.966976, 0.970031, 0.972940, 0.975702, 0.978317,
- 0.980785, 0.983105, 0.985278, 0.987301, 0.989177, 0.990903, 0.992480, 0.993907,
- 0.995185, 0.996313, 0.997290, 0.998118, 0.998795, 0.999322, 0.999699, 0.999925,
- 1.000000
+ 0.0000000000000000, 0.0122715382857199, 0.0245412285229123, 0.0368072229413588,
+ 0.0490676743274180, 0.0613207363022086, 0.0735645635996674, 0.0857973123444399,
+ 0.0980171403295606, 0.1102222072938831, 0.1224106751992162, 0.1345807085071262,
+ 0.1467304744553618, 0.1588581433338614, 0.1709618887603012, 0.1830398879551409,
+ 0.1950903220161282, 0.2071113761922186, 0.2191012401568698, 0.2310581082806711,
+ 0.2429801799032639, 0.2548656596045146, 0.2667127574748984, 0.2785196893850531,
+ 0.2902846772544623, 0.3020059493192281, 0.3136817403988915, 0.3253102921622629,
+ 0.3368898533922201, 0.3484186802494346, 0.3598950365349881, 0.3713171939518375,
+ 0.3826834323650898, 0.3939920400610481, 0.4052413140049899, 0.4164295600976371,
+ 0.4275550934302821, 0.4386162385385277, 0.4496113296546065, 0.4605387109582400,
+ 0.4713967368259976, 0.4821837720791227, 0.4928981922297840, 0.5035383837257176,
+ 0.5141027441932217, 0.5245896826784690, 0.5349976198870972, 0.5453249884220465,
+ 0.5555702330196022, 0.5657318107836131, 0.5758081914178453, 0.5857978574564389,
+ 0.5956993044924334, 0.6055110414043255, 0.6152315905806268, 0.6248594881423863,
+ 0.6343932841636455, 0.6438315428897914, 0.6531728429537768, 0.6624157775901718,
+ 0.6715589548470183, 0.6806010002929786, 0.6895405447370668, 0.6983762494089729,
+ 0.7071067811865475, 0.7157308252838186, 0.7242470829514669, 0.7326542716724128,
+ 0.7409511253549591, 0.7491363945234594, 0.7572088465064846, 0.7651672656224590,
+ 0.7730104533627370, 0.7807372285720945, 0.7883464276266062, 0.7958369046088836,
+ 0.8032075314806448, 0.8104571982525948, 0.8175848131515837, 0.8245893027850253,
+ 0.8314696123025452, 0.8382247055548381, 0.8448535652497070, 0.8513551931052652,
+ 0.8577286100002721, 0.8639728561215867, 0.8700869911087113, 0.8760700941954066,
+ 0.8819212643483550, 0.8876396204028539, 0.8932243011955153, 0.8986744656939538,
+ 0.9039892931234433, 0.9091679830905224, 0.9142097557035307, 0.9191138516900578,
+ 0.9238795325112867, 0.9285060804732156, 0.9329927988347390, 0.9373390119125749,
+ 0.9415440651830208, 0.9456073253805213, 0.9495281805930367, 0.9533060403541939,
+ 0.9569403357322088, 0.9604305194155658, 0.9637760657954398, 0.9669764710448521,
+ 0.9700312531945440, 0.9729400179601073, 0.9757021300385286, 0.9783173707196277,
+ 0.9807852804032304, 0.9831054874312163, 0.9852776423889412, 0.9873014181578584,
+ 0.9891765099647809, 0.9909026354277800, 0.9924795345987100, 0.9939069700023561,
+ 0.9951847266721968, 0.9963126121827780, 0.9972904566786902, 0.9981181129001492,
+ 0.9987954562051724, 0.9993223845883495, 0.9996988186962042, 0.9999247018391445,
+ 1.0000000000000000
];
pub const rand -> fn (seed: *int) int {
diff --git a/std/string.lx b/std/string.lx
index 5fce10e8..ee44f1d6 100644
--- a/std/string.lx
+++ b/std/string.lx
@@ -1,5 +1,8 @@
@module "string"
+pub const cat -> fn (dest: *char, s1: *char, s2: *char) *char;
+pub const strlen -> fn (s: *char) int;
+
#returns_ownership
pub const from_char -> fn (c: char) *char {
let s: *char = cast<*char>(alloc(2 * sizeof));
@@ -45,6 +48,62 @@ pub const from_int -> fn (n: int) *char {
return s;
}
+#returns_ownership
+pub const from_float -> fn (f: float, precision: int) *char {
+ let int_part: int = cast(f);
+ let frac_part: float = f - cast(int_part);
+
+ let int_str: *char = from_int(int_part);
+ defer { free(int_str); }
+
+ let frac_str: *char = cast<*char>(alloc((precision + 2) * sizeof)); // +1 for '.' +1 for null
+ frac_str[0] = '.';
+
+ loop [i: int = 1](i <= precision) : (++i) {
+ frac_part = frac_part * 10.0;
+ let digit: int = cast(frac_part);
+ frac_str[i] = cast(digit + cast('0'));
+ frac_part = frac_part - cast(digit);
+ }
+
+ frac_str[precision + 1] = cast(0); // null terminator
+
+ let result_len: int = strlen(int_str) + strlen(frac_str) + 1;
+ let result: *char = cast<*char>(alloc(result_len * sizeof));
+
+ cat(result, int_str, frac_str);
+
+ return result;
+}
+
+pub const int_to_str -> fn (num: int, buf: *char, buf_size: int) void {
+ let idx: int = 0;
+ let temp: int = num;
+
+ if (temp == 0) {
+ buf[0] = '0';
+ buf[1] = '\0';
+ return;
+ }
+
+ loop (temp > 0 && idx < buf_size - 1) {
+ buf[idx] = cast((temp % 10) + 48);
+ temp = temp / 10;
+ idx = idx + 1;
+ }
+
+ // Reverse the digits
+ let j: int = 0;
+ loop (j < idx / 2) {
+ let tmp: char = buf[j];
+ buf[j] = buf[idx - 1 - j];
+ buf[idx - 1 - j] = tmp;
+ j = j + 1;
+ }
+
+ buf[idx] = '\0';
+}
+
pub const strlen -> fn (s: *char) int {
let length: int = 0;
loop (s[length] != cast(0)) : (++length) {}
@@ -134,4 +193,28 @@ pub const putchar -> fn(c: char) int {
if (char_val != 10 && char_val != 9) { system(cmd); }
return cast(c);
-}
\ No newline at end of file
+}
+
+pub const is_digit -> fn (ch: char) bool {
+ return (ch >= '0' && ch <= '9');
+}
+
+pub const is_alpha -> fn (ch: char) bool {
+ return ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_' || ch == '@');
+}
+
+pub const is_alnum -> fn (ch: char) bool {
+ return (is_alpha(ch) || is_digit(ch));
+}
+
+pub const atio -> fn (value: *char) int {
+ let num: int = 0;
+ loop [i: int = 0](value[i] != '\0') : (++i) {
+ if (cast(value[i]) >= 48 && cast(value[i]) <= 57) {
+ num = num * 10 + (cast(value[i]) - 48);
+ } else {
+ break;
+ }
+ }
+ return num;
+}
diff --git a/std/sys.lx b/std/sys.lx
new file mode 100644
index 00000000..ccb845cd
--- /dev/null
+++ b/std/sys.lx
@@ -0,0 +1,303 @@
+/*
+ * sys.lx - Linux System Call Interface
+ *
+ * PLATFORM: This module is ONLY compatible with x86_64 Linux systems.
+ * It provides direct system call wrappers using the Linux syscall ABI.
+ *
+ * WARNING: This code will NOT work on:
+ * - macOS (different syscall numbers and ABI)
+ * - Windows (completely different system call mechanism)
+ * - ARM/ARM64 Linux (different syscall numbers)
+ * - 32-bit x86 Linux (different syscall numbers and calling convention)
+ *
+ * The syscall numbers and behavior are specific to the Linux kernel
+ * on x86_64 architecture. Port carefully if targeting other platforms.
+ *
+ * KNOWN ISSUES:
+ * - pipe() syscall may have issues with file descriptor handling
+ */
+@module "sys"
+
+@use "string" as string
+
+// TODO: Allow enums to have a custom assigned value
+// Syscall numbers (Linux x86_64)
+pub const SYS_READ: int = 0;
+pub const SYS_WRITE: int = 1;
+pub const SYS_OPEN: int = 2;
+pub const SYS_CLOSE: int = 3;
+pub const SYS_STAT: int = 4;
+pub const SYS_FSTAT: int = 5;
+pub const SYS_LSEEK: int = 8;
+pub const SYS_MMAP: int = 9;
+pub const SYS_MUNMAP: int = 11;
+pub const SYS_BRK: int = 12;
+pub const SYS_IOCTL: int = 16;
+pub const SYS_PREAD: int = 17;
+pub const SYS_PWRITE: int = 18;
+pub const SYS_PIPE: int = 22;
+pub const SYS_SELECT: int = 23;
+pub const SYS_DUP: int = 32;
+pub const SYS_DUP2: int = 33;
+pub const SYS_GETPID: int = 39;
+pub const SYS_FORK: int = 57;
+pub const SYS_EXECVE: int = 59;
+pub const SYS_EXIT: int = 60;
+pub const SYS_WAIT4: int = 61;
+pub const SYS_KILL: int = 62;
+pub const SYS_FCNTL: int = 72;
+pub const SYS_GETCWD: int = 79;
+pub const SYS_CHDIR: int = 80;
+pub const SYS_MKDIR: int = 83;
+pub const SYS_RMDIR: int = 84;
+pub const SYS_UNLINK: int = 87;
+pub const SYS_GETUID: int = 102;
+pub const SYS_GETGID: int = 104;
+pub const SYS_GETTIMEOFDAY: int = 96;
+pub const SYS_CLOCK_GETTIME: int = 228;
+
+// File flags
+pub const O_RDONLY: int = 0;
+pub const O_WRONLY: int = 1;
+pub const O_RDWR: int = 2;
+pub const O_CREAT: int = 64;
+pub const O_EXCL: int = 128;
+pub const O_NOCTTY: int = 256;
+pub const O_TRUNC: int = 512;
+pub const O_APPEND: int = 1024;
+pub const O_NONBLOCK: int = 2048;
+pub const O_DIRECTORY: int = 65536;
+pub const O_CLOEXEC: int = 524288;
+
+// File permissions
+pub const S_IRWXU: int = 448; // 0700 - user rwx
+pub const S_IRUSR: int = 256; // 0400 - user read
+pub const S_IWUSR: int = 128; // 0200 - user write
+pub const S_IXUSR: int = 64; // 0100 - user execute
+
+pub const S_IRWXG: int = 56; // 0070 - group rwx
+pub const S_IRGRP: int = 32; // 0040 - group read
+pub const S_IWGRP: int = 16; // 0020 - group write
+pub const S_IXGRP: int = 8; // 0010 - group execute
+
+pub const S_IRWXO: int = 7; // 0007 - other rwx
+pub const S_IROTH: int = 4; // 0004 - other read
+pub const S_IWOTH: int = 2; // 0002 - other write
+pub const S_IXOTH: int = 1; // 0001 - other execute
+
+// Common permission combinations
+pub const MODE_0644: int = 420; // rw-r--r--
+pub const MODE_0755: int = 493; // rwxr-xr-x
+pub const MODE_0777: int = 511; // rwxrwxrwx
+
+// lseek whence values
+pub const SEEK_SET: int = 0;
+pub const SEEK_CUR: int = 1;
+pub const SEEK_END: int = 2;
+
+// Standard file descriptors
+pub const STDIN: int = 0;
+pub const STDOUT: int = 1;
+pub const STDERR: int = 2;
+
+// mmap protection flags
+pub const PROT_NONE: int = 0;
+pub const PROT_READ: int = 1;
+pub const PROT_WRITE: int = 2;
+pub const PROT_EXEC: int = 4;
+
+// mmap flags
+pub const MAP_SHARED: int = 1;
+pub const MAP_PRIVATE: int = 2;
+pub const MAP_ANONYMOUS: int = 32;
+pub const MAP_ANON: int = 32; // Alias for MAP_ANONYMOUS
+pub const MAP_FIXED: int = 16;
+
+// Signals
+pub const SIGHUP: int = 1;
+pub const SIGINT: int = 2;
+pub const SIGQUIT: int = 3;
+pub const SIGILL: int = 4;
+pub const SIGTRAP: int = 5;
+pub const SIGABRT: int = 6;
+pub const SIGBUS: int = 7;
+pub const SIGFPE: int = 8;
+pub const SIGKILL: int = 9;
+pub const SIGSEGV: int = 11;
+pub const SIGPIPE: int = 13;
+pub const SIGALRM: int = 14;
+pub const SIGTERM: int = 15;
+pub const SIGCHLD: int = 17;
+
+// Wait flags
+pub const WNOHANG: int = 1;
+pub const WUNTRACED: int = 2;
+
+// Error codes (commonly returned as negative values)
+pub const EPERM: int = 1; // Operation not permitted
+pub const ENOENT: int = 2; // No such file or directory
+pub const ESRCH: int = 3; // No such process
+pub const EINTR: int = 4; // Interrupted system call
+pub const EIO: int = 5; // I/O error
+pub const ENXIO: int = 6; // No such device or address
+pub const E2BIG: int = 7; // Argument list too long
+pub const EBADF: int = 9; // Bad file descriptor
+pub const ECHILD: int = 10; // No child processes
+pub const EAGAIN: int = 11; // Try again
+pub const ENOMEM: int = 12; // Out of memory
+pub const EACCES: int = 13; // Permission denied
+pub const EFAULT: int = 14; // Bad address
+pub const EBUSY: int = 16; // Device or resource busy
+pub const EEXIST: int = 17; // File exists
+pub const ENODEV: int = 19; // No such device
+pub const ENOTDIR: int = 20; // Not a directory
+pub const EISDIR: int = 21; // Is a directory
+pub const EINVAL: int = 22; // Invalid argument
+pub const ENFILE: int = 23; // File table overflow
+pub const EMFILE: int = 24; // Too many open files
+pub const ENOSPC: int = 28; // No space left on device
+pub const EPIPE: int = 32; // Broken pipe
+
+// ============= Process Management =============
+
+pub const exit -> fn (code: int) void {
+ __syscall__(SYS_EXIT, code);
+}
+
+pub const fork -> fn () int {
+ return __syscall__(SYS_FORK);
+}
+
+pub const getpid -> fn () int {
+ return __syscall__(SYS_GETPID);
+}
+
+pub const getuid -> fn () int {
+ return __syscall__(SYS_GETUID);
+}
+
+pub const getgid -> fn () int {
+ return __syscall__(SYS_GETGID);
+}
+
+pub const kill -> fn (pid: int, sig: int) int {
+ return __syscall__(SYS_KILL, pid, sig);
+}
+
+pub const wait4 -> fn (pid: int, status: *int, options: int, rusage: *void) int {
+ return __syscall__(SYS_WAIT4, pid, cast(status), options, cast(rusage));
+}
+
+pub const execve -> fn (path: *char, argv: **char, envp: **char) int {
+ return __syscall__(SYS_EXECVE, cast(path), cast(argv), cast(envp));
+}
+
+// ============= File Operations =============
+
+pub const read -> fn (fd: int, buf: *void, count: int) int {
+ return __syscall__(SYS_READ, fd, cast(buf), count);
+}
+
+pub const write -> fn (fd: int, buf: *void, count: int) int {
+ return __syscall__(SYS_WRITE, fd, cast(buf), count);
+}
+
+pub const open -> fn (path: *char, flags: int, mode: int) int {
+ return __syscall__(SYS_OPEN, cast(path), flags, mode);
+}
+
+pub const close -> fn (fd: int) int {
+ return __syscall__(SYS_CLOSE, fd);
+}
+
+pub const lseek -> fn (fd: int, offset: int, whence: int) int {
+ return __syscall__(SYS_LSEEK, fd, offset, whence);
+}
+
+pub const pread -> fn (fd: int, buf: *void, count: int, offset: int) int {
+ return __syscall__(SYS_PREAD, fd, cast(buf), count, offset);
+}
+
+pub const pwrite -> fn (fd: int, buf: *void, count: int, offset: int) int {
+ return __syscall__(SYS_PWRITE, fd, cast(buf), count, offset);
+}
+
+pub const dup -> fn (oldfd: int) int {
+ return __syscall__(SYS_DUP, oldfd);
+}
+
+pub const dup2 -> fn (oldfd: int, newfd: int) int {
+ return __syscall__(SYS_DUP2, oldfd, newfd);
+}
+
+pub const pipe -> fn (pipefd: *int) int {
+ return __syscall__(SYS_PIPE, cast(pipefd));
+}
+
+pub const unlink -> fn (path: *char) int {
+ return __syscall__(SYS_UNLINK, cast(path));
+}
+
+// ============= Directory Operations =============
+
+pub const mkdir -> fn (path: *char, mode: int) int {
+ return __syscall__(SYS_MKDIR, cast(path), mode);
+}
+
+pub const rmdir -> fn (path: *char) int {
+ return __syscall__(SYS_RMDIR, cast(path));
+}
+
+pub const chdir -> fn (path: *char) int {
+ return __syscall__(SYS_CHDIR, cast(path));
+}
+
+pub const getcwd -> fn (buf: *char, size: int) *char {
+ let result: int = __syscall__(SYS_GETCWD, cast(buf), size);
+ if (result < 0) {
+ return cast<*char>(0);
+ }
+ return buf;
+}
+
+// ============= Memory Management =============
+
+pub const brk -> fn (addr: *void) int {
+ return __syscall__(SYS_BRK, cast(addr));
+}
+
+pub const mmap -> fn (addr: *void, length: int, prot: int, flags: int, fd: int, offset: int) *void {
+ let result: int = __syscall__(SYS_MMAP, cast(addr), length, prot, flags, fd, offset);
+ return cast<*void>(result);
+}
+
+pub const munmap -> fn (addr: *void, length: int) int {
+ return __syscall__(SYS_MUNMAP, cast(addr), length);
+}
+
+// ============= Helper Functions =============
+
+// Check if result is an error
+pub const is_error -> fn (result: int) bool {
+ return result < 0 && result >= -4095;
+}
+
+// Get error number from result
+pub const get_errno -> fn (result: int) int {
+ if (is_error(result)) {
+ return -result;
+ }
+ return 0;
+}
+
+// Write string to file descriptor
+pub const write_str -> fn (fd: int, s: *char) int {
+ let len: int = 0;
+ loop (s[len] != cast(0)) : (++len) {}
+ return write(fd, cast<*void>(s), len);
+}
+
+// Print to stderr
+pub const eprint -> fn (s: *char) int {
+ return write_str(STDERR, s);
+}
diff --git a/std/termfx.lx b/std/termfx.lx
index 1b55ead0..7f6230f8 100644
--- a/std/termfx.lx
+++ b/std/termfx.lx
@@ -48,11 +48,11 @@ pub const INVERT: *char = "\x1b[7m"; // swap fg/bg
pub const HIDDEN: *char = "\x1b[8m"; // invisible text
pub const STRIKETHROUGH: *char = "\x1b[9m";
-pub const CLEAR_SCREEN: *char = "\x1b[2J";
-pub const CLEAR_LINE: *char = "\x1b[2K";
-pub const CURSOR_HOME: *char = "\x1b[H";
-pub const CURSOR_HIDE: *char = "\x1b[?25l";
-pub const CURSOR_SHOW: *char = "\x1b[?25h";
+pub const CLEAR_SCREEN: *char = "\x1b[2J";
+pub const CLEAR_LINE: *char = "\x1b[2K";
+pub const CURSOR_HOME: *char = "\x1b[H";
+pub const CURSOR_HIDE: *char = "\x1b[?25l";
+pub const CURSOR_SHOW: *char = "\x1b[?25h";
pub const SAVE_CURSOR: *char = "\x1b[s";
pub const RESTORE_CURSOR: *char = "\x1b[u";
diff --git a/std/terminal.lx b/std/terminal.lx
index 3295f46d..c012d62f 100644
--- a/std/terminal.lx
+++ b/std/terminal.lx
@@ -180,3 +180,16 @@ pub const get_terminal_size -> fn () void {
system("tput lines > /tmp/luma_lines");
// Read back from files...
}
+
+pub const get_line -> fn (prompt: *char, buffer: *char, size: int) void {
+ output(prompt);
+ let i: int = 0;
+ loop (i < size - 1) : (++i) {
+ let c: char = getch();
+ if (c == '\n' || c == '\r') {
+ break;
+ }
+ buffer[i] = c;
+ }
+ buffer[i] = '\0';
+}
diff --git a/std/time.lx b/std/time.lx
new file mode 100644
index 00000000..9dfcd38c
--- /dev/null
+++ b/std/time.lx
@@ -0,0 +1,75 @@
+@module "time"
+
+const NANOSLEEP: int = 35;
+const CLOCK_GETTIME: int = 228;
+const CLOCK_REALTIME: int = 0;
+
+const TimeSpec -> struct {
+ sec: int,
+ nsec: int,
+};
+
+const Timer -> struct {
+ start_time: TimeSpec,
+};
+
+pub const usleep -> fn (usec: int) int {
+ let req: TimeSpec;
+
+ req.sec = usec / 1000000;
+ req.nsec = (usec % 1000000) * 1000;
+
+ return __syscall__(NANOSLEEP, cast<*int>(&req), 0);
+}
+
+pub const to_millis -> fn (t: TimeSpec) int {
+ return (t.sec * 1000) + (t.nsec / 1000000);
+}
+
+pub const to_micros -> fn (t: TimeSpec) int {
+ return (t.sec * 1000000) + (t.nsec / 1000);
+}
+
+pub const to_nanos -> fn (t: TimeSpec) int {
+ return (t.sec * 1000000000) + t.nsec;
+}
+
+pub const clock_gettime -> fn (clk_id: int, ts: *TimeSpec) int {
+ return __syscall__(CLOCK_GETTIME, clk_id, cast<*int>(ts));
+}
+
+pub const now -> fn () TimeSpec {
+ let ts: TimeSpec;
+ clock_gettime(CLOCK_REALTIME, &ts);
+ return ts;
+}
+
+pub const timer_start -> fn () Timer {
+ let t: Timer;
+ t.start_time = now();
+ return t;
+}
+
+pub const timespec_sub -> fn (a: TimeSpec, b: TimeSpec) TimeSpec {
+ let res: TimeSpec;
+
+ res.sec = a.sec - b.sec;
+ res.nsec = a.nsec - b.nsec;
+
+ if (res.nsec < 0) {
+ res.nsec = res.nsec + 1000000000;
+ res.sec = res.sec - 1;
+ }
+
+ return res;
+}
+
+pub const elapsed_ms -> fn (start: TimeSpec, end: TimeSpec) int {
+ let diff: TimeSpec = timespec_sub(end, start);
+ return to_millis(diff);
+}
+
+pub const timer_elapsed_ms -> fn (t: Timer) int {
+ let end: TimeSpec = now();
+ return elapsed_ms(t.start_time, end);
+}
diff --git a/tests/LPBS/build.lmb b/tests/LPBS/build.lmb
new file mode 100644
index 00000000..c495c943
--- /dev/null
+++ b/tests/LPBS/build.lmb
@@ -0,0 +1 @@
+-(1 + 6 * (2 + 7) - 9 / 9)
\ No newline at end of file
diff --git a/tests/LPBS/build_lpbs.mk b/tests/LPBS/build_lpbs.mk
new file mode 100644
index 00000000..b0ac5c00
--- /dev/null
+++ b/tests/LPBS/build_lpbs.mk
@@ -0,0 +1,99 @@
+LUMA = ./../../luma
+NAME = lpbs
+
+MAIN = ./src/main.lx
+SRCS := $(filter-out $(MAIN), $(shell find . -type f -name '*.lx'))
+SRCS := $(filter-out ,$(SRCS)) # remove any empty strings
+
+STD_LIBS = ../../std/time.lx \
+ ../../std/string.lx \
+ ../../std/sys.lx \
+ ../../std/io.lx \
+ ../../std/memory.lx
+
+ALL_SRCS = $(MAIN) $(SRCS) $(STD_LIBS)
+
+# Detect OS (Windows_NT is predefined on Windows)
+ifeq ($(OS),Windows_NT)
+ EXE_EXT := .exe
+ RM := del /Q
+ MKDIR := if not exist build mkdir build
+ RUN_PREFIX :=
+else
+ EXE_EXT :=
+ RM := rm -f
+ MKDIR := mkdir -p build
+ RUN_PREFIX := ./
+endif
+
+TARGET = $(NAME)$(EXE_EXT)
+
+.PHONY: all
+all: $(TARGET)
+
+# Build spinning cube demo
+$(TARGET): $(ALL_SRCS)
+ @echo "Building LPBS..."
+ $(LUMA) $(MAIN) -name $(NAME) -l $(SRCS) $(STD_LIBS)
+ifeq ($(OS),Windows_NT)
+ @if exist nul del nul
+else
+ @rm -f nul
+endif
+ @echo "Build complete: $(TARGET)"
+
+.PHONY: run
+run: $(TARGET)
+ @echo "Starting LPBS..."
+ $(RUN_PREFIX)$(TARGET)
+
+.PHONY: valgrind
+valgrind: $(TARGET)
+ifeq ($(OS),Windows_NT)
+ @echo "Valgrind not supported on Windows natively."
+else
+ @echo "Running LPBS with valgrind..."
+ valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET)
+endif
+
+.PHONY: clean
+clean:
+ @echo "Cleaning build artifacts..."
+ -$(RM) $(TARGET)
+ -$(RM) obj\*.o 2>nul || true
+ -$(RM) obj/*.o 2>/dev/null || true
+
+.PHONY: rebuild
+rebuild: clean all
+
+.PHONY: list
+list:
+ @echo "Main file:"
+ @echo " $(MAIN)"
+ @echo ""
+ @echo "LPBS sources:"
+ @for src in $(SRCS); do echo " $$src"; done
+ @echo ""
+ @echo "Standard library:"
+ @for lib in $(STD_LIBS); do echo " $$lib"; done
+
+.PHONY: help
+help:
+ @echo "Luma Build System"
+ @echo ""
+ @echo "Build Targets:"
+ @echo " all - Build LPBS (default)"
+ @echo " rebuild - Clean and rebuild"
+ @echo ""
+ @echo "Run Targets:"
+ @echo " run - Build and run"
+ @echo " valgrind - Run with memory leak detection (Linux only)"
+ @echo ""
+ @echo "Development:"
+ @echo " list - Show all source files"
+ @echo " clean - Remove build artifacts"
+ @echo ""
+ @echo "Examples:"
+ @echo " make # Build"
+ @echo " make run # Build and run"
+ @echo " make list # Show what will be compiled"
diff --git a/tests/LPBS/src/ast/ast.lx b/tests/LPBS/src/ast/ast.lx
new file mode 100644
index 00000000..d10caef4
--- /dev/null
+++ b/tests/LPBS/src/ast/ast.lx
@@ -0,0 +1,166 @@
+@module "ast"
+
+@use "string" as string
+
+pub const ExprKind -> enum {
+ EXPR_NUMBER,
+ EXPR_IDENT,
+ EXPR_STRING,
+ EXPR_BINARY,
+ EXPR_UNARY,
+ EXPR_GROUP
+};
+
+pub const StmtKind -> enum {
+ STMT_PROGRAM
+};
+
+pub const Expr -> struct { type: int };
+pub const Stmt -> struct { type: int };
+
+pub const Program -> struct {
+ base: Stmt,
+ nodes: *Expr,
+};
+
+pub const Number -> struct {
+ base: Expr,
+ value: int
+};
+
+pub const Ident -> struct {
+ base: Expr,
+ name: *char
+};
+
+pub const StringLit -> struct {
+ base: Expr,
+ value: *char
+};
+
+pub const Binary -> struct {
+ base: Expr,
+ op: char,
+ left: *Expr,
+ right: *Expr
+};
+
+pub const Unary -> struct {
+ base: Expr,
+ op: char,
+ operand: *Expr
+};
+
+pub const Group -> struct {
+ base: Expr,
+ expr: *Expr
+};
+
+#returns_ownership
+pub const create_program_node -> fn (nodes: *Expr, size: int) *Stmt {
+ let node: *Program = cast<*Program>(alloc(size * sizeof));
+ let base_node: *Stmt = cast<*Stmt>(node);
+
+ base_node.type = StmtKind::STMT_PROGRAM;
+ node.nodes = nodes;
+ return cast<*Stmt>(node);
+}
+
+#returns_ownership
+pub const create_number_node -> fn (value: int) *Expr {
+ let node: *Number = cast<*Number>(alloc(sizeof));
+ let base_node: *Expr = cast<*Expr>(node);
+
+ base_node.type = ExprKind::EXPR_NUMBER;
+ node.value = value;
+ return cast<*Expr>(node);
+}
+
+#returns_ownership
+pub const create_ident_node -> fn (name: *char) *Expr {
+ let node: *Ident = cast<*Ident>(alloc(sizeof));
+ let base_node: *Expr = cast<*Expr>(node);
+
+ base_node.type = ExprKind::EXPR_IDENT;
+ node.name = name;
+ return cast<*Expr>(node);
+}
+
+#returns_ownership
+pub const create_string_node -> fn (value: *char) *Expr {
+ let node: *StringLit = cast<*StringLit>(alloc(sizeof));
+ let base_node: *Expr = cast<*Expr>(node);
+
+ base_node.type = ExprKind::EXPR_STRING;
+ node.value = value;
+ return cast<*Expr>(node);
+}
+
+#returns_ownership
+pub const create_binary_node -> fn (op: char, left: *Expr,
+ right: *Expr) *Expr {
+ let node: *Binary = cast<*Binary>(alloc(sizeof));
+ let base_node: *Expr = cast<*Expr>(node);
+
+ base_node.type = ExprKind::EXPR_BINARY;
+ node.op = op;
+ node.left = left;
+ node.right = right;
+ return cast<*Expr>(node);
+}
+
+#returns_ownership
+pub const create_unary_node -> fn (op: char, operand: *Expr) *Expr {
+ let node: *Unary = cast<*Unary>(alloc(sizeof));
+ let base_node: *Expr = cast<*Expr>(node);
+
+ base_node.type = ExprKind::EXPR_UNARY;
+ node.op = op;
+ node.operand = operand;
+ return cast<*Expr>(node);
+}
+
+#returns_ownership
+pub const create_group_node -> fn (expr: *Expr) *Expr {
+ let node: *Group = cast<*Group>(alloc(sizeof));
+ let base_node: *Expr = cast<*Expr>(node);
+ base_node.type = ExprKind::EXPR_GROUP;
+ node.expr = expr;
+ return cast<*Expr>(node);
+}
+
+#takes_ownership
+pub const free_expr -> fn (node: *Expr) void {
+ if (node == cast<*Expr>(0)) { return; }
+
+ switch(node.type) {
+ ExprKind::EXPR_NUMBER, ExprKind::EXPR_IDENT, ExprKind::EXPR_STRING -> { /* No child nodes to free */ }
+ ExprKind::EXPR_BINARY -> {
+ let bin_node: *Binary = cast<*Binary>(node);
+ free_expr(bin_node.left); free_expr(bin_node.right);
+ }
+ ExprKind::EXPR_UNARY -> {
+ let unary_node: *Unary = cast<*Unary>(node);
+ free_expr(unary_node.operand);
+ }
+ ExprKind::EXPR_GROUP -> {
+ let group_node: *Group = cast<*Group>(node);
+ free_expr(group_node.expr);
+ }
+ _ -> output("Node not found to free: ", node.type);
+ }
+
+ free(node);
+}
+
+#takes_ownership
+pub const free_stmt -> fn (node: *Stmt) void {
+ if (node == cast<*Stmt>(0)) { return; }
+
+ if (node.type == StmtKind::STMT_PROGRAM) {
+ let program_node: *Program = cast<*Program>(node);
+ free_expr(program_node.nodes);
+ }
+
+ free(node);
+}
diff --git a/tests/LPBS/src/ast/ast_helper.lx b/tests/LPBS/src/ast/ast_helper.lx
new file mode 100644
index 00000000..17cbf5ea
--- /dev/null
+++ b/tests/LPBS/src/ast/ast_helper.lx
@@ -0,0 +1,65 @@
+@module "ast_helper"
+
+@use "string" as string
+@use "ast" as ast
+
+pub const print_expr -> fn (node: *Expr) void {
+ if (node == cast<*Expr>(0)) { return; }
+
+ switch (node.type) {
+ ast::ExprKind::EXPR_NUMBER -> {
+ let num_node: *Number = cast<*Number>(node);
+ output(num_node.value);
+ }
+ ast::ExprKind::EXPR_IDENT -> {
+ let ident_node: *Ident = cast<*Ident>(node);
+ output(ident_node.name);
+ }
+ ast::ExprKind::EXPR_STRING -> {
+ let str_node: *StringLit = cast<*StringLit>(node);
+ output("''", str_node.value, "''");
+ }
+ ast::ExprKind::EXPR_BINARY -> {
+ let bin_node: *Binary = cast<*Binary>(node);
+ let binop: *char = string::from_char(bin_node.op);
+ defer { free(binop); }
+
+ output("(");
+ print_expr(bin_node.left);
+ output(" ", binop, " ");
+ print_expr(bin_node.right);
+ output(")");
+ }
+ ast::ExprKind::EXPR_UNARY -> {
+ let unary_node: *Unary = cast<*Unary>(node);
+ let unop: *char = string::from_char(unary_node.op);
+ defer { free(unop); }
+
+ output(unop);
+ print_expr(unary_node.operand);
+ }
+ ast::ExprKind::EXPR_GROUP -> {
+ let group_node: *Group = cast<*Group>(node);
+
+ output("(");
+ print_expr(group_node.expr);
+ output(")");
+ }
+ _ -> output("Node not found: ", node.type);
+ }
+}
+
+pub const print_stmt -> fn (node: *Stmt) void {
+ if (node == cast<*Stmt>(0)) { return; }
+
+ switch(node.type) {
+ ast::StmtKind::STMT_PROGRAM -> {
+ let prog_node: *Program = cast<*Program>(node);
+ print_expr(prog_node.nodes);
+ }
+
+ _ -> output("Node not found: ", node.type);
+ }
+
+ output("\n");
+}
diff --git a/tests/LPBS/src/lexer/lexer.lx b/tests/LPBS/src/lexer/lexer.lx
new file mode 100644
index 00000000..2f98a96d
--- /dev/null
+++ b/tests/LPBS/src/lexer/lexer.lx
@@ -0,0 +1,437 @@
+@module "lexer"
+
+@use "string" as string
+@use "sys" as sys
+
+pub const TokenType -> enum {
+ TOK_NUMBER,
+ TOK_IDENT,
+ TOK_STRING,
+ TOK_WHITESPACE,
+
+ // Symbols
+ TOK_EQUALS,
+ TOK_SEMICOLON,
+ TOK_LPAREN,
+ TOK_RPAREN,
+ TOK_LBRACE,
+ TOK_RBRACE,
+ TOK_LBRACKET,
+ TOK_RBRACKET,
+ TOK_EQEQ,
+ TOK_NOTEQ,
+ TOK_ARROW,
+ TOK_DCOLON,
+
+ TOK_PLUS,
+ TOK_MINUS,
+ TOK_SLASH,
+ TOK_STAR,
+
+ // Keywords
+ TOK_TARGET,
+ TOK_IF,
+ TOK_ELSE,
+ TOK_LET,
+
+ // Built-ins
+ TOK_RUN,
+ TOK_COPY,
+ TOK_MKDIR,
+ TOK_GLOB,
+ TOK_IMPORT,
+ TOK_EXISTS,
+
+ // Defaults
+ TOK_SYMBOL,
+ TOK_UNKNOWN,
+};
+
+// Keywords: target, if, else, let
+// Built-ins: run, copy, mkdir, glob, import, exists
+const K_Symbol_Map -> struct { value: *char, type: int };
+const D_Symbol_Map -> struct { value: *char, type: int };
+const S_Symbol_Map -> struct { value: char, type: int };
+
+const K_Symbol_Size: int = 10;
+const D_Symbol_Size: int = 4;
+const S_Symbol_Size: int = 8;
+
+pub const Token -> struct {
+ list: *Token,
+ size: int,
+ capacity: int,
+ value: *char,
+ type: int,
+ line: int,
+ col: int,
+};
+
+const k_symbol: [K_Symbol_Map; 10] = [
+ K_Symbol_Map{value: "target", type: TokenType::TOK_TARGET},
+ K_Symbol_Map{value: "if", type: TokenType::TOK_IF},
+ K_Symbol_Map{value: "else", type: TokenType::TOK_ELSE},
+ K_Symbol_Map{value: "run", type: TokenType::TOK_RUN},
+ K_Symbol_Map{value: "copy", type: TokenType::TOK_COPY},
+ K_Symbol_Map{value: "mkdir", type: TokenType::TOK_MKDIR},
+ K_Symbol_Map{value: "glob", type: TokenType::TOK_GLOB},
+ K_Symbol_Map{value: "import", type: TokenType::TOK_IMPORT},
+ K_Symbol_Map{value: "exists", type: TokenType::TOK_EXISTS},
+ K_Symbol_Map{value: "let", type: TokenType::TOK_LET}
+];
+
+const d_symbol: [D_Symbol_Map; 4] = [
+ D_Symbol_Map{value: "==", type: TokenType::TOK_EQEQ},
+ D_Symbol_Map{value: "!=", type: TokenType::TOK_NOTEQ},
+ D_Symbol_Map{value: "->", type: TokenType::TOK_ARROW},
+ D_Symbol_Map{value: "::", type: TokenType::TOK_DCOLON}
+];
+
+const s_symbol: [S_Symbol_Map; 12] = [
+ S_Symbol_Map{value: '=', type: TokenType::TOK_EQUALS},
+ S_Symbol_Map{value: ';', type: TokenType::TOK_SEMICOLON},
+ S_Symbol_Map{value: '(', type: TokenType::TOK_LPAREN},
+ S_Symbol_Map{value: ')', type: TokenType::TOK_RPAREN},
+ S_Symbol_Map{value: '{', type: TokenType::TOK_LBRACE},
+ S_Symbol_Map{value: '}', type: TokenType::TOK_RBRACE},
+ S_Symbol_Map{value: '[', type: TokenType::TOK_LBRACKET},
+ S_Symbol_Map{value: ']', type: TokenType::TOK_RBRACKET},
+ S_Symbol_Map{value: '+', type: TokenType::TOK_PLUS},
+ S_Symbol_Map{value: '-', type: TokenType::TOK_MINUS},
+ S_Symbol_Map{value: '/', type: TokenType::TOK_SLASH},
+ S_Symbol_Map{value: '*', type: TokenType::TOK_STAR},
+];
+
+// Helper function to print error messages with line and column info
+const print_error -> fn (msg: *char, line: int, col: int) void {
+ sys::eprint("Lexer Error at line ");
+
+ // Allocate buffer for line number
+ let line_buf: *char = cast<*char>(alloc(20));
+ string::int_to_str(line, line_buf, 20);
+ sys::eprint(line_buf);
+ free(line_buf);
+
+ sys::eprint(", column ");
+
+ // Allocate buffer for column number
+ let col_buf: *char = cast<*char>(alloc(20));
+ string::int_to_str(col, col_buf, 20);
+ sys::eprint(col_buf);
+ free(col_buf);
+
+ sys::eprint(": ");
+ sys::eprint(msg);
+ sys::eprint("\n");
+}
+
+#takes_ownership
+pub const free_tokens -> fn (tks: *Token) void {
+ if (tks == cast<*Token>(0)) {
+ return;
+ }
+
+ if (tks.list != cast<*Token>(0)) {
+ loop [i: int = 0](i < tks.size) : (++i) {
+ if (tks.list[i].value != cast<*char>(0)) {
+ free(tks.list[i].value);
+ }
+ }
+ free(tks.list);
+ }
+ free(tks);
+}
+
+const make_token -> fn (type: int, value: *char, line: int, col: int) Token {
+ let tk: Token;
+ tk.type = type;
+ tk.value = value;
+ tk.line = line;
+ tk.col = col;
+ return tk;
+}
+
+const is_whitespace -> fn (ch: char) bool {
+ return (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r');
+}
+
+#returns_ownership
+pub const scan -> fn (path: *char) *Token {
+ // Validate input
+ if (path == cast<*char>(0)) {
+ sys::eprint("Lexer Error: NULL input provided to scan()\n");
+ return cast<*Token>(0);
+ }
+
+ let src_len: int = string::strlen(path);
+ if (src_len == 0) {
+ sys::eprint("Lexer Warning: Empty input provided to scan()\n");
+ }
+
+ let tks: *Token = cast<*Token>(alloc(sizeof));
+ if (tks == cast<*Token>(0)) {
+ sys::eprint("Lexer Error: Failed to allocate memory for token structure\n");
+ return cast<*Token>(0);
+ }
+
+ tks.capacity = src_len * 2;
+ tks.list = cast<*Token>(alloc(tks.capacity * sizeof));
+ if (tks.list == cast<*Token>(0)) {
+ sys::eprint("Lexer Error: Failed to allocate memory for token list\n");
+ free(tks);
+ return cast<*Token>(0);
+ }
+
+ tks.size = 0;
+ tks.line = 1;
+ tks.col = 1;
+
+ let i: int = 0;
+ loop (i < src_len) {
+ let ch: char = path[i];
+
+ // Skip whitespace but count lines/cols
+ if (is_whitespace(ch)) {
+ if (ch == '\n') {
+ tks.line = tks.line + 1;
+ tks.col = 1;
+ } else {
+ tks.col = tks.col + 1;
+ }
+ i = i + 1;
+ continue;
+ }
+
+ // Handle comments (if they start with #)
+ if (ch == '#') {
+ // Skip until end of line
+ loop ((i < src_len) && (path[i] != '\n')) {
+ i = i + 1;
+ }
+ continue;
+ }
+
+ // Handle identifiers and keywords
+ if (string::is_alpha(ch)) {
+ let start_pos: int = i;
+ let start_col: int = tks.col;
+
+ // Scan to end of identifier
+ loop ((i < src_len) && string::is_alnum(path[i])) {
+ i = i + 1;
+ tks.col = tks.col + 1;
+ }
+
+ let length: int = i - start_pos;
+ let word: *char = cast<*char>(alloc(length + 1));
+
+ if (word == cast<*char>(0)) {
+ print_error("Failed to allocate memory for identifier",
+ tks.line, start_col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+
+ // Copy the identifier
+ let k: int = 0;
+ loop (k < length) {
+ word[k] = path[start_pos + k];
+ k = k + 1;
+ }
+ word[length] = '\0';
+
+ // Check keywords
+ let matched: int = 0;
+ loop [j: int = 0](j < 10) : (++j) {
+ if (string::strcmp(word, k_symbol[j].value) == 0) {
+ tks.list[tks.size] = make_token(k_symbol[j].type, word,
+ tks.line, start_col);
+ tks.size = tks.size + 1;
+ matched = 1;
+ break;
+ }
+ }
+
+ if (matched == 0) {
+ tks.list[tks.size] = make_token(TokenType::TOK_IDENT, word,
+ tks.line, start_col);
+ tks.size = tks.size + 1;
+ }
+
+ continue;
+ }
+
+ // Handle numbers
+ if (string::is_digit(ch)) {
+ let start_pos: int = i;
+ let start_col: int = tks.col;
+
+ loop ((i < src_len) && string::is_digit(path[i])) {
+ i = i + 1;
+ tks.col = tks.col + 1;
+ }
+
+ let length: int = i - start_pos;
+ let num: *char = cast<*char>(alloc(length + 1));
+
+ if (num == cast<*char>(0)) {
+ print_error("Failed to allocate memory for number", tks.line, start_col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+
+ let k: int = 0;
+ loop (k < length) {
+ num[k] = path[start_pos + k];
+ k = k + 1;
+ }
+ num[length] = '\0';
+
+ tks.list[tks.size] = make_token(TokenType::TOK_NUMBER, num,
+ tks.line, start_col);
+ tks.size = tks.size + 1;
+ continue;
+ }
+
+ // Handle string literals
+ if (ch == '"') {
+ let start_col: int = tks.col;
+ i = i + 1;
+ tks.col = tks.col + 1;
+ let start_pos: int = i;
+
+ loop ((i < src_len) && (path[i] != '"')) {
+ if (path[i] == '\n') {
+ print_error("Unterminated string literal (newline encountered)",
+ tks.line, start_col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+ i = i + 1;
+ tks.col = tks.col + 1;
+ }
+
+ // Check if we reached end of file without closing quote
+ if (i >= src_len) {
+ print_error("Unterminated string literal (end of file)",
+ tks.line, start_col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+
+ let length: int = i - start_pos;
+ let str_val: *char = cast<*char>(alloc(length + 1));
+
+ if (str_val == cast<*char>(0)) {
+ print_error("Failed to allocate memory for string", tks.line, start_col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+
+ let k: int = 0;
+ loop (k < length) {
+ str_val[k] = path[start_pos + k];
+ k = k + 1;
+ }
+ str_val[length] = '\0';
+
+ tks.list[tks.size] = make_token(TokenType::TOK_STRING, str_val,
+ tks.line, start_col);
+ tks.size = tks.size + 1;
+
+ i = i + 1; // Skip closing quote
+ tks.col = tks.col + 1;
+ continue;
+ }
+
+ // Handle double-character symbols
+ let matched: int = 0;
+ if (i + 1 < src_len) {
+ let two: *char = cast<*char>(alloc(3 * sizeof));
+
+ if (two == cast<*char>(0)) {
+ print_error("Failed to allocate memory for symbol", tks.line, tks.col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+
+ two[0] = path[i];
+ two[1] = path[i + 1];
+ two[2] = '\0';
+
+ loop [k: int = 0](k < 4) : (++k) {
+ if (string::strcmp(two, d_symbol[k].value) == 0) {
+ let start_col: int = tks.col;
+ tks.list[tks.size] = make_token(d_symbol[k].type, two,
+ tks.line, start_col);
+ tks.size = tks.size + 1;
+ tks.col = tks.col + 2;
+ i = i + 2;
+ matched = 1;
+ break;
+ }
+ }
+
+ if (matched == 0) {
+ free(two);
+ }
+ }
+
+ // Handle single-character symbols
+ if (matched == 0) {
+ matched = 0;
+ loop [k: int = 0](k < 12) : (++k) {
+ if (ch == s_symbol[k].value) {
+ let start_col: int = tks.col;
+
+ if (ch == '\0') {
+ print_error("Failed to allocate memory for symbol", tks.line,
+ start_col);
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+
+ // Ownership transferred to token - will be freed by free_tokens()
+ tks.list[tks.size] =
+ make_token(s_symbol[k].type, string::from_char(ch),
+ tks.line, start_col);
+ tks.size = tks.size + 1;
+ tks.col = tks.col + 1;
+ i = i + 1;
+ matched = 1;
+ break;
+ }
+ }
+
+ // If still no match, we have an unknown character
+ if (matched == 0) {
+ sys::eprint("Lexer Error at line ");
+
+ let line_buf: *char = cast<*char>(alloc(20));
+ string::int_to_str(tks.line, line_buf, 20);
+ sys::eprint(line_buf);
+ free(line_buf);
+
+ sys::eprint(", column ");
+
+ let col_buf: *char = cast<*char>(alloc(20));
+ string::int_to_str(tks.col, col_buf, 20);
+ sys::eprint(col_buf);
+ free(col_buf);
+
+ sys::eprint(": Unknown character '");
+ let unknown: *char = cast<*char>(alloc(2));
+ unknown[0] = ch;
+ unknown[1] = '\0';
+ sys::eprint(unknown);
+ free(unknown);
+ sys::eprint("'\n");
+
+ free_tokens(tks);
+ return cast<*Token>(0);
+ }
+ }
+ }
+
+ return tks;
+}
diff --git a/tests/LPBS/src/main.lx b/tests/LPBS/src/main.lx
new file mode 100644
index 00000000..f0e27b03
--- /dev/null
+++ b/tests/LPBS/src/main.lx
@@ -0,0 +1,27 @@
+@module "main"
+
+@use "ast_helper" as ast_helper
+@use "lexer" as lexer
+@use "parser" as psr
+@use "time" as time
+@use "ast" as ast
+@use "io" as io
+
+pub const main -> fn () int {
+ let speed: Timer = time::timer_start();
+
+ let path: *char = "/home/thedevconnor/Projects/Luma/tests/LPBS/build.lmb";
+ let file: *char = io::read_file(path);
+ let lex: *Token = lexer::scan(file);
+
+ let parser: *Stmt = psr::parse(lex, path);
+ ast_helper::print_stmt(parser);
+
+ free(file);
+ lexer::free_tokens(lex);
+ ast::free_stmt(parser);
+
+ let elapsed: int = time::timer_elapsed_ms(speed);
+ io::print_int("Took %dms\n", [elapsed]);
+ return 0;
+}
diff --git a/tests/LPBS/src/parser/helper.lx b/tests/LPBS/src/parser/helper.lx
new file mode 100644
index 00000000..1e89d974
--- /dev/null
+++ b/tests/LPBS/src/parser/helper.lx
@@ -0,0 +1,42 @@
+@module "parser_helper"
+
+@use "lexer" as lexer
+
+pub const BindingPower -> enum {
+ BP_NONE, /**< No binding power */
+ BP_LOWEST, /**< Lowest binding power */
+ BP_ASSIGN, /**< Assignment operators (=, +=, etc.) */
+ BP_TERNARY, /**< Ternary conditional operator (? :) */
+ BP_LOGICAL_OR, /**< Logical OR operator (||) */
+ BP_LOGICAL_AND, /**< Logical AND operator (&&) */
+ BP_BITWISE_OR, /**< Bitwise OR operator (|) */
+ BP_BITWISE_XOR, /**< Bitwise XOR operator (^) */
+ BP_BITWISE_AND, /**< Bitwise AND operator (&) */
+ BP_EQUALITY, /**< Equality operators (==, !=) */
+ BP_RELATIONAL, /**< Relational operators (<, >, <=, >=) */
+ BP_RANGE, /**< Range operations (..) */
+ BP_SHIFT, /**< Shift operators (<<, >>) */
+ BP_SUM, /**< Addition and subtraction (+, -) */
+ BP_PRODUCT, /**< Multiplication, division, modulo (*, /, %) */
+ BP_EXPONENT, /**< Exponentiation operator (**) */
+ BP_UNARY, /**< Unary operators (!, ~, +, -, prefix ++/--) */
+ BP_POSTFIX, /**< Postfix operators (++/-- postfix) */
+ BP_CALL, /**< Function call or indexing */
+ BP_PRIMARY /**< Primary expressions (literals, variables) */
+};
+
+pub const Parser -> struct {
+ tks: *Token,
+ path: *char,
+ pos: int,
+};
+
+pub const init_parser -> fn (psr: *Parser, tks: *Token, path: *char) void {
+ psr.path = path;
+ psr.tks = tks;
+ psr.pos = 0;
+}
+
+pub const at_end -> fn (psr: *Parser) int { return cast(psr.pos >= psr.tks.size); }
+pub const peek -> fn (psr: *Parser) Token { return psr.tks.list[psr.pos]; }
+pub const advance -> fn (psr: *Parser) void { psr.pos = psr.pos + 1; }
\ No newline at end of file
diff --git a/tests/LPBS/src/parser/parser.lx b/tests/LPBS/src/parser/parser.lx
new file mode 100644
index 00000000..17f1a2a5
--- /dev/null
+++ b/tests/LPBS/src/parser/parser.lx
@@ -0,0 +1,116 @@
+@module "parser"
+
+@use "parser_helper" as helper
+@use "string" as string
+@use "lexer" as lexer
+@use "ast" as ast
+@use "sys" as sys
+
+const parse_expr -> fn (psr: *Parser, bp: int) *Expr;
+
+const get_infix_bp -> fn (token_type: int) int {
+ switch (token_type) {
+ lexer::TokenType::TOK_PLUS, lexer::TokenType::TOK_MINUS
+ -> return helper::BindingPower::BP_SUM;
+ lexer::TokenType::TOK_SLASH, lexer::TokenType::TOK_STAR
+ -> return helper::BindingPower::BP_PRODUCT;
+ _ -> return helper::BindingPower::BP_NONE;
+ }
+}
+
+const parse_literal -> fn (psr: *Parser) *Expr {
+ let tok: Token = helper::peek(psr);
+
+ switch (tok.type) {
+ lexer::TokenType::TOK_NUMBER -> {
+ let value: int = string::atio(tok.value);
+ helper::advance(psr);
+ return ast::create_number_node(value);
+ }
+ lexer::TokenType::TOK_IDENT -> {
+ let name: *char = tok.value;
+ helper::advance(psr);
+ return ast::create_ident_node(name);
+ }
+ lexer::TokenType::TOK_STRING -> {
+ let value: *char = tok.value;
+ helper::advance(psr);
+ return ast::create_string_node(value);
+ }
+ _ -> {
+ output("Unexpected token in literal parsing\n");
+ return cast<*Expr>(0);
+ }
+ }
+}
+
+const nud -> fn (psr: *Parser) *Expr {
+ let tok: Token = helper::peek(psr);
+
+ switch (tok.type) {
+ lexer::TokenType::TOK_NUMBER, lexer::TokenType::TOK_IDENT,
+ lexer::TokenType::TOK_STRING -> {
+ return parse_literal(psr);
+ }
+ lexer::TokenType::TOK_LPAREN -> {
+ helper::advance(psr);
+ let expr: *Expr = parse_expr(psr, helper::BindingPower::BP_NONE);
+ let next_tok: Token = helper::peek(psr);
+ if (next_tok.type != lexer::TokenType::TOK_RPAREN) {
+ output("Expected closing parenthesis\n");
+ return cast<*Expr>(0);
+ }
+ helper::advance(psr);
+ return ast::create_group_node(expr);
+ }
+ lexer::TokenType::TOK_PLUS, lexer::TokenType::TOK_MINUS -> {
+ let op: *char = tok.value;
+ helper::advance(psr);
+ let right: *Expr = parse_expr(psr, helper::BindingPower::BP_UNARY);
+ return ast::create_unary_node(op[0], right);
+ }
+ _ -> {
+ output("Unexpected token in prefix position\n");
+ return cast<*Expr>(0);
+ }
+ }
+}
+
+const led -> fn (psr: *Parser, left: *Expr, token: Token) *Expr {
+ switch (token.type) {
+ lexer::TokenType::TOK_EQEQ, lexer::TokenType::TOK_NOTEQ, lexer::TokenType::TOK_PLUS,
+ lexer::TokenType::TOK_MINUS, lexer::TokenType::TOK_STAR, lexer::TokenType::TOK_SLASH -> {
+ let op: *char = psr.tks.list[psr.pos].value;
+ helper::advance(psr);
+ let right: *Expr = parse_expr(psr, get_infix_bp(token.type));
+ return ast::create_binary_node(op[0], left, right);
+ }
+ _ -> {
+ output("Unexpected token in infix position\n");
+ return cast<*Expr>(0);
+ }
+ }
+}
+
+const parse_expr -> fn (psr: *Parser, bp: int) *Expr {
+ let left: *Expr = nud(psr);
+
+ // Inline the led logic here
+ loop (helper::at_end(psr) == 0 && bp < get_infix_bp(helper::peek(psr).type)) {
+ let tok: Token = helper::peek(psr);
+ let next_bp: int = get_infix_bp(tok.type);
+
+ left = led(psr, left, tok);
+ }
+
+ return left;
+}
+
+pub const parse -> fn (tks: *Token, path: *char) *Stmt {
+ let psr: Parser;
+ helper::init_parser(&psr, tks, path);
+
+ let node: *Expr = parse_expr(&psr, helper::BindingPower::BP_NONE);
+
+ return ast::create_program_node(node, tks.size);
+}
diff --git a/tests/chess_engine/board.lx b/tests/chess_engine/board.lx
new file mode 100644
index 00000000..9eca49f7
--- /dev/null
+++ b/tests/chess_engine/board.lx
@@ -0,0 +1,343 @@
+@module "board"
+
+@use "string" as string
+@use "piece" as piece
+
+pub const Move -> struct {
+ from_col: int,
+ from_row: int,
+ to_col: int,
+ to_row: int
+};
+
+pub const Board -> struct {
+ starting_fen: *char,
+ length_fen: int,
+ squares: *char,
+
+ white_can_castle_kingside: int,
+ white_can_castle_queenside: int,
+ black_can_castle_kingside: int,
+ black_can_castle_queenside: int,
+};
+
+#returns_ownership
+pub const init_board -> fn (bd: *Board) *Board {
+ bd.squares = cast<*char>(alloc(8 * sizeof<*char>));
+
+ // Initialize the chess board with starting positions
+ bd.starting_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
+ bd.length_fen = string::strlen(bd.starting_fen);
+
+ bd.white_can_castle_kingside = 1;
+ bd.white_can_castle_queenside = 1;
+ bd.black_can_castle_kingside = 1;
+ bd.black_can_castle_queenside = 1;
+
+ loop [i: int = 0, row: int = 0, col: int = 0](i < bd.length_fen) : (++i) {
+ let c: char = bd.starting_fen[i];
+ if (c == '/') {
+ row = row + 1;
+ col = 0;
+ } elif (c >= '1' && c <= '8') {
+ let empty_squares: int = cast(c - '0');
+ loop [j: int = 0](j < empty_squares) : (++j) {
+ bd.squares[row * 8 + col] = '.';
+ col = col + 1;
+ }
+ } else {
+ bd.squares[row * 8 + col] = c;
+ col = col + 1;
+ }
+ }
+
+ return bd;
+}
+
+pub const parse_move -> fn (mv: *Move, move: *char) *Move {
+ let from_col: char = move[0];
+ let from_row: char = move[1];
+ let to_col: char = move[2];
+ let to_row: char = move[3];
+
+ mv.from_col = cast(from_col) - cast('a');
+ mv.from_row = 7 - (cast(from_row) - cast('1'));
+ mv.to_col = cast(to_col) - cast('a');
+ mv.to_row = 7 - (cast(to_row) - cast('1'));
+
+ return mv;
+}
+
+pub const validate_pawn_path -> fn (bd: *Board, mv: *Move, dr: int, dc: int) int {
+ if (dc == 0 && (dr == -2 || dr == 2)) {
+ let mid_row: int = (mv.from_row + mv.to_row) / 2;
+ let mid: char = bd.squares[mid_row * 8 + mv.from_col];
+ if (mid != '.') { return 0; }
+ }
+ return 1;
+}
+
+pub const is_path_clear -> fn (bd: *Board, mv: *Move) int {
+ let dc: int = mv.to_col - mv.from_col;
+ let dr: int = mv.to_row - mv.from_row;
+
+ let step_c: int;
+ if (dc == 0) { step_c = 0;
+ } elif (dc > 0) { step_c = 1;
+ } else { step_c = -1; }
+
+ let step_r: int;
+ if (dr == 0) { step_r = 0;
+ } elif (dr > 0) { step_r = 1;
+ } else { step_r = -1; }
+
+ let curr_col: int = mv.from_col + step_c;
+ let curr_row: int = mv.from_row + step_r;
+
+ loop (curr_col != mv.to_col || curr_row != mv.to_row) {
+ if (bd.squares[curr_row * 8 + curr_col] != '.') {
+ return 0;
+ }
+
+ curr_col = curr_col + step_c;
+ curr_row = curr_row + step_r;
+ }
+
+ return 1;
+}
+
+pub const is_move_legal -> fn (rule: *PieceRules, bd: *Board, mv: *Move) int {
+ let dc: int = mv.to_col - mv.from_col;
+ let dr: int = mv.to_row - mv.from_row;
+ if (piece::can_move_direction(rule, dc, dr) == 0) { return 0; }
+ if (rule.can_slide == 1 && rule.can_jump == 0) {
+ if (is_path_clear(bd, mv) == 0) { return 0; }
+ }
+ return 1;
+}
+
+pub const is_king_in_check -> fn (bd: *Board, is_white: int) int {
+ let king_char: char;
+ if (is_white == 1) { king_char = 'K'; }
+ else { king_char = 'k'; }
+
+ let king_row: int = -1;
+ let king_col: int = -1;
+
+ // Find the king position
+ loop [i: int = 0](i < 64) : (++i) {
+ if (bd.squares[i] == king_char) {
+ king_row = i / 8;
+ king_col = i % 8;
+ break;
+ }
+ }
+
+ if (king_row == -1) {
+ // Should never happen unless the king was captured (illegal position)
+ output("Error: King not found on board!\n");
+ return 1;
+ }
+
+ loop [i: int = 0](i < 64) : (++i) {
+ let ch: char = bd.squares[i];
+ if (ch == '.') { continue; }
+
+ // Skip same color pieces
+ if (is_white == 1 && piece::is_white_piece(ch) == 1) { continue; }
+ if (is_white == 0 && piece::is_black_piece(ch) == 1) { continue; }
+
+ let rule: *PieceRules = piece::get_rules(ch);
+ if (rule == cast<*PieceRules>(0)) { continue; }
+
+ let from_row: int = i / 8;
+ let from_col: int = i % 8;
+ let dr: int = king_row - from_row;
+ let dc: int = king_col - from_col;
+
+ // Special handling for pawn attacks
+ if (ch == 'P' || ch == 'p') {
+ // Pawns attack diagonally forward only
+ if (ch == 'P' && dr == -1 && (dc == -1 || dc == 1)) { return 1; }
+ if (ch == 'p' && dr == 1 && (dc == -1 || dc == 1)) { return 1; }
+ continue;
+ }
+
+ // Check if move direction matches the piece’s legal move
+ let can_attack: int = piece::can_move_direction(rule, dc, dr);
+ if (can_attack == 0) { continue; }
+
+ // Check path clearance for sliding pieces
+ if (rule.can_slide == 1 && rule.can_jump == 0) {
+ let mv: Move = {
+ from_row: from_row,
+ from_col: from_col,
+ to_row: king_row,
+ to_col: king_col
+ };
+ let clear: int = is_path_clear(bd, &mv);
+ if (clear == 0) { continue; }
+ }
+
+ // If we reach this point → piece can attack the king
+ return 1;
+ }
+
+ return 0;
+}
+
+pub const update_board -> fn (bd: *Board, mv: *Move, white_to_move: *int, is_valid: *int) void {
+ let from_index: int = mv.from_row * 8 + mv.from_col;
+ let to_index: int = mv.to_row * 8 + mv.to_col;
+
+ // Safety: Ensure coordinates are within bounds
+ if (from_index < 0 || from_index >= 64 || to_index < 0 || to_index >= 64) {
+ output("Error: Move out of bounds.\n");
+ *is_valid = 1;
+ return;
+ }
+
+ let piece_char: char = bd.squares[from_index];
+ if (piece_char == '.') {
+ output("Error: No piece at source square.\n");
+ *is_valid = 1;
+ return;
+ }
+
+ let target: char = bd.squares[to_index];
+
+ // Determine piece color
+ // FIXME: Need to fix when cast the result of a bool it returns -1 and 0 instead of 1 and 0
+ let is_white_piece: int = piece::is_white_piece(piece_char);
+ let is_black_piece: int = piece::is_black_piece(piece_char);
+
+ // Validate turn (fixed)
+ if (*white_to_move == 1 && is_white_piece == 0) {
+ output("Error: It's White's turn.\n");
+ *is_valid = 1;
+ return;
+ }
+ if (*white_to_move == 0 && is_black_piece == 0) {
+ output("Error: It's Black's turn.\n");
+ *is_valid = 1;
+ return;
+ }
+
+ // Check capture legality
+ if (piece::can_capture(piece_char, target) == 0) {
+ output("Error: Cannot capture your own piece.\n");
+ *is_valid = 1;
+ return;
+ }
+
+ // Validate piece movement rules
+ let rule: *PieceRules = piece::get_rules(piece_char);
+ if (rule == cast<*PieceRules>(0)) {
+ output("Error: Unknown piece type.\n");
+ *is_valid = 1;
+ return;
+ }
+
+ let dc: int = mv.to_col - mv.from_col;
+ let dr: int = mv.to_row - mv.from_row;
+
+ // Special handling for pawns
+ if (piece_char == 'P' || piece_char == 'p') {
+ if (piece::validate_pawn_move(rule, dc, dr, mv.from_row, target, is_white_piece) == 0 ||
+ validate_pawn_path(bd, mv, dr, dc) == 0) {
+ output("Error: Illegal pawn move.\n");
+ *is_valid = 1;
+ return;
+ }
+ } else {
+ if (is_move_legal(rule, bd, mv) == 0) {
+ output("Error: Illegal move for this piece.\n");
+ *is_valid = 1;
+ return;
+ }
+ }
+
+ // Save old state
+ let backup_from: char = bd.squares[from_index];
+ let backup_to: char = bd.squares[to_index];
+
+ // Simulate move
+ bd.squares[to_index] = piece_char;
+ bd.squares[from_index] = '.';
+
+ // Check if own king is now in check
+ let in_check: int = is_king_in_check(bd, -cast(is_white_piece == 1));
+ if (in_check == 1) {
+ // Undo move
+ bd.squares[from_index] = backup_from;
+ bd.squares[to_index] = backup_to;
+ output("Error: Move leaves king in check.\n");
+ *is_valid = 1;
+ return;
+ }
+
+ // Flip turn
+ if (*white_to_move == 1) {
+ *white_to_move = 0;
+ } else {
+ *white_to_move = 1;
+ }
+
+ // Mark move as valid (0)
+ *is_valid = 0;
+}
+
+pub const print_board -> fn (bd: *Board, moves: **char, move_count: int) void {
+ output(" ╔═════════════════╗");
+
+ // Show moves on the right side of the board
+ if (move_count > 0) {
+ output(" Move History");
+ }
+ output("\n");
+
+ let total_rows: int = (move_count + 1) / 2;
+ if (total_rows < 8) { total_rows = 8; }
+
+ loop [i: int = 0](i < total_rows) : (++i) {
+ if (i < 8) {
+ output(8 - i, " ║ ");
+ loop [j: int = 0](j < 8) : (++j) {
+ let piece: *char;
+ switch (bd.squares[i * 8 + j]) {
+ 'P' -> { piece = "♟"; }
+ 'N' -> { piece = "♞"; }
+ 'B' -> { piece = "♝"; }
+ 'R' -> { piece = "♜"; }
+ 'Q' -> { piece = "♛"; }
+ 'K' -> { piece = "♚"; }
+ 'p' -> { piece = "♙"; }
+ 'n' -> { piece = "♘"; }
+ 'b' -> { piece = "♗"; }
+ 'r' -> { piece = "♖"; }
+ 'q' -> { piece = "♕"; }
+ 'k' -> { piece = "♔"; }
+ '.' -> { piece = "·"; }
+ }
+ output(piece, " ");
+ }
+ output("║");
+ } else {
+ output(" ");
+ }
+
+ let move_pair_index: int = i * 2;
+ if (move_pair_index < move_count) {
+ let move_num: int = move_pair_index / 2 + 1;
+ output(" ", move_num, ". ", moves[move_pair_index]);
+ if (move_pair_index + 1 < move_count) {
+ output(" ", moves[move_pair_index + 1]);
+ }
+ }
+ output("\n");
+ }
+
+ output(" ╚═════════════════╝\n");
+ output(" a b c d e f g h\n\n");
+}
+
diff --git a/tests/chess_engine/build_chess.mk b/tests/chess_engine/build_chess.mk
new file mode 100644
index 00000000..034383d9
--- /dev/null
+++ b/tests/chess_engine/build_chess.mk
@@ -0,0 +1,90 @@
+# Makefile for Luma Chess Engine
+LUMA = ./../../luma
+NAME = chess
+
+MAIN = main.lx
+CHESS_SRCS = $(filter-out $(MAIN), $(wildcard *.lx))
+
+STD_LIBS = ../../std/string.lx \
+ ../../std/terminal.lx \
+ ../../std/termfx.lx \
+ ../../std/memory.lx
+
+ALL_SRCS = $(MAIN) $(CHESS_SRCS) $(STD_LIBS)
+
+# Detect OS (Windows_NT is predefined on Windows)
+ifeq ($(OS),Windows_NT)
+ EXE_EXT := .exe
+ RM := del /Q
+ MKDIR := if not exist build mkdir build
+ SEP := &
+ RUN_PREFIX :=
+else
+ EXE_EXT :=
+ RM := rm -f
+ MKDIR := mkdir -p build
+ SEP := ;
+ RUN_PREFIX := ./
+endif
+
+TARGET = $(NAME)$(EXE_EXT)
+
+.PHONY: all
+all: $(TARGET)
+
+# Build chess engine
+$(TARGET): $(ALL_SRCS)
+ @echo "Building chess engine..."
+ $(LUMA) $(MAIN) -name $(NAME) -l $(CHESS_SRCS) $(STD_LIBS)
+ @echo "Build complete: $(TARGET)"
+
+.PHONY: run
+run: $(TARGET)
+ @echo "Starting chess engine..."
+ $(RUN_PREFIX)$(TARGET)
+
+.PHONY: valgrind
+valgrind: $(TARGET)
+ifeq ($(OS),Windows_NT)
+ @echo "Valgrind not supported on Windows natively."
+else
+ @echo "Running chess engine with valgrind..."
+ valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET)
+endif
+
+.PHONY: clean
+clean:
+ @echo "Cleaning build artifacts..."
+ -$(RM) $(TARGET)
+ -$(RM) obj\*.o 2>nul || true
+ -$(RM) obj/*.o 2>/dev/null || true
+
+.PHONY: rebuild
+rebuild: clean all
+
+.PHONY: list
+list:
+ @echo "Main file:"
+ @echo " $(MAIN)"
+ @echo ""
+ @echo "Chess engine sources:"
+ @for src in $(CHESS_SRCS); do echo " $$src"; done
+ @echo ""
+ @echo "Standard library:"
+ @for lib in $(STD_LIBS); do echo " $$lib"; done
+
+.PHONY: help
+help:
+ @echo "Luma Chess Engine Build System"
+ @echo ""
+ @echo "Build Targets:"
+ @echo " all - Build the chess engine (default)"
+ @echo " rebuild - Clean and rebuild"
+ @echo ""
+ @echo "Run Targets:"
+ @echo " run - Build and run"
+ @echo " valgrind - Run with memory leak detection (Linux only)"
+ @echo ""
+ @echo "Development:"
+ @echo " list - Show all source files"
+ @echo " clean - Remove build artifacts"
diff --git a/tests/chess_engine/main.lx b/tests/chess_engine/main.lx
new file mode 100644
index 00000000..48c9793c
--- /dev/null
+++ b/tests/chess_engine/main.lx
@@ -0,0 +1,60 @@
+@module "main"
+
+@use "terminal" as term
+@use "string" as string
+@use "termfx" as termfx
+@use "memory" as mem
+@use "board" as board
+
+// TODO: add in castle, en passant logic
+
+pub const main -> fn () int {
+ let bd: Board;
+ let mv: Move;
+ board::init_board(&bd);
+
+ let user_input: *char = cast<*char>(alloc(5 * sizeof));
+ let moves: **char = cast<**char>(alloc(256 * sizeof<*char>));
+ loop [j: int = 0](j < 256) : (++j) {
+ moves[j] = cast<*char>(alloc(6 * sizeof));
+ }
+
+ defer {
+ free(bd.squares); free(user_input);
+ loop [j: int = 0](j < 256) : (++j) { free(moves[j]); }
+ free(moves);
+ }
+
+ let white_to_move: int = 1; // 1 = white's turn, 0 = black's turn
+ let move_count: int = 0;
+ let is_valid: int = 0;
+ loop {
+ output(termfx::CLEAR_SCREEN, termfx::CURSOR_HOME);
+
+ if (white_to_move == 1) { output("White to move\n"); }
+ else { output("Black to move\n"); }
+
+ board::print_board(&bd, moves, move_count);
+
+ term::get_line("Enter your move (or 'exit' to quit): ", user_input, 5);
+
+ if (string::strcmp(user_input, "exit") == 0 ||
+ string::strcmp(user_input, "quit") == 0) {
+ break;
+ }
+
+ let move: *Move = board::parse_move(&mv, user_input);
+ board::update_board(&bd, move, &white_to_move, &is_valid);
+
+ if (is_valid == 0) {
+ string::copy(moves[move_count], user_input);
+ move_count = move_count + 1;
+ }
+
+ is_valid = 0;
+ input("");
+ output("\n");
+ }
+
+ return 0;
+}
diff --git a/tests/chess_engine/piece.lx b/tests/chess_engine/piece.lx
new file mode 100644
index 00000000..028627ab
--- /dev/null
+++ b/tests/chess_engine/piece.lx
@@ -0,0 +1,142 @@
+@module "piece"
+
+pub const PieceRules -> struct {
+ piece_type: char,
+ can_jump: int,
+ can_slide: int,
+ move_offsets: [int; 16],
+ num_offsets: int
+};
+
+const RULES: [PieceRules; 6] = [
+ PieceRules {
+ piece_type: 'P', can_jump: 0, can_slide: 0, num_offsets: 4,
+ // [forward 1 white, forward 2 white, forward 1 black, forward 2 black]
+ // Note: Captures handled separately in board logic
+ move_offsets: [0, -1, 0, -2, 0, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0],
+ },
+ PieceRules {
+ piece_type: 'N', can_jump: 1, can_slide: 0, num_offsets: 8,
+ move_offsets: [-2, -1, -2, 1, -1, -2, -1, 2, 1, -2, 1, 2, 2, -1, 2, 1],
+ },
+ PieceRules {
+ piece_type: 'B', can_jump: 0, can_slide: 1, num_offsets: 4,
+ move_offsets: [-1, -1, -1, 1, 1, -1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0],
+ },
+ PieceRules {
+ piece_type: 'R', can_jump: 0, can_slide: 1, num_offsets: 4,
+ move_offsets: [-1, 0, 1, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
+ },
+ PieceRules {
+ piece_type: 'Q', can_jump: 0, can_slide: 1, num_offsets: 8,
+ move_offsets: [-1, -1, -1, 1, 1, -1, 1, 1, -1, 0, 1, 0, 0, -1, 0, 1],
+ },
+ PieceRules {
+ piece_type: 'K', can_jump: 0, can_slide: 0, num_offsets: 8,
+ move_offsets: [-1, -1, -1, 1, 1, -1, 1, 1, -1, 0, 1, 0, 0, -1, 0, 1],
+ }
+];
+
+pub const is_white_piece -> fn (ch: char) int { return -cast(ch >= 'A' && ch <= 'Z'); }
+pub const is_black_piece -> fn (ch: char) int { return -cast(ch >= 'a' && ch <= 'z'); }
+
+// Get rules for a specific piece
+pub const get_rules -> fn (piece: char) *PieceRules {
+ let piece_upper: char = piece;
+ if (piece >= 'a' && piece <= 'z') {
+ piece_upper = cast(cast(piece) - 32);
+ }
+
+ loop [i: int = 0](i < 6) : (++i) { // Changed from 5 to 6
+ if (RULES[i].piece_type == piece_upper) {
+ return &RULES[i];
+ }
+ }
+
+ return cast<*PieceRules>(0);
+}
+
+pub const can_capture -> fn (attacker: char, target: char) int {
+ if (target == '.') { return 1; }
+ let atk_white: int = is_white_piece(attacker);
+ let tgt_white: int = is_white_piece(target);
+ return -cast(atk_white != tgt_white);
+}
+
+// Special pawn validation function (pawns have unique rules)
+pub const validate_pawn_move -> fn (rule: *PieceRules, dc: int, dr: int,
+ from_row: int, target: char, is_white: int) int {
+ // Determine direction based on color
+ let direction: int;
+ let start_row: int;
+
+ if (is_white == 1) {
+ direction = -1;
+ start_row = 6;
+ } else {
+ direction = 1;
+ start_row = 1;
+ }
+
+ // Case 1: Forward one square (must be empty)
+ if (dc == 0 && dr == direction && target == '.') {
+ return 1;
+ }
+
+ // Case 2: Forward two squares from starting position (both must be empty)
+ if (dc == 0 && dr == direction * 2 && from_row == start_row && target == '.') {
+ return 1; // Note: Caller must check middle square
+ }
+
+ // Case 3: Diagonal capture (must have enemy piece)
+ let abs_dc: int;
+ if (dc < 0) {
+ abs_dc = -dc;
+ } else {
+ abs_dc = dc;
+ }
+
+ if (abs_dc == 1 && dr == direction && target != '.') {
+ return 1;
+ }
+
+ return 0;
+}
+
+pub const can_move_direction -> fn (rule: *PieceRules, dc: int, dr: int) int {
+ if (rule.can_slide == 1) {
+ let step_c: int;
+ if (dc == 0) {
+ step_c = 0;
+ } elif (dc > 0) {
+ step_c = 1;
+ } else {
+ step_c = -1;
+ }
+
+ let step_r: int;
+ if (dr == 0) {
+ step_r = 0;
+ } elif (dr > 0) {
+ step_r = 1;
+ } else {
+ step_r = -1;
+ }
+
+ loop [i: int = 0](i < rule.num_offsets) : (++i) {
+ if (step_c == rule.move_offsets[i * 2] &&
+ step_r == rule.move_offsets[i * 2 + 1]) {
+ return 1;
+ }
+ }
+ } else {
+ loop [i: int = 0](i < rule.num_offsets) : (++i) {
+ if (dc == rule.move_offsets[i * 2] &&
+ dr == rule.move_offsets[i * 2 + 1]) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
diff --git a/tests/3d_spinning_cube.lx b/tests/rotating_cube/3d_spinning_cube.lx
similarity index 67%
rename from tests/3d_spinning_cube.lx
rename to tests/rotating_cube/3d_spinning_cube.lx
index 4480b6f9..2122ddd4 100644
--- a/tests/3d_spinning_cube.lx
+++ b/tests/rotating_cube/3d_spinning_cube.lx
@@ -4,10 +4,8 @@
@use "memory" as mem
@use "string" as string
@use "termfx" as fx
-
-let A: double;
-let B: double;
-let C: double;
+@use "time" as time
+@use "io" as io
let zBuffer: *double;
let _colorBuffer: **char;
@@ -15,42 +13,40 @@ let _buffer: *char;
let cube_width: double;
const width: int = 160;
-const height: int = 44;
+const height: int = 50;
-const distance_from_cam: int = 100;
+const distance_from_cam: int = 30;
const increment_speed: double = 0.6;
let horizontal_offset: double;
const k1: double = 40.0;
-let cube_gradient_size: int = 12;
-
-const calculateX -> fn (i: int, j: int, k: int) double {
+const calculateX -> fn (i: int, j: int, k: int, A: double, B: double, C: double) double {
let sA: double = math::sin(A);
let cA: double = math::cos(A);
let sB: double = math::sin(B);
let cB: double = math::cos(B);
let sC: double = math::sin(C);
let cC: double = math::cos(C);
-
+
return j * sA * sB * cC - k * cA * sB * cC +
j * cA * sC + k * sA * sC + i * cB * cC;
}
-const calculateY -> fn (i: int, j: int, k: int) double {
+const calculateY -> fn (i: int, j: int, k: int, A: double, B: double, C: double) double {
let sA: double = math::sin(A);
let cA: double = math::cos(A);
let sB: double = math::sin(B);
let cB: double = math::cos(B);
let sC: double = math::sin(C);
let cC: double = math::cos(C);
-
+
return j * cA * cC + k * sA * cC -
j * sA * sB * sC + k * cA * sB * sC -
i * cB * sC;
}
-const calculateZ -> fn (i: int, j: int, k: int) double {
+const calculateZ -> fn (i: int, j: int, k: int, A: double, B: double) double {
let sA: double = math::sin(A);
let cA: double = math::cos(A);
let sB: double = math::sin(B);
@@ -59,16 +55,16 @@ const calculateZ -> fn (i: int, j: int, k: int) double {
return k * cA * cB - j * sA * cB + i * sB;
}
-const calculateForSurface -> fn (cX: double, cY: double, cZ: double,
- ch: char, color: *char) void {
- let x: double = calculateX(cast(cX), cast(cY), cast(cZ));
- let y: double = calculateY(cast(cX), cast(cY), cast(cZ));
- let z: double = calculateZ(cast(cX), cast(cY), cast(cZ)) + distance_from_cam;
+const calculateForSurface -> fn (cX: double, cY: double, cZ: double,
+ ch: char, color: *char,
+ A: double, B: double, C: double) void {
+ let x: double = calculateX(cast(cX), cast(cY), cast(cZ), A, B, C);
+ let y: double = calculateY(cast(cX), cast(cY), cast(cZ), A, B, C);
+ let z: double = calculateZ(cast(cX), cast(cY), cast(cZ), A, B) + distance_from_cam;
if (z <= 0.001) { return; }
let ooz: double = 1.0 / z;
-
if (ooz > 1000.0 || ooz < -1000.0) { return; }
let xp: int = cast(width / 2 + horizontal_offset + k1 * ooz * x * 2);
@@ -84,47 +80,58 @@ const calculateForSurface -> fn (cX: double, cY: double, cZ: double,
}
}
-const print_cube -> fn () void {
+
+const print_cube -> fn (A: double, B: double, C: double) void {
loop [cubeX: double = -cube_width](cubeX < cube_width) : (cubeX = cubeX + increment_speed) {
loop [cubeY: double = -cube_width](cubeY < cube_width) : (cubeY = cubeY + increment_speed) {
- calculateForSurface(cubeX, cubeY, -cube_width, '@', fx::BRIGHT_RED);
- calculateForSurface(cube_width, cubeY, cubeX, '+', fx::BRIGHT_GREEN);
- calculateForSurface(-cube_width, cubeY, -cubeX, '~', fx::BRIGHT_YELLOW);
- calculateForSurface(-cubeX, cubeY, cube_width, '#', fx::BRIGHT_BLUE);
- calculateForSurface(cubeX, -cube_width, -cubeY, '*', fx::BRIGHT_CYAN);
- calculateForSurface(cubeX, cube_width, cubeY, '-', fx::BRIGHT_MAGENTA);
+ calculateForSurface(cubeX, cubeY, -cube_width, '@', fx::BRIGHT_RED, A, B, C);
+ calculateForSurface(cube_width, cubeY, cubeX, '+', fx::BRIGHT_GREEN, A, B, C);
+ calculateForSurface(-cube_width, cubeY, -cubeX, '~', fx::BRIGHT_YELLOW, A, B, C);
+ calculateForSurface(-cubeX, cubeY, cube_width, '#', fx::BRIGHT_BLUE, A, B, C);
+ calculateForSurface(cubeX, -cube_width, -cubeY, '*', fx::BRIGHT_CYAN, A, B, C);
+ calculateForSurface(cubeX, cube_width, cubeY, '-', fx::BRIGHT_MAGENTA, A, B, C);
}
- }
+ }
}
+
pub const main -> fn () int {
- zBuffer = cast<*double>(alloc(160 * 44 * sizeof));
- _colorBuffer = cast<**char>(alloc(160 * 44 * 8));
- _buffer = cast<*char>(alloc(160 * 44));
+ zBuffer = cast<*double>(alloc(width * height * sizeof));
+ _colorBuffer = cast<**char>(alloc(width * height * 8));
+ _buffer = cast<*char>(alloc(width * height));
defer { free(zBuffer); free(_colorBuffer); free(_buffer); }
- output(fx::CLEAR_SCREEN, fx::CURSOR_HOME, fx::BRIGHT_MAGENTA, "3D", fx::RESET,
- fx::BRIGHT_CYAN, " Spinning Cubes in Luma\n", fx::RESET, fx::CURSOR_HIDE);
+ io::print_str("%s%s%s3D%s%s Spinning Cubes in Luma%s%s\n",
+ [fx::CLEAR_SCREEN, fx::CURSOR_HOME, fx::BRIGHT_MAGENTA,
+ fx::RESET, fx::BRIGHT_CYAN, fx::RESET, fx::CURSOR_HIDE]);
let frame_count: int = 0;
+ let A: double = 1.2;
+ let B: double = 0.8;
+ let C: double = 0.4;
+
loop {
mem::memset(cast<*void>(_buffer), 32, width * height);
mem::memset(cast<*void>(_colorBuffer), 0, width * height * 8);
mem::memset(cast<*void>(zBuffer), 0, width * height * sizeof);
- cube_width = 20.0;
- horizontal_offset = -2 * cube_width;
- print_cube();
+ // cube_width = 20.0;
+ // horizontal_offset = -2 * cube_width;
+ // print_cube(A, B, C);
cube_width = 10.0;
- horizontal_offset = 1 * cube_width;
- print_cube();
-
+ horizontal_offset = -15.0;
+ print_cube(A, B, C); // Cube 1
+
cube_width = 5.0;
- horizontal_offset = 8 * cube_width;
- print_cube();
+ horizontal_offset = -15.0;
+ print_cube(-A, -B, C * 1.5);
+
+ cube_width = 2.5;
+ horizontal_offset = -15.0;
+ print_cube(-A, B, -C * 4.5);
output(fx::CURSOR_HOME);
loop [k: int = 0](k < width * height) : (++k) {
@@ -150,8 +157,9 @@ pub const main -> fn () int {
// Increment frame count
++frame_count;
+
+ time::usleep(20000);
}
return 0;
}
-
diff --git a/tests/rotating_cube/build_cube.mk b/tests/rotating_cube/build_cube.mk
new file mode 100644
index 00000000..15079d98
--- /dev/null
+++ b/tests/rotating_cube/build_cube.mk
@@ -0,0 +1,99 @@
+# Makefile for Luma 3D Spinning Cube
+# Example:
+# ./luma tests/3d_spinning_cube.lx -name 3d -l std/math.lx std/termfx.lx std/string.lx std/memory.lx
+
+LUMA = ./../../luma
+NAME = spinning_cube
+
+MAIN = 3d_spinning_cube.lx
+SPINNING_SRCS = $(filter-out $(MAIN), $(wildcard *.lx))
+
+STD_LIBS = ../../std/termfx.lx \
+ ../../std/string.lx \
+ ../../std/math.lx \
+ ../../std/memory.lx \
+ ../../std/io.lx \
+ ../../std/time.lx \
+ ../../std/sys.lx
+
+ALL_SRCS = $(MAIN) $(SPINNING_SRCS) $(STD_LIBS)
+
+# Detect OS (Windows_NT is predefined on Windows)
+ifeq ($(OS),Windows_NT)
+ EXE_EXT := .exe
+ RM := del /Q
+ MKDIR := if not exist build mkdir build
+ RUN_PREFIX :=
+else
+ EXE_EXT :=
+ RM := rm -f
+ MKDIR := mkdir -p build
+ RUN_PREFIX := ./
+endif
+
+TARGET = $(NAME)$(EXE_EXT)
+
+.PHONY: all
+all: $(TARGET)
+
+# Build spinning cube demo
+$(TARGET): $(ALL_SRCS)
+ @echo "Building 3D spinning cube..."
+ $(LUMA) $(MAIN) -name $(NAME) -l $(SPINNING_SRCS) $(STD_LIBS)
+ @echo "Build complete: $(TARGET)"
+
+.PHONY: run
+run: $(TARGET)
+ @echo "Starting spinning cube..."
+ $(RUN_PREFIX)$(TARGET)
+
+.PHONY: valgrind
+valgrind: $(TARGET)
+ifeq ($(OS),Windows_NT)
+ @echo "Valgrind not supported on Windows natively."
+else
+ @echo "Running spinning cube with valgrind..."
+ valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET)
+endif
+
+.PHONY: clean
+clean:
+ @echo "Cleaning build artifacts..."
+ -$(RM) $(TARGET)
+ -$(RM) obj\*.o 2>nul || true
+ -$(RM) obj/*.o 2>/dev/null || true
+
+.PHONY: rebuild
+rebuild: clean all
+
+.PHONY: list
+list:
+ @echo "Main file:"
+ @echo " $(MAIN)"
+ @echo ""
+ @echo "Spinning cube sources:"
+ @for src in $(SPINNING_SRCS); do echo " $$src"; done
+ @echo ""
+ @echo "Standard library:"
+ @for lib in $(STD_LIBS); do echo " $$lib"; done
+
+.PHONY: help
+help:
+ @echo "Luma 3D Spinning Cube Build System"
+ @echo ""
+ @echo "Build Targets:"
+ @echo " all - Build Spinning Cube (default)"
+ @echo " rebuild - Clean and rebuild"
+ @echo ""
+ @echo "Run Targets:"
+ @echo " run - Build and run"
+ @echo " valgrind - Run with memory leak detection (Linux only)"
+ @echo ""
+ @echo "Development:"
+ @echo " list - Show all source files"
+ @echo " clean - Remove build artifacts"
+ @echo ""
+ @echo "Examples:"
+ @echo " make # Build"
+ @echo " make run # Build and run"
+ @echo " make list # Show what will be compiled"
diff --git a/tests/sys_test.lx b/tests/sys_test.lx
new file mode 100644
index 00000000..edd1f418
--- /dev/null
+++ b/tests/sys_test.lx
@@ -0,0 +1,343 @@
+@module "main"
+
+@use "sys" as sys
+
+// Helper function to combine flags (bitwise OR)
+const combine_flags -> fn (flag1: int, flag2: int) int {
+ return flag1 + flag2;
+}
+
+const combine_flags3 -> fn (flag1: int, flag2: int, flag3: int) int {
+ return flag1 + flag2 + flag3;
+}
+
+// Test file I/O operations
+const test_file_operations -> fn () int {
+ sys::print("=== Testing File Operations ===\n");
+
+ // Test 1: Create and write to a file
+ // O_CREAT | O_WRONLY | O_TRUNC = 64 + 1 + 512 = 577
+ let flags: int = combine_flags3(sys::O_CREAT, sys::O_WRONLY, sys::O_TRUNC);
+ let fd: int = sys::open("/tmp/luma_test.txt", flags, sys::MODE_0644);
+
+ if (sys::is_error(fd)) {
+ sys::eprint("Error: Failed to create file\n");
+ return 1;
+ }
+
+ let test_data: *char = "Hello from Luma!\n";
+ let bytes_written: int = sys::write_str(fd, test_data);
+
+ if (sys::is_error(bytes_written)) {
+ sys::eprint("Error: Failed to write to file\n");
+ sys::close(fd);
+ return 1;
+ }
+
+ sys::print("✓ File write successful\n");
+ sys::close(fd);
+
+ // Test 2: Read back the file
+ fd = sys::open("/tmp/luma_test.txt", sys::O_RDONLY, 0);
+
+ if (sys::is_error(fd)) {
+ sys::eprint("Error: Failed to open file for reading\n");
+ return 1;
+ }
+
+ let buffer: *char = cast<*char>(alloc(100));
+ defer { free(buffer); }
+
+ let bytes_read: int = sys::read(fd, cast<*void>(buffer), 99);
+
+ if (sys::is_error(bytes_read)) {
+ sys::eprint("Error: Failed to read from file\n");
+ sys::close(fd);
+ return 1;
+ }
+
+ buffer[bytes_read] = cast(0); // Null terminate
+ sys::print("✓ File read successful: ");
+ sys::print(buffer);
+
+ sys::close(fd);
+
+ // Test 3: Delete the file
+ let unlink_result: int = sys::unlink("/tmp/luma_test.txt");
+
+ if (sys::is_error(unlink_result)) {
+ sys::eprint("Error: Failed to delete file\n");
+ return 1;
+ }
+
+ sys::print("✓ File deletion successful\n\n");
+ return 0;
+}
+
+// Test directory operations
+const test_directory_operations -> fn () int {
+ sys::print("=== Testing Directory Operations ===\n");
+
+ // Test 1: Create directory
+ let mkdir_result: int = sys::mkdir("/tmp/luma_test_dir", sys::MODE_0755);
+
+ if (sys::is_error(mkdir_result)) {
+ sys::eprint("Error: Failed to create directory\n");
+ return 1;
+ }
+
+ sys::print("✓ Directory creation successful\n");
+
+ // Test 2: Change to directory
+ let chdir_result: int = sys::chdir("/tmp/luma_test_dir");
+
+ if (sys::is_error(chdir_result)) {
+ sys::eprint("Error: Failed to change directory\n");
+ sys::rmdir("/tmp/luma_test_dir");
+ return 1;
+ }
+
+ sys::print("✓ Directory change successful\n");
+
+ // Test 3: Get current directory
+ let cwd_buffer: *char = cast<*char>(alloc(256));
+ defer { free(cwd_buffer); }
+
+ let getcwd_result: *char = sys::getcwd(cwd_buffer, 256);
+
+ if (getcwd_result == cast<*char>(0)) {
+ sys::eprint("Error: Failed to get current directory\n");
+ sys::chdir("/tmp");
+ sys::rmdir("/tmp/luma_test_dir");
+ return 1;
+ }
+
+ sys::print("✓ Current directory: ");
+ sys::print(cwd_buffer);
+ sys::print("\n");
+
+ // Test 4: Return to /tmp and remove test directory
+ sys::chdir("/tmp");
+ let rmdir_result: int = sys::rmdir("/tmp/luma_test_dir");
+
+ if (sys::is_error(rmdir_result)) {
+ sys::eprint("Error: Failed to remove directory\n");
+ return 1;
+ }
+
+ sys::print("✓ Directory removal successful\n\n");
+ return 0;
+}
+
+// Test process information
+const test_process_info -> fn () int {
+ sys::print("=== Testing Process Information ===\n");
+
+ // Test 1: Get process ID
+ let pid: int = sys::getpid();
+ sys::print("✓ Process ID: ");
+ output(pid, "\n");
+
+ // Test 2: Get user ID
+ let uid: int = sys::getuid();
+ sys::print("✓ User ID: ");
+ output(uid, "\n");
+
+ // Test 3: Get group ID
+ let gid: int = sys::getgid();
+ sys::print("✓ Group ID: ");
+ output(gid, "\n\n");
+
+ return 0;
+}
+
+// Test pipe operations
+const test_pipe_operations -> fn () int {
+ sys::print("=== Testing Pipe Operations ===\n");
+ sys::print("⚠ Pipe test skipped (known issue with blocking reads)\n\n");
+ return 0;
+
+ // TODO: Debug why pipe reads block
+ // The issue appears to be that sys::read() on pipes blocks indefinitely
+ // even after successful writes. This might be a syscall wrapper issue
+ // or a problem with how the pipe file descriptors are being handled.
+}
+
+// Test file descriptor duplication
+const test_dup_operations -> fn () int {
+ sys::print("=== Testing File Descriptor Duplication ===\n");
+
+ // Create a test file (O_CREAT | O_WRONLY | O_TRUNC)
+ let flags: int = combine_flags3(sys::O_CREAT, sys::O_WRONLY, sys::O_TRUNC);
+ let fd: int = sys::open("/tmp/luma_dup_test.txt", flags, sys::MODE_0644);
+
+ if (sys::is_error(fd)) {
+ sys::eprint("Error: Failed to create test file\n");
+ return 1;
+ }
+
+ // Test dup
+ let dup_fd: int = sys::dup(fd);
+
+ if (sys::is_error(dup_fd)) {
+ sys::eprint("Error: dup() failed\n");
+ sys::close(fd);
+ sys::unlink("/tmp/luma_dup_test.txt");
+ return 1;
+ }
+
+ sys::print("✓ dup() successful\n");
+
+ // Write using duplicated fd
+ sys::write_str(dup_fd, "Written via dup'd fd\n");
+
+ sys::close(fd);
+ sys::close(dup_fd);
+
+ // Test dup2 (O_WRONLY | O_APPEND)
+ let append_flags: int = combine_flags(sys::O_WRONLY, sys::O_APPEND);
+ fd = sys::open("/tmp/luma_dup_test.txt", append_flags, 0);
+ let dup2_result: int = sys::dup2(fd, 100);
+
+ if (sys::is_error(dup2_result)) {
+ sys::eprint("Error: dup2() failed\n");
+ sys::close(fd);
+ sys::unlink("/tmp/luma_dup_test.txt");
+ return 1;
+ }
+
+ sys::print("✓ dup2() successful\n");
+
+ // Write using dup2'd fd
+ sys::write_str(100, "Written via dup2'd fd\n");
+
+ sys::close(fd);
+ sys::close(100);
+ sys::unlink("/tmp/luma_dup_test.txt");
+
+ sys::print("✓ File descriptor duplication tests complete\n\n");
+ return 0;
+}
+
+// Test error handling
+const test_error_handling -> fn () int {
+ sys::print("=== Testing Error Handling ===\n");
+
+ // Try to open non-existent file
+ let fd: int = sys::open("/nonexistent/file.txt", sys::O_RDONLY, 0);
+
+ if (sys::is_error(fd)) {
+ let errno: int = sys::get_errno(fd);
+ sys::print("✓ Error detected correctly: errno = ");
+ output(errno);
+
+ sys::print(" (");
+ if (errno == sys::ENOENT) {
+ sys::print("ENOENT - No such file or directory");
+ } else {
+ sys::print("Unknown error");
+ }
+ sys::print(")\n");
+ } else {
+ sys::eprint("Error: Should have failed to open non-existent file\n");
+ sys::close(fd);
+ return 1;
+ }
+
+ // Try to create file in non-writable directory
+ let flags: int = combine_flags(sys::O_CREAT, sys::O_WRONLY);
+ let fd2: int = sys::open("/root/test.txt", flags, sys::MODE_0644);
+
+ if (sys::is_error(fd2)) {
+ let errno2: int = sys::get_errno(fd2);
+ sys::print("✓ Permission error detected: errno = ");
+ output(errno2);
+
+ sys::print(" (");
+ if (errno2 == sys::EACCES) {
+ sys::print("EACCES - Permission denied");
+ } else {
+ sys::print("Unknown error");
+ }
+ sys::print(")\n");
+ }
+
+ sys::print("\n");
+ return 0;
+}
+
+// Test seek operations
+const test_seek_operations -> fn () int {
+ sys::print("=== Testing Seek Operations ===\n");
+
+ // Create test file with content (O_CREAT | O_RDWR | O_TRUNC)
+ let flags: int = combine_flags3(sys::O_CREAT, sys::O_RDWR, sys::O_TRUNC);
+ let fd: int = sys::open("/tmp/luma_seek_test.txt", flags, sys::MODE_0644);
+
+ if (sys::is_error(fd)) {
+ sys::eprint("Error: Failed to create test file\n");
+ return 1;
+ }
+
+ sys::write_str(fd, "0123456789ABCDEFGHIJ");
+
+ // Test SEEK_SET
+ let pos: int = sys::lseek(fd, 5, sys::SEEK_SET);
+
+ if (sys::is_error(pos)) {
+ sys::eprint("Error: SEEK_SET failed\n");
+ sys::close(fd);
+ sys::unlink("/tmp/luma_seek_test.txt");
+ return 1;
+ }
+
+ sys::print("✓ SEEK_SET to position 5 successful\n");
+
+ // Test SEEK_CUR
+ pos = sys::lseek(fd, 3, sys::SEEK_CUR);
+ sys::print("✓ SEEK_CUR +3 successful, now at: ");
+ output(pos, "\n");
+
+ // Test SEEK_END
+ pos = sys::lseek(fd, -5, sys::SEEK_END);
+ sys::print("✓ SEEK_END -5 successful, now at: ");
+ output(pos, "\n");
+
+ sys::close(fd);
+ sys::unlink("/tmp/luma_seek_test.txt");
+ sys::print("\n");
+
+ return 0;
+}
+
+pub const main -> fn () int {
+ sys::print("\n");
+ sys::print("╔════════════════════════════════════════╗\n");
+ sys::print("║ Luma System Call Test Suite ║\n");
+ sys::print("║ Platform: Linux x86_64 ║\n");
+ sys::print("╚════════════════════════════════════════╝\n\n");
+
+ let total_failures: int = 0;
+
+ total_failures = total_failures + test_process_info();
+ total_failures = total_failures + test_file_operations();
+ total_failures = total_failures + test_directory_operations();
+ total_failures = total_failures + test_pipe_operations();
+ total_failures = total_failures + test_dup_operations();
+ total_failures = total_failures + test_seek_operations();
+ total_failures = total_failures + test_error_handling();
+
+ sys::print("╔════════════════════════════════════════╗\n");
+
+ if (total_failures == 0) {
+ sys::print("║ ✓ All tests passed! ║\n");
+ } else {
+ sys::print("║ ✗ Some tests failed: ");
+ output(total_failures);
+ sys::print(" ║\n");
+ }
+
+ sys::print("╚════════════════════════════════════════╝\n\n");
+
+ return total_failures;
+}
diff --git a/tests/terminal_fps.lx b/tests/terminal_fps.lx
deleted file mode 100644
index 216337f9..00000000
--- a/tests/terminal_fps.lx
+++ /dev/null
@@ -1,156 +0,0 @@
-@module "main"
-
-@use "math" as math
-@use "memory" as mem
-@use "string" as string
-@use "termfx" as fx
-
-let A: double;
-let B: double;
-let C: double;
-
-let zBuffer: *double;
-let _colorBuffer: **char;
-let _buffer: *char;
-
-let cube_width: double;
-const width: int = 160;
-const height: int = 44;
-
-const distance_from_cam: int = 100;
-
-const increment_speed: double = 0.6;
-let horizontal_offset: double;
-const k1: double = 40.0;
-
-let cube_gradient_size: int = 12;
-
-const calculateX -> fn (i: int, j: int, k: int) double {
- let sA: double = math::sin(A);
- let cA: double = math::cos(A);
- let sB: double = math::sin(B);
- let cB: double = math::cos(B);
- let sC: double = math::sin(C);
- let cC: double = math::cos(C);
-
- return j * sA * sB * cC - k * cA * sB * cC +
- j * cA * sC + k * sA * sC + i * cB * cC;
-}
-
-const calculateY -> fn (i: int, j: int, k: int) double {
- let sA: double = math::sin(A);
- let cA: double = math::cos(A);
- let sB: double = math::sin(B);
- let cB: double = math::cos(B);
- let sC: double = math::sin(C);
- let cC: double = math::cos(C);
-
- return j * cA * cC + k * sA * cC -
- j * sA * sB * sC + k * cA * sB * sC -
- i * cB * sC;
-}
-
-const calculateZ -> fn (i: int, j: int, k: int) double {
- let sA: double = math::sin(A);
- let cA: double = math::cos(A);
- let sB: double = math::sin(B);
- let cB: double = math::cos(B);
-
- return k * cA * cB - j * sA * cB + i * sB;
-}
-
-const calculateForSurface -> fn (cX: double, cY: double, cZ: double,
- ch: char, color: *char) void {
- let x: double = calculateX(cast(cX), cast(cY), cast(cZ));
- let y: double = calculateY(cast(cX), cast(cY), cast(cZ));
- let z: double = calculateZ(cast(cX), cast(cY), cast(cZ)) + distance_from_cam;
-
- if (z <= 0.001) { return; }
-
- let ooz: double = 1.0 / z;
-
- if (ooz > 1000.0 || ooz < -1000.0) { return; }
-
- let xp: int = cast(width / 2 + horizontal_offset + k1 * ooz * x * 2);
- let yp: int = cast(height / 2 + k1 * ooz * y);
-
- if (xp >= 0 && xp < width && yp >= 0 && yp < height) {
- let idx: int = xp + yp * width;
- if (ooz > zBuffer[idx]) {
- zBuffer[idx] = ooz;
- _buffer[idx] = ch;
- _colorBuffer[idx] = color;
- }
- }
-}
-
-const print_cube -> fn () void {
- loop [cubeX: double = -cube_width](cubeX < cube_width) : (cubeX = cubeX + increment_speed) {
- loop [cubeY: double = -cube_width](cubeY < cube_width) : (cubeY = cubeY + increment_speed) {
- calculateForSurface(cubeX, cubeY, -cube_width, '@', fx::BRIGHT_RED);
- calculateForSurface(cube_width, cubeY, cubeX, '+', fx::BRIGHT_GREEN);
- calculateForSurface(-cube_width, cubeY, -cubeX, '~', fx::BRIGHT_YELLOW);
- calculateForSurface(-cubeX, cubeY, cube_width, '#', fx::BRIGHT_BLUE);
- calculateForSurface(cubeX, -cube_width, -cubeY, '*', fx::BRIGHT_CYAN);
- calculateForSurface(cubeX, cube_width, cubeY, '-', fx::BRIGHT_MAGENTA);
- }
- }
-}
-
-pub const main -> fn () int {
- zBuffer = cast<*double>(alloc(160 * 44 * sizeof));
- _colorBuffer = cast<**char>(alloc(160 * 44 * 8));
- _buffer = cast<*char>(alloc(160 * 44));
-
- defer { free(zBuffer); free(_colorBuffer); free(_buffer); }
-
- output(fx::CLEAR_SCREEN, fx::CURSOR_HOME, fx::BRIGHT_MAGENTA, "3D", fx::RESET,
- fx::BRIGHT_CYAN, " Spinning Cubes in Luma\n", fx::RESET, fx::CURSOR_HIDE);
-
- let frame_count: int = 0;
-
- loop {
- mem::memset(cast<*void>(_buffer), 32, width * height);
- mem::memset(cast<*void>(_colorBuffer), 0, width * height * 8);
- mem::memset(cast<*void>(zBuffer), 0, width * height * sizeof);
-
- cube_width = 20.0;
- horizontal_offset = -2 * cube_width;
- print_cube();
-
- cube_width = 10.0;
- horizontal_offset = 1 * cube_width;
- print_cube();
-
- cube_width = 5.0;
- horizontal_offset = 8 * cube_width;
- print_cube();
-
- output(fx::CURSOR_HOME);
- loop [k: int = 0](k < width * height) : (++k) {
- if (k % width == 0) { output("\n"); }
-
- if (_colorBuffer[k] != cast<*char>(0)) {
- output(_colorBuffer[k]);
- }
- output(string::from_char(_buffer[k]), fx::RESET);
- }
-
- output(
- fx::move_cursor(1, 29),
- fx::BOLD, fx::WHITE, " Frame: ",
- fx::BRIGHT_GREEN, frame_count, " ",
- fx::RESET
- );
-
- // Rotate cube
- A = (A + 0.05) % math::TWO_PI;
- B = (B + 0.05) % math::TWO_PI;
- C = (C + 0.01) % math::TWO_PI;
-
- // Increment frame count
- ++frame_count;
- }
-
- return 0;
-}
diff --git a/tests/test.lx b/tests/test.lx
index 8ca73e76..9c3f56e6 100644
--- a/tests/test.lx
+++ b/tests/test.lx
@@ -1,27 +1,19 @@
@module "main"
-const randomStruct -> struct {
- x: int,
- y: int,
- z: int
-};
-
-pub const main -> fn () int {
+@use "io" as io
- impl [func1: int] -> [randomStruct] {
- const func1 -> fn() int {
- self.x = 10;
- self.y = 20;
- self.z = 30;
+pub const Point -> struct {
+ vertex: *char,
+ x: int,
+ y: int
+};
- if (self.x < self.y && self.y < self.z) {
- return 1;
- }
- return 0;
- }
- }
+pub const main -> fn () int {
+ let y: Point = Point { vertex: "A" ,x: 50, y: 10 };
- randomStruct.func1();
+ io::print_int("Point coordinates: (%d, %d)\n", [y.x, y.y]);
+ io::print_str("Point vertex: %s\n", [y.vertex]);
+ io::print_char("Point vertex first char: %c\n", [y.vertex[0]]);
- return 0;
+ return 0;
}
diff --git a/tests/tetris/build_tetris.mk b/tests/tetris/build_tetris.mk
new file mode 100644
index 00000000..904fbba9
--- /dev/null
+++ b/tests/tetris/build_tetris.mk
@@ -0,0 +1,98 @@
+# Makefile for Luma Tetris
+# Example:
+# ./luma tests/tetris/tetris.lx -name tetris -l std/termfx.lx std/terminal.lx std/string.lx std/sys.lx std/time.lx std/math.lx
+
+LUMA = ./../../luma
+NAME = tetris
+
+MAIN = tetris.lx
+TETRIS_SRCS = $(filter-out $(MAIN), $(wildcard *.lx))
+
+STD_LIBS = ../../std/termfx.lx \
+ ../../std/terminal.lx \
+ ../../std/string.lx \
+ ../../std/sys.lx \
+ ../../std/time.lx \
+ ../../std/math.lx
+
+ALL_SRCS = $(MAIN) $(TETRIS_SRCS) $(STD_LIBS)
+
+# Detect OS (Windows_NT is predefined on Windows)
+ifeq ($(OS),Windows_NT)
+ EXE_EXT := .exe
+ RM := del /Q
+ MKDIR := if not exist build mkdir build
+ RUN_PREFIX :=
+else
+ EXE_EXT :=
+ RM := rm -f
+ MKDIR := mkdir -p build
+ RUN_PREFIX := ./
+endif
+
+TARGET = $(NAME)$(EXE_EXT)
+
+.PHONY: all
+all: $(TARGET)
+
+# Build Tetris
+$(TARGET): $(ALL_SRCS)
+ @echo "Building Luma Tetris..."
+ $(LUMA) $(MAIN) -name $(NAME) -l $(TETRIS_SRCS) $(STD_LIBS)
+ @echo "Build complete: $(TARGET)"
+
+.PHONY: run
+run: $(TARGET)
+ @echo "Starting Tetris..."
+ $(RUN_PREFIX)$(TARGET)
+
+.PHONY: valgrind
+valgrind: $(TARGET)
+ifeq ($(OS),Windows_NT)
+ @echo "Valgrind not supported on Windows natively."
+else
+ @echo "Running Tetris with valgrind..."
+ valgrind --leak-check=full --show-leak-kinds=all ./$(TARGET)
+endif
+
+.PHONY: clean
+clean:
+ @echo "Cleaning build artifacts..."
+ -$(RM) $(TARGET)
+ -$(RM) obj\*.o 2>nul || true
+ -$(RM) obj/*.o 2>/dev/null || true
+
+.PHONY: rebuild
+rebuild: clean all
+
+.PHONY: list
+list:
+ @echo "Main file:"
+ @echo " $(MAIN)"
+ @echo ""
+ @echo "Tetris sources:"
+ @for src in $(TETRIS_SRCS); do echo " $$src"; done
+ @echo ""
+ @echo "Standard library:"
+ @for lib in $(STD_LIBS); do echo " $$lib"; done
+
+.PHONY: help
+help:
+ @echo "Luma Tetris Build System"
+ @echo ""
+ @echo "Build Targets:"
+ @echo " all - Build Tetris (default)"
+ @echo " rebuild - Clean and rebuild"
+ @echo ""
+ @echo "Run Targets:"
+ @echo " run - Build and run"
+ @echo " valgrind - Run with memory leak detection (Linux only)"
+ @echo ""
+ @echo "Development:"
+ @echo " list - Show all source files"
+ @echo " clean - Remove build artifacts"
+ @echo ""
+ @echo "Examples:"
+ @echo " make # Build"
+ @echo " make run # Build and play"
+ @echo " make list # Show what will be compiled"
diff --git a/tests/tetris.lx b/tests/tetris/tetris.lx
similarity index 98%
rename from tests/tetris.lx
rename to tests/tetris/tetris.lx
index 26c70258..2402b548 100644
--- a/tests/tetris.lx
+++ b/tests/tetris/tetris.lx
@@ -1,7 +1,9 @@
@module "main"
@use "terminal" as term
+@use "time" as time
@use "termfx" as tx
+@use "math" as math
const FIELD_W: int = 14;
const FIELD_H: int = 24;
@@ -295,6 +297,10 @@ pub const main -> fn () int {
if (lines > 0) {
lines_cleared_total = lines_cleared_total + lines;
score = score + (lines * lines * 100);
+
+ if (lines_cleared_total % 10 == 0 && speed > 2) {
+ --speed;
+ }
}
// Spawn new piece
@@ -326,6 +332,7 @@ pub const main -> fn () int {
break;
}
}
+ time::usleep(10000);
}
}
}
@@ -335,8 +342,8 @@ pub const main -> fn () int {
draw_screen(current_piece, current_rotation, current_x, current_y,
score, lines_cleared_total, next_piece);
-
- loop [i: int = 0](i < 20000000) :(++i) {}
+
+ time::usleep(20000);
}
term::disable_raw_mode();
diff --git a/todo.md b/todo.md
index fce485dd..81ff61e2 100644
--- a/todo.md
+++ b/todo.md
@@ -1,9 +1,11 @@
-# Lux Language Compiler TODO
+# Luma Language Compiler TODO
## ✅ Implemented (Up to Codegen)
+
These AST node types are fully implemented in code generation:
### Expressions
+
- [x] `AST_EXPR_LITERAL`
- [x] `AST_EXPR_IDENTIFIER`
- [x] `AST_EXPR_BINARY`
@@ -22,6 +24,7 @@ These AST node types are fully implemented in code generation:
- [x] `AST_EXPR_DEC`
### Statements
+
- [x] `AST_PROGRAM` (multi-module support)
- [x] `AST_PREPROCESSOR_MODULE`
- [x] `AST_PREPROCESSOR_USE`
@@ -37,6 +40,7 @@ These AST node types are fully implemented in code generation:
- [x] `AST_STMT_IMPL`
### Types
+
- [x] `AST_TYPE_BASIC`
- [x] `AST_TYPE_POINTER`
- [x] `AST_TYPE_ARRAY`
@@ -49,6 +53,7 @@ These AST node types are fully implemented in code generation:
## 🧠 Static Memory Analysis
### ✅ Currently Implemented
+
- [x] Basic allocation/free tracking by variable name
- [x] Memory leak detection (allocated but never freed)
- [x] Double-free detection with count tracking
@@ -59,6 +64,7 @@ These AST node types are fully implemented in code generation:
### 🔧 Memory Analysis Improvements Needed
#### High Priority
+
- [ ] **Pointer aliasing detection**
- [ ] Track when `ptr2 = ptr1` creates aliases
- [ ] Warn when analyzer can't track aliased pointers
@@ -66,44 +72,50 @@ These AST node types are fully implemented in code generation:
- [ ] Allowing structs to point to itself -- name struct {some: *name};
#### Control Flow Analysis
+
- [ ] **Conditional path tracking**
- [ ] Detect leaks in conditional branches (`if/else` without free in all paths)
- [ ] Handle early returns and breaks
- [ ] Track memory across loop iterations
#### Function Call Analysis
+
- [ ] **Cross-function tracking**
- [ ] Track pointers passed to functions as parameters
- [ ] Handle functions that free parameters
- [ ] Return value allocation tracking
- [ ] Support for ownership transfer through function calls
-
#### Advanced Pointer Operations
+
- [ ] **Complex pointer arithmetic**
- [ ] Handle `ptr + offset` operations
- [ ] Track array element allocations
- [ ] Detect out-of-bounds access potential
#### Memory Operation Extensions
+
- [ ] **Additional memory functions**
- [ ] Track `realloc()` operations
- [ ] Handle `calloc()` and `malloc()` variants
- [ ] Monitor `memcpy()` for potential use-after-free
#### Data Structure Tracking
+
- [ ] **Struct/array memory management**
- [ ] Track allocations within struct members
- [ ] Handle nested pointer structures
- [ ] Monitor array of pointers
#### Use-After-Free Detection
+
- [ ] **Access after free tracking**
- [ ] Detect reads/writes to freed pointers
- [ ] Track freed pointer usage across scopes
- [ ] Integration with dereference operations
#### Scope and Lifetime Analysis
+
- [ ] **Advanced scope tracking**
- [ ] Detect pointers escaping local scope
- [ ] Handle static/global pointer lifetimes
@@ -114,6 +126,7 @@ These AST node types are fully implemented in code generation:
## 📝 Next Steps
### Parsing
+
- [ ] Add parsing for templates (`fn[T]`, `struct[T]`)
- [ ] Add parsing for type aliases using `type` keyword
- [ ] Add parsing for modules and imports refinements
@@ -121,10 +134,12 @@ These AST node types are fully implemented in code generation:
- [ ] Consider Go/Odin-style loop syntax improvements
### Semantic Analysis
+
- [ ] Type inference for generics
- [ ] Detect unused imports and symbols
### Codegen
+
- [ ] Implement codegen for `switch` or `match` constructs
- [ ] Support more LLVM optimizations
- [ ] **Add structs and enums support** in codegen
@@ -132,15 +147,18 @@ These AST node types are fully implemented in code generation:
- [ ] **Add in memcpy and streq** streq === strcmp
### Lexer & Parser
+
- [ ] Add tokens and grammar for unions
### Type Checker
+
- [ ] Implement type checking for structs
-- [ ] Implement type checking for unions
+- [ ] Implement type checking for unions
---
## 🚀 Future Features Ideas (Maybe)
+
- [ ] Investigate pattern matching
- [ ] Build minimal standard library
- [ ] Consider ownership/borrowing system for advanced memory safety