├── .clang-format ├── .github └── FUNDING.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── boot ├── ast.c ├── ast.h ├── codegen.c ├── codegen.h ├── commands.c ├── commands.h ├── compilation.c ├── compilation.h ├── comptime.c ├── comptime.h ├── config.c ├── config.h ├── filesystem.c ├── filesystem.h ├── ioutil.c ├── ioutil.h ├── lexer.c ├── lexer.h ├── main.c ├── module.c ├── module.h ├── parser.c ├── parser.h ├── semantic.c ├── semantic.h ├── symbol.c ├── symbol.h ├── token.c ├── token.h ├── type.c └── type.h ├── doc ├── README.md ├── getting-started.md ├── language-spec.md └── project-layout.md ├── mach.toml ├── src ├── commands.mach └── main.mach └── std ├── crypto └── hash.mach ├── io ├── console.mach ├── fs.mach └── path.mach ├── runtime.mach ├── system ├── env.mach ├── memory.mach ├── platform │ ├── darwin │ │ ├── sys.mach │ │ └── syscall.mach │ ├── linux │ │ ├── sys.mach │ │ └── syscall.mach │ └── windows │ │ └── sys.mach └── time.mach ├── text ├── ascii.mach └── parse.mach └── types ├── bool.mach ├── list.mach ├── option.mach ├── result.mach └── string.mach /.clang-format: -------------------------------------------------------------------------------- 1 | IndentWidth: 4 2 | BreakBeforeBraces: Allman 3 | ColumnLimit: 256 4 | AllowShortFunctionsOnASingleLine: false 5 | AllowShortIfStatementsOnASingleLine: false 6 | AllowShortLoopsOnASingleLine: false 7 | AllowShortBlocksOnASingleLine: false 8 | AllowShortCaseLabelsOnASingleLine: false 9 | AllowShortEnumsOnASingleLine: false 10 | AllowAllConstructorInitializersOnNextLine: true 11 | BinPackArguments: false 12 | BinPackParameters: false 13 | AlignAfterOpenBracket: Align 14 | AlignConsecutiveDeclarations: Consecutive 15 | AlignConsecutiveAssignments: Consecutive 16 | AlignEscapedNewlines: Left 17 | AlignOperands: true 18 | AlignTrailingComments: true 19 | Cpp11BracedListStyle: true 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [octalide] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .vscode 3 | out 4 | cmach 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Mach 2 | 3 | Thank you for your interest in contributing to Mach! 4 | 5 | - [Contributing to Mach](#contributing-to-mach) 6 | - [Code of Conduct](#code-of-conduct) 7 | - [Getting Started](#getting-started) 8 | - [Prerequisites](#prerequisites) 9 | - [Building](#building) 10 | - [Branch Workflow](#branch-workflow) 11 | - [Contributing Changes](#contributing-changes) 12 | - [Reporting Issues](#reporting-issues) 13 | - [Coding Standards](#coding-standards) 14 | - [C Code (Bootstrap Compiler in `boot/`)](#c-code-bootstrap-compiler-in-boot) 15 | - [Mach Code (in `src/` and `std/`)](#mach-code-in-src-and-std) 16 | - [Project Structure](#project-structure) 17 | - [License](#license) 18 | 19 | --- 20 | 21 | ## Code of Conduct 22 | 23 | Be respectful, constructive, and professional. Treat Mach like a passion project and its community like family. 24 | 25 | --- 26 | 27 | ## Getting Started 28 | 29 | ### Prerequisites 30 | - C compiler: `clang` with C23 support 31 | - LLVM 14+ with development headers 32 | - GNU Make 33 | - Git 34 | 35 | ### Building 36 | 37 | ```bash 38 | git clone https://github.com/octalide/mach.git 39 | cd mach 40 | make full 41 | ``` 42 | 43 | --- 44 | 45 | ## Branch Workflow 46 | 47 | We use a two-branch model: 48 | 49 | - **`main`** - Stable branch. Production-ready code only. 50 | - **`dev`** - Development branch. Integration point for features. 51 | 52 | ### Contributing Changes 53 | 54 | 1. **Fork the repository** 55 | 2. **Create a feature branch from `dev`:** 56 | ```bash 57 | git checkout dev 58 | git pull origin dev 59 | git checkout -b feat/your-feature-name 60 | ``` 61 | 3. **Make your changes** following the coding standards below 62 | 4. **Commit with clear messages:** 63 | ``` 64 | component: brief description 65 | 66 | Longer explanation if needed. 67 | ``` 68 | 5. **Push to your fork:** 69 | ```bash 70 | git push origin feat/your-feature-name 71 | ``` 72 | 6. **Open a Pull Request** targeting the `dev` branch 73 | 74 | **Pull Request Guidelines:** 75 | - Target `dev` branch (not `main`) 76 | - Provide clear description of changes 77 | - Link related issues 78 | - Ensure code builds and works 79 | - Follow coding standards 80 | 81 | --- 82 | 83 | ## Reporting Issues 84 | 85 | **Bugs:** 86 | - Check existing issues first 87 | - Provide minimal reproduction case 88 | - Include version info, OS, error messages 89 | - Attach relevant files if applicable 90 | 91 | **Enhancements:** 92 | - Align with Mach's philosophy (simplicity, explicitness, predictability) 93 | - Provide concrete use cases 94 | - Consider if it can be a library instead 95 | 96 | --- 97 | 98 | ## Coding Standards 99 | 100 | ### C Code (Bootstrap Compiler in `boot/`) 101 | 102 | **Standards:** 103 | - Target C23 using `clang` 104 | - Only use C standard library 105 | 106 | **Naming:** 107 | - Functions: `lowercase_with_underscores` 108 | - Structs: `PascalCase` 109 | 110 | **Memory Management:** 111 | ```c 112 | // structs use init/dnit pattern 113 | void ast_node_init(ASTNode *node); // initialize fields, don't allocate struct 114 | void ast_node_dnit(ASTNode *node); // clean up internals, don't free struct 115 | 116 | // usage 117 | ASTNode node; 118 | ast_node_init(&node); 119 | // use node 120 | ast_node_dnit(&node); 121 | ``` 122 | 123 | ### Mach Code (in `src/` and `std/`) 124 | 125 | Mach coding standards are in flux while syntax stabalizes and the userbase grows. Follow existing patterns and refer to the [language specification](doc/language-spec.md) for language features. 126 | 127 | --- 128 | 129 | ## Project Structure 130 | 131 | ``` 132 | mach/ 133 | ├── boot/ # bootstrap C compiler 134 | ├── doc/ # documentation 135 | ├── src/ # mach compiler (written in mach) 136 | ├── std/ # standard library 137 | ├── Makefile # build system 138 | └── mach.toml # project configuration 139 | ``` 140 | 141 | --- 142 | 143 | ## License 144 | 145 | By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). 146 | 147 | --- 148 | 149 | Thank you for contributing to Mach! 150 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2025 octalide 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # mach unified build system 2 | # builds: 3 | # 1. bootstrap c compiler (boot/ -> out/bin/cmach) 4 | # 2. intermediary compiler with cmach (src/ -> out/bin/imach) 5 | # 3. final compiler with imach (src/ -> out/bin/mach) 6 | # 7 | # directory structure: 8 | # out/ 9 | # bin/ # final binaries (cmach, imach, mach) 10 | # cmach/ # cmach artifacts (obj/) 11 | # imach// # imach artifacts (ast/, ir/, asm/, obj/) 12 | # mach// # mach artifacts (ast/, ir/, asm/, obj/) 13 | 14 | # compiler and flags 15 | CC := clang 16 | CFLAGS := -std=c23 -Wall -Wextra -Werror -pedantic -O2 17 | CFLAGS_DEBUG := -std=c23 -Wall -Wextra -Werror -pedantic -g -O0 -DDEBUG 18 | 19 | # llvm configuration 20 | LLVM_CFLAGS := $(shell llvm-config --cflags) 21 | LLVM_LDFLAGS := $(shell llvm-config --ldflags --libs core) 22 | 23 | OUT_DIR := out 24 | BIN_DIR := $(OUT_DIR)/bin 25 | 26 | # standard library (source only, referenced directly) 27 | STD_DIR := std 28 | MACH_SRC_DIR := src 29 | 30 | # bootstrap compiler 31 | BOOT_DIR := boot 32 | CMACH_OBJ_DIR := $(OUT_DIR)/cmach/obj 33 | 34 | # final executables 35 | CMACH := $(BIN_DIR)/cmach 36 | IMACH := $(BIN_DIR)/imach 37 | MACH := $(BIN_DIR)/mach 38 | 39 | # bootstrap compiler sources 40 | BOOT_SOURCES := $(wildcard $(BOOT_DIR)/*.c) 41 | BOOT_OBJECTS := $(BOOT_SOURCES:$(BOOT_DIR)/%.c=$(CMACH_OBJ_DIR)/%.o) 42 | BOOT_HEADERS := $(wildcard $(BOOT_DIR)/*.h) 43 | 44 | # main targets 45 | .PHONY: help cmach-clean cmach-build cmach imach-clean imach-build imach mach-clean mach-build mach full clean 46 | 47 | # default target: print help 48 | .DEFAULT_GOAL := help 49 | 50 | help: 51 | @echo "mach compiler build system" 52 | @echo "" 53 | @echo "bootstrap (c compiler):" 54 | @echo " cmach-clean - clean cmach build artifacts" 55 | @echo " cmach-build - build cmach" 56 | @echo " cmach - clean and build cmach" 57 | @echo "" 58 | @echo "intermediary (compiled with cmach):" 59 | @echo " imach-clean - clean imach build artifacts" 60 | @echo " imach-build - build imach" 61 | @echo " imach - clean and build imach" 62 | @echo "" 63 | @echo "final (compiled with imach):" 64 | @echo " mach-clean - clean mach build artifacts" 65 | @echo " mach-build - build mach" 66 | @echo " mach - clean and build mach" 67 | @echo "" 68 | @echo "meta:" 69 | @echo " clean - clean all build artifacts" 70 | @echo "" 71 | @echo "note: target platform determined by mach.toml (target = \"native\")" 72 | @echo " imach - clean and build imach" 73 | @echo "" 74 | @echo "final (compiled with imach):" 75 | @echo " mach-clean - clean mach build artifacts" 76 | @echo " mach-build - build mach" 77 | @echo " mach - clean and build mach" 78 | @echo "" 79 | @echo "meta:" 80 | @echo " clean - clean all build artifacts" 81 | 82 | # bootstrap compiler 83 | cmach-clean: 84 | @echo "cleaning cmach" 85 | @rm -rf $(OUT_DIR)/cmach 86 | @rm -f $(CMACH) 87 | 88 | cmach-build: $(CMACH) 89 | 90 | cmach: cmach-clean cmach-build 91 | 92 | # intermediary compiler 93 | imach-clean: 94 | @echo "cleaning imach" 95 | @rm -rf $(OUT_DIR)/imach 96 | @rm -f $(IMACH) 97 | 98 | imach-build: $(IMACH) 99 | 100 | imach: imach-clean imach-build 101 | 102 | # final compiler 103 | mach-clean: 104 | @echo "cleaning mach" 105 | @rm -rf $(OUT_DIR)/mach 106 | @rm -f $(MACH) 107 | 108 | mach-build: $(MACH) 109 | 110 | mach: mach-clean mach-build 111 | 112 | # clean everything 113 | clean: 114 | @echo "cleaning all" 115 | @rm -rf $(OUT_DIR) 116 | 117 | $(BIN_DIR): 118 | @mkdir -p $(BIN_DIR) 119 | 120 | $(CMACH_OBJ_DIR): 121 | @mkdir -p $(CMACH_OBJ_DIR) 122 | 123 | $(CMACH_OBJ_DIR)/%.o: $(BOOT_DIR)/%.c $(BOOT_HEADERS) | $(CMACH_OBJ_DIR) 124 | @echo " cc $<" 125 | @$(CC) $(CFLAGS) $(LLVM_CFLAGS) -I$(BOOT_DIR) -c $< -o $@ 126 | 127 | # bootstrap compiler build 128 | $(CMACH): $(BOOT_OBJECTS) | $(BIN_DIR) 129 | @echo " ld $@" 130 | @$(CC) $(BOOT_OBJECTS) $(LLVM_LDFLAGS) -o $@ 131 | @echo "bootstrap compiler ready: $@" 132 | 133 | # intermediary compiler build (using cmach) 134 | # mach.toml determines target and output paths 135 | $(IMACH): $(CMACH) 136 | @echo " cleaning artifacts for imach build" 137 | @rm -rf $(OUT_DIR)/imach 138 | @echo " cmach -> imach" 139 | @$(CMACH) build . 140 | @echo "intermediary compiler ready: $@" 141 | 142 | # final compiler build (using imach) 143 | # mach.toml determines target and output paths 144 | $(MACH): $(IMACH) 145 | @echo " cleaning artifacts for mach build" 146 | @rm -rf $(OUT_DIR)/mach 147 | @echo " imach -> mach" 148 | @echo "" 149 | @echo " NOTE: This stage of the pipeline is incomplete and included for scaffolding purposes" 150 | @echo "" 151 | @$(IMACH) build . 152 | @echo "final compiler ready: $@" 153 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MACH 2 | === 3 | 4 | ![License](https://img.shields.io/github/license/octalide/mach) 5 | ![Code Size](https://img.shields.io/github/languages/code-size/octalide/mach) 6 | ![Last Commit](https://img.shields.io/github/last-commit/octalide/mach) 7 | ![Issues](https://img.shields.io/github/issues/octalide/mach) 8 | 9 | Mach is a statically-typed, compiled programming language designed to be simple, fast, and easy to use. 10 | It is intended to be a systems programming language, but can be used for a wide variety of applications. 11 | 12 | > Mach is still alpha quality. Expect breaking changes as the compiler and standard library iterate. 13 | 14 | We have an official [Discord](https://discord.com/invite/dfWG9NhGj7)! 15 | 16 | # Overview 17 | 18 | - [MACH](#mach) 19 | - [Overview](#overview) 20 | - [Philosophy](#philosophy) 21 | - [Key Features](#key-features) 22 | - [Getting Started](#getting-started) 23 | - [Building From Source](#building-from-source) 24 | - [Simple Examples](#simple-examples) 25 | - [Hello World](#hello-world) 26 | - [Fibonacci](#fibonacci) 27 | - [Factorial](#factorial) 28 | - [Credit](#credit) 29 | - [Contributing](#contributing) 30 | - [License](#license) 31 | 32 | 33 | ## Philosophy 34 | 35 | Mach is designed with the following principles in mind: 36 | - **Simplicity**: Mach is easy to learn and use. 37 | - **Readability**: Mach is easy to read and understand. 38 | - **Explicivity**: Mach is explicit and verbose. WYSIWYG, always. Computers are not magic. Your code should not promote this illusion. 39 | - **Performance**: Mach is fast and efficient. 40 | - **Predictability**: Mach is predictable and consistent. There are no hidden behaviors or side effects. 41 | 42 | Mach is NOT designed to prioritize: 43 | - **Features**: Batteries are not included. 44 | - **Flexibility**: Mach is rigid and opinionated. It should not be flexible or allow for many ways to do the same thing. 45 | - **Code Reduction**: Mach is explicit and verbose. More code is not bad code. 46 | - **Safety**: Safety is the responsibility of the programmer and is often project-specific. Mach does not hold your hand or put you on a leash. 47 | 48 | 49 | ## Key Features 50 | 51 | - No magic. No side effects. No bullshit. Code written in Mach follows the WYSIWYG principle down to the metal. 52 | - Small, clearly defined, and well documented feature set. 53 | - Familiar and easy to read syntax. You do not need to take a class to use mach. 54 | - Barebones standard library with enough to do the basics, but not enough to overload the language. 55 | - Compatibility with C at the ABI level. 56 | 57 | 58 | # Getting Started 59 | 60 | We encourage you to not even install Mach until you have read the [language documentation](doc/README.md). The docs are written more like a pamphlet then a bible, and assumes that you are familiar with basic programming concepts from other languages. 61 | 62 | The reason for this is that Mach may not be for you. If the language does not include some features you hope to use, includes things you despise, or if you just don't like the syntax, then you should look elsewhere. If you read the documentation and have decided that you like the language, then you will have learned the basics and should be capable of diving in. 63 | 64 | If you are new to programming in general, then Mach may not be for you. There are lots of other languages that are better suited for beginners (mostly because of the level of documentation), and we encourage you to look into those instead. Some good "first" programming languages are: 65 | - [Python](https://www.python.org/) 66 | - [Lua](https://www.lua.org/) 67 | - [Javascript](https://www.javascript.com/) 68 | 69 | ## Building From Source 70 | 71 | The docs include a [getting started guide](doc/getting-started.md) that walks you through building the compiler from source and running your first Mach program. 72 | 73 | ## Simple Examples 74 | 75 | The following examples are provided to give a sense of the language's syntax and structure. 76 | 77 | 78 | ### Hello World 79 | 80 | ```mach 81 | use std.runtime; 82 | use std.types.string; 83 | use console: std.io.console; 84 | 85 | $main.symbol = "main"; 86 | fun main(args: []string) i64 { 87 | console.print("Hello, World!\n"); 88 | ret 0; 89 | } 90 | ``` 91 | 92 | 93 | ### Fibonacci 94 | 95 | ```mach 96 | use std.runtime; 97 | use std.types.string; 98 | use console: std.io.console; 99 | 100 | fun fibr(n: i64) i64 { 101 | if (n < 2) { 102 | ret n; 103 | } 104 | 105 | ret fibr(n - 1) + fibr(n - 2); 106 | } 107 | 108 | $main.symbol = "main"; 109 | fun main(args: []string) i64 { 110 | var max: i64 = 10; 111 | console.print("fib(%d) = %d\n", max, fibr(max)); 112 | ret 0; 113 | } 114 | ``` 115 | 116 | 117 | ### Factorial 118 | 119 | ```mach 120 | use std.runtime; 121 | use std.types.string; 122 | use console: std.io.console; 123 | 124 | fun fact(n: i64) i64 { 125 | if (n == 0) { 126 | ret 1; 127 | } 128 | 129 | ret n * fact(n - 1); 130 | } 131 | 132 | $main.symbol = "main"; 133 | fun main(args: []string) i64 { 134 | var max: i64 = 10; 135 | console.print("fact(%d) = %d\n", max, fact(max)); 136 | ret 0; 137 | } 138 | ``` 139 | 140 | # Credit 141 | 142 | The inspiration for Mach comes from too many languages to count. Almost every language has problems that Mach attempts to elegantly resolve (most often by the process of reductive simplification). 143 | 144 | Direct inspiration for the compiler, however, comes from a few more specific sources, the most notable of which are listed below: 145 | 146 | - [Golang](https://golang.org/) 147 | - [Vlang](https://vlang.org/) 148 | - [Zig](https://ziglang.org/) 149 | - [Rust](https://www.rust-lang.org/) 150 | 151 | The original compiler would not have been written without the ability to reference the source code of these languages. 152 | 153 | Mach, at its core, stands on the shoulders of countles giants that have contributed to the development of these languages either directly or by proxy. It is out of respect for their work that Mach will always be fully open source. Thank you all. 154 | 155 | ## Contributing 156 | 157 | We welcome contributions to Mach! If you would like to contribute, please read our [contributing guidelines](CONTRIBUTING.md) first. 158 | 159 | # License 160 | 161 | Mach is licensed under the [MIT License](LICENSE). 162 | -------------------------------------------------------------------------------- /boot/ast.h: -------------------------------------------------------------------------------- 1 | #ifndef AST_H 2 | #define AST_H 3 | 4 | #include "token.h" 5 | #include 6 | 7 | // forward statements 8 | typedef struct Type Type; 9 | typedef struct Symbol Symbol; 10 | 11 | typedef enum AstKind 12 | { 13 | AST_PROGRAM, 14 | AST_MODULE, 15 | 16 | // statements 17 | AST_STMT_USE, 18 | AST_STMT_EXT, 19 | AST_STMT_DEF, 20 | AST_STMT_VAL, 21 | AST_STMT_VAR, 22 | AST_STMT_FUN, 23 | AST_STMT_FIELD, 24 | AST_STMT_PARAM, 25 | AST_STMT_REC, 26 | AST_STMT_UNI, 27 | AST_STMT_IF, 28 | AST_STMT_OR, 29 | AST_STMT_FOR, 30 | AST_STMT_BRK, 31 | AST_STMT_CNT, 32 | AST_STMT_RET, 33 | AST_STMT_BLOCK, 34 | AST_STMT_EXPR, 35 | AST_STMT_ASM, 36 | 37 | // compile-time constructs 38 | AST_COMPTIME, 39 | 40 | // expressions 41 | AST_EXPR_BINARY, 42 | AST_EXPR_UNARY, 43 | AST_EXPR_CALL, 44 | AST_EXPR_INDEX, 45 | AST_EXPR_FIELD, 46 | AST_EXPR_CAST, 47 | AST_EXPR_IDENT, 48 | AST_EXPR_LIT, 49 | AST_EXPR_NULL, 50 | AST_EXPR_ARRAY, 51 | AST_EXPR_VARARGS, 52 | AST_EXPR_STRUCT, 53 | 54 | // types 55 | AST_TYPE_NAME, 56 | AST_TYPE_PTR, 57 | AST_TYPE_ARRAY, 58 | AST_TYPE_PARAM, 59 | AST_TYPE_FUN, 60 | AST_TYPE_REC, 61 | AST_TYPE_UNI, 62 | } AstKind; 63 | 64 | typedef struct AstNode AstNode; 65 | typedef struct AstList AstList; 66 | 67 | // generic list for child nodes 68 | struct AstList 69 | { 70 | AstNode **items; 71 | int count; 72 | int capacity; 73 | }; 74 | 75 | // base AST node 76 | struct AstNode 77 | { 78 | AstKind kind; 79 | Token *token; // source token for error reporting 80 | Type *type; // resolved type (filled during semantic analysis) 81 | Symbol *symbol; // symbol table entry (if applicable) 82 | 83 | union 84 | { 85 | // program root 86 | struct 87 | { 88 | AstList *stmts; 89 | } program; 90 | 91 | // module statement 92 | struct 93 | { 94 | char *name; // module name 95 | AstList *stmts; // statements in this module 96 | } module; 97 | 98 | // use statement 99 | struct 100 | { 101 | char *module_path; 102 | char *alias; 103 | } use_stmt; 104 | 105 | // external statement 106 | struct 107 | { 108 | char *name; // function name in Mach code 109 | char *convention; // calling convention (e.g., "C") 110 | char *symbol; // target symbol name (default: same as name) 111 | AstNode *type; 112 | bool is_public; 113 | } ext_stmt; 114 | 115 | // type definition 116 | struct 117 | { 118 | char *name; 119 | AstNode *type; 120 | bool is_public; 121 | } def_stmt; 122 | 123 | // value/variable statement 124 | struct 125 | { 126 | char *name; 127 | AstNode *type; // explicit type or null 128 | AstNode *init; // initializer expression 129 | bool is_val; 130 | bool is_public; 131 | } var_stmt; 132 | 133 | // function statement 134 | struct 135 | { 136 | char *name; 137 | AstList *params; 138 | AstList *generics; // optional generic parameters 139 | AstNode *return_type; // null for no return 140 | AstNode *body; // null for external functions 141 | bool is_variadic; // true if function has variadic arguments 142 | bool is_public; 143 | bool is_method; 144 | AstNode *method_receiver; // typename before '.' for method declarations 145 | } fun_stmt; 146 | 147 | // record statement 148 | struct 149 | { 150 | char *name; 151 | AstList *generics; 152 | AstList *fields; 153 | bool is_public; 154 | } rec_stmt; 155 | 156 | // union statement 157 | struct 158 | { 159 | char *name; 160 | AstList *generics; 161 | AstList *fields; 162 | bool is_public; 163 | } uni_stmt; 164 | 165 | // field statement 166 | struct 167 | { 168 | char *name; 169 | AstNode *type; 170 | } field_stmt; 171 | 172 | // parameter statement 173 | struct 174 | { 175 | char *name; 176 | AstNode *type; 177 | bool is_variadic; // sentinel for '...' 178 | } param_stmt; 179 | 180 | // block statement 181 | struct 182 | { 183 | AstList *stmts; 184 | } block_stmt; 185 | 186 | // expression statement 187 | struct 188 | { 189 | AstNode *expr; 190 | } expr_stmt; 191 | 192 | // inline asm statement 193 | struct 194 | { 195 | char *code; // raw assembly text (single line for now) 196 | char *constraints; // optional LLVM asm constraints/clobbers string 197 | } asm_stmt; 198 | 199 | // compile-time construct (unified $ prefix handler) 200 | struct 201 | { 202 | AstNode *inner; // the expression/statement following $ 203 | } comptime; 204 | 205 | // return statement 206 | struct 207 | { 208 | AstNode *expr; // null for void return 209 | } ret_stmt; 210 | 211 | // if statement 212 | struct 213 | { 214 | AstNode *cond; 215 | AstNode *body; 216 | AstNode *stmt_or; // can be another conditional (or) 217 | } cond_stmt; 218 | 219 | // for loop 220 | struct 221 | { 222 | AstNode *cond; // null for infinite loop 223 | AstNode *body; 224 | } for_stmt; 225 | 226 | // binary expression 227 | struct 228 | { 229 | AstNode *left; 230 | AstNode *right; 231 | TokenKind op; 232 | } binary_expr; 233 | 234 | // unary expression 235 | struct 236 | { 237 | AstNode *expr; 238 | TokenKind op; 239 | } unary_expr; 240 | 241 | // function call 242 | struct 243 | { 244 | AstNode *func; 245 | AstList *args; 246 | AstList *type_args; 247 | bool is_method_call; // true when rewritten from receiver.method() 248 | } call_expr; 249 | 250 | // array indexing 251 | struct 252 | { 253 | AstNode *array; 254 | AstNode *index; 255 | } index_expr; 256 | 257 | // field access 258 | struct 259 | { 260 | AstNode *object; 261 | char *field; 262 | bool is_method; // true when referring to method symbol 263 | } field_expr; 264 | 265 | // type cast 266 | struct 267 | { 268 | AstNode *expr; 269 | AstNode *type; 270 | } cast_expr; 271 | 272 | // identifier 273 | struct 274 | { 275 | char *name; 276 | } ident_expr; 277 | 278 | // literal 279 | struct 280 | { 281 | TokenKind kind; // TOKEN_LIT_INT, etc. 282 | union 283 | { 284 | unsigned long long int_val; 285 | double float_val; 286 | char char_val; 287 | char *string_val; 288 | }; 289 | } lit_expr; 290 | 291 | // null literal (nil) 292 | struct 293 | { 294 | bool unused; 295 | } null_expr; 296 | 297 | // array literal 298 | struct 299 | { 300 | AstNode *type; // element type 301 | AstList *elems; // elements 302 | bool is_slice_literal; // true when expressed as pointer+length literal 303 | } array_expr; 304 | 305 | // struct literal 306 | struct 307 | { 308 | AstNode *type; // struct type 309 | AstList *fields; // field initializers 310 | bool is_union_literal; // true when literal uses 'uni' keyword without explicit type 311 | bool is_anonymous_literal; // true when literal omits explicit type name 312 | } struct_expr; 313 | 314 | // type expressions 315 | struct 316 | { 317 | char *module_alias; 318 | char *name; 319 | AstList *generic_args; 320 | } type_name; 321 | 322 | struct 323 | { 324 | AstNode *base; 325 | } type_ptr; 326 | 327 | struct 328 | { 329 | AstNode *elem_type; 330 | AstNode *size; // null for unbound arrays [_] 331 | } type_array; 332 | 333 | struct 334 | { 335 | char *name; 336 | } type_param; 337 | 338 | struct 339 | { 340 | AstList *params; 341 | AstNode *return_type; // null for no return 342 | bool is_variadic; // true if function type is variadic 343 | } type_fun; 344 | 345 | struct 346 | { 347 | char *name; // can be null for anonymous 348 | AstList *fields; 349 | } type_rec; 350 | 351 | struct 352 | { 353 | char *name; // can be null for anonymous 354 | AstList *fields; 355 | } type_uni; 356 | }; 357 | }; 358 | 359 | // ast node operations 360 | void ast_node_init(AstNode *node, AstKind kind); 361 | void ast_node_dnit(AstNode *node); 362 | 363 | // list operations 364 | void ast_list_init(AstList *list); 365 | void ast_list_dnit(AstList *list); 366 | void ast_list_append(AstList *list, AstNode *node); 367 | void ast_list_prepend(AstList *list, AstNode *node); 368 | 369 | // cloning helpers 370 | AstNode *ast_clone(const AstNode *node); 371 | AstList *ast_list_clone(const AstList *list); 372 | 373 | // pretty printing for debugging 374 | void ast_print(AstNode *node, int indent); 375 | 376 | const char *ast_node_kind_to_string(AstKind kind); 377 | 378 | bool ast_emit(AstNode *node, const char *file_path); 379 | 380 | #endif 381 | -------------------------------------------------------------------------------- /boot/codegen.h: -------------------------------------------------------------------------------- 1 | #ifndef CODEGEN_H 2 | #define CODEGEN_H 3 | 4 | #include "ast.h" 5 | #include "lexer.h" 6 | #include "semantic.h" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | typedef struct CodegenContext CodegenContext; 14 | typedef struct CodegenError CodegenError; 15 | 16 | struct CodegenError 17 | { 18 | char *message; 19 | AstNode *node; 20 | CodegenError *next; 21 | }; 22 | 23 | struct CodegenContext 24 | { 25 | // llvm core 26 | LLVMContextRef context; 27 | LLVMModuleRef module; 28 | LLVMBuilderRef builder; 29 | LLVMTargetMachineRef target_machine; 30 | LLVMTargetDataRef data_layout; 31 | LLVMDIBuilderRef di_builder; 32 | LLVMMetadataRef di_compile_unit; 33 | LLVMMetadataRef di_file; 34 | LLVMMetadataRef current_di_scope; 35 | LLVMMetadataRef current_di_subprogram; 36 | LLVMMetadataRef di_unknown_type; 37 | 38 | // symbol mapping 39 | struct 40 | { 41 | Symbol **symbols; 42 | LLVMValueRef *values; 43 | int count; 44 | int capacity; 45 | } symbol_map; 46 | 47 | // type cache 48 | struct 49 | { 50 | Type **types; 51 | LLVMTypeRef *llvm_types; 52 | int count; 53 | int capacity; 54 | } type_cache; 55 | 56 | // current function context 57 | LLVMValueRef current_function; 58 | Type *current_function_type; // mach type for current function 59 | LLVMBasicBlockRef break_block; 60 | LLVMBasicBlockRef continue_block; 61 | 62 | // initialization context 63 | bool generating_mutable_init; // true when generating initializer for var (not val) 64 | 65 | // module-level assembly aggregation 66 | char *module_inline_asm; 67 | size_t module_inline_asm_len; 68 | 69 | // error tracking 70 | CodegenError *errors; 71 | bool has_errors; 72 | 73 | // options 74 | int opt_level; 75 | bool debug_info; 76 | bool debug_finalized; 77 | bool no_pie; // disable position independent executable 78 | char *debug_full_path; 79 | char *debug_dir; 80 | char *debug_file; 81 | 82 | // source context for diagnostics 83 | const char *source_file; // current file being compiled 84 | Lexer *source_lexer; // lexer for mapping pos->line/column 85 | 86 | // variadic function support (Mach ABI) 87 | LLVMValueRef current_vararg_count_value; // u64 count parameter passed to current function (null if none) 88 | LLVMValueRef current_vararg_array; // i8** pointing to packed variadic argument slots 89 | size_t current_fixed_param_count; // number of fixed parameters in current function 90 | 91 | // specialization cache for cross-module generic instantiation 92 | struct SpecializationCache *spec_cache; 93 | }; 94 | 95 | // context lifecycle 96 | void codegen_context_init(CodegenContext *ctx, const char *module_name, bool no_pie); 97 | void codegen_context_dnit(CodegenContext *ctx); 98 | 99 | // main entry point 100 | bool codegen_generate(CodegenContext *ctx, AstNode *root, SymbolTable *symbols); 101 | 102 | // output generation 103 | bool codegen_emit_object(CodegenContext *ctx, const char *filename); 104 | bool codegen_emit_llvm_ir(CodegenContext *ctx, const char *filename); 105 | bool codegen_emit_assembly(CodegenContext *ctx, const char *filename); 106 | 107 | // error handling 108 | void codegen_error(CodegenContext *ctx, AstNode *node, const char *fmt, ...); 109 | void codegen_print_errors(CodegenContext *ctx); 110 | 111 | // type conversion 112 | LLVMTypeRef codegen_get_llvm_type(CodegenContext *ctx, Type *type); 113 | 114 | // value lookup 115 | LLVMValueRef codegen_get_symbol_value(CodegenContext *ctx, Symbol *symbol); 116 | void codegen_set_symbol_value(CodegenContext *ctx, Symbol *symbol, LLVMValueRef value); 117 | 118 | // statement generation 119 | LLVMValueRef codegen_stmt(CodegenContext *ctx, AstNode *stmt); 120 | LLVMValueRef codegen_stmt_use(CodegenContext *ctx, AstNode *stmt); 121 | LLVMValueRef codegen_stmt_ext(CodegenContext *ctx, AstNode *stmt); 122 | LLVMValueRef codegen_stmt_block(CodegenContext *ctx, AstNode *stmt); 123 | LLVMValueRef codegen_stmt_var(CodegenContext *ctx, AstNode *stmt); 124 | LLVMValueRef codegen_stmt_fun(CodegenContext *ctx, AstNode *stmt); 125 | LLVMValueRef codegen_stmt_ret(CodegenContext *ctx, AstNode *stmt); 126 | LLVMValueRef codegen_stmt_if(CodegenContext *ctx, AstNode *stmt); 127 | LLVMValueRef codegen_stmt_for(CodegenContext *ctx, AstNode *stmt); 128 | LLVMValueRef codegen_stmt_expr(CodegenContext *ctx, AstNode *stmt); 129 | 130 | // expression generation 131 | LLVMValueRef codegen_expr(CodegenContext *ctx, AstNode *expr); 132 | LLVMValueRef codegen_expr_lit(CodegenContext *ctx, AstNode *expr); 133 | LLVMValueRef codegen_expr_null(CodegenContext *ctx, AstNode *expr); 134 | LLVMValueRef codegen_expr_ident(CodegenContext *ctx, AstNode *expr); 135 | LLVMValueRef codegen_expr_binary(CodegenContext *ctx, AstNode *expr); 136 | LLVMValueRef codegen_expr_unary(CodegenContext *ctx, AstNode *expr); 137 | LLVMValueRef codegen_expr_call(CodegenContext *ctx, AstNode *expr); 138 | LLVMValueRef codegen_expr_cast(CodegenContext *ctx, AstNode *expr); 139 | LLVMValueRef codegen_expr_field(CodegenContext *ctx, AstNode *expr); 140 | LLVMValueRef codegen_expr_index(CodegenContext *ctx, AstNode *expr); 141 | LLVMValueRef codegen_expr_array(CodegenContext *ctx, AstNode *expr); 142 | LLVMValueRef codegen_expr_struct(CodegenContext *ctx, AstNode *expr); 143 | 144 | // utility functions 145 | LLVMValueRef codegen_create_alloca(CodegenContext *ctx, LLVMTypeRef type, const char *name); 146 | LLVMValueRef codegen_load_if_needed(CodegenContext *ctx, LLVMValueRef value, Type *type, AstNode *source_expr); 147 | bool codegen_is_lvalue(AstNode *expr); 148 | 149 | #endif 150 | -------------------------------------------------------------------------------- /boot/commands.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMANDS_H 2 | #define COMMANDS_H 3 | 4 | // minimal command entrypoints 5 | int mach_cmd_build(int argc, char **argv); 6 | void mach_print_usage(const char *program_name); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /boot/compilation.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPILATION_H 2 | #define COMPILATION_H 3 | 4 | #include "ast.h" 5 | #include "codegen.h" 6 | #include "config.h" 7 | #include "module.h" 8 | #include "semantic.h" 9 | #include 10 | 11 | typedef struct 12 | { 13 | char **items; 14 | int count; 15 | int cap; 16 | } StringVec; 17 | 18 | typedef struct 19 | { 20 | char **names; 21 | char **dirs; 22 | int count; 23 | int cap; 24 | } AliasVec; 25 | 26 | typedef struct 27 | { 28 | const char *input_file; 29 | const char *output_file; 30 | int opt_level; 31 | int link_exe; 32 | int no_pie; 33 | int debug_info; 34 | int emit_ast; 35 | int emit_ir; 36 | int emit_asm; 37 | const char *emit_ast_path; 38 | const char *emit_ir_path; 39 | const char *emit_asm_path; 40 | const char *obj_dir; 41 | const char *target_name; // for project builds - determines out// structure 42 | StringVec include_paths; 43 | StringVec link_objects; 44 | AliasVec aliases; 45 | } BuildOptions; 46 | 47 | typedef struct 48 | { 49 | BuildOptions *options; 50 | SemanticDriver *driver; 51 | ProjectConfig *config; 52 | char *project_root; 53 | char *source; 54 | AstNode *ast; 55 | Parser parser; 56 | Lexer lexer; 57 | CodegenContext codegen; 58 | char *module_name; 59 | char **dep_objects; 60 | int dep_count; 61 | bool had_error; 62 | bool lexer_initialized; 63 | bool parser_initialized; 64 | bool codegen_initialized; 65 | } CompilationContext; 66 | 67 | // build options 68 | void build_options_init(BuildOptions *opts); 69 | void build_options_dnit(BuildOptions *opts); 70 | void build_options_add_include(BuildOptions *opts, const char *path); 71 | void build_options_add_link_object(BuildOptions *opts, const char *obj); 72 | void build_options_add_alias(BuildOptions *opts, const char *name, const char *dir); 73 | 74 | // compilation context lifecycle 75 | bool compilation_context_init(CompilationContext *ctx, BuildOptions *opts); 76 | void compilation_context_dnit(CompilationContext *ctx); 77 | 78 | // compilation pipeline 79 | bool compilation_load_and_preprocess(CompilationContext *ctx); 80 | bool compilation_parse(CompilationContext *ctx); 81 | bool compilation_analyze(CompilationContext *ctx); 82 | bool compilation_codegen(CompilationContext *ctx); 83 | bool compilation_emit_artifacts(CompilationContext *ctx); 84 | bool compilation_compile_dependencies(CompilationContext *ctx); 85 | bool compilation_link(CompilationContext *ctx); 86 | 87 | // high-level orchestration 88 | bool compilation_run(CompilationContext *ctx); 89 | 90 | // helper: derive module name from file path and aliases 91 | char *derive_module_name(const char *filename, const AliasVec *aliases); 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /boot/comptime.c: -------------------------------------------------------------------------------- 1 | #include "comptime.h" 2 | #include 3 | 4 | // Detect host platform at compile time 5 | #if defined(__linux__) 6 | #define HOST_OS COMPTIME_OS_LINUX 7 | #define HOST_OS_STRING "linux" 8 | #elif defined(__APPLE__) && defined(__MACH__) 9 | #define HOST_OS COMPTIME_OS_DARWIN 10 | #define HOST_OS_STRING "darwin" 11 | #elif defined(_WIN32) || defined(_WIN64) 12 | #define HOST_OS COMPTIME_OS_WINDOWS 13 | #define HOST_OS_STRING "windows" 14 | #elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) 15 | #define HOST_OS COMPTIME_OS_BSD 16 | #define HOST_OS_STRING "bsd" 17 | #else 18 | #define HOST_OS COMPTIME_OS_UNKNOWN 19 | #define HOST_OS_STRING "unknown" 20 | #endif 21 | 22 | #if defined(__x86_64__) || defined(_M_X64) 23 | #define HOST_ARCH COMPTIME_ARCH_X86_64 24 | #define HOST_ARCH_STRING "x86_64" 25 | #define HOST_POINTER_SIZE 8 26 | #elif defined(__aarch64__) || defined(_M_ARM64) 27 | #define HOST_ARCH COMPTIME_ARCH_ARM64 28 | #define HOST_ARCH_STRING "arm64" 29 | #define HOST_POINTER_SIZE 8 30 | #elif defined(__arm__) || defined(_M_ARM) 31 | #define HOST_ARCH COMPTIME_ARCH_ARM 32 | #define HOST_ARCH_STRING "arm" 33 | #define HOST_POINTER_SIZE 4 34 | #elif defined(__riscv) && (__riscv_xlen == 64) 35 | #define HOST_ARCH COMPTIME_ARCH_RISCV64 36 | #define HOST_ARCH_STRING "riscv64" 37 | #define HOST_POINTER_SIZE 8 38 | #else 39 | #define HOST_ARCH COMPTIME_ARCH_UNKNOWN 40 | #define HOST_ARCH_STRING "unknown" 41 | #define HOST_POINTER_SIZE 8 42 | #endif 43 | 44 | #define MACH_VERSION "0.1.0" 45 | 46 | void comptime_build_context_init_host(ComptimeBuildContext *ctx) 47 | { 48 | if (!ctx) 49 | return; 50 | 51 | ctx->target_os = HOST_OS; 52 | ctx->target_arch = HOST_ARCH; 53 | ctx->target_pointer_size = HOST_POINTER_SIZE; 54 | ctx->target_triple = HOST_ARCH_STRING "-unknown-" HOST_OS_STRING; 55 | ctx->build_debug = true; 56 | ctx->opt_level = 0; 57 | ctx->mach_version = MACH_VERSION; 58 | } 59 | 60 | bool comptime_get_constant(const char *name, ComptimeValue *out_value, const ComptimeBuildContext *ctx) 61 | { 62 | if (!name || !out_value) 63 | return false; 64 | 65 | // Default to host context if none provided 66 | static ComptimeBuildContext default_ctx; 67 | static bool default_initialized = false; 68 | if (!ctx) 69 | { 70 | if (!default_initialized) 71 | { 72 | comptime_build_context_init_host(&default_ctx); 73 | default_initialized = true; 74 | } 75 | ctx = &default_ctx; 76 | } 77 | 78 | // OS enum values (constants, not dependent on target) 79 | if (strcmp(name, "OS_LINUX") == 0) 80 | { 81 | out_value->kind = COMPTIME_U8; 82 | out_value->u8_val = COMPTIME_OS_LINUX; 83 | return true; 84 | } 85 | if (strcmp(name, "OS_DARWIN") == 0) 86 | { 87 | out_value->kind = COMPTIME_U8; 88 | out_value->u8_val = COMPTIME_OS_DARWIN; 89 | return true; 90 | } 91 | if (strcmp(name, "OS_WINDOWS") == 0) 92 | { 93 | out_value->kind = COMPTIME_U8; 94 | out_value->u8_val = COMPTIME_OS_WINDOWS; 95 | return true; 96 | } 97 | if (strcmp(name, "OS_BSD") == 0) 98 | { 99 | out_value->kind = COMPTIME_U8; 100 | out_value->u8_val = COMPTIME_OS_BSD; 101 | return true; 102 | } 103 | 104 | // ARCH enum values (constants, not dependent on target) 105 | if (strcmp(name, "ARCH_X86_64") == 0) 106 | { 107 | out_value->kind = COMPTIME_U8; 108 | out_value->u8_val = COMPTIME_ARCH_X86_64; 109 | return true; 110 | } 111 | if (strcmp(name, "ARCH_ARM64") == 0) 112 | { 113 | out_value->kind = COMPTIME_U8; 114 | out_value->u8_val = COMPTIME_ARCH_ARM64; 115 | return true; 116 | } 117 | if (strcmp(name, "ARCH_ARM") == 0) 118 | { 119 | out_value->kind = COMPTIME_U8; 120 | out_value->u8_val = COMPTIME_ARCH_ARM; 121 | return true; 122 | } 123 | if (strcmp(name, "ARCH_RISCV64") == 0) 124 | { 125 | out_value->kind = COMPTIME_U8; 126 | out_value->u8_val = COMPTIME_ARCH_RISCV64; 127 | return true; 128 | } 129 | 130 | // Target information (uses build context) 131 | if (strcmp(name, "target.os") == 0) 132 | { 133 | out_value->kind = COMPTIME_U8; 134 | out_value->u8_val = (unsigned char)ctx->target_os; 135 | return true; 136 | } 137 | if (strcmp(name, "target.arch") == 0) 138 | { 139 | out_value->kind = COMPTIME_U8; 140 | out_value->u8_val = (unsigned char)ctx->target_arch; 141 | return true; 142 | } 143 | if (strcmp(name, "target.pointer_size") == 0) 144 | { 145 | out_value->kind = COMPTIME_U64; 146 | out_value->u64_val = ctx->target_pointer_size; 147 | return true; 148 | } 149 | if (strcmp(name, "target.word_size") == 0) 150 | { 151 | out_value->kind = COMPTIME_U64; 152 | out_value->u64_val = ctx->target_pointer_size; 153 | return true; 154 | } 155 | if (strcmp(name, "target.triple") == 0) 156 | { 157 | out_value->kind = COMPTIME_STRING; 158 | out_value->string_val = ctx->target_triple; 159 | return true; 160 | } 161 | 162 | // Build configuration (uses build context) 163 | if (strcmp(name, "build.debug") == 0) 164 | { 165 | out_value->kind = COMPTIME_U8; 166 | out_value->u8_val = ctx->build_debug ? 1 : 0; 167 | return true; 168 | } 169 | 170 | // Mach language information 171 | if (strcmp(name, "mach.version") == 0) 172 | { 173 | out_value->kind = COMPTIME_STRING; 174 | out_value->string_val = ctx->mach_version; 175 | return true; 176 | } 177 | 178 | // Legacy constants (for backward compatibility - use target from context) 179 | if (strcmp(name, "OS") == 0) 180 | { 181 | out_value->kind = COMPTIME_U8; 182 | out_value->u8_val = (unsigned char)ctx->target_os; 183 | return true; 184 | } 185 | if (strcmp(name, "ARCH") == 0) 186 | { 187 | out_value->kind = COMPTIME_U8; 188 | out_value->u8_val = (unsigned char)ctx->target_arch; 189 | return true; 190 | } 191 | if (strcmp(name, "PTR_WIDTH") == 0) 192 | { 193 | out_value->kind = COMPTIME_U64; 194 | out_value->u64_val = ctx->target_pointer_size * 8; 195 | return true; 196 | } 197 | 198 | return false; 199 | } 200 | -------------------------------------------------------------------------------- /boot/comptime.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPTIME_H 2 | #define COMPTIME_H 3 | 4 | #include 5 | 6 | typedef enum ComptimeOS 7 | { 8 | COMPTIME_OS_UNKNOWN = 0, 9 | COMPTIME_OS_LINUX = 1, 10 | COMPTIME_OS_DARWIN = 2, 11 | COMPTIME_OS_WINDOWS = 3, 12 | COMPTIME_OS_BSD = 4, 13 | } ComptimeOS; 14 | 15 | typedef enum ComptimeArch 16 | { 17 | COMPTIME_ARCH_UNKNOWN = 0, 18 | COMPTIME_ARCH_X86_64 = 1, 19 | COMPTIME_ARCH_ARM64 = 2, 20 | COMPTIME_ARCH_ARM = 3, 21 | COMPTIME_ARCH_RISCV64 = 4, 22 | } ComptimeArch; 23 | 24 | typedef struct ComptimeBuildContext 25 | { 26 | ComptimeOS target_os; 27 | ComptimeArch target_arch; 28 | unsigned int target_pointer_size; 29 | const char *target_triple; 30 | 31 | bool build_debug; 32 | int opt_level; 33 | 34 | const char *mach_version; 35 | } ComptimeBuildContext; 36 | 37 | typedef enum ComptimeValueKind 38 | { 39 | COMPTIME_U8, 40 | COMPTIME_U64, 41 | COMPTIME_STRING, 42 | } ComptimeValueKind; 43 | 44 | typedef struct ComptimeValue 45 | { 46 | ComptimeValueKind kind; 47 | union 48 | { 49 | unsigned char u8_val; 50 | unsigned long u64_val; 51 | const char *string_val; 52 | }; 53 | } ComptimeValue; 54 | 55 | void comptime_build_context_init_host(ComptimeBuildContext *ctx); 56 | bool comptime_get_constant(const char *name, ComptimeValue *out_value, const ComptimeBuildContext *ctx); 57 | 58 | #endif // COMPTIME_H 59 | -------------------------------------------------------------------------------- /boot/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include 5 | 6 | // target-specific configuration 7 | typedef struct TargetConfig 8 | { 9 | char *name; // target name (e.g., "linux", "macos", "windows") 10 | char *target_triple; // target architecture triple 11 | char *entrypoint; // main source file for this target (relative to src_dir) 12 | char *artifacts_dir; // artifact output directory for this target (relative to project root) 13 | char *out; // final executable/library output path (relative to artifacts_dir/bin or absolute) 14 | 15 | // build options 16 | int opt_level; // optimization level (0-3) 17 | bool emit_ast; // emit AST files 18 | bool emit_ir; // emit LLVM IR 19 | bool emit_asm; // emit assembly 20 | bool emit_object; // emit object files 21 | bool build_library; // build as library 22 | bool no_pie; // disable PIE 23 | bool shared; // build shared library when building a library 24 | 25 | // linking options 26 | char **link_libraries; // libraries to link (paths to .a, .so, .dylib, etc.) 27 | int link_lib_count; // number of libraries to link 28 | } TargetConfig; 29 | 30 | // explicit dependency specification (parsed from [dependencies] table) 31 | typedef struct DepSpec 32 | { 33 | char *name; // dependency/package name (key, also becomes module alias) 34 | char *path; // relative or absolute path to dependency source or mach.toml 35 | char *src_dir; // resolved source directory inside dependency (computed) 36 | } DepSpec; 37 | 38 | // project configuration 39 | typedef struct ProjectConfig 40 | { 41 | char *name; // project name 42 | char *version; // project version 43 | char *main_file; // main source file (relative to src_dir) - deprecated, use target entrypoint 44 | char *target; // target name (or "native" to auto-detect, or "all") 45 | 46 | // project directory structure 47 | char *src_dir; // source files directory 48 | 49 | // targets (per-target out_dir: targets[i].out_dir) 50 | TargetConfig **targets; // array of target configurations 51 | int target_count; // number of targets 52 | 53 | // dependencies (also serve as module aliases) 54 | DepSpec **deps; // dependency specs 55 | int dep_count; // number of deps 56 | } ProjectConfig; 57 | 58 | // configuration file management 59 | ProjectConfig *config_load(const char *config_path); 60 | ProjectConfig *config_load_from_dir(const char *dir_path); 61 | bool config_save(ProjectConfig *config, const char *config_path); 62 | ProjectConfig *config_create_default(const char *project_name); 63 | 64 | // configuration lifecycle 65 | void config_init(ProjectConfig *config); 66 | void config_dnit(ProjectConfig *config); 67 | 68 | // target management 69 | TargetConfig *target_config_create(const char *name, const char *target_triple); 70 | void target_config_init(TargetConfig *target); 71 | void target_config_dnit(TargetConfig *target); 72 | bool config_add_target(ProjectConfig *config, const char *name, const char *target_triple); 73 | TargetConfig *config_get_target(ProjectConfig *config, const char *name); 74 | TargetConfig *config_get_target_by_triple(ProjectConfig *config, const char *target_triple); 75 | TargetConfig *config_get_default_target(ProjectConfig *config); 76 | TargetConfig *config_resolve_native_target(ProjectConfig *config); 77 | char **config_get_target_names(ProjectConfig *config); 78 | bool config_is_build_all_targets(ProjectConfig *config); 79 | 80 | // configuration queries (target-specific) 81 | bool config_has_main_file(ProjectConfig *config); 82 | bool config_should_emit_ast(ProjectConfig *config, const char *target_name); 83 | bool config_should_emit_ir(ProjectConfig *config, const char *target_name); 84 | bool config_should_emit_asm(ProjectConfig *config, const char *target_name); 85 | bool config_should_emit_object(ProjectConfig *config, const char *target_name); 86 | bool config_should_build_library(ProjectConfig *config, const char *target_name); 87 | bool config_should_link_executable(ProjectConfig *config, const char *target_name); 88 | // library type queries 89 | bool config_is_shared_library(ProjectConfig *config, const char *target_name); 90 | // derive default output names 91 | char *config_default_executable_name(ProjectConfig *config); 92 | char *config_default_library_name(ProjectConfig *config, bool shared); 93 | 94 | // path resolution (target-specific) 95 | char *config_resolve_main_file(ProjectConfig *config, const char *project_dir); // deprecated 96 | char *config_resolve_target_entrypoint(ProjectConfig *config, const char *project_dir, const char *target_name); 97 | char *config_resolve_src_dir(ProjectConfig *config, const char *project_dir); 98 | char *config_resolve_artifacts_dir(ProjectConfig *config, const char *project_dir, const char *target_name); 99 | char *config_resolve_bin_dir(ProjectConfig *config, const char *project_dir, const char *target_name); 100 | char *config_resolve_obj_dir(ProjectConfig *config, const char *project_dir, const char *target_name); 101 | char *config_resolve_asm_dir(ProjectConfig *config, const char *project_dir, const char *target_name); 102 | char *config_resolve_ir_dir(ProjectConfig *config, const char *project_dir, const char *target_name); 103 | char *config_resolve_ast_dir(ProjectConfig *config, const char *project_dir, const char *target_name); 104 | char *config_resolve_final_output_path(ProjectConfig *config, const char *project_dir, const char *target_name); 105 | 106 | // dependency management 107 | DepSpec *config_get_dep(ProjectConfig *config, const char *name); 108 | bool config_has_dep(ProjectConfig *config, const char *name); 109 | // resolve package root directory (root project or dependency). returns malloc'd string 110 | char *config_resolve_package_root(ProjectConfig *config, const char *project_dir, const char *package_name); 111 | // get package src dir (may load dependency mach.toml lazily) 112 | char *config_get_package_src_dir(ProjectConfig *config, const char *project_dir, const char *package_name); 113 | // load dependency config to fill missing src_dir if needed 114 | bool config_ensure_dep_loaded(ProjectConfig *config, const char *project_dir, DepSpec *dep); 115 | 116 | // module path expansion (deps serve as aliases automatically) 117 | char *config_expand_module_path(ProjectConfig *config, const char *module_path); 118 | 119 | // canonical module resolution: given FQN pkg.segment1.segment2 -> absolute file path (.mach) 120 | char *config_resolve_module_fqn(ProjectConfig *config, const char *project_dir, const char *fqn); 121 | 122 | // directory management 123 | bool config_ensure_directories(ProjectConfig *config, const char *project_dir); 124 | 125 | // configuration validation 126 | bool config_validate(ProjectConfig *config); 127 | 128 | #endif 129 | -------------------------------------------------------------------------------- /boot/filesystem.c: -------------------------------------------------------------------------------- 1 | #include "filesystem.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | char *fs_read_file(const char *path) 10 | { 11 | FILE *file = fopen(path, "rb"); 12 | if (!file) 13 | return NULL; 14 | fseek(file, 0, SEEK_END); 15 | long size = ftell(file); 16 | if (size < 0) 17 | { 18 | fclose(file); 19 | return NULL; 20 | } 21 | fseek(file, 0, SEEK_SET); 22 | char *buf = malloc(size + 1); 23 | if (!buf) 24 | { 25 | fclose(file); 26 | return NULL; 27 | } 28 | size_t n = fread(buf, 1, size, file); 29 | buf[n] = '\0'; 30 | fclose(file); 31 | return buf; 32 | } 33 | 34 | int fs_file_exists(const char *path) 35 | { 36 | if (!path) 37 | return 0; 38 | struct stat st; 39 | return stat(path, &st) == 0; 40 | } 41 | 42 | int fs_is_directory(const char *path) 43 | { 44 | if (!path) 45 | return 0; 46 | struct stat st; 47 | if (stat(path, &st) != 0) 48 | return 0; 49 | return S_ISDIR(st.st_mode); 50 | } 51 | 52 | int fs_is_mach_file(const char *path) 53 | { 54 | if (!path) 55 | return 0; 56 | size_t len = strlen(path); 57 | if (len < 6) // minimum: "a.mach" 58 | return 0; 59 | return strcmp(path + len - 5, ".mach") == 0; 60 | } 61 | 62 | int fs_ensure_dir_recursive(const char *path) 63 | { 64 | if (!path || !*path) 65 | return 1; 66 | 67 | char *copy = strdup(path); 68 | if (!copy) 69 | return 0; 70 | 71 | for (char *p = copy + 1; *p; ++p) 72 | { 73 | if (*p == '/') 74 | { 75 | *p = '\0'; 76 | mkdir(copy, 0755); 77 | *p = '/'; 78 | } 79 | } 80 | int r = mkdir(copy, 0755); 81 | (void)r; 82 | free(copy); 83 | return 1; 84 | } 85 | 86 | char *fs_dirname(const char *path) 87 | { 88 | const char *slash = strrchr(path, '/'); 89 | if (!slash) 90 | return strdup("."); 91 | size_t len = (size_t)(slash - path); 92 | char *out = malloc(len + 1); 93 | memcpy(out, path, len); 94 | out[len] = '\0'; 95 | return out; 96 | } 97 | 98 | char *fs_find_project_root(const char *start_path) 99 | { 100 | char resolved[PATH_MAX]; 101 | if (realpath(start_path, resolved)) 102 | { 103 | struct stat st; 104 | if (stat(resolved, &st) != 0) 105 | return strdup("."); 106 | 107 | if (!S_ISDIR(st.st_mode)) 108 | { 109 | char *slash = strrchr(resolved, '/'); 110 | if (slash) 111 | *slash = '\0'; 112 | } 113 | 114 | if (resolved[0] == '\0') 115 | strcpy(resolved, "/"); 116 | 117 | char *dir = strdup(resolved); 118 | if (!dir) 119 | return NULL; 120 | 121 | for (int depth = 0; depth < 64; depth++) 122 | { 123 | char cfg[PATH_MAX]; 124 | snprintf(cfg, sizeof(cfg), "%s/mach.toml", dir); 125 | if (fs_file_exists(cfg)) 126 | return dir; 127 | 128 | if (strcmp(dir, "/") == 0 || dir[0] == '\0') 129 | break; 130 | 131 | char *slash = strrchr(dir, '/'); 132 | if (!slash) 133 | { 134 | dir[0] = '\0'; 135 | } 136 | else if (slash == dir) 137 | { 138 | slash[1] = '\0'; 139 | } 140 | else 141 | { 142 | *slash = '\0'; 143 | } 144 | } 145 | 146 | free(dir); 147 | return strdup("."); 148 | } 149 | 150 | // fallback to relative walk if realpath fails 151 | char *dir = fs_dirname(start_path); 152 | for (int i = 0; i < 16 && dir; i++) 153 | { 154 | char cfg[1024]; 155 | snprintf(cfg, sizeof(cfg), "%s/mach.toml", dir); 156 | if (fs_file_exists(cfg)) 157 | return dir; 158 | const char *slash = strrchr(dir, '/'); 159 | if (!slash) 160 | { 161 | free(dir); 162 | return strdup("."); 163 | } 164 | if (slash == dir) 165 | { 166 | dir[1] = '\0'; 167 | return dir; 168 | } 169 | size_t nlen = (size_t)(slash - dir); 170 | char *up = malloc(nlen + 1); 171 | memcpy(up, dir, nlen); 172 | up[nlen] = '\0'; 173 | free(dir); 174 | dir = up; 175 | } 176 | return dir; 177 | } 178 | 179 | char *fs_get_base_filename(const char *path) 180 | { 181 | const char *last_slash = strrchr(path, '/'); 182 | const char *filename = last_slash ? last_slash + 1 : path; 183 | const char *last_dot = strrchr(filename, '.'); 184 | if (!last_dot) 185 | return strdup(filename); 186 | size_t len = (size_t)(last_dot - filename); 187 | char *base = malloc(len + 1); 188 | strncpy(base, filename, len); 189 | base[len] = '\0'; 190 | return base; 191 | } 192 | -------------------------------------------------------------------------------- /boot/filesystem.h: -------------------------------------------------------------------------------- 1 | #ifndef FILESYSTEM_H 2 | #define FILESYSTEM_H 3 | 4 | // read entire file into memory (binary safe) 5 | char *fs_read_file(const char *path); 6 | 7 | // check if file exists 8 | int fs_file_exists(const char *path); 9 | 10 | // check if path is a directory 11 | int fs_is_directory(const char *path); 12 | 13 | // check if path ends with .mach extension 14 | int fs_is_mach_file(const char *path); 15 | 16 | // recursively create directory and all parent directories 17 | int fs_ensure_dir_recursive(const char *path); 18 | 19 | // find project root by searching for mach.toml 20 | char *fs_find_project_root(const char *start_path); 21 | 22 | // get base filename without extension 23 | char *fs_get_base_filename(const char *path); 24 | 25 | // duplicate directory portion of path 26 | char *fs_dirname(const char *path); 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /boot/ioutil.c: -------------------------------------------------------------------------------- 1 | #include "ioutil.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | #else 10 | #include 11 | #include 12 | #include 13 | #endif 14 | 15 | bool is_directory(char *path) 16 | { 17 | #ifdef _WIN32 18 | DWORD attributes = GetFileAttributesA(path); 19 | if (attributes == INVALID_FILE_ATTRIBUTES) 20 | { 21 | return false; 22 | } 23 | return (attributes & FILE_ATTRIBUTE_DIRECTORY) != 0; 24 | #else 25 | struct stat path_stat; 26 | if (stat(path, &path_stat) != 0) 27 | { 28 | return false; 29 | } 30 | return S_ISDIR(path_stat.st_mode); 31 | #endif 32 | } 33 | 34 | bool file_exists(char *path) 35 | { 36 | #ifdef _WIN32 37 | wchar_t wpath[MAX_PATH]; 38 | mbstowcs(wpath, path, MAX_PATH); 39 | DWORD attributes = GetFileAttributesW(wpath); 40 | return (attributes != INVALID_FILE_ATTRIBUTES && !(attributes & FILE_ATTRIBUTE_DIRECTORY)); 41 | #else 42 | struct stat path_stat; 43 | return stat(path, &path_stat) == 0 && !S_ISDIR(path_stat.st_mode); 44 | #endif 45 | } 46 | 47 | bool path_is_absolute(char *path) 48 | { 49 | #ifdef _WIN32 50 | // "C:\ or C:/" 51 | return (path[1] == ':' && (path[2] == '\\' || path[2] == '/')); 52 | #else 53 | // "/home/user" 54 | return path[0] == '/'; 55 | #endif 56 | } 57 | 58 | char *path_dirname(char *path) 59 | { 60 | #ifdef _WIN32 61 | char *last_slash = strrchr(path, '\\'); 62 | #else 63 | char *last_slash = strrchr(path, '/'); 64 | #endif 65 | 66 | if (last_slash == NULL) 67 | { 68 | return "."; 69 | } 70 | 71 | int len = last_slash - path; 72 | char *dirname = malloc(len + 1); 73 | strncpy(dirname, path, len); 74 | dirname[len] = '\0'; 75 | return dirname; 76 | } 77 | 78 | char *path_lastname(char *path) 79 | { 80 | #ifdef _WIN32 81 | char *last_slash = strrchr(path, '\\'); 82 | #else 83 | char *last_slash = strrchr(path, '/'); 84 | #endif 85 | 86 | if (last_slash == NULL) 87 | { 88 | return path; 89 | } 90 | 91 | return last_slash + 1; 92 | } 93 | 94 | char *path_join(char *a, char *b) 95 | { 96 | int len_a = strlen(a); 97 | int len_b = strlen(b); 98 | char *joined = malloc(len_a + len_b + 2); 99 | strcpy(joined, a); 100 | 101 | #ifdef _WIN32 102 | if (a[len_a - 1] != '\\' && a[len_a - 1] != '/') 103 | { 104 | strcat(joined, "\\"); 105 | } 106 | #else 107 | if (a[len_a - 1] != '/') 108 | { 109 | strcat(joined, "/"); 110 | } 111 | #endif 112 | 113 | strcat(joined, b); 114 | return joined; 115 | } 116 | 117 | // subtract the base path from the path 118 | // e.g: 119 | // - base = "C:\project_root" 120 | // - path = "C:\project_root\src\main.mach" 121 | // - result = "src\main.mach" 122 | char *path_relative(char *base, char *path) 123 | { 124 | int len_base = strlen(base); 125 | int len_path = strlen(path); 126 | 127 | if (len_path < len_base) 128 | { 129 | return NULL; 130 | } 131 | 132 | if (strncmp(base, path, len_base) != 0) 133 | { 134 | return NULL; 135 | } 136 | 137 | if (path[len_base] == '\\' || path[len_base] == '/') 138 | { 139 | return path + len_base + 1; 140 | } 141 | 142 | return path + len_base; 143 | } 144 | 145 | char *path_get_extension(char *path) 146 | { 147 | char *last_dot = strrchr(path, '.'); 148 | if (last_dot == NULL) 149 | { 150 | return NULL; 151 | } 152 | 153 | return last_dot + 1; 154 | } 155 | 156 | char *read_file(char *path) 157 | { 158 | FILE *file = fopen(path, "r"); 159 | if (file == NULL) 160 | { 161 | return NULL; 162 | } 163 | 164 | fseek(file, 0, SEEK_END); 165 | long length = ftell(file); 166 | fseek(file, 0, SEEK_SET); 167 | 168 | char *buffer = malloc(length + 1); 169 | fread(buffer, 1, length, file); 170 | buffer[length] = '\0'; 171 | 172 | fclose(file); 173 | return buffer; 174 | } 175 | 176 | char **list_files(char *path) 177 | { 178 | char **file_list = NULL; 179 | int count = 0; 180 | 181 | #ifdef _WIN32 182 | WIN32_FIND_DATAW find_file_data; 183 | HANDLE h_find = INVALID_HANDLE_VALUE; 184 | wchar_t search_path[MAX_PATH]; 185 | 186 | // Convert the input path to a wide character string 187 | size_t path_len = strlen(path) + 1; 188 | wchar_t *wpath = malloc(path_len * sizeof(wchar_t)); 189 | mbstowcs(wpath, path, path_len); 190 | 191 | // Append the wildcard to the path 192 | swprintf(search_path, MAX_PATH, L"%s\\*", wpath); 193 | free(wpath); 194 | 195 | h_find = FindFirstFileW(search_path, &find_file_data); 196 | 197 | if (h_find == INVALID_HANDLE_VALUE) 198 | { 199 | fprintf(stderr, "FindFirstFileW failed with error code %lu\n", GetLastError()); 200 | return NULL; 201 | } 202 | 203 | do 204 | { 205 | if (wcscmp(find_file_data.cFileName, L".") != 0 && wcscmp(find_file_data.cFileName, L"..") != 0) 206 | { 207 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 208 | if (file_list == NULL) 209 | { 210 | perror("realloc"); 211 | FindClose(h_find); 212 | return NULL; 213 | } 214 | 215 | size_t len = wcslen(find_file_data.cFileName) + 1; 216 | file_list[count] = malloc(len * sizeof(char)); 217 | if (file_list[count] == NULL) 218 | { 219 | perror("malloc"); 220 | FindClose(h_find); 221 | return NULL; 222 | } 223 | 224 | wcstombs(file_list[count], find_file_data.cFileName, len); 225 | 226 | count++; 227 | } 228 | } while (FindNextFileW(h_find, &find_file_data) != 0); 229 | 230 | FindClose(h_find); 231 | 232 | if (GetLastError() != ERROR_NO_MORE_FILES) 233 | { 234 | fprintf(stderr, "FindNextFileW failed with error code %lu\n", GetLastError()); 235 | for (int i = 0; i < count; i++) 236 | { 237 | free(file_list[i]); 238 | } 239 | free(file_list); 240 | return NULL; 241 | } 242 | 243 | #else 244 | DIR *dir; 245 | struct dirent *entry; 246 | 247 | dir = opendir(path); 248 | if (dir == NULL) 249 | { 250 | perror("opendir"); 251 | return NULL; 252 | } 253 | 254 | while ((entry = readdir(dir)) != NULL) 255 | { 256 | if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) 257 | { 258 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 259 | if (file_list == NULL) 260 | { 261 | perror("realloc"); 262 | closedir(dir); 263 | return NULL; 264 | } 265 | 266 | file_list[count] = strdup(entry->d_name); 267 | if (file_list[count] == NULL) 268 | { 269 | perror("strdup"); 270 | closedir(dir); 271 | return NULL; 272 | } 273 | 274 | count++; 275 | } 276 | } 277 | 278 | closedir(dir); 279 | #endif 280 | 281 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 282 | if (file_list == NULL) 283 | { 284 | perror("realloc"); 285 | return NULL; 286 | } 287 | file_list[count] = NULL; 288 | 289 | return file_list; 290 | } 291 | 292 | char **list_files_recursive(char *path, char **file_list, int count) 293 | { 294 | #ifdef _WIN32 295 | WIN32_FIND_DATAA find_file_data; 296 | HANDLE h_find = NULL; 297 | char search_path[MAX_PATH]; 298 | 299 | snprintf(search_path, MAX_PATH, "%s\\*", path); 300 | 301 | h_find = FindFirstFileA(search_path, &find_file_data); 302 | if (h_find == INVALID_HANDLE_VALUE) 303 | { 304 | fprintf(stderr, "FindFirstFile failed with error code %lu\n", GetLastError()); 305 | return NULL; 306 | } 307 | 308 | do 309 | { 310 | if (strcmp(find_file_data.cFileName, ".") != 0 && strcmp(find_file_data.cFileName, "..") != 0) 311 | { 312 | char *full_path = path_join(path, find_file_data.cFileName); 313 | if (is_directory(full_path)) 314 | { 315 | char **subdir_files = list_files_recursive(full_path, NULL, 0); 316 | if (subdir_files == NULL) 317 | { 318 | FindClose(h_find); 319 | return NULL; 320 | } 321 | 322 | for (int i = 0; subdir_files[i] != NULL; i++) 323 | { 324 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 325 | if (file_list == NULL) 326 | { 327 | perror("realloc"); 328 | FindClose(h_find); 329 | return NULL; 330 | } 331 | 332 | char *full_subdir_path = path_join(find_file_data.cFileName, subdir_files[i]); 333 | 334 | file_list[count] = full_subdir_path; 335 | count++; 336 | } 337 | } 338 | else 339 | { 340 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 341 | if (file_list == NULL) 342 | { 343 | perror("realloc"); 344 | FindClose(h_find); 345 | return NULL; 346 | } 347 | 348 | file_list[count] = strdup(find_file_data.cFileName); 349 | if (file_list[count] == NULL) 350 | { 351 | perror("strdup"); 352 | FindClose(h_find); 353 | return NULL; 354 | } 355 | 356 | count++; 357 | } 358 | 359 | free(full_path); 360 | } 361 | } while (FindNextFileA(h_find, &find_file_data) != 0); 362 | 363 | FindClose(h_find); 364 | 365 | if (GetLastError() != ERROR_NO_MORE_FILES) 366 | { 367 | fprintf(stderr, "FindNextFileW failed with error code %lu\n", GetLastError()); 368 | for (int i = 0; i < count; i++) 369 | { 370 | free(file_list[i]); 371 | } 372 | free(file_list); 373 | return NULL; 374 | } 375 | 376 | #else 377 | DIR *dir; 378 | struct dirent *entry; 379 | 380 | dir = opendir(path); 381 | if (dir == NULL) 382 | { 383 | perror("opendir"); 384 | return NULL; 385 | } 386 | 387 | while ((entry = readdir(dir)) != NULL) 388 | { 389 | if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) 390 | { 391 | char *full_path = path_join(path, entry->d_name); 392 | if (is_directory(full_path)) 393 | { 394 | file_list = list_files_recursive(full_path, file_list, count); 395 | if (file_list == NULL) 396 | { 397 | closedir(dir); 398 | return NULL; 399 | } 400 | } 401 | else 402 | { 403 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 404 | if (file_list == NULL) 405 | { 406 | perror("realloc"); 407 | closedir(dir); 408 | return NULL; 409 | } 410 | 411 | file_list[count] = strdup(entry->d_name); 412 | if (file_list[count] == NULL) 413 | { 414 | perror("strdup"); 415 | closedir(dir); 416 | return NULL; 417 | } 418 | 419 | count++; 420 | } 421 | 422 | free(full_path); 423 | } 424 | } 425 | 426 | closedir(dir); 427 | #endif 428 | 429 | file_list = realloc(file_list, sizeof(char *) * (count + 1)); 430 | if (file_list == NULL) 431 | { 432 | perror("realloc"); 433 | return NULL; 434 | } 435 | file_list[count] = NULL; 436 | 437 | return file_list; 438 | } 439 | -------------------------------------------------------------------------------- /boot/ioutil.h: -------------------------------------------------------------------------------- 1 | #ifndef IOUTIL_H 2 | #define IOUTIL_H 3 | 4 | #include 5 | 6 | bool is_directory(char *path); 7 | bool file_exists(char *path); 8 | bool path_is_absolute(char *path); 9 | char *path_dirname(char *path); 10 | char *path_lastname(char *path); 11 | char *path_join(char *a, char *b); 12 | char *path_relative(char *base, char *path); 13 | char *path_get_extension(char *path); 14 | 15 | char *read_file(char *path); 16 | 17 | char **list_files(char *path); 18 | char **list_files_recursive(char *path, char **file_list, int count); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /boot/lexer.h: -------------------------------------------------------------------------------- 1 | #ifndef LEXER_H 2 | #define LEXER_H 3 | 4 | #include "token.h" 5 | 6 | #include 7 | 8 | typedef struct Lexer 9 | { 10 | int pos; 11 | char *source; 12 | } Lexer; 13 | 14 | void lexer_init(Lexer *lexer, char *source); 15 | void lexer_dnit(Lexer *lexer); 16 | 17 | bool lexer_at_end(Lexer *lexer); 18 | char lexer_current(Lexer *lexer); 19 | char lexer_peek(Lexer *lexer, int offset); 20 | char lexer_advance(Lexer *lexer); 21 | 22 | int lexer_get_pos_line(Lexer *lexer, int pos); 23 | int lexer_get_pos_line_offset(Lexer *lexer, int pos); 24 | char *lexer_get_line_text(Lexer *lexer, int line); 25 | 26 | void lexer_skip_whitespace(Lexer *lexer); 27 | 28 | Token *lexer_parse_identifier(Lexer *lexer); 29 | Token *lexer_parse_lit_number(Lexer *lexer); 30 | Token *lexer_parse_lit_char(Lexer *lexer); 31 | Token *lexer_parse_lit_string(Lexer *lexer); 32 | 33 | unsigned long long lexer_eval_lit_int(Lexer *lexer, Token *token); 34 | double lexer_eval_lit_float(Lexer *lexer, Token *token); 35 | char lexer_eval_lit_char(Lexer *lexer, Token *token); 36 | char *lexer_eval_lit_string(Lexer *lexer, Token *token); 37 | char *lexer_raw_value(Lexer *lexer, Token *token); 38 | 39 | Token *lexer_emit(Lexer *lexer, TokenKind kind, int len); 40 | 41 | Token *lexer_next(Lexer *lexer); 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /boot/main.c: -------------------------------------------------------------------------------- 1 | // small dispatcher for commands; implementation is in src/commands.c 2 | #include "commands.h" 3 | #include 4 | #include 5 | 6 | int main(int argc, char **argv) 7 | { 8 | if (argc < 2) 9 | { 10 | mach_print_usage(argv[0]); 11 | return 1; 12 | } 13 | 14 | const char *command = argv[1]; 15 | 16 | if (strcmp(command, "help") == 0) 17 | { 18 | mach_print_usage(argv[0]); 19 | return 0; 20 | } 21 | else if (strcmp(command, "build") == 0) 22 | { 23 | return mach_cmd_build(argc, argv); 24 | } 25 | else 26 | { 27 | fprintf(stderr, "error: unknown command '%s'\n", command); 28 | mach_print_usage(argv[0]); 29 | return 1; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /boot/module.h: -------------------------------------------------------------------------------- 1 | #ifndef MODULE_H 2 | #define MODULE_H 3 | 4 | #include "ast.h" 5 | #include "parser.h" 6 | #include 7 | 8 | typedef struct Module Module; 9 | typedef struct ModuleManager ModuleManager; 10 | typedef struct SpecializationCache SpecializationCache; 11 | 12 | // forward statement 13 | typedef struct SymbolTable SymbolTable; 14 | 15 | // represents a loaded module 16 | struct Module 17 | { 18 | char *name; // module name 19 | char *file_path; // absolute file path 20 | char *object_path; // compiled object file path 21 | char *source; // cached source content 22 | AstNode *ast; // parsed AST 23 | SymbolTable *symbols; // module's symbol table 24 | bool is_parsed; // parsing complete 25 | bool is_analyzed; // semantic analysis complete 26 | bool is_compiled; // object file compilation complete 27 | bool needs_linking; // true if this module should be linked 28 | Module *next; // linked list for dependencies 29 | }; 30 | 31 | // error tracking for modules 32 | typedef struct ModuleError 33 | { 34 | char *module_path; 35 | char *file_path; 36 | char *message; 37 | } ModuleError; 38 | 39 | typedef struct ModuleErrorList 40 | { 41 | ModuleError **errors; 42 | int count; 43 | int capacity; 44 | } ModuleErrorList; 45 | 46 | // manages module loading and dependency resolution 47 | struct ModuleManager 48 | { 49 | Module **modules; // hash table of loaded modules 50 | int capacity; // hash table size 51 | int count; // number of loaded modules 52 | char **search_paths; // directories to search for modules 53 | int search_count; // number of search paths 54 | // simple alias map for config-less resolution: name -> base directory 55 | char **alias_names; 56 | char **alias_paths; 57 | int alias_count; 58 | ModuleErrorList errors; // accumulated errors during loading 59 | bool had_error; // true if any module failed to load/parse 60 | 61 | // configuration for dependency resolution 62 | void *config; // ProjectConfig* (void* to avoid circular includes) 63 | const char *project_dir; // project directory for resolving paths 64 | }; 65 | 66 | // module manager lifecycle 67 | void module_manager_init(ModuleManager *manager); 68 | void module_manager_dnit(ModuleManager *manager); 69 | 70 | // search path management 71 | void module_manager_add_search_path(ModuleManager *manager, const char *path); 72 | void module_manager_add_alias(ModuleManager *manager, const char *name, const char *base_dir); 73 | void module_manager_set_config(ModuleManager *manager, void *config, const char *project_dir); 74 | 75 | // module loading 76 | Module *module_manager_load_module(ModuleManager *manager, const char *module_path); 77 | Module *module_manager_find_module(ModuleManager *manager, const char *name); 78 | Module *module_manager_find_by_file_path(ModuleManager *manager, const char *file_path); 79 | 80 | // dependency compilation and linking 81 | bool module_manager_compile_dependencies(ModuleManager *manager, const char *output_dir, int opt_level, bool no_pie, bool debug_info, bool emit_asm, bool emit_ir, bool emit_ast, SpecializationCache *spec_cache); 82 | bool module_manager_get_link_objects(ModuleManager *manager, char ***object_files, int *count); 83 | 84 | // utility helpers 85 | char *module_make_object_path(const char *output_dir, const char *module_name); 86 | 87 | // error handling 88 | void module_error_list_init(ModuleErrorList *list); 89 | void module_error_list_dnit(ModuleErrorList *list); 90 | void module_error_list_add(ModuleErrorList *list, const char *module_path, const char *file_path, const char *message); 91 | void module_error_list_print(ModuleErrorList *list); 92 | 93 | // module operations 94 | void module_init(Module *module, const char *name, const char *file_path); 95 | void module_dnit(Module *module); 96 | 97 | // utility functions 98 | char *module_path_to_file_path(ModuleManager *manager, const char *module_path); 99 | bool module_has_circular_dependency(ModuleManager *manager, Module *module, const char *target); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /boot/parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSER_H 2 | #define PARSER_H 3 | 4 | #include "ast.h" 5 | #include "lexer.h" 6 | #include 7 | 8 | typedef enum 9 | { 10 | PREC_NONE, 11 | PREC_ASSIGNMENT, 12 | PREC_OR, 13 | PREC_AND, 14 | PREC_BIT_OR, 15 | PREC_BIT_XOR, 16 | PREC_BIT_AND, 17 | PREC_EQUALITY, 18 | PREC_COMPARISON, 19 | PREC_SHIFT, 20 | PREC_TERM, 21 | PREC_FACTOR, 22 | PREC_UNARY, 23 | PREC_POSTFIX, 24 | PREC_PRIMARY 25 | } Precedence; 26 | 27 | typedef struct ParserError 28 | { 29 | Token *token; 30 | char *message; 31 | } ParserError; 32 | 33 | typedef struct ParserErrorList 34 | { 35 | ParserError *errors; 36 | int count; 37 | int capacity; 38 | } ParserErrorList; 39 | 40 | typedef struct Parser 41 | { 42 | Lexer *lexer; 43 | Token *current; 44 | Token *previous; 45 | bool panic_mode; 46 | bool had_error; 47 | ParserErrorList errors; 48 | } Parser; 49 | 50 | // parser lifecycle 51 | void parser_init(Parser *parser, Lexer *lexer); 52 | void parser_dnit(Parser *parser); 53 | 54 | // error list operations 55 | void parser_error_list_init(ParserErrorList *list); 56 | void parser_error_list_dnit(ParserErrorList *list); 57 | void parser_error_list_add(ParserErrorList *list, Token *token, const char *message); 58 | void parser_error_list_print(ParserErrorList *list, Lexer *lexer, const char *file_path); 59 | 60 | // token navigation 61 | void parser_advance(Parser *parser); 62 | bool parser_check(Parser *parser, TokenKind kind); 63 | bool parser_match(Parser *parser, TokenKind kind); 64 | bool parser_consume(Parser *parser, TokenKind kind, const char *message); 65 | void parser_synchronize(Parser *parser); 66 | bool parser_is_at_end(Parser *parser); 67 | 68 | // error handling 69 | void parser_error(Parser *parser, Token *token, const char *message); 70 | void parser_error_at_current(Parser *parser, const char *message); 71 | void parser_error_at_previous(Parser *parser, const char *message); 72 | 73 | // parsing entry point 74 | AstNode *parser_parse_program(Parser *parser); 75 | 76 | // statement parsing 77 | AstNode *parser_parse_stmt_top(Parser *parser); 78 | AstNode *parser_parse_stmt(Parser *parser); 79 | AstNode *parser_parse_stmt_use(Parser *parser); 80 | AstNode *parser_parse_stmt_ext(Parser *parser, bool is_public); 81 | AstNode *parser_parse_stmt_def(Parser *parser, bool is_public); 82 | AstNode *parser_parse_stmt_val(Parser *parser, bool is_public); 83 | AstNode *parser_parse_stmt_var(Parser *parser, bool is_public); 84 | AstNode *parser_parse_stmt_fun(Parser *parser, bool is_public); 85 | AstNode *parser_parse_stmt_rec(Parser *parser, bool is_public); 86 | AstNode *parser_parse_stmt_uni(Parser *parser, bool is_public); 87 | AstNode *parser_parse_stmt_if(Parser *parser); 88 | AstNode *parser_parse_stmt_for(Parser *parser); 89 | AstNode *parser_parse_stmt_brk(Parser *parser); 90 | AstNode *parser_parse_stmt_cnt(Parser *parser); 91 | AstNode *parser_parse_stmt_ret(Parser *parser); 92 | AstNode *parser_parse_stmt_block(Parser *parser); 93 | AstNode *parser_parse_stmt_expr(Parser *parser); 94 | 95 | AstNode *parser_parse_expr(Parser *parser); 96 | AstNode *parser_parse_expr_prec(Parser *parser, Precedence min_prec); 97 | AstNode *parser_parse_expr_prefix(Parser *parser); 98 | AstNode *parser_parse_expr_postfix(Parser *parser); 99 | AstNode *parser_parse_expr_atom(Parser *parser); 100 | AstNode *parser_parse_array_literal(Parser *parser); 101 | AstNode *parser_parse_struct_literal(Parser *parser, AstNode *type); 102 | AstNode *parser_parse_typed_literal(Parser *parser, AstNode *type); 103 | 104 | // type parsing 105 | AstNode *parser_parse_type(Parser *parser); 106 | AstNode *parser_parse_type_name(Parser *parser); 107 | AstNode *parser_parse_type_ptr(Parser *parser); 108 | AstNode *parser_parse_type_array(Parser *parser); 109 | AstNode *parser_parse_type_fun(Parser *parser); 110 | AstNode *parser_parse_type_rec(Parser *parser); 111 | AstNode *parser_parse_type_uni(Parser *parser); 112 | 113 | // list parsing 114 | AstList *parser_parse_field_list(Parser *parser); 115 | AstList *parser_parse_parameter_list(Parser *parser); 116 | 117 | // utilities 118 | char *parser_parse_identifier(Parser *parser); 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /boot/semantic.h: -------------------------------------------------------------------------------- 1 | #ifndef SEMANTIC_NEW_H 2 | #define SEMANTIC_NEW_H 3 | 4 | #include "ast.h" 5 | #include "comptime.h" 6 | #include "module.h" 7 | #include "symbol.h" 8 | #include "type.h" 9 | #include 10 | #include 11 | 12 | // forward declarations 13 | typedef struct SemanticDriver SemanticDriver; 14 | typedef struct AnalysisContext AnalysisContext; 15 | typedef struct DiagnosticSink DiagnosticSink; 16 | typedef struct GenericBindingCtx GenericBindingCtx; 17 | typedef struct SpecializationKey SpecializationKey; 18 | typedef struct SpecializationCache SpecializationCache; 19 | typedef struct InstantiationQueue InstantiationQueue; 20 | 21 | // diagnostic severity 22 | typedef enum 23 | { 24 | DIAG_ERROR, 25 | DIAG_WARNING, 26 | DIAG_NOTE 27 | } DiagnosticLevel; 28 | 29 | // diagnostic entry 30 | typedef struct Diagnostic 31 | { 32 | DiagnosticLevel level; 33 | char *message; 34 | char *file_path; 35 | Token *token; 36 | int line; 37 | int column; 38 | } Diagnostic; 39 | 40 | // source cache entry for diagnostic printing 41 | typedef struct SourceCacheEntry 42 | { 43 | char *file_path; 44 | char *source; 45 | struct SourceCacheEntry *next; 46 | } SourceCacheEntry; 47 | 48 | // diagnostic sink: collects errors/warnings 49 | struct DiagnosticSink 50 | { 51 | Diagnostic *entries; 52 | size_t count; 53 | size_t capacity; 54 | bool has_errors; 55 | bool has_fatal; 56 | SourceCacheEntry **source_cache; 57 | size_t cache_size; 58 | }; 59 | 60 | // generic binding: immutable type parameter -> concrete type mapping 61 | typedef struct GenericBinding 62 | { 63 | const char *param_name; 64 | Type *concrete_type; 65 | } GenericBinding; 66 | 67 | // generic binding context: stack of bindings for nested generics 68 | struct GenericBindingCtx 69 | { 70 | GenericBinding *bindings; 71 | size_t count; 72 | size_t capacity; 73 | }; 74 | 75 | // analysis context: immutable snapshot of scope + bindings 76 | struct AnalysisContext 77 | { 78 | Scope *current_scope; 79 | Scope *module_scope; 80 | Scope *global_scope; 81 | GenericBindingCtx bindings; 82 | const char *module_name; 83 | const char *file_path; 84 | Symbol *current_function; 85 | }; 86 | 87 | // specialization key: identifies unique type argument tuple 88 | struct SpecializationKey 89 | { 90 | Symbol *generic_symbol; 91 | Type **type_args; 92 | size_t type_arg_count; 93 | }; 94 | 95 | // specialization cache entry 96 | typedef struct SpecializationEntry 97 | { 98 | SpecializationKey key; 99 | Symbol *specialized_symbol; 100 | struct SpecializationEntry *next; 101 | } SpecializationEntry; 102 | 103 | // specialization cache: hash table of instantiated generics 104 | struct SpecializationCache 105 | { 106 | SpecializationEntry **buckets; 107 | size_t bucket_count; 108 | size_t entry_count; 109 | }; 110 | 111 | // instantiation request kinds 112 | typedef enum 113 | { 114 | INST_FUNCTION, 115 | INST_STRUCT, 116 | INST_UNION 117 | } InstantiationKind; 118 | 119 | // instantiation request: deferred generic specialization 120 | typedef struct InstantiationRequest 121 | { 122 | InstantiationKind kind; 123 | Symbol *generic_symbol; 124 | Type **type_args; 125 | size_t type_arg_count; 126 | AstNode *call_site; 127 | struct InstantiationRequest *next; 128 | } InstantiationRequest; 129 | 130 | // instantiation queue: work list for monomorphization 131 | struct InstantiationQueue 132 | { 133 | InstantiationRequest *head; 134 | InstantiationRequest *tail; 135 | size_t count; 136 | }; 137 | 138 | // symbol attribute: compile-time attribute associated with a symbol 139 | typedef struct SymbolAttribute 140 | { 141 | char *symbol_name; // the symbol this attribute applies to 142 | char *attr_name; // attribute name (e.g., "symbol", "inline") 143 | char *attr_value; // attribute value (string literal) 144 | struct SymbolAttribute *next; // linked list 145 | } SymbolAttribute; 146 | 147 | // symbol attribute map: stores all compile-time attributes 148 | typedef struct SymbolAttributeMap 149 | { 150 | SymbolAttribute *head; 151 | } SymbolAttributeMap; 152 | 153 | // semantic driver: orchestrates multi-pass analysis 154 | struct SemanticDriver 155 | { 156 | ModuleManager module_manager; 157 | SymbolTable symbol_table; // for codegen compatibility 158 | SpecializationCache spec_cache; 159 | InstantiationQueue inst_queue; 160 | DiagnosticSink diagnostics; 161 | AstNode *program_root; 162 | const char *entry_module_name; 163 | SymbolAttributeMap attributes; // compile-time attributes 164 | ComptimeBuildContext comptime_ctx; // compile-time build context 165 | }; 166 | 167 | // driver lifecycle 168 | SemanticDriver *semantic_driver_create(void); 169 | void semantic_driver_destroy(SemanticDriver *driver); 170 | 171 | // main entry point 172 | bool semantic_driver_analyze(SemanticDriver *driver, AstNode *root, const char *module_name, const char *module_path); 173 | 174 | // diagnostics 175 | void diagnostic_sink_init(DiagnosticSink *sink); 176 | void diagnostic_sink_dnit(DiagnosticSink *sink); 177 | void diagnostic_emit(DiagnosticSink *sink, DiagnosticLevel level, AstNode *node, const char *file_path, const char *fmt, ...); 178 | void diagnostic_print_all(DiagnosticSink *sink, ModuleManager *module_manager); 179 | 180 | // generic bindings 181 | GenericBindingCtx generic_binding_ctx_create(void); 182 | void generic_binding_ctx_destroy(GenericBindingCtx *ctx); 183 | GenericBindingCtx generic_binding_ctx_push(GenericBindingCtx *parent, const char *param_name, Type *concrete_type); 184 | Type *generic_binding_ctx_lookup(const GenericBindingCtx *ctx, const char *param_name); 185 | 186 | // specialization cache 187 | void specialization_cache_init(SpecializationCache *cache); 188 | void specialization_cache_dnit(SpecializationCache *cache); 189 | Symbol *specialization_cache_find(SpecializationCache *cache, Symbol *generic_symbol, Type **type_args, size_t type_arg_count); 190 | void specialization_cache_insert(SpecializationCache *cache, Symbol *generic_symbol, Type **type_args, size_t type_arg_count, Symbol *specialized); 191 | void specialization_cache_foreach(SpecializationCache *cache, void (*callback)(Symbol *specialized, void *user_data), void *user_data); 192 | 193 | // instantiation queue 194 | void instantiation_queue_init(InstantiationQueue *queue); 195 | void instantiation_queue_dnit(InstantiationQueue *queue); 196 | void instantiation_queue_push(InstantiationQueue *queue, InstantiationKind kind, Symbol *generic_symbol, Type **type_args, size_t type_arg_count, AstNode *call_site); 197 | InstantiationRequest *instantiation_queue_pop(InstantiationQueue *queue); 198 | 199 | // name mangling 200 | char *mangle_generic_type(const char *module_name, const char *base_name, Type **type_args, size_t type_arg_count); 201 | char *mangle_generic_function(const char *module_name, const char *base_name, Type **type_args, size_t type_arg_count); 202 | char *mangle_method(const char *module_name, const char *owner_name, const char *method_name, bool receiver_is_pointer); 203 | char *mangle_global_symbol(const char *module_name, const char *symbol_name); 204 | 205 | // analysis context helpers 206 | AnalysisContext analysis_context_create(Scope *global_scope, Scope *module_scope, const char *module_name, const char *module_path); 207 | AnalysisContext analysis_context_with_scope(const AnalysisContext *parent, Scope *new_scope); 208 | AnalysisContext analysis_context_with_bindings(const AnalysisContext *parent, GenericBindingCtx new_bindings); 209 | AnalysisContext analysis_context_with_function(const AnalysisContext *parent, Symbol *function); 210 | 211 | // main analysis entry point 212 | bool semantic_analyze_new(SemanticDriver *driver, AstNode *root, const char *module_name, const char *module_path); 213 | 214 | #endif // SEMANTIC_NEW_H 215 | -------------------------------------------------------------------------------- /boot/symbol.c: -------------------------------------------------------------------------------- 1 | #include "symbol.h" 2 | #include "ast.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | void symbol_table_init(SymbolTable *table) 9 | { 10 | table->global_scope = scope_create(NULL, "global"); 11 | table->current_scope = table->global_scope; 12 | table->module_scope = NULL; 13 | } 14 | 15 | void symbol_table_dnit(SymbolTable *table) 16 | { 17 | scope_destroy(table->global_scope); 18 | table->global_scope = NULL; 19 | table->current_scope = NULL; 20 | table->module_scope = NULL; 21 | } 22 | 23 | Scope *scope_create(Scope *parent, const char *name) 24 | { 25 | Scope *scope = malloc(sizeof(Scope)); 26 | scope->parent = parent; 27 | scope->symbols = NULL; 28 | scope->is_module = false; 29 | scope->name = name ? strdup(name) : NULL; 30 | return scope; 31 | } 32 | 33 | void scope_destroy(Scope *scope) 34 | { 35 | if (!scope) 36 | return; 37 | 38 | // destroy all symbols 39 | Symbol *symbol = scope->symbols; 40 | while (symbol) 41 | { 42 | Symbol *next = symbol->next; 43 | symbol_destroy(symbol); 44 | symbol = next; 45 | } 46 | 47 | free(scope->name); 48 | free(scope); 49 | } 50 | 51 | void scope_enter(SymbolTable *table, Scope *scope) 52 | { 53 | table->current_scope = scope; 54 | } 55 | 56 | void scope_exit(SymbolTable *table) 57 | { 58 | if (table->current_scope && table->current_scope->parent) 59 | { 60 | table->current_scope = table->current_scope->parent; 61 | } 62 | } 63 | 64 | Scope *scope_push(SymbolTable *table, const char *name) 65 | { 66 | Scope *new_scope = scope_create(table->current_scope, name); 67 | table->current_scope = new_scope; 68 | return new_scope; 69 | } 70 | 71 | void scope_pop(SymbolTable *table) 72 | { 73 | if (table->current_scope && table->current_scope->parent) 74 | { 75 | Scope *old_scope = table->current_scope; 76 | table->current_scope = old_scope->parent; 77 | scope_destroy(old_scope); 78 | } 79 | } 80 | 81 | Symbol *symbol_create(SymbolKind kind, const char *name, Type *type, AstNode *decl) 82 | { 83 | Symbol *symbol = malloc(sizeof(Symbol)); 84 | if (!symbol) 85 | return NULL; 86 | 87 | memset(symbol, 0, sizeof(Symbol)); 88 | 89 | symbol->kind = kind; 90 | symbol->name = name ? strdup(name) : NULL; 91 | symbol->type = type; 92 | symbol->decl = decl; 93 | symbol->home_scope = NULL; 94 | symbol->next = NULL; 95 | symbol->method_next = NULL; 96 | symbol->is_imported = false; 97 | symbol->is_public = false; 98 | symbol->has_const_i64 = false; 99 | symbol->const_i64 = 0; 100 | symbol->import_module = NULL; 101 | symbol->module_name = NULL; 102 | symbol->import_origin = NULL; 103 | 104 | // initialize kind-specific data 105 | switch (kind) 106 | { 107 | case SYMBOL_VAR: 108 | case SYMBOL_VAL: 109 | symbol->var.is_global = false; 110 | symbol->var.is_const = (kind == SYMBOL_VAL); 111 | symbol->var.mangled_name = NULL; 112 | break; 113 | 114 | case SYMBOL_FUNC: 115 | symbol->func.is_external = false; 116 | symbol->func.is_defined = false; 117 | symbol->func.uses_mach_varargs = false; 118 | symbol->func.extern_name = NULL; 119 | symbol->func.convention = NULL; 120 | symbol->func.mangled_name = NULL; 121 | symbol->func.is_generic = false; 122 | symbol->func.generic_param_count = 0; 123 | symbol->func.generic_param_names = NULL; 124 | symbol->func.generic_specializations = NULL; 125 | symbol->func.is_specialized_instance = false; 126 | symbol->func.is_method = false; 127 | symbol->func.method_owner = NULL; 128 | symbol->func.method_forwarded_generic_count = 0; 129 | symbol->func.method_receiver_is_pointer = false; 130 | symbol->func.method_receiver_name = NULL; 131 | break; 132 | 133 | case SYMBOL_TYPE: 134 | symbol->type_def.is_alias = false; 135 | symbol->type_def.methods = NULL; 136 | break; 137 | 138 | case SYMBOL_FIELD: 139 | symbol->field.offset = 0; 140 | break; 141 | 142 | case SYMBOL_PARAM: 143 | symbol->param.index = 0; 144 | break; 145 | 146 | case SYMBOL_MODULE: 147 | symbol->module.path = NULL; 148 | symbol->module.scope = NULL; 149 | break; 150 | } 151 | 152 | return symbol; 153 | } 154 | 155 | void symbol_destroy(Symbol *symbol) 156 | { 157 | if (!symbol) 158 | return; 159 | 160 | free(symbol->name); 161 | free(symbol->module_name); 162 | symbol->module_name = NULL; 163 | 164 | if (symbol->kind == SYMBOL_MODULE) 165 | { 166 | if (symbol->module.scope && symbol->is_imported) 167 | { 168 | scope_destroy(symbol->module.scope); 169 | symbol->module.scope = NULL; 170 | } 171 | free(symbol->module.path); 172 | symbol->module.path = NULL; 173 | // non-imported module scopes are managed separately 174 | } 175 | else if (symbol->kind == SYMBOL_FUNC) 176 | { 177 | free(symbol->func.extern_name); 178 | free(symbol->func.convention); 179 | free(symbol->func.mangled_name); 180 | symbol->func.extern_name = NULL; 181 | symbol->func.convention = NULL; 182 | symbol->func.mangled_name = NULL; 183 | free(symbol->func.method_receiver_name); 184 | symbol->func.method_receiver_name = NULL; 185 | 186 | if (symbol->func.generic_param_names) 187 | { 188 | for (size_t i = 0; i < symbol->func.generic_param_count; i++) 189 | { 190 | free(symbol->func.generic_param_names[i]); 191 | } 192 | free(symbol->func.generic_param_names); 193 | symbol->func.generic_param_names = NULL; 194 | } 195 | GenericSpecialization *spec = symbol->func.generic_specializations; 196 | if (!symbol->import_origin || symbol->import_origin == symbol) 197 | { 198 | while (spec) 199 | { 200 | GenericSpecialization *next = spec->next; 201 | free(spec->type_args); 202 | free(spec); 203 | spec = next; 204 | } 205 | } 206 | symbol->func.generic_specializations = NULL; 207 | } 208 | else if (symbol->kind == SYMBOL_VAR || symbol->kind == SYMBOL_VAL) 209 | { 210 | free(symbol->var.mangled_name); 211 | symbol->var.mangled_name = NULL; 212 | } 213 | else if (symbol->kind == SYMBOL_TYPE) 214 | { 215 | if (symbol->type_def.generic_param_names) 216 | { 217 | for (size_t i = 0; i < symbol->type_def.generic_param_count; i++) 218 | { 219 | free(symbol->type_def.generic_param_names[i]); 220 | } 221 | free(symbol->type_def.generic_param_names); 222 | symbol->type_def.generic_param_names = NULL; 223 | } 224 | if (!symbol->is_imported && symbol->type_def.methods) 225 | { 226 | Symbol *method = symbol->type_def.methods; 227 | while (method) 228 | { 229 | Symbol *next_method = method->method_next; 230 | method->method_next = NULL; 231 | symbol_destroy(method); 232 | method = next_method; 233 | } 234 | } 235 | symbol->type_def.methods = NULL; 236 | } 237 | 238 | free(symbol); 239 | } 240 | 241 | void symbol_add(Scope *scope, Symbol *symbol) 242 | { 243 | if (!scope || !symbol) 244 | return; 245 | 246 | if (!symbol->home_scope) 247 | symbol->home_scope = scope; 248 | 249 | // add to front of list 250 | symbol->next = scope->symbols; 251 | scope->symbols = symbol; 252 | } 253 | 254 | Symbol *symbol_lookup(SymbolTable *table, const char *name) 255 | { 256 | if (!table || !name) 257 | return NULL; 258 | 259 | // search from current scope up to global 260 | for (Scope *scope = table->current_scope; scope; scope = scope->parent) 261 | { 262 | Symbol *symbol = symbol_lookup_scope(scope, name); 263 | if (symbol) 264 | return symbol; 265 | } 266 | 267 | return NULL; 268 | } 269 | 270 | Symbol *symbol_lookup_scope(Scope *scope, const char *name) 271 | { 272 | if (!scope || !name) 273 | return NULL; 274 | 275 | for (Symbol *symbol = scope->symbols; symbol; symbol = symbol->next) 276 | { 277 | if (symbol->name && strcmp(symbol->name, name) == 0) 278 | { 279 | return symbol; 280 | } 281 | } 282 | 283 | return NULL; 284 | } 285 | 286 | Symbol *symbol_lookup_module(SymbolTable *table, const char *module, const char *name) 287 | { 288 | if (!table || !module || !name) 289 | return NULL; 290 | 291 | // find module symbol first 292 | Symbol *module_symbol = symbol_lookup(table, module); 293 | if (!module_symbol || module_symbol->kind != SYMBOL_MODULE) 294 | { 295 | return NULL; 296 | } 297 | 298 | // search in module scope 299 | Symbol *symbol = symbol_lookup_scope(module_symbol->module.scope, name); 300 | if (symbol && !symbol->is_public) 301 | { 302 | return NULL; 303 | } 304 | return symbol; 305 | } 306 | 307 | Symbol *symbol_add_field(Symbol *composite_symbol, const char *field_name, Type *field_type, AstNode *decl) 308 | { 309 | if (!composite_symbol || !field_name || !field_type) 310 | return NULL; 311 | if (composite_symbol->type->kind != TYPE_STRUCT && composite_symbol->type->kind != TYPE_UNION) 312 | { 313 | return NULL; 314 | } 315 | 316 | Symbol *field_symbol = symbol_create(SYMBOL_FIELD, field_name, field_type, decl); 317 | 318 | // add to composite type's field list 319 | if (!composite_symbol->type->composite.fields) 320 | { 321 | composite_symbol->type->composite.fields = field_symbol; 322 | } 323 | else 324 | { 325 | // find last field and append 326 | Symbol *last = composite_symbol->type->composite.fields; 327 | while (last->next) 328 | last = last->next; 329 | last->next = field_symbol; 330 | } 331 | 332 | composite_symbol->type->composite.field_count++; 333 | 334 | return field_symbol; 335 | } 336 | 337 | Symbol *symbol_find_field(Symbol *composite_symbol, const char *field_name) 338 | { 339 | if (!composite_symbol || !field_name) 340 | return NULL; 341 | if (composite_symbol->type->kind != TYPE_STRUCT && composite_symbol->type->kind != TYPE_UNION) 342 | { 343 | return NULL; 344 | } 345 | 346 | for (Symbol *field = composite_symbol->type->composite.fields; field; field = field->next) 347 | { 348 | if (field->name && strcmp(field->name, field_name) == 0) 349 | { 350 | return field; 351 | } 352 | } 353 | 354 | return NULL; 355 | } 356 | 357 | void type_add_method(Symbol *type_symbol, Symbol *method_symbol) 358 | { 359 | if (!type_symbol || !method_symbol) 360 | return; 361 | if (type_symbol->kind != SYMBOL_TYPE || method_symbol->kind != SYMBOL_FUNC) 362 | return; 363 | 364 | method_symbol->method_next = type_symbol->type_def.methods; 365 | type_symbol->type_def.methods = method_symbol; 366 | method_symbol->func.is_method = true; 367 | method_symbol->func.method_owner = type_symbol; 368 | 369 | if (!method_symbol->home_scope && type_symbol->home_scope) 370 | { 371 | method_symbol->home_scope = type_symbol->home_scope; 372 | } 373 | } 374 | 375 | Symbol *type_find_method(Symbol *type_symbol, const char *method_name, bool receiver_is_pointer) 376 | { 377 | if (!type_symbol || type_symbol->kind != SYMBOL_TYPE || !method_name) 378 | return NULL; 379 | 380 | for (Symbol *method = type_symbol->type_def.methods; method; method = method->method_next) 381 | { 382 | if (!method->name || strcmp(method->name, method_name) != 0) 383 | continue; 384 | if (method->func.method_receiver_is_pointer != receiver_is_pointer) 385 | continue; 386 | return method; 387 | } 388 | 389 | return NULL; 390 | } 391 | 392 | static size_t align_offset(size_t offset, size_t alignment) 393 | { 394 | return (offset + alignment - 1) & ~(alignment - 1); 395 | } 396 | 397 | size_t symbol_calculate_struct_layout(Symbol *struct_symbol) 398 | { 399 | if (!struct_symbol || struct_symbol->type->kind != TYPE_STRUCT) 400 | return 0; 401 | 402 | size_t offset = 0; 403 | size_t max_alignment = 1; 404 | 405 | for (Symbol *field = struct_symbol->type->composite.fields; field; field = field->next) 406 | { 407 | size_t field_alignment = type_alignof(field->type); 408 | offset = align_offset(offset, field_alignment); 409 | 410 | field->field.offset = offset; 411 | offset += type_sizeof(field->type); 412 | 413 | if (field_alignment > max_alignment) 414 | { 415 | max_alignment = field_alignment; 416 | } 417 | } 418 | 419 | // align struct size to largest member alignment 420 | offset = align_offset(offset, max_alignment); 421 | 422 | struct_symbol->type->size = offset; 423 | struct_symbol->type->alignment = max_alignment; 424 | 425 | return offset; 426 | } 427 | 428 | size_t symbol_calculate_union_layout(Symbol *union_symbol) 429 | { 430 | if (!union_symbol || union_symbol->type->kind != TYPE_UNION) 431 | return 0; 432 | 433 | size_t max_size = 0; 434 | size_t max_alignment = 1; 435 | 436 | for (Symbol *field = union_symbol->type->composite.fields; field; field = field->next) 437 | { 438 | field->field.offset = 0; // all fields start at offset 0 in union 439 | 440 | size_t field_size = type_sizeof(field->type); 441 | size_t field_alignment = type_alignof(field->type); 442 | 443 | if (field_size > max_size) 444 | { 445 | max_size = field_size; 446 | } 447 | if (field_alignment > max_alignment) 448 | { 449 | max_alignment = field_alignment; 450 | } 451 | } 452 | 453 | // align union size to largest member alignment 454 | max_size = align_offset(max_size, max_alignment); 455 | 456 | union_symbol->type->size = max_size; 457 | union_symbol->type->alignment = max_alignment; 458 | 459 | return max_size; 460 | } 461 | 462 | Symbol *symbol_create_module(const char *name, const char *path) 463 | { 464 | Symbol *module_symbol = symbol_create(SYMBOL_MODULE, name, NULL, NULL); 465 | module_symbol->module.path = path ? strdup(path) : NULL; 466 | module_symbol->module.scope = scope_create(NULL, name); 467 | module_symbol->module.scope->is_module = true; 468 | return module_symbol; 469 | } 470 | 471 | void symbol_add_module(SymbolTable *table, Symbol *module_symbol) 472 | { 473 | if (!table || !module_symbol || module_symbol->kind != SYMBOL_MODULE) 474 | return; 475 | 476 | symbol_add(table->global_scope, module_symbol); 477 | } 478 | 479 | Symbol *symbol_find_module(SymbolTable *table, const char *name) 480 | { 481 | if (!table || !name) 482 | return NULL; 483 | 484 | Symbol *symbol = symbol_lookup_scope(table->global_scope, name); 485 | if (symbol && symbol->kind == SYMBOL_MODULE) 486 | { 487 | return symbol; 488 | } 489 | 490 | return NULL; 491 | } 492 | 493 | void symbol_print(Symbol *symbol, int indent) 494 | { 495 | if (!symbol) 496 | return; 497 | 498 | for (int i = 0; i < indent; i++) 499 | printf(" "); 500 | 501 | const char *kind_name; 502 | switch (symbol->kind) 503 | { 504 | case SYMBOL_VAR: 505 | kind_name = "var"; 506 | break; 507 | case SYMBOL_VAL: 508 | kind_name = "val"; 509 | break; 510 | case SYMBOL_FUNC: 511 | kind_name = "fun"; 512 | break; 513 | case SYMBOL_TYPE: 514 | kind_name = "type"; 515 | break; 516 | case SYMBOL_FIELD: 517 | kind_name = "field"; 518 | break; 519 | case SYMBOL_PARAM: 520 | kind_name = "param"; 521 | break; 522 | case SYMBOL_MODULE: 523 | kind_name = "module"; 524 | break; 525 | default: 526 | kind_name = "unknown"; 527 | break; 528 | } 529 | 530 | printf("%s %s: ", kind_name, symbol->name ? symbol->name : "(anonymous)"); 531 | type_print(symbol->type); 532 | printf("\n"); 533 | } 534 | 535 | void scope_print(Scope *scope, int indent) 536 | { 537 | if (!scope) 538 | return; 539 | 540 | for (int i = 0; i < indent; i++) 541 | printf(" "); 542 | printf("scope %s {\n", scope->name ? scope->name : "(anonymous)"); 543 | 544 | for (Symbol *symbol = scope->symbols; symbol; symbol = symbol->next) 545 | { 546 | symbol_print(symbol, indent + 1); 547 | } 548 | 549 | for (int i = 0; i < indent; i++) 550 | printf(" "); 551 | printf("}\n"); 552 | } 553 | -------------------------------------------------------------------------------- /boot/symbol.h: -------------------------------------------------------------------------------- 1 | #ifndef SYMBOL_H 2 | #define SYMBOL_H 3 | 4 | #include "type.h" 5 | #include 6 | #include 7 | 8 | // forward declarations 9 | typedef struct AstNode AstNode; 10 | struct Symbol; 11 | struct Scope; 12 | 13 | typedef enum SymbolKind 14 | { 15 | SYMBOL_VAR, // variable 16 | SYMBOL_VAL, // constant value 17 | SYMBOL_FUNC, // function 18 | SYMBOL_TYPE, // type definition 19 | SYMBOL_FIELD, // struct/union field 20 | SYMBOL_PARAM, // function parameter 21 | SYMBOL_MODULE, // module 22 | } SymbolKind; 23 | 24 | typedef struct GenericSpecialization GenericSpecialization; 25 | 26 | struct GenericSpecialization 27 | { 28 | size_t arg_count; 29 | Type **type_args; 30 | struct Symbol *generic_symbol; // owning generic symbol 31 | struct Symbol *symbol; 32 | GenericSpecialization *next; 33 | }; 34 | 35 | typedef struct Symbol 36 | { 37 | SymbolKind kind; 38 | char *name; 39 | Type *type; 40 | AstNode *decl; // declaration node 41 | struct Scope *home_scope; // scope where the symbol is registered 42 | struct Symbol *next; // for linked list in scope 43 | struct Symbol *method_next; // linked list for type methods 44 | bool is_imported; // true if this symbol was imported from another module 45 | bool is_public; // true if symbol should be exported from module 46 | bool has_const_i64; // semantic constant folding result (integer/bool) 47 | int64_t const_i64; 48 | const char *import_module; // source module for imported symbols 49 | char *module_name; // canonical module owning this symbol 50 | struct Symbol *import_origin; // original symbol when imported 51 | 52 | union 53 | { 54 | // SYMBOL_VAR, SYMBOL_VAL 55 | struct 56 | { 57 | bool is_global; 58 | bool is_const; // true for val 59 | char *mangled_name; 60 | } var; 61 | 62 | // SYMBOL_FUNC 63 | struct 64 | { 65 | bool is_external; 66 | bool is_defined; // false for forward declarations 67 | bool uses_mach_varargs; // true when Mach compiler handles variadics (vs C ABI) 68 | char *extern_name; // c-level symbol name for externs (defaults to mach name) 69 | char *convention; // calling convention hint (e.g. "C") 70 | char *mangled_name; // cached mangled name for codegen 71 | bool is_generic; 72 | size_t generic_param_count; 73 | char **generic_param_names; 74 | GenericSpecialization *generic_specializations; 75 | bool is_specialized_instance; 76 | bool is_method; 77 | struct Symbol *method_owner; 78 | size_t method_forwarded_generic_count; 79 | bool method_receiver_is_pointer; 80 | char *method_receiver_name; 81 | } func; 82 | // SYMBOL_TYPE 83 | struct 84 | { 85 | bool is_alias; 86 | bool is_generic; 87 | size_t generic_param_count; 88 | char **generic_param_names; 89 | GenericSpecialization *generic_specializations; 90 | bool is_specialized_instance; 91 | struct Symbol *methods; 92 | } type_def; 93 | 94 | // SYMBOL_FIELD 95 | struct 96 | { 97 | size_t offset; // offset within struct/union 98 | } field; 99 | 100 | // SYMBOL_PARAM 101 | struct 102 | { 103 | size_t index; // parameter index 104 | } param; 105 | 106 | // SYMBOL_MODULE 107 | struct 108 | { 109 | char *path; // module path 110 | struct Scope *scope; // module scope 111 | } module; 112 | }; 113 | } Symbol; 114 | 115 | typedef struct Scope 116 | { 117 | struct Scope *parent; // parent scope 118 | Symbol *symbols; // linked list of symbols 119 | bool is_module; // true for module scope 120 | char *name; // scope name (for debugging) 121 | } Scope; 122 | 123 | typedef struct SymbolTable 124 | { 125 | Scope *current_scope; 126 | Scope *global_scope; 127 | Scope *module_scope; // current module scope 128 | } SymbolTable; 129 | 130 | // symbol table operations 131 | void symbol_table_init(SymbolTable *table); 132 | void symbol_table_dnit(SymbolTable *table); 133 | 134 | // scope operations 135 | Scope *scope_create(Scope *parent, const char *name); 136 | void scope_destroy(Scope *scope); 137 | void scope_enter(SymbolTable *table, Scope *scope); 138 | void scope_exit(SymbolTable *table); 139 | Scope *scope_push(SymbolTable *table, const char *name); 140 | void scope_pop(SymbolTable *table); 141 | 142 | // symbol operations 143 | Symbol *symbol_create(SymbolKind kind, const char *name, Type *type, AstNode *decl); 144 | void symbol_destroy(Symbol *symbol); 145 | void symbol_add(Scope *scope, Symbol *symbol); 146 | Symbol *symbol_lookup(SymbolTable *table, const char *name); 147 | Symbol *symbol_lookup_scope(Scope *scope, const char *name); 148 | Symbol *symbol_lookup_module(SymbolTable *table, const char *module, const char *name); 149 | 150 | // field operations for structs/unions 151 | Symbol *symbol_add_field(Symbol *composite_symbol, const char *field_name, Type *field_type, AstNode *decl); 152 | Symbol *symbol_find_field(Symbol *composite_symbol, const char *field_name); 153 | size_t symbol_calculate_struct_layout(Symbol *struct_symbol); 154 | size_t symbol_calculate_union_layout(Symbol *union_symbol); 155 | 156 | // method operations for types 157 | void type_add_method(Symbol *type_symbol, Symbol *method_symbol); 158 | Symbol *type_find_method(Symbol *type_symbol, const char *method_name, bool receiver_is_pointer); 159 | 160 | // module operations 161 | Symbol *symbol_create_module(const char *name, const char *path); 162 | void symbol_add_module(SymbolTable *table, Symbol *module_symbol); 163 | Symbol *symbol_find_module(SymbolTable *table, const char *name); 164 | 165 | // debug 166 | void symbol_print(Symbol *symbol, int indent); 167 | void scope_print(Scope *scope, int indent); 168 | 169 | #endif 170 | -------------------------------------------------------------------------------- /boot/token.c: -------------------------------------------------------------------------------- 1 | #include "token.h" 2 | 3 | #include 4 | 5 | void token_init(Token *token, TokenKind kind, int pos, int len) 6 | { 7 | token->kind = kind; 8 | token->pos = pos; 9 | token->len = len; 10 | } 11 | 12 | void token_dnit(Token *token) 13 | { 14 | token->kind = TOKEN_ERROR; 15 | token->pos = -1; 16 | token->len = -1; 17 | } 18 | 19 | void token_copy(Token *src, Token *dst) 20 | { 21 | if (src == NULL || dst == NULL) 22 | { 23 | return; 24 | } 25 | 26 | dst->kind = src->kind; 27 | dst->pos = src->pos; 28 | dst->len = src->len; 29 | } 30 | 31 | char *token_kind_to_string(TokenKind kind) 32 | { 33 | switch (kind) 34 | { 35 | case TOKEN_ERROR: 36 | return "ERROR"; 37 | case TOKEN_EOF: 38 | return "EOF"; 39 | case TOKEN_COMMENT: 40 | return "COMMENT"; 41 | 42 | case TOKEN_LIT_INT: 43 | return "LIT_INT"; 44 | case TOKEN_LIT_FLOAT: 45 | return "LIT_FLOAT"; 46 | case TOKEN_LIT_CHAR: 47 | return "LIT_CHAR"; 48 | case TOKEN_LIT_STRING: 49 | return "LIT_STRING"; 50 | 51 | case TOKEN_IDENTIFIER: 52 | return "IDENTIFIER"; 53 | 54 | case TOKEN_KW_USE: 55 | return "use"; 56 | case TOKEN_KW_EXT: 57 | return "ext"; 58 | case TOKEN_KW_DEF: 59 | return "def"; 60 | case TOKEN_KW_PUB: 61 | return "pub"; 62 | case TOKEN_KW_REC: 63 | return "rec"; 64 | case TOKEN_KW_UNI: 65 | return "uni"; 66 | case TOKEN_KW_VAL: 67 | return "val"; 68 | case TOKEN_KW_VAR: 69 | return "var"; 70 | case TOKEN_KW_FUN: 71 | return "fun"; 72 | case TOKEN_KW_RET: 73 | return "ret"; 74 | case TOKEN_KW_IF: 75 | return "if"; 76 | case TOKEN_KW_OR: 77 | return "or"; 78 | case TOKEN_KW_FOR: 79 | return "for"; 80 | case TOKEN_KW_CNT: 81 | return "cnt"; 82 | case TOKEN_KW_BRK: 83 | return "brk"; 84 | case TOKEN_KW_ASM: 85 | return "asm"; 86 | case TOKEN_KW_NIL: 87 | return "nil"; 88 | 89 | case TOKEN_L_PAREN: 90 | return "("; 91 | case TOKEN_R_PAREN: 92 | return ")"; 93 | case TOKEN_L_BRACKET: 94 | return "["; 95 | case TOKEN_R_BRACKET: 96 | return "]"; 97 | case TOKEN_L_BRACE: 98 | return "{"; 99 | case TOKEN_R_BRACE: 100 | return "}"; 101 | case TOKEN_COLON: 102 | return ":"; 103 | case TOKEN_SEMICOLON: 104 | return ";"; 105 | case TOKEN_QUESTION: 106 | return "?"; 107 | case TOKEN_AT: 108 | return "@"; 109 | case TOKEN_DOLLAR: 110 | return "$"; 111 | case TOKEN_DOT: 112 | return "."; 113 | case TOKEN_COMMA: 114 | return ","; 115 | case TOKEN_UNDERSCORE: 116 | return "_"; 117 | 118 | case TOKEN_PLUS: 119 | return "+"; 120 | case TOKEN_MINUS: 121 | return "-"; 122 | case TOKEN_STAR: 123 | return "*"; 124 | case TOKEN_PERCENT: 125 | return "%"; 126 | case TOKEN_CARET: 127 | return "^"; 128 | case TOKEN_AMPERSAND: 129 | return "&"; 130 | case TOKEN_PIPE: 131 | return "|"; 132 | case TOKEN_TILDE: 133 | return "~"; 134 | case TOKEN_LESS: 135 | return "<"; 136 | case TOKEN_GREATER: 137 | return ">"; 138 | case TOKEN_EQUAL: 139 | return "="; 140 | case TOKEN_BANG: 141 | return "!"; 142 | case TOKEN_SLASH: 143 | return "/"; 144 | 145 | case TOKEN_EQUAL_EQUAL: 146 | return "=="; 147 | case TOKEN_BANG_EQUAL: 148 | return "!="; 149 | case TOKEN_LESS_EQUAL: 150 | return "<="; 151 | case TOKEN_GREATER_EQUAL: 152 | return ">="; 153 | case TOKEN_LESS_LESS: 154 | return "<<"; 155 | case TOKEN_GREATER_GREATER: 156 | return ">>"; 157 | case TOKEN_AMPERSAND_AMPERSAND: 158 | return "&&"; 159 | case TOKEN_PIPE_PIPE: 160 | return "||"; 161 | case TOKEN_COLON_COLON: 162 | return "::"; 163 | 164 | default: 165 | return "UNKNOWN"; 166 | } 167 | } 168 | 169 | TokenKind token_kind_from_identifier(const char *text, int len) 170 | { 171 | // check built-in types first 172 | if (len == 2) 173 | { 174 | if (strncmp(text, "if", 2) == 0) 175 | { 176 | return TOKEN_KW_IF; 177 | } 178 | if (strncmp(text, "or", 2) == 0) 179 | { 180 | return TOKEN_KW_OR; 181 | } 182 | } 183 | if (len == 3) 184 | { 185 | if (strncmp(text, "nil", 3) == 0) 186 | { 187 | return TOKEN_KW_NIL; 188 | } 189 | if (strncmp(text, "asm", 3) == 0) 190 | { 191 | return TOKEN_KW_ASM; 192 | } 193 | if (strncmp(text, "use", 3) == 0) 194 | { 195 | return TOKEN_KW_USE; 196 | } 197 | if (strncmp(text, "ext", 3) == 0) 198 | { 199 | return TOKEN_KW_EXT; 200 | } 201 | if (strncmp(text, "def", 3) == 0) 202 | { 203 | return TOKEN_KW_DEF; 204 | } 205 | if (strncmp(text, "pub", 3) == 0) 206 | { 207 | return TOKEN_KW_PUB; 208 | } 209 | if (strncmp(text, "rec", 3) == 0) 210 | { 211 | return TOKEN_KW_REC; 212 | } 213 | if (strncmp(text, "uni", 3) == 0) 214 | { 215 | return TOKEN_KW_UNI; 216 | } 217 | if (strncmp(text, "val", 3) == 0) 218 | { 219 | return TOKEN_KW_VAL; 220 | } 221 | if (strncmp(text, "var", 3) == 0) 222 | { 223 | return TOKEN_KW_VAR; 224 | } 225 | if (strncmp(text, "fun", 3) == 0) 226 | { 227 | return TOKEN_KW_FUN; 228 | } 229 | if (strncmp(text, "ret", 3) == 0) 230 | { 231 | return TOKEN_KW_RET; 232 | } 233 | if (strncmp(text, "if", 3) == 0) 234 | { 235 | return TOKEN_KW_IF; 236 | } 237 | if (strncmp(text, "or", 3) == 0) 238 | { 239 | return TOKEN_KW_OR; 240 | } 241 | if (strncmp(text, "for", 3) == 0) 242 | { 243 | return TOKEN_KW_FOR; 244 | } 245 | if (strncmp(text, "cnt", 3) == 0) 246 | { 247 | return TOKEN_KW_CNT; 248 | } 249 | if (strncmp(text, "brk", 3) == 0) 250 | { 251 | return TOKEN_KW_BRK; 252 | } 253 | } 254 | 255 | return TOKEN_IDENTIFIER; 256 | } 257 | -------------------------------------------------------------------------------- /boot/token.h: -------------------------------------------------------------------------------- 1 | #ifndef TOKEN_H 2 | #define TOKEN_H 3 | 4 | typedef enum TokenKind 5 | { 6 | TOKEN_ERROR = -1, 7 | TOKEN_EOF = 0, 8 | TOKEN_COMMENT, 9 | 10 | // literals 11 | TOKEN_LIT_INT, 12 | TOKEN_LIT_FLOAT, 13 | TOKEN_LIT_CHAR, 14 | TOKEN_LIT_STRING, 15 | 16 | TOKEN_IDENTIFIER, 17 | 18 | // keywords 19 | TOKEN_KW_USE, // use 20 | TOKEN_KW_EXT, // ext 21 | TOKEN_KW_DEF, // def 22 | TOKEN_KW_PUB, // pub 23 | TOKEN_KW_REC, // rec 24 | TOKEN_KW_UNI, // uni 25 | TOKEN_KW_VAL, // val 26 | TOKEN_KW_VAR, // var 27 | TOKEN_KW_FUN, // fun 28 | TOKEN_KW_RET, // ret 29 | TOKEN_KW_IF, // if 30 | TOKEN_KW_OR, // or 31 | TOKEN_KW_FOR, // for 32 | TOKEN_KW_CNT, // cnt 33 | TOKEN_KW_BRK, // brk 34 | TOKEN_KW_ASM, // asm 35 | TOKEN_KW_NIL, // nil 36 | 37 | // punctuation 38 | TOKEN_L_PAREN, // ( 39 | TOKEN_R_PAREN, // ) 40 | TOKEN_L_BRACKET, // [ 41 | TOKEN_R_BRACKET, // ] 42 | TOKEN_L_BRACE, // { 43 | TOKEN_R_BRACE, // } 44 | TOKEN_COLON, // : 45 | TOKEN_SEMICOLON, // ; 46 | TOKEN_QUESTION, // ? 47 | TOKEN_AT, // @ 48 | TOKEN_DOLLAR, // $ 49 | TOKEN_DOT, // . 50 | TOKEN_COMMA, // , 51 | TOKEN_UNDERSCORE, // _ 52 | TOKEN_ELLIPSIS, // ... 53 | 54 | // operators - single character 55 | TOKEN_PLUS, // + 56 | TOKEN_MINUS, // - 57 | TOKEN_STAR, // * 58 | TOKEN_PERCENT, // % 59 | TOKEN_CARET, // ^ 60 | TOKEN_AMPERSAND, // & 61 | TOKEN_PIPE, // | 62 | TOKEN_TILDE, // ~ 63 | TOKEN_LESS, // < 64 | TOKEN_GREATER, // > 65 | TOKEN_EQUAL, // = 66 | TOKEN_BANG, // ! 67 | TOKEN_SLASH, // / 68 | 69 | // operators - multi-character 70 | TOKEN_EQUAL_EQUAL, // == 71 | TOKEN_BANG_EQUAL, // != 72 | TOKEN_LESS_EQUAL, // <= 73 | TOKEN_GREATER_EQUAL, // >= 74 | TOKEN_LESS_LESS, // << 75 | TOKEN_GREATER_GREATER, // >> 76 | TOKEN_AMPERSAND_AMPERSAND, // && 77 | TOKEN_PIPE_PIPE, // || 78 | TOKEN_COLON_COLON, // :: 79 | } TokenKind; 80 | 81 | typedef struct Token 82 | { 83 | TokenKind kind; 84 | int pos; 85 | int len; 86 | } Token; 87 | 88 | void token_init(Token *token, TokenKind kind, int pos, int len); 89 | void token_dnit(Token *token); 90 | 91 | void token_copy(Token *src, Token *dst); 92 | char *token_kind_to_string(TokenKind kind); 93 | TokenKind token_kind_from_identifier(const char *text, int len); 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /boot/type.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPE_H 2 | #define TYPE_H 3 | 4 | #include 5 | #include 6 | 7 | // forward declarations 8 | typedef struct Symbol Symbol; 9 | typedef struct AstNode AstNode; 10 | typedef struct SymbolTable SymbolTable; 11 | 12 | typedef enum TypeKind 13 | { 14 | TYPE_U8, 15 | TYPE_U16, 16 | TYPE_U32, 17 | TYPE_U64, 18 | TYPE_I8, 19 | TYPE_I16, 20 | TYPE_I32, 21 | TYPE_I64, 22 | TYPE_F16, 23 | TYPE_F32, 24 | TYPE_F64, 25 | TYPE_PTR, // generic pointer 26 | TYPE_POINTER, // typed pointer 27 | TYPE_ARRAY, 28 | TYPE_STRUCT, 29 | TYPE_UNION, 30 | TYPE_FUNCTION, 31 | TYPE_ALIAS, // type alias from 'def' 32 | TYPE_ERROR, // placeholder for failed type resolution 33 | } TypeKind; 34 | 35 | typedef struct Type 36 | { 37 | TypeKind kind; 38 | size_t size; // size in bytes 39 | size_t alignment; // alignment requirement 40 | char *name; // for named types (structs, unions, aliases) 41 | 42 | // generic specialization info 43 | Symbol *generic_origin; // the generic symbol this was instantiated from 44 | struct Type **type_args; // type arguments used for specialization 45 | size_t type_arg_count; // number of type arguments 46 | 47 | union 48 | { 49 | // TYPE_POINTER 50 | struct 51 | { 52 | struct Type *base; 53 | } pointer; 54 | 55 | // TYPE_ARRAY 56 | struct 57 | { 58 | struct Type *elem_type; 59 | size_t size; // 0 for slices/fat pointers, >0 for fixed-size arrays 60 | bool is_slice; // true for []T (fat pointer), false for [N]T (fixed-size) 61 | } array; 62 | 63 | // TYPE_STRUCT, TYPE_UNION 64 | struct 65 | { 66 | Symbol *fields; 67 | size_t field_count; 68 | } composite; 69 | 70 | // TYPE_FUNCTION 71 | struct 72 | { 73 | struct Type *return_type; 74 | struct Type **param_types; 75 | size_t param_count; 76 | bool is_variadic; 77 | } function; 78 | 79 | // TYPE_ALIAS 80 | struct 81 | { 82 | struct Type *target; 83 | } alias; 84 | }; 85 | } Type; 86 | 87 | // type system initialization 88 | void type_system_init(void); 89 | void type_system_dnit(void); 90 | 91 | // builtin type accessors 92 | Type *type_u8(void); 93 | Type *type_u16(void); 94 | Type *type_u32(void); 95 | Type *type_u64(void); 96 | Type *type_i8(void); 97 | Type *type_i16(void); 98 | Type *type_i32(void); 99 | Type *type_i64(void); 100 | Type *type_f16(void); 101 | Type *type_f32(void); 102 | Type *type_f64(void); 103 | Type *type_ptr(void); 104 | Type *type_error(void); 105 | 106 | // type constructors 107 | Type *type_pointer_create(Type *base); 108 | Type *type_array_create(Type *elem_type); // creates slice/fat pointer []T 109 | Type *type_fixed_array_create(Type *elem_type, size_t size); // creates fixed-size [N]T 110 | Type *type_struct_create(const char *name); 111 | Type *type_union_create(const char *name); 112 | Type *type_function_create(Type *return_type, Type **param_types, size_t param_count, bool is_variadic); 113 | Type *type_alias_create(const char *name, Type *target); 114 | 115 | // type operations 116 | bool type_equals(Type *a, Type *b); 117 | bool type_is_numeric(Type *type); 118 | bool type_is_integer(Type *type); 119 | bool type_is_float(Type *type); 120 | bool type_is_signed(Type *type); 121 | bool type_is_pointer_like(Type *type); 122 | bool type_is_truthy(Type *type); // true when type is the mach boolean (u8) 123 | bool type_is_error(Type *type); 124 | bool type_can_cast_to(Type *from, Type *to); 125 | bool type_can_assign_to(Type *from, Type *to); 126 | size_t type_sizeof(Type *type); 127 | size_t type_alignof(Type *type); 128 | 129 | // builtin lookup 130 | Type *type_lookup_builtin(const char *name); 131 | 132 | // type resolution from AST 133 | Type *type_resolve(AstNode *type_node, SymbolTable *symbol_table); 134 | Type *type_resolve_alias(Type *type); // resolve alias to underlying type 135 | 136 | // debug 137 | void type_print(Type *type); 138 | char *type_to_string(Type *type); 139 | 140 | #endif // TYPE_H 141 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Mach documentation 2 | 3 | This directory is the authoritative home of the Mach language specification and project guides. The self-hosted compiler will live in this repository; keeping the language docs alongside the source ensures they evolve together. 4 | 5 | ## Contents 6 | 7 | - [getting-started.md](./getting-started.md) — Installation, build instructions, and running your first Mach program. 8 | - [language-spec.md](./language-spec.md) — Complete language reference covering syntax, semantics, intrinsics, and module conventions. 9 | - [project-layout.md](./project-layout.md) — How `mach.toml`, module mapping, and the `Makefile` fit together. 10 | 11 | When the language changes, update `language-spec.md` first, then adjust the supplemental guides as needed. The bootstrap compiler (`cmach`) should continue to match the spec until the self-hosted compiler takes over. 12 | -------------------------------------------------------------------------------- /doc/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Mach 2 | 3 | This guide walks you through setting up Mach, building the compiler from source, and running your first Mach program. 4 | 5 | --- 6 | 7 | ## Table of Contents 8 | 9 | - [Prerequisites](#prerequisites) 10 | - [Building the Compiler](#building-the-compiler) 11 | - [Quick Start](#quick-start) 12 | - [Understanding the Build Process](#understanding-the-build-process) 13 | - [Build Targets](#build-targets) 14 | - [Running Your First Program](#running-your-first-program) 15 | - [Project Structure](#project-structure) 16 | - [Next Steps](#next-steps) 17 | 18 | --- 19 | 20 | ## Prerequisites 21 | 22 | Before building Mach, ensure you have the following installed: 23 | 24 | - **C compiler**: `clang` (C23 support required) 25 | - **LLVM**: Version 14 or later with development headers 26 | - `llvm-config` must be in your PATH 27 | - **Make**: Standard GNU Make 28 | - **Git**: For cloning the repository 29 | 30 | ### Installation by Platform 31 | 32 | **Linux (Debian/Ubuntu):** 33 | ```bash 34 | sudo apt update 35 | sudo apt install clang llvm-dev make git 36 | ``` 37 | 38 | **Linux (Fedora/RHEL):** 39 | ```bash 40 | sudo dnf install clang llvm-devel make git 41 | ``` 42 | 43 | **macOS:** 44 | ```bash 45 | brew install llvm make 46 | # add llvm to path 47 | export PATH="/opt/homebrew/opt/llvm/bin:$PATH" 48 | ``` 49 | 50 | **Verify installation:** 51 | ```bash 52 | clang --version 53 | llvm-config --version 54 | make --version 55 | ``` 56 | 57 | --- 58 | 59 | ## Building the Compiler 60 | 61 | ### Quick Start 62 | 63 | Clone and build the complete compiler chain: 64 | 65 | ```bash 66 | # clone repository 67 | git clone https://github.com/octalide/mach.git 68 | cd mach 69 | 70 | # build entire compiler chain (cmach -> imach -> mach) 71 | make full 72 | ``` 73 | 74 | This builds three compiler stages: 75 | 1. **cmach** - Bootstrap C compiler (`boot/` → `out/bin/cmach`) 76 | 2. **imach** - Intermediary Mach compiler (`src/` → `out/bin/imach`) 77 | 3. **mach** - Final Mach compiler (`src/` → `out/bin/mach`) 78 | 79 | All executables are placed in `out/bin/` with stage-specific artifacts in `out/cmach/`, `out/imach//`, and `out/mach//`. 80 | 81 | ### Understanding the Build Process 82 | 83 | Mach uses a bootstrapping approach: 84 | 85 | 1. **Bootstrap (cmach)**: The C compiler in `boot/` compiles Mach source files to LLVM IR and native code. This is written in C23 and uses LLVM libraries directly. 86 | 87 | 2. **Intermediary (imach)**: The bootstrap compiler builds the Mach compiler (written in Mach) from `src/`. This validates the compiler can compile itself. 88 | 89 | 3. **Final (mach)**: The intermediary compiler rebuilds itself, creating the final production compiler. 90 | 91 | ### Build Targets 92 | 93 | The Makefile provides granular control over the build process: 94 | 95 | **Bootstrap compiler (cmach):** 96 | ```bash 97 | make cmach-build # build cmach 98 | make cmach-clean # clean cmach artifacts 99 | make cmach # clean and build 100 | ``` 101 | 102 | **Intermediary compiler (imach):** 103 | ```bash 104 | make imach-build # build imach 105 | make imach-clean # clean imach artifacts 106 | make imach # clean and build 107 | ``` 108 | 109 | **Final compiler (mach):** 110 | ```bash 111 | make mach-build # build mach 112 | make mach-clean # clean mach artifacts 113 | make mach # clean and build 114 | ``` 115 | 116 | **Meta targets:** 117 | ```bash 118 | make full # build entire chain 119 | make clean # clean all artifacts 120 | make help # show all targets 121 | ``` 122 | 123 | **Build output:** 124 | - All executables: `out/bin/` directory 125 | - Bootstrap artifacts: `out/cmach/obj/` 126 | - Intermediary artifacts: `out/imach//` (ast, ir, asm, obj subdirectories) 127 | - Final artifacts: `out/mach//` (ast, ir, asm, obj subdirectories) 128 | 129 | --- 130 | 131 | ## Running Your First Program 132 | 133 | ### Hello World 134 | 135 | Create a file `hello.mach`: 136 | 137 | ```mach 138 | use std.runtime; 139 | use std.types.string; 140 | use console: std.io.console; 141 | 142 | $main.symbol = "main"; 143 | fun main(args: []string) i64 { 144 | console.print("hello, world!\n"); 145 | ret 0; 146 | } 147 | ``` 148 | 149 | **Compile and run:** 150 | 151 | ```bash 152 | # using the bootstrap compiler 153 | ./out/bin/cmach build hello.mach -o hello 154 | 155 | # or using the final compiler 156 | ./out/bin/mach build hello.mach -o hello 157 | 158 | # run the program 159 | ./hello 160 | ``` 161 | 162 | ### Compiler Flags 163 | 164 | Common `cmach` flags: 165 | 166 | - `-o ` - Output executable path 167 | - `-I ` - Add include directory 168 | - `-M =` - Map module to directory 169 | - `--obj-dir=` - Object file output directory 170 | - `--dep-dir=` - Dependency file directory 171 | - `--emit-asm` - Emit assembly listings 172 | - `--emit-ast` - Emit abstract syntax tree 173 | - `--emit-ir` - Emit LLVM IR 174 | 175 | **Example with flags:** 176 | ```bash 177 | ./out/bin/cmach build hello.mach \ 178 | -I std \ 179 | -M std=std \ 180 | --obj-dir=build/obj \ 181 | --emit-ir \ 182 | -o hello 183 | ``` 184 | 185 | --- 186 | 187 | ## Project Structure 188 | 189 | Mach projects typically follow this layout: 190 | 191 | ``` 192 | my-project/ 193 | ├── mach.toml # project configuration 194 | ├── src/ 195 | │ ├── main.mach # entry point 196 | │ └── ... # other source files 197 | └── dep/ # dependencies (optional) 198 | ``` 199 | 200 | ### Project Configuration (`mach.toml`) 201 | 202 | Create a `mach.toml` for your project: 203 | 204 | ```toml 205 | [project] 206 | name = "my-project" 207 | version = "0.1.0" 208 | src = "src" 209 | target = "native" 210 | 211 | [dependencies] 212 | std = "std" 213 | 214 | [targets.linux] 215 | triple = "x86_64-pc-linux-gnu" 216 | entrypoint = "main.mach" 217 | artifacts = "out/my-project/linux" 218 | out = "bin/my-project" 219 | opt-level = 2 220 | emit-ast = false 221 | emit-ir = false 222 | emit-asm = false 223 | emit-object = true 224 | build-library = false 225 | no-pie = false 226 | ``` 227 | 228 | See [`doc/project-layout.md`](project-layout.md) for complete configuration details. 229 | 230 | ### Using the Standard Library 231 | 232 | The Mach standard library is located in the `std/` directory. Import modules with `use`: 233 | 234 | ```mach 235 | use std.types.string; # string utilities 236 | use std.types.list; # dynamic arrays 237 | use std.types.option; # optional values 238 | use std.types.result; # result type 239 | use std.io.console; # console i/o 240 | use std.io.fs; # file system 241 | use std.system.memory; # memory management 242 | use std.system.time; # time utilities 243 | use std.system.env; # environment variables 244 | ``` 245 | 246 | **Aliasing imports:** 247 | ```mach 248 | use mem: std.system.memory; # use as 'mem' 249 | ``` 250 | 251 | --- 252 | 253 | ## Next Steps 254 | 255 | Now that you have Mach installed: 256 | 257 | 1. **Read the language specification**: [`doc/language-spec.md`](language-spec.md) covers all language features in detail. 258 | 259 | 2. **Explore examples**: Check `src/` and `std/` for real-world Mach code. 260 | 261 | 3. **Understand project layout**: See [`doc/project-layout.md`](project-layout.md) for organizing larger projects. 262 | 263 | 4. **Contribute**: Read [`CONTRIBUTING.md`](../CONTRIBUTING.md) to help improve Mach. 264 | 265 | 5. **Join the community**: Report issues and contribute on [GitHub](https://github.com/octalide/mach). 266 | 267 | --- 268 | 269 | ## Troubleshooting 270 | 271 | **Build fails with LLVM errors:** 272 | - Ensure `llvm-config` is in your PATH 273 | - Verify LLVM version is 14 or later 274 | - Check that development headers are installed 275 | 276 | **Compiler crashes or produces errors:** 277 | - File an issue with minimal reproduction case 278 | - Include compiler version and platform details 279 | - Check existing issues for known problems 280 | 281 | **Import errors:** 282 | - Verify module paths in `-M` flags 283 | - Ensure standard library path is correct 284 | - Check `mach.toml` module mappings 285 | 286 | For more help, consult the documentation or open an issue on GitHub. 287 | -------------------------------------------------------------------------------- /doc/project-layout.md: -------------------------------------------------------------------------------- 1 | # Project Layout 2 | 3 | This document defines the standard layout for Mach projects, including the project manifest (`mach.toml`), directory structure conventions, and how modules are organized and referenced. 4 | 5 | It is meant to serve as a "best practices" guide for Mach projects to ensure consistency and compatibility with tooling. It is not strictly enforced; projects may deviate as needed, but following these conventions will improve developer experience and tool support. 6 | 7 | --- 8 | 9 | ## Table of Contents 10 | 11 | - [Project Layout](#project-layout) 12 | - [Table of Contents](#table-of-contents) 13 | - [Standard Directory Structure](#standard-directory-structure) 14 | - [Project Manifest (`mach.toml`)](#project-manifest-machtoml) 15 | - [Basic structure](#basic-structure) 16 | - [`[project]` section](#project-section) 17 | - [`[targets.]` section](#targetsname-section) 18 | - [`[dependencies]` section (formerly `[directories]` and `[deps]`)](#dependencies-section-formerly-directories-and-deps) 19 | - [Build Organization](#build-organization) 20 | - [Build artifacts](#build-artifacts) 21 | - [Dependencies](#dependencies) 22 | - [Local dependency example](#local-dependency-example) 23 | - [Standard library](#standard-library) 24 | - [Entry points](#entry-points) 25 | 26 | --- 27 | 28 | ## Standard Directory Structure 29 | 30 | A typical Mach project follows this layout: 31 | 32 | ``` 33 | my-project/ 34 | ├── dep/ # external dependencies (optional) 35 | │ └── some-lib/ 36 | ├── doc/ # documentation (optional) 37 | ├── out/ # build artifacts (generated) 38 | │ ├── bin/ # final executables (all stages) 39 | │ │ ├── cmach # bootstrap compiler 40 | │ │ ├── imach # intermediate compiler 41 | │ │ └── mach # final compiler 42 | │ ├── cmach/ # bootstrap compiler artifacts 43 | │ │ └── obj/ # object files 44 | │ ├── imach/ # intermediate compiler artifacts 45 | │ │ └── / # per-target artifacts 46 | │ │ ├── ast/ # AST files 47 | │ │ ├── ir/ # LLVM IR 48 | │ │ ├── asm/ # assembly 49 | │ │ └── obj/ # object files 50 | │ └── mach/ # final compiler artifacts 51 | │ └── / # per-target artifacts 52 | ├── src/ # source files 53 | │ ├── main.mach # entry point 54 | │ └── ... # other source files specific to the project 55 | ├── mach.toml # project manifest 56 | └── Makefile # build system (optional) 57 | ``` 58 | 59 | **Directory conventions:** 60 | 61 | - **`src/`** – Primary source directory. Contains `.mach` files for your project. 62 | - **`dep/`** – External dependencies. Each dependency is a subdirectory containing its own Mach source. 63 | - **`doc/`** – Project documentation. 64 | - **`out/`** – Generated build artifacts. Should be excluded from version control. 65 | - **`out/bin/`** – Final executables from all build stages 66 | - **`out///`** – Per-stage, per-target build artifacts 67 | 68 | **Minimal project:** 69 | 70 | At minimum, a Mach project needs: 71 | - `mach.toml` – project configuration 72 | - `src/` – at least one `.mach` file with an entry point 73 | 74 | --- 75 | 76 | ## Project Manifest (`mach.toml`) 77 | 78 | Every Mach project requires a `mach.toml` file at the root. This TOML file configures the project, specifies build targets, and declares dependencies. 79 | 80 | > NOTE: The mach project file is technically NOT required to build simple programs or to customize the build process, as the compiler can be invoked with explicit flags. However, using `mach.toml` simplifies builds by providing defaults and reducing command-line complexity. 81 | 82 | ### Basic structure 83 | 84 | ```toml 85 | [project] 86 | name = "my_project" 87 | version = "0.1.0" 88 | src = "src" 89 | target = "native" # or "all", or a specific target name 90 | 91 | [dependencies] 92 | std = "std" 93 | 94 | [targets.linux] 95 | triple = "x86_64-pc-linux-gnu" 96 | entrypoint = "main.mach" 97 | artifacts = "out/mach/linux" 98 | out = "bin/mach" 99 | opt-level = 2 100 | emit-ast = true 101 | emit-ir = true 102 | emit-asm = true 103 | emit-object = true 104 | build-library = false 105 | no-pie = false 106 | ``` 107 | 108 | ### `[project]` section 109 | 110 | Identifies the project and build configuration: 111 | 112 | - **`name`** (required) – Project name. Used for build artifacts and module namespace. 113 | - **`version`** (required) – Semantic version string (e.g., `"0.1.0"`). 114 | - **`src`** (required) – Source directory containing `.mach` files (default: `"src"`). 115 | - **`target`** (required) – Default target to build: 116 | - `"native"` – Build for the host platform (auto-detected via LLVM) 117 | - `"all"` – Build for all defined targets 118 | - `""` – Build a specific target (e.g., `"linux"`, `"darwin"`, `"windows"`) 119 | 120 | **Example:** 121 | ```toml 122 | [project] 123 | name = "http-server" 124 | version = "1.2.3" 125 | src = "src" 126 | target = "native" 127 | ``` 128 | 129 | ### `[targets.]` section 130 | 131 | Each target defines a build configuration for a specific platform: 132 | 133 | - **`triple`** (required) – LLVM target triple (e.g., `"x86_64-pc-linux-gnu"`) 134 | - **`entrypoint`** (required) – Main source file relative to the `src` directory 135 | - **`artifacts`** (required) – Directory for build artifacts (AST, IR, ASM, OBJ) relative to project root 136 | - **`out`** (required) – Final executable/library path (relative to project root or absolute) 137 | - **`opt-level`** (required) – Optimization level (0-3) 138 | - **`emit-ast`** (required) – Emit AST files (true/false) 139 | - **`emit-ir`** (required) – Emit LLVM IR files (true/false) 140 | - **`emit-asm`** (required) – Emit assembly files (true/false) 141 | - **`emit-object`** (required) – Emit object files (true/false) 142 | - **`build-library`** (required) – Build as library instead of executable (true/false) 143 | - **`shared`** (optional) – Build shared library if build-library=true (true/false) 144 | - **`no-pie`** (optional) – Disable position-independent executable (true/false) 145 | - **`link`** (optional, repeatable) – External libraries to link (e.g., `link = "/usr/lib/libglfw.so"`) 146 | 147 | **Example:** 148 | ```toml 149 | [targets.linux] 150 | triple = "x86_64-pc-linux-gnu" 151 | entrypoint = "main.mach" 152 | artifacts = "out/mach/linux" 153 | out = "bin/mach" 154 | opt-level = 2 155 | emit-ast = true 156 | emit-ir = true 157 | emit-asm = true 158 | emit-object = true 159 | build-library = false 160 | no-pie = false 161 | 162 | [targets.darwin] 163 | triple = "x86_64-apple-darwin" 164 | entrypoint = "main.mach" 165 | artifacts = "out/mach/darwin" 166 | out = "bin/mach" 167 | opt-level = 2 168 | emit-ast = false 169 | emit-ir = false 170 | emit-asm = false 171 | emit-object = true 172 | build-library = false 173 | no-pie = false 174 | ``` 175 | 176 | ### `[dependencies]` section (formerly `[directories]` and `[deps]`) 177 | 178 | Maps dependency names to their locations. Dependencies are effectively mappings for module resolution. 179 | 180 | **Simple local dependency:** 181 | ```toml 182 | [dependencies] 183 | std = "std" # points to ./std/ 184 | mylib = "dep/mylib/src" # points to ./dep/mylib/src 185 | ``` 186 | 187 | Dependencies must point to mach SOURCE directories, not project roots. 188 | 189 | > NOTE: This is likely to change in the future to support more complex dependency specifications. 190 | 191 | --- 192 | 193 | ## Build Organization 194 | 195 | ### Build artifacts 196 | 197 | The compiler generates several types of artifacts organized by build stage and target: 198 | 199 | ``` 200 | out/ 201 | ├── bin/ # final executables from all build stages 202 | │ ├── cmach # bootstrap compiler (C-based) 203 | │ ├── imach # intermediate compiler (Mach-based) 204 | │ └── mach # final compiler (Mach-based) 205 | ├── cmach/ # bootstrap compiler artifacts 206 | │ └── obj/ # object files (.o) 207 | ├── imach/ # intermediate compiler artifacts 208 | │ └── / # per-target (e.g., linux, darwin, windows) 209 | │ ├── ast/ # AST files (.ast) 210 | │ │ └── / # module namespace (e.g., mach/) 211 | │ ├── ir/ # LLVM IR files (.ll) 212 | │ │ └── / 213 | │ ├── asm/ # assembly files (.s) 214 | │ │ └── / 215 | │ └── obj/ # object files (.o) 216 | │ └── / 217 | └── mach/ # final compiler artifacts 218 | └── / # per-target organization 219 | ├── ast/ 220 | ├── ir/ 221 | ├── asm/ 222 | └── obj/ 223 | ``` 224 | 225 | **Key points:** 226 | 227 | - All final executables go in `out/bin/` regardless of build stage 228 | - Artifacts are organized by stage (`cmach`, `imach`, `mach`) 229 | - Multi-stage builds use per-target subdirectories for cross-compilation 230 | - Modules are namespaced within artifact directories (e.g., `mach/main.ll`) 231 | 232 | **Exclude from version control:** 233 | 234 | Add to `.gitignore`: 235 | ``` 236 | out/ 237 | *.o 238 | *.a 239 | ``` 240 | 241 | --- 242 | 243 | ## Dependencies 244 | 245 | ### Local dependency example 246 | 247 | Place dependencies in the `dep/` directory: 248 | 249 | ``` 250 | my-project/ 251 | ├── mach.toml 252 | ├── src/ 253 | │ └── main.mach 254 | └── dep/ 255 | ├── json-parser/ 256 | │ ├── parser.mach 257 | │ └── types.mach 258 | └── http-client/ 259 | └── client.mach 260 | ``` 261 | 262 | Configure in `mach.toml`: 263 | ```toml 264 | [dependencies] 265 | json = "dep/json-parser" 266 | http = "dep/http-client" 267 | ``` 268 | 269 | Import in code: 270 | ```mach 271 | use json.parser; 272 | use http.client; 273 | ``` 274 | 275 | ### Standard library 276 | 277 | The Mach standard library is typically a dependency: 278 | 279 | ```toml 280 | [dependencies] 281 | std = "path/to/std" 282 | ``` 283 | 284 | Common imports: 285 | ```mach 286 | use std.types.string; # string utilities 287 | use std.types.list; # dynamic arrays (List[T]) 288 | use std.types.option; # optional values (Option[T]) 289 | use std.types.result; # result type (Result[T, E]) 290 | use std.io.console; # console i/o 291 | use std.io.fs; # file system 292 | use std.system.memory; # memory allocation 293 | use std.system.time; # time utilities 294 | use std.system.env; # environment variables 295 | ``` 296 | 297 | > NOTE: The standard library path will be eventually resolved through environment variables or global configuration rather than hardcoding in each project. This option will still be required, but the syntax and usage may change to support this feature in the future. 298 | 299 | --- 300 | 301 | ## Entry points 302 | 303 | The entry point must contain a main function: 304 | 305 | ```mach 306 | use std.runtime; 307 | use std.types.string; 308 | 309 | $main.symbol = "main"; 310 | fun main(args: []string) i64 { 311 | # program logic 312 | ret 0; 313 | } 314 | ``` 315 | 316 | The `$main.symbol = "main"` attribute assignment ensures the function is exported with an unmangled symbol name. This is a requirement for the runtime included in the standard library to link properly. If you are seeing issues related to undefined references to `main` or lack of a `_start` symbol, ensure this attribute is set and the runtime is imported without an alias. 317 | 318 | --- 319 | 320 | This layout provides a consistent structure for Mach projects, making them easy to understand and maintain. Projects can deviate from this structure when needed, but following these conventions improves tooling compatibility and developer experience. 321 | -------------------------------------------------------------------------------- /mach.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mach" 3 | version = "0.3.2" 4 | src = "src" 5 | target = "all" 6 | 7 | [dependencies] 8 | std = "std" 9 | 10 | [targets.linux] 11 | triple = "x86_64-pc-linux-gnu" 12 | entrypoint = "main.mach" 13 | artifacts = "out/imach/linux" 14 | out = "out/bin/imach" 15 | opt-level = 2 16 | emit-ast = true 17 | emit-ir = true 18 | emit-asm = true 19 | emit-object = true 20 | build-library = false 21 | no-pie = false 22 | 23 | [targets.darwin] 24 | triple = "x86_64-apple-darwin" 25 | entrypoint = "main.mach" 26 | artifacts = "out/imach/darwin" 27 | out = "out/bin/imach" 28 | opt-level = 2 29 | emit-ast = true 30 | emit-ir = true 31 | emit-asm = true 32 | emit-object = true 33 | build-library = false 34 | no-pie = false 35 | 36 | [targets.windows] 37 | triple = "x86_64-pc-windows-msvc" 38 | entrypoint = "main.mach" 39 | artifacts = "out/imach/windows" 40 | out = "bin/imach.exe" 41 | opt-level = 2 42 | emit-ast = true 43 | emit-ir = true 44 | emit-asm = true 45 | emit-object = true 46 | build-library = false 47 | no-pie = false 48 | -------------------------------------------------------------------------------- /src/commands.mach: -------------------------------------------------------------------------------- 1 | use std.io.console; 2 | use std.types.string; 3 | use std.types.bool; 4 | use std.types.list; 5 | use std.types.option; 6 | use std.types.result; 7 | use mem: std.system.memory; 8 | use console: std.io.console; 9 | 10 | val PROJECT_FILE_NAME: string = "mach.toml"; 11 | 12 | pub rec BuildOptions { 13 | target: string; # target to build (project or single module) 14 | debug: bool; # build with debug info 15 | optimize: bool; # enable optimizations 16 | output: Option[string]; # manual output filename override 17 | link_to: List[string]; # libraries to link against 18 | include: List[string]; # manual include paths for the compiler 19 | modules: List[string]; # manual module mapping, e.g `n=dir` where `n` is the module prefix at `dir` 20 | # NOTE: emit options are only valid for single module builds 21 | emit_obj: Option[string]; # emit object file to this path 22 | emit_asm: Option[string]; # emit assembly file to this path 23 | emit_ir: Option[string]; # emit intermediate representation file to this path 24 | emit_ast: Option[string]; # emit abstract syntax tree file to this path 25 | 26 | is_project: bool; # true if building a full project, false if single module 27 | } 28 | 29 | fun is_project_file(path: string) bool { 30 | ret path.equals(PROJECT_FILE_NAME); 31 | } 32 | 33 | fun get_module_name(path: string) string { 34 | val dot_index: Option[u64] = path.last_index_of("."); 35 | if (dot_index.is_none()) { 36 | ret path; 37 | } 38 | val res_name: Option[string] = path.substring(0, dot_index.unwrap()); 39 | if (res_name.is_none()) { 40 | ret path; 41 | } 42 | ret res_name.unwrap(); 43 | } 44 | 45 | fun handle_help(args: []string) i64 { 46 | console.print("usage: mach [options]\n"); 47 | console.print("\n"); 48 | console.print("available commands:\n"); 49 | console.print(" help show this help message\n"); 50 | console.print(" build build the project or a named target\n"); 51 | ret 0; 52 | } 53 | 54 | fun handle_build(args: []string) i64 { 55 | if (args.len < 3) { 56 | console.print("error: no target specified for build command\n"); 57 | ret 1; 58 | } 59 | 60 | var options: BuildOptions; 61 | options.target = args[2]; 62 | options.debug = true; 63 | options.optimize = true; 64 | options.output = none[string](); 65 | 66 | # Initialize lists 67 | val link_to_res: Result[List[string], string] = list_new[string](4); 68 | if (link_to_res.is_err()) { 69 | console.print("error: failed to initialize link_to list\n"); 70 | ret 1; 71 | } 72 | options.link_to = link_to_res.unwrap_ok(); 73 | 74 | val include_res: Result[List[string], string] = list_new[string](4); 75 | if (include_res.is_err()) { 76 | console.print("error: failed to initialize include list\n"); 77 | ret 1; 78 | } 79 | options.include = include_res.unwrap_ok(); 80 | 81 | val modules_res: Result[List[string], string] = list_new[string](4); 82 | if (modules_res.is_err()) { 83 | console.print("error: failed to initialize modules list\n"); 84 | ret 1; 85 | } 86 | options.modules = modules_res.unwrap_ok(); 87 | 88 | options.emit_obj = none[string](); 89 | options.emit_asm = none[string](); 90 | options.emit_ir = none[string](); 91 | options.emit_ast = none[string](); 92 | 93 | # validate target 94 | if (options.target.is_empty()) { 95 | console.print("error: empty target specified for build command\n"); 96 | ret 1; 97 | } 98 | 99 | # determine if building a project or single module 100 | options.is_project = is_project_file(options.target); 101 | 102 | # set default output name 103 | options.output = some[string](get_module_name(options.target)); 104 | 105 | var arg: u64 = 3; 106 | for (arg < args.len) { 107 | if (args[arg].equals("--debug")) { 108 | options.debug = true; 109 | } 110 | or (args[arg].equals("--release")) { 111 | options.debug = false; 112 | options.optimize = true; 113 | } 114 | or (args[arg].equals("--optimize")) { 115 | options.optimize = true; 116 | } 117 | or (args[arg].equals("--no-optimize")) { 118 | options.optimize = false; 119 | } 120 | or (args[arg].equals("--output")) { 121 | if (arg + 1 >= args.len) { 122 | console.print("error: --output requires a filename argument\n"); 123 | ret 1; 124 | } 125 | options.output = some[string](args[arg + 1]); 126 | arg = arg + 1; 127 | } 128 | or (args[arg].equals("--link-to")) { 129 | if (arg + 1 >= args.len) { 130 | console.print("error: --link-to requires a library name argument\n"); 131 | ret 1; 132 | } 133 | val push_err: Option[string] = options.link_to.push(args[arg + 1]); 134 | if (push_err.is_some()) { 135 | console.print("error: failed to store --link-to argument: %s\n", push_err.unwrap()); 136 | ret 1; 137 | } 138 | arg = arg + 1; 139 | } 140 | or (args[arg].equals("--include")) { 141 | if (arg + 1 >= args.len) { 142 | console.print("error: --include requires a path argument\n"); 143 | ret 1; 144 | } 145 | val push_err: Option[string] = options.include.push(args[arg + 1]); 146 | if (push_err.is_some()) { 147 | console.print("error: failed to store --include argument: %s\n", push_err.unwrap()); 148 | ret 1; 149 | } 150 | arg = arg + 1; 151 | } 152 | or (args[arg].equals("--module")) { 153 | if (arg + 1 >= args.len) { 154 | console.print("error: --module requires a mapping argument\n"); 155 | ret 1; 156 | } 157 | val push_err: Option[string] = options.modules.push(args[arg + 1]); 158 | if (push_err.is_some()) { 159 | console.print("error: failed to store --module argument: %s\n", push_err.unwrap()); 160 | ret 1; 161 | } 162 | arg = arg + 1; 163 | } 164 | or (args[arg].equals("--emit-obj")) { 165 | if (arg + 1 >= args.len) { 166 | console.print("error: --emit-obj requires a filename argument\n"); 167 | ret 1; 168 | } 169 | options.emit_obj = some[string](args[arg + 1]); 170 | arg = arg + 1; 171 | } 172 | or (args[arg].equals("--emit-asm")) { 173 | if (arg + 1 >= args.len) { 174 | console.print("error: --emit-asm requires a filename argument\n"); 175 | ret 1; 176 | } 177 | options.emit_asm = some[string](args[arg + 1]); 178 | arg = arg + 1; 179 | } 180 | or (args[arg].equals("--emit-ir")) { 181 | if (arg + 1 >= args.len) { 182 | console.print("error: --emit-ir requires a filename argument\n"); 183 | ret 1; 184 | } 185 | options.emit_ir = some[string](args[arg + 1]); 186 | arg = arg + 1; 187 | } 188 | or (args[arg].equals("--emit-ast")) { 189 | if (arg + 1 >= args.len) { 190 | console.print("error: --emit-ast requires a filename argument\n"); 191 | ret 1; 192 | } 193 | options.emit_ast = some[string](args[arg + 1]); 194 | arg = arg + 1; 195 | } 196 | or { 197 | console.print("error: unknown option '%s' for build command\n", args[arg]); 198 | ret 1; 199 | } 200 | arg = arg + 1; 201 | } 202 | 203 | ret 0; 204 | } 205 | 206 | pub fun dispatch(args: []string) i64 { 207 | if (args.len < 2) { 208 | console.print("error: no arguments provided\n"); 209 | ret 1; 210 | } 211 | 212 | val command: string = args[1]; 213 | 214 | if (command.equals("help")) { 215 | ret handle_help(args); 216 | } 217 | or (command.equals("build")) { 218 | ret handle_build(args); 219 | } 220 | 221 | console.print("error: unknown command '%s'\n", command); 222 | console.print("use 'mach help' for usage information\n"); 223 | ret 1; 224 | } 225 | -------------------------------------------------------------------------------- /src/main.mach: -------------------------------------------------------------------------------- 1 | use std.runtime; 2 | use std.types.string; 3 | use console: std.io.console; 4 | 5 | use commands; 6 | 7 | $main.symbol = "main"; 8 | fun main(args: []string) i64 { 9 | ret dispatch(args); 10 | } 11 | -------------------------------------------------------------------------------- /std/crypto/hash.mach: -------------------------------------------------------------------------------- 1 | # simple fnv-1a 64-bit for identifiers/maps 2 | val FNV_OFFSET64: u64 = 14695981039346656037; 3 | val FNV_PRIME64: u64 = 1099511628211; 4 | 5 | fun fnv1a64(data: []u8) u64 { 6 | var h: u64 = FNV_OFFSET64; 7 | var i: u64 = 0; 8 | 9 | for (i < data.len) { 10 | h = h ^ (data[i]::u64); 11 | h = h * FNV_PRIME64; 12 | i = i + 1; 13 | } 14 | 15 | ret h; 16 | } 17 | -------------------------------------------------------------------------------- /std/io/console.mach: -------------------------------------------------------------------------------- 1 | use std.types.string; 2 | use std.types.result; 3 | 4 | use mem: std.system.memory; 5 | 6 | $if (OS == OS_LINUX) { 7 | use sys: std.system.platform.linux.sys; 8 | } 9 | 10 | $if (OS == OS_DARWIN) { 11 | use sys: std.system.platform.darwin.sys; 12 | } 13 | 14 | $if (OS == OS_WINDOWS) { 15 | use sys: std.system.platform.windows.sys; 16 | } 17 | 18 | pub val STDIN: i32 = 0; 19 | pub val STDOUT: i32 = 1; 20 | pub val STDERR: i32 = 2; 21 | 22 | $if (OS == OS_LINUX || OS == OS_DARWIN) { 23 | 24 | fun platform_write(fd: i32, data: *u8, size: u64) i64 { 25 | ret sys.write(fd, data, size); 26 | } 27 | 28 | fun platform_read(fd: i32, dest: *u8, max: u64) i64 { 29 | ret sys.read(fd, dest, max); 30 | } 31 | } 32 | 33 | $if (OS == OS_WINDOWS) { 34 | 35 | fun windows_handle(fd: i32) *u8 { 36 | if (fd == STDIN) { 37 | ret sys.get_std_handle(sys.STD_INPUT_HANDLE); 38 | } 39 | or (fd == STDOUT) { 40 | ret sys.get_std_handle(sys.STD_OUTPUT_HANDLE); 41 | } 42 | or (fd == STDERR) { 43 | ret sys.get_std_handle(sys.STD_ERROR_HANDLE); 44 | } 45 | 46 | ret nil; 47 | } 48 | 49 | fun windows_chunked_io(handle: *u8, buffer: *u8, size: u64, is_write: u8) i64 { 50 | var remaining: u64 = size; 51 | var offset: u64 = 0; 52 | var total: u64 = 0; 53 | 54 | for (remaining > 0) { 55 | var chunk: u64 = remaining; 56 | if (chunk > 0xffff_ffff) { 57 | chunk = 0xffff_ffff; 58 | } 59 | 60 | var count32: u32 = 0; 61 | var ok: u32 = 0; 62 | if (is_write != 0) { 63 | ok = sys.write_file(handle, (buffer + offset), chunk :: u32, (?count32) :: *u32); 64 | } or { 65 | ok = sys.read_file(handle, (buffer + offset), chunk :: u32, (?count32) :: *u32); 66 | } 67 | 68 | if (ok == 0) { 69 | ret -1; 70 | } 71 | 72 | val processed: u64 = count32 :: u64; 73 | if (processed == 0) { 74 | brk; 75 | } 76 | 77 | offset = offset + processed; 78 | total = total + processed; 79 | remaining = remaining - processed; 80 | } 81 | 82 | ret total :: i64; 83 | } 84 | 85 | fun platform_write(fd: i32, data: *u8, size: u64) i64 { 86 | val handle: *u8 = windows_handle(fd); 87 | if (handle == nil || (handle :: i64) == -1) { 88 | ret -1; 89 | } 90 | 91 | ret windows_chunked_io(handle, data, size, 1); 92 | } 93 | 94 | fun platform_read(fd: i32, dest: *u8, max: u64) i64 { 95 | val handle: *u8 = windows_handle(fd); 96 | if (handle == nil || (handle :: i64) == -1) { 97 | ret -1; 98 | } 99 | 100 | ret windows_chunked_io(handle, dest, max, 0); 101 | } 102 | } 103 | 104 | fun write_segment(fd: i32, part: string) i64 { 105 | if (part.len == 0) { ret 0; } 106 | ret platform_write(fd, part.data, part.len); 107 | } 108 | 109 | pub fun write_to(fd: i32, pattern: string, ...) i64 { 110 | val extra: u64 = va_count(); 111 | if (extra == 0) { 112 | ret write_segment(fd, pattern); 113 | } 114 | 115 | val res: Result[string, string] = pattern.format(...); 116 | if (res.is_err()) { 117 | # TODO: cascade result 118 | ret -1; 119 | } 120 | 121 | val rendered: string = res.unwrap_ok(); 122 | if (rendered.len == 0) { 123 | ret 0; 124 | } 125 | 126 | val wrote: i64 = write_segment(fd, rendered); 127 | 128 | if (rendered.data != nil) { 129 | mem.dealloc[u8](rendered.data, rendered.len); 130 | } 131 | 132 | ret wrote; 133 | } 134 | 135 | pub fun write(pattern: string, ...) i64 { 136 | ret write_to(STDOUT, pattern, ...); 137 | } 138 | 139 | pub fun print(pattern: string, ...) i64 { 140 | ret write_to(STDOUT, pattern, ...); 141 | } 142 | 143 | pub fun error(pattern: string, ...) i64 { 144 | ret write_to(STDERR, pattern, ...); 145 | } 146 | 147 | pub fun read_from(fd: i32, dest: *u8, max: u64) i64 { 148 | if (dest == nil || max == 0) { 149 | ret 0; 150 | } 151 | ret platform_read(fd, dest, max); 152 | } 153 | 154 | pub fun read(dest: *u8, max: u64) i64 { 155 | ret read_from(STDIN, dest, max); 156 | } 157 | -------------------------------------------------------------------------------- /std/io/fs.mach: -------------------------------------------------------------------------------- 1 | use mem: std.system.memory; 2 | use std.types.string; 3 | 4 | $if (OS == OS_LINUX) { 5 | use sys: std.system.platform.linux.sys; 6 | } 7 | 8 | $if (OS == OS_DARWIN) { 9 | use sys: std.system.platform.darwin.sys; 10 | } 11 | 12 | $if (OS == OS_WINDOWS) { 13 | use sys: std.system.platform.windows.sys; 14 | } 15 | 16 | $if ((OS == OS_LINUX) || (OS == OS_DARWIN)) { 17 | fun path_to_cstr(path: string) []u8 { 18 | val size: u64 = path.len + 1; 19 | val buf: *u8 = mem.allocate(size); 20 | if (buf == nil) { 21 | ret []u8{ nil, 0 }; 22 | } 23 | 24 | if (path.len > 0) { 25 | mem.raw_copy(buf, path.data, path.len); 26 | } 27 | @(buf + path.len) = 0; 28 | 29 | ret []u8{ buf, size }; 30 | } 31 | } 32 | 33 | $if (OS == OS_WINDOWS) { 34 | fun path_to_utf16(path: string) []u16 { 35 | val units: u64 = path.len + 1; 36 | val bytes: u64 = units * 2; 37 | val buf_raw: *u8 = mem.allocate(bytes); 38 | if (buf_raw == nil) { 39 | ret []u16{ nil, 0 }; 40 | } 41 | 42 | val buf16: *u16 = buf_raw :: *u16; 43 | var i: u64 = 0; 44 | for (i < path.len) { 45 | val ch: u8 = @(path.data + i); 46 | if (ch >= 128) { 47 | mem.deallocate(buf_raw, bytes); 48 | ret []u16{ nil, 0 }; 49 | } 50 | @(buf16 + i) = ch :: u16; 51 | i = i + 1; 52 | } 53 | 54 | @(buf16 + path.len) = 0; 55 | 56 | ret []u16{ buf16, units }; 57 | } 58 | } 59 | 60 | pub val AT_FDCWD: i32 = -100; 61 | 62 | # basic posix flags subset for open(2) 63 | pub val O_RDONLY: i32 = 0; 64 | pub val O_WRONLY: i32 = 1; 65 | pub val O_RDWR: i32 = 2; 66 | pub val O_CREAT: i32 = 64; # 0o100 67 | pub val O_TRUNC: i32 = 512; # 0o1000 68 | 69 | # unix style permissions subset 70 | pub val MODE_644: i32 = 420; # 0644 71 | pub val MODE_600: i32 = 384; # 0600 72 | 73 | $if ((OS == OS_LINUX) || (OS == OS_DARWIN)) { 74 | 75 | pub fun fs_read_all(path: string) []u8 { 76 | val c_path: []u8 = path_to_cstr(path); 77 | if (c_path.data == nil) { 78 | ret []u8{ nil, 0 }; 79 | } 80 | 81 | val fd_res: i64 = sys.openat(AT_FDCWD, c_path.data, O_RDONLY, 0); 82 | mem.deallocate(c_path.data, c_path.len); 83 | if (fd_res < 0) { 84 | ret []u8{ nil, 0 }; 85 | } 86 | 87 | val fd: i32 = fd_res :: i32; 88 | var cap: u64 = 4096; 89 | var buf: *u8 = mem.allocate(cap); 90 | if (buf == nil) { 91 | sys.close(fd); 92 | ret []u8{ nil, 0 }; 93 | } 94 | 95 | var len: u64 = 0; 96 | for { 97 | if (cap == len) { 98 | val ncap: u64 = cap + (cap >> 1) + 1; 99 | val nbuf: *u8 = mem.reallocate(buf, cap, ncap); 100 | if (nbuf == nil) { 101 | mem.deallocate(buf, cap); 102 | sys.close(fd); 103 | ret []u8{ nil, 0 }; 104 | } 105 | buf = nbuf; 106 | cap = ncap; 107 | } 108 | 109 | val space: u64 = cap - len; 110 | val got: i64 = sys.read(fd, (buf + len), space); 111 | if (got < 0) { 112 | mem.deallocate(buf, cap); 113 | sys.close(fd); 114 | ret []u8{ nil, 0 }; 115 | } 116 | or (got == 0) { 117 | brk; 118 | } 119 | 120 | len = len + (got :: u64); 121 | } 122 | 123 | sys.close(fd); 124 | 125 | if (len < cap) { 126 | val nbuf: *u8 = mem.reallocate(buf, cap, len); 127 | if (nbuf != nil) { 128 | buf = nbuf; 129 | } 130 | } 131 | 132 | ret []u8{ buf, len }; 133 | } 134 | 135 | pub fun fs_write_all(path: string, data: []u8) i32 { 136 | val c_path: []u8 = path_to_cstr(path); 137 | if (c_path.data == nil) { 138 | ret -1; 139 | } 140 | 141 | val fd_res: i64 = sys.openat(AT_FDCWD, c_path.data, O_WRONLY | O_CREAT | O_TRUNC, MODE_644); 142 | mem.deallocate(c_path.data, c_path.len); 143 | if (fd_res < 0) { 144 | ret -1; 145 | } 146 | 147 | val fd: i32 = fd_res :: i32; 148 | var off: u64 = 0; 149 | for (off < data.len) { 150 | val wrote: i64 = sys.write(fd, (data.data + off), (data.len - off)); 151 | if (wrote <= 0) { 152 | sys.close(fd); 153 | ret -1; 154 | } 155 | off = off + (wrote :: u64); 156 | } 157 | 158 | sys.close(fd); 159 | ret 0; 160 | } 161 | 162 | } 163 | 164 | $if (OS == OS_WINDOWS) { 165 | 166 | pub fun fs_read_all(path: string) []u8 { 167 | val w_path: []u16 = path_to_utf16(path); 168 | if (w_path.data == nil) { 169 | ret []u8{ nil, 0 }; 170 | } 171 | 172 | val handle: *u8 = sys.create_file_read(w_path.data); 173 | mem.deallocate((w_path.data :: *u8), w_path.len * 2); 174 | if (handle == nil || (handle :: i64) == -1) { 175 | ret []u8{ nil, 0 }; 176 | } 177 | 178 | var cap: u64 = 4096; 179 | var buf: *u8 = mem.allocate(cap); 180 | if (buf == nil) { 181 | sys.close_handle(handle); 182 | ret []u8{ nil, 0 }; 183 | } 184 | 185 | var len: u64 = 0; 186 | for { 187 | if (cap == len) { 188 | val ncap: u64 = cap + (cap >> 1) + 1; 189 | val nbuf: *u8 = mem.reallocate(buf, cap, ncap); 190 | if (nbuf == nil) { 191 | mem.deallocate(buf, cap); 192 | sys.close_handle(handle); 193 | ret []u8{ nil, 0 }; 194 | } 195 | buf = nbuf; 196 | cap = ncap; 197 | } 198 | 199 | var chunk: u64 = cap - len; 200 | if (chunk > 0xffff_ffff) { 201 | chunk = 0xffff_ffff; 202 | } 203 | var read32: u32 = 0; 204 | val ok: u32 = sys.read_file(handle, (buf + len), chunk :: u32, (?read32) :: *u32); 205 | if (ok == 0) { 206 | mem.deallocate(buf, cap); 207 | sys.close_handle(handle); 208 | ret []u8{ nil, 0 }; 209 | } 210 | or (read32 == 0) { 211 | brk; 212 | } 213 | 214 | len = len + (read32 :: u64); 215 | } 216 | 217 | sys.close_handle(handle); 218 | 219 | if (len < cap) { 220 | val nbuf: *u8 = mem.reallocate(buf, cap, len); 221 | if (nbuf != nil) { 222 | buf = nbuf; 223 | } 224 | } 225 | 226 | ret []u8{ buf, len }; 227 | } 228 | 229 | pub fun fs_write_all(path: string, data: []u8) i32 { 230 | val w_path: []u16 = path_to_utf16(path); 231 | if (w_path.data == nil) { 232 | ret -1; 233 | } 234 | 235 | val handle: *u8 = sys.create_file_write(w_path.data, 1); 236 | mem.deallocate((w_path.data :: *u8), w_path.len * 2); 237 | if (handle == nil || (handle :: i64) == -1) { 238 | ret -1; 239 | } 240 | 241 | var off: u64 = 0; 242 | for (off < data.len) { 243 | var chunk: u64 = data.len - off; 244 | if (chunk > 0xffff_ffff) { 245 | chunk = 0xffff_ffff; 246 | } 247 | var written: u32 = 0; 248 | val ok: u32 = sys.write_file(handle, (data.data + off), chunk :: u32, (?written) :: *u32); 249 | if (ok == 0 || written == 0) { 250 | sys.close_handle(handle); 251 | ret -1; 252 | } 253 | off = off + (written :: u64); 254 | } 255 | 256 | sys.close_handle(handle); 257 | ret 0; 258 | } 259 | 260 | } 261 | -------------------------------------------------------------------------------- /std/io/path.mach: -------------------------------------------------------------------------------- 1 | use mem: std.system.memory; 2 | 3 | pub fun is_separator(b: u8) u8 { 4 | ret (b == '/'); 5 | } 6 | 7 | pub fun basename(p: []u8) []u8 { 8 | if (p.len == 0) { 9 | ret p; 10 | } 11 | 12 | var i: u64 = p.len; 13 | for (i > 0) { 14 | i = i - 1; 15 | if (p[i] == '/') { 16 | ret []u8{ p.data + i + 1, p.len - i - 1 }; 17 | } 18 | } 19 | 20 | ret p; 21 | } 22 | 23 | pub fun dirname(p: []u8) []u8 { 24 | if (p.len == 0) { 25 | ret p; 26 | } 27 | 28 | var i: u64 = p.len; 29 | for (i > 0) { 30 | i = i - 1; 31 | if (p[i] == '/') { 32 | ret []u8{ p.data, i }; 33 | } 34 | } 35 | 36 | ret []u8{ p.data, 0 }; 37 | } 38 | 39 | pub fun extname(p: []u8) []u8 { 40 | var i: u64 = p.len; 41 | 42 | for (i > 0) { 43 | i = i - 1; 44 | 45 | if (p[i] == '.') { ret []u8{ p.data + i + 1, p.len - i - 1 }; } 46 | if (p[i] == '/') { brk; } 47 | } 48 | 49 | ret []u8{ p.data + p.len, 0 }; 50 | } 51 | 52 | pub fun join(a: []u8, b: []u8) []u8 { 53 | if (a.len == 0) { ret b; } 54 | if (b.len == 0) { ret a; } 55 | 56 | val need: u64 = a.len + 1 + b.len; 57 | val p: *u8 = mem.allocate(need); 58 | 59 | if (p == nil) { 60 | ret []u8{ nil, 0 }; 61 | } 62 | 63 | mem.raw_copy(p, a.data, a.len); 64 | @(p + a.len) = '/'::u8; 65 | mem.raw_copy(p + a.len + 1, b.data, b.len); 66 | 67 | ret []u8{ p, need }; 68 | } 69 | -------------------------------------------------------------------------------- /std/runtime.mach: -------------------------------------------------------------------------------- 1 | use mem: std.system.memory; 2 | 3 | pub ext main: fun([][]u8) i64; 4 | pub ext "mach_runtime_exit" mach_runtime_exit: fun(i64); 5 | 6 | val WORD_BYTES: u64 = 8; 7 | 8 | $if (OS == OS_WINDOWS) { 9 | 10 | val WIN_CP_UTF8: u32 = 65001; 11 | 12 | ext "C:GetCommandLineW" win32_get_command_line_w: fun() *u16; 13 | ext "C:CommandLineToArgvW" win32_command_line_to_argv_w: fun(*u16, *i32) **u16; 14 | ext "C:LocalFree" win32_local_free: fun(*u8) *u8; 15 | ext "C:WideCharToMultiByte" win32_wide_char_to_multi_byte: fun(u32, u32, *u16, i32, *u8, i32, *u8, *u8) i32; 16 | ext "C:ExitProcess" win32_exit_process: fun(u32); 17 | 18 | fun utf16_length(s: *u16) u64 { 19 | if (s == nil) { 20 | ret 0; 21 | } 22 | 23 | var length: u64 = 0; 24 | var cursor: *u16 = s; 25 | for { 26 | if (@cursor == 0::u16) { 27 | brk; 28 | } 29 | length = length + 1; 30 | cursor = cursor + 1; 31 | } 32 | 33 | ret length; 34 | } 35 | 36 | fun utf16_to_utf8(src: *u16) []u8 { 37 | if (src == nil) { 38 | ret []u8{ nil, 0 }; 39 | } 40 | 41 | val char_count64: u64 = utf16_length(src); 42 | if (char_count64 == 0) { 43 | ret []u8{ nil, 0 }; 44 | } 45 | 46 | val char_count: i32 = char_count64::i32; 47 | val required: i32 = win32_wide_char_to_multi_byte(WIN_CP_UTF8, 0, src, char_count, nil, 0, nil, nil); 48 | if (required <= 0) { 49 | ret []u8{ nil, 0 }; 50 | } 51 | 52 | val buffer_size: u64 = required::u64; 53 | val buffer: *u8 = mem.allocate(buffer_size); 54 | if (buffer == nil) { 55 | ret []u8{ nil, 0 }; 56 | } 57 | 58 | val written: i32 = win32_wide_char_to_multi_byte(WIN_CP_UTF8, 0, src, char_count, buffer, required, nil, nil); 59 | if (written <= 0) { 60 | mem.deallocate(buffer, buffer_size); 61 | ret []u8{ nil, 0 }; 62 | } 63 | 64 | ret []u8{ buffer, written::u64 }; 65 | } 66 | 67 | fun build_args_windows(argc: u64, argv_wide: **u16) [][]u8 { 68 | if (argv_wide == nil) { 69 | ret [][]u8{ nil, 0 }; 70 | } 71 | 72 | val entry_size: u64 = $size_of([]u8); 73 | val total_bytes: u64 = argc * entry_size; 74 | val storage: *u8 = mem.allocate(total_bytes); 75 | if (storage == nil) { 76 | ret [][]u8{ nil, 0 }; 77 | } 78 | 79 | var index: u64 = 0; 80 | for (index < argc) { 81 | val wide_arg: *u16 = @(argv_wide + index); 82 | val utf8: []u8 = utf16_to_utf8(wide_arg); 83 | 84 | val slot_offset: u64 = index * entry_size; 85 | val slot_addr: *u8 = storage + slot_offset; 86 | 87 | val data_slot: **u8 = slot_addr::**u8; 88 | @data_slot = utf8.data; 89 | 90 | val length_addr: *u8 = slot_addr + WORD_BYTES; 91 | val length_slot: *u64 = length_addr::*u64; 92 | @length_slot = utf8.len; 93 | 94 | index = index + 1; 95 | } 96 | 97 | ret [][]u8{ storage::*[]u8, argc }; 98 | } 99 | 100 | fun collect_args_windows() [][]u8 { 101 | val cmd_line: *u16 = win32_get_command_line_w(); 102 | var argc32: i32 = 0; 103 | val argv_wide: **u16 = win32_command_line_to_argv_w(cmd_line, (?argc32)::*i32); 104 | 105 | if (argv_wide == nil || argc32 <= 0) { 106 | if (argv_wide != nil) { 107 | win32_local_free(argv_wide::*u8); 108 | } 109 | ret [][]u8{ nil, 0 }; 110 | } 111 | 112 | val argc: u64 = argc32::u64; 113 | val args: [][]u8 = build_args_windows(argc, argv_wide); 114 | win32_local_free(argv_wide::*u8); 115 | ret args; 116 | } 117 | 118 | $mach_runtime_entry.symbol = "mach_runtime_entry"; 119 | fun mach_runtime_entry() { 120 | val args: [][]u8 = collect_args_windows(); 121 | val exit_code: i64 = main(args); 122 | mach_runtime_exit(exit_code); 123 | } 124 | 125 | asm { 126 | .text 127 | .globl _start 128 | _start: 129 | and $-16, %rsp 130 | sub $32, %rsp 131 | call mach_runtime_entry 132 | hlt 133 | ; 134 | .globl mach_runtime_exit 135 | mach_runtime_exit: 136 | mov %rcx, %rcx 137 | call win32_exit_process 138 | hlt 139 | } 140 | 141 | } 142 | 143 | $if (OS == OS_LINUX || OS == OS_DARWIN) { 144 | 145 | fun cstring_length(s: *u8) u64 { 146 | if (s == nil) { 147 | ret 0; 148 | } 149 | 150 | var length: u64 = 0; 151 | var cursor: *u8 = s; 152 | for { 153 | if (@cursor == 0::u8) { 154 | brk; 155 | } 156 | length = length + 1; 157 | cursor = cursor + 1; 158 | } 159 | 160 | ret length; 161 | } 162 | 163 | fun build_args_posix(argc: u64, stack: *u8) [][]u8 { 164 | if (argc == 0) { 165 | ret [][]u8{ nil, 0 }; 166 | } 167 | 168 | val entry_size: u64 = $size_of([]u8); 169 | val total_bytes: u64 = argc * entry_size; 170 | val storage: *u8 = mem.allocate(total_bytes); 171 | if (storage == nil) { 172 | ret [][]u8{ nil, 0 }; 173 | } 174 | 175 | val argv_base: *u8 = stack + WORD_BYTES; 176 | 177 | var index: u64 = 0; 178 | for (index < argc) { 179 | val offset: u64 = index * WORD_BYTES; 180 | val addr: *u8 = argv_base + offset; 181 | val raw: u64 = @(addr::*u64); 182 | val arg_ptr: *u8 = raw::*u8; 183 | val length: u64 = cstring_length(arg_ptr); 184 | 185 | val slot_offset: u64 = index * entry_size; 186 | val slot_addr: *u8 = storage + slot_offset; 187 | 188 | val data_slot: **u8 = slot_addr::**u8; 189 | @data_slot = arg_ptr; 190 | 191 | val length_addr: *u8 = slot_addr + WORD_BYTES; 192 | val length_slot: *u64 = length_addr::*u64; 193 | @length_slot = length; 194 | 195 | index = index + 1; 196 | } 197 | 198 | ret [][]u8{ storage::*[]u8, argc }; 199 | } 200 | 201 | $mach_runtime_entry.symbol = "mach_runtime_entry"; 202 | fun mach_runtime_entry(stack: *u8) i64 { 203 | val argc: u64 = @(stack::*u64); 204 | val args: [][]u8 = build_args_posix(argc, stack); 205 | ret main(args); 206 | } 207 | 208 | $if (OS == OS_LINUX) { 209 | 210 | asm { 211 | .text 212 | .globl _start 213 | _start: 214 | mov %rsp, %rdi 215 | call mach_runtime_entry 216 | mov %rax, %rdi 217 | call mach_runtime_exit 218 | hlt 219 | ; 220 | .globl mach_runtime_exit 221 | mach_runtime_exit: 222 | mov $60, %rax 223 | syscall 224 | hlt 225 | }; 226 | 227 | } 228 | 229 | $if (OS == OS_DARWIN) { 230 | 231 | asm { 232 | .text 233 | .globl _start 234 | _start: 235 | mov %rsp, %rdi 236 | call mach_runtime_entry 237 | mov %rax, %rdi 238 | call mach_runtime_exit 239 | hlt 240 | ; 241 | .globl mach_runtime_exit 242 | mach_runtime_exit: 243 | mov $0x2000001, %rax 244 | syscall 245 | hlt 246 | }; 247 | 248 | } 249 | 250 | } 251 | 252 | $abort.symbol = "abort"; 253 | pub fun abort() i64 { 254 | mach_runtime_exit(255); 255 | ret 255; 256 | } 257 | -------------------------------------------------------------------------------- /std/system/env.mach: -------------------------------------------------------------------------------- 1 | use std.types.string; 2 | 3 | var env_blob_data: *u8; 4 | var env_blob_len: u64; 5 | var env_loaded: u8; 6 | 7 | $if (OS == OS_LINUX) { 8 | use fs: std.io.fs; 9 | 10 | fun load_env() u8 { 11 | val blob: []u8 = fs.fs_read_all("/proc/self/environ"); 12 | 13 | if (blob.data == nil || blob.len == 0) { 14 | env_blob_data = nil; 15 | env_blob_len = 0; 16 | ret 0; 17 | } 18 | 19 | env_blob_data = blob.data; 20 | env_blob_len = blob.len; 21 | ret 1; 22 | } 23 | } 24 | 25 | $if (OS == OS_DARWIN) { 26 | use mem: std.system.memory; 27 | 28 | ext "C:_NSGetEnviron" darwin_ns_get_environ: fun() **u8; 29 | 30 | fun copy_c_string(src: *u8) u64 { 31 | var len: u64 = 0; 32 | for { 33 | if (@(src + len) == 0) { 34 | ret len; 35 | } 36 | len = len + 1; 37 | } 38 | 39 | ret len; 40 | } 41 | 42 | fun load_env() u8 { 43 | val envp_ptr: **u8 = darwin_ns_get_environ(); 44 | if (envp_ptr == nil) { 45 | ret 0; 46 | } 47 | 48 | var idx: u64 = 0; 49 | var total: u64 = 0; 50 | 51 | for { 52 | val entry: *u8 = @(envp_ptr + idx); 53 | if (entry == nil) { 54 | brk; 55 | } 56 | total = total + copy_c_string(entry) + 1; 57 | idx = idx + 1; 58 | } 59 | 60 | if (total == 0) { 61 | env_blob_data = nil; 62 | env_blob_len = 0; 63 | ret 1; 64 | } 65 | 66 | val buf: *u8 = mem.allocate(total); 67 | if (buf == nil) { 68 | ret 0; 69 | } 70 | 71 | idx = 0; 72 | var offset: u64 = 0; 73 | for { 74 | val entry: *u8 = @(envp_ptr + idx); 75 | if (entry == nil) { 76 | brk; 77 | } 78 | 79 | val len: u64 = copy_c_string(entry); 80 | mem.raw_copy(buf + offset, entry, len); 81 | @(buf + offset + len) = 0; 82 | offset = offset + len + 1; 83 | idx = idx + 1; 84 | } 85 | 86 | env_blob_data = buf; 87 | env_blob_len = offset; 88 | ret 1; 89 | } 90 | } 91 | 92 | $if (OS == OS_WINDOWS) { 93 | use mem: std.system.memory; 94 | use sys: std.system.platform.windows.sys; 95 | 96 | fun load_env() u8 { 97 | val wide: *u16 = sys.get_environment_strings(); 98 | if (wide == nil) { 99 | ret 0; 100 | } 101 | 102 | var units: u64 = 0; 103 | var bytes: u64 = 0; 104 | var consecutive_zero: u8 = 0; 105 | 106 | for { 107 | val ch: u16 = @(wide + units); 108 | if (ch == 0) { 109 | bytes = bytes + 1; 110 | if (consecutive_zero != 0) { 111 | units = units + 1; 112 | brk; 113 | } 114 | consecutive_zero = 1; 115 | } 116 | or { 117 | if (ch >= 128) { 118 | sys.free_environment_strings(wide); 119 | ret 0; 120 | } 121 | bytes = bytes + 1; 122 | consecutive_zero = 0; 123 | } 124 | units = units + 1; 125 | } 126 | 127 | if (bytes == 0) { 128 | sys.free_environment_strings(wide); 129 | env_blob_data = nil; 130 | env_blob_len = 0; 131 | ret 1; 132 | } 133 | 134 | val buf: *u8 = mem.allocate(bytes); 135 | if (buf == nil) { 136 | sys.free_environment_strings(wide); 137 | ret 0; 138 | } 139 | 140 | var src: u64 = 0; 141 | var dst: u64 = 0; 142 | consecutive_zero = 0; 143 | for (src < units) { 144 | val ch: u16 = @(wide + src); 145 | src = src + 1; 146 | 147 | if (ch == 0) { 148 | @(buf + dst) = 0; 149 | dst = dst + 1; 150 | if (consecutive_zero != 0) { 151 | brk; 152 | } 153 | consecutive_zero = 1; 154 | } 155 | or { 156 | @(buf + dst) = ch :: u8; 157 | dst = dst + 1; 158 | consecutive_zero = 0; 159 | } 160 | } 161 | 162 | sys.free_environment_strings(wide); 163 | 164 | env_blob_data = buf; 165 | env_blob_len = dst; 166 | ret 1; 167 | } 168 | } 169 | 170 | fun ensure_env_blob() u8 { 171 | if (env_loaded != 0) { 172 | if (env_blob_data != nil && env_blob_len != 0) { 173 | ret 1; 174 | } 175 | 176 | ret 0; 177 | } 178 | 179 | env_loaded = 1; 180 | ret load_env(); 181 | } 182 | 183 | fun find_value(name: string) []u8 { 184 | if (env_blob_data == nil || env_blob_len == 0) { 185 | ret []u8{ nil, 0 }; 186 | } 187 | 188 | val target_len: u64 = name.len; 189 | val data: *u8 = env_blob_data; 190 | val total: u64 = env_blob_len; 191 | 192 | var pos: u64 = 0; 193 | for (pos < total) { 194 | val segment_start: u64 = pos; 195 | 196 | for (pos < total && @(data + pos) != 0) { 197 | pos = pos + 1; 198 | } 199 | 200 | val segment_end: u64 = pos; 201 | val segment_len: u64 = segment_end - segment_start; 202 | 203 | if (segment_len > target_len) { 204 | var match: u8 = 1; 205 | var idx: u64 = 0; 206 | 207 | for (idx < target_len) { 208 | val a: u8 = @(data + segment_start + idx); 209 | val b: u8 = @(name.data + idx); 210 | 211 | if (a != b) { 212 | match = 0; 213 | brk; 214 | } 215 | 216 | idx = idx + 1; 217 | } 218 | 219 | if (match == 1 && (segment_start + target_len) < segment_end && @(data + segment_start + target_len) == '=') { 220 | val value_start: u64 = segment_start + target_len + 1; 221 | val value_len: u64 = segment_len - target_len - 1; 222 | 223 | ret []u8{ data + value_start, value_len }; 224 | } 225 | } 226 | 227 | if (pos >= total) { 228 | brk; 229 | } 230 | 231 | pos = pos + 1; 232 | } 233 | 234 | ret []u8{ nil, 0 }; 235 | } 236 | 237 | pub fun env_get(name: string) []u8 { 238 | if (name.len == 0) { 239 | ret []u8{ nil, 0 }; 240 | } 241 | 242 | if (ensure_env_blob() == 0) { 243 | ret []u8{ nil, 0 }; 244 | } 245 | 246 | ret find_value(name); 247 | } 248 | -------------------------------------------------------------------------------- /std/system/memory.mach: -------------------------------------------------------------------------------- 1 | $if (OS == OS_LINUX) { 2 | use sys: std.system.platform.linux.sys; 3 | } 4 | 5 | $if (OS == OS_DARWIN) { 6 | use sys: std.system.platform.darwin.sys; 7 | } 8 | 9 | $if (OS == OS_WINDOWS) { 10 | use sys: std.system.platform.windows.sys; 11 | } 12 | 13 | $memset.symbol = "memset"; 14 | pub fun memset(dest: ptr, value: i32, count: u64) ptr { 15 | if (dest == nil || count == 0) { 16 | ret dest; 17 | } 18 | 19 | var i: u64 = 0; 20 | val byte: u8 = (value :: u8); 21 | val bytes: *u8 = (dest :: *u8); 22 | for (i < count) { 23 | @(bytes + i) = byte; 24 | i = i + 1; 25 | } 26 | 27 | ret dest; 28 | } 29 | 30 | $memcpy.symbol = "memcpy"; 31 | pub fun memcpy(dst: ptr, src: ptr, count: u64) ptr { 32 | if (dst == nil || src == nil || count == 0) { 33 | ret dst; 34 | } 35 | 36 | var i: u64 = 0; 37 | val dst_bytes: *u8 = (dst :: *u8); 38 | val src_bytes: *u8 = (src :: *u8); 39 | for (i < count) { 40 | val b: u8 = @(src_bytes + i); 41 | @(dst_bytes + i) = b; 42 | i = i + 1; 43 | } 44 | 45 | ret dst; 46 | } 47 | 48 | pub fun raw_fill(p: ptr, value: u8, size: u64) { 49 | if (p == nil || size == 0) { 50 | ret; 51 | } 52 | 53 | var i: u64 = 0; 54 | val bytes: *u8 = (p :: *u8); 55 | for (i < size) { 56 | @(bytes + i) = value; 57 | i = i + 1; 58 | } 59 | } 60 | 61 | pub fun raw_copy(dst: ptr, src: ptr, size: u64) { 62 | if (dst == nil || src == nil || size == 0) { 63 | ret; 64 | } 65 | 66 | var i: u64 = 0; 67 | val dst_bytes: *u8 = (dst :: *u8); 68 | val src_bytes: *u8 = (src :: *u8); 69 | for (i < size) { 70 | val b: u8 = @(src_bytes + i); 71 | @(dst_bytes + i) = b; 72 | i = i + 1; 73 | } 74 | } 75 | 76 | pub fun raw_zero(p: ptr, size: u64) { 77 | raw_fill(p, 0, size); 78 | } 79 | 80 | $if ((OS == OS_LINUX) || (OS == OS_DARWIN)) { 81 | pub val SYSTEM_PROT_READ: i32 = 1; 82 | pub val SYSTEM_PROT_WRITE: i32 = 2; 83 | pub val SYSTEM_MAP_PRIVATE: i32 = 2; 84 | pub val SYSTEM_MAP_ANON: i32 = 32; # MAP_ANONYMOUS 85 | 86 | pub fun allocate(size: u64) ptr { 87 | if (size == 0) { 88 | ret nil; 89 | } 90 | 91 | ret (sys.mmap(nil, size, SYSTEM_PROT_READ | SYSTEM_PROT_WRITE, SYSTEM_MAP_PRIVATE | SYSTEM_MAP_ANON, -1, 0) :: ptr); 92 | } 93 | 94 | pub fun deallocate(p: ptr, size: u64) u8 { 95 | if (p == nil || size == 0) { 96 | ret 0; 97 | } 98 | 99 | val r: i32 = sys.munmap((p :: *u8), size); 100 | if (r == 0) { 101 | ret 0; 102 | } 103 | 104 | ret 1; 105 | } 106 | 107 | pub fun reallocate(p: ptr, old_size: u64, new_size: u64) ptr { 108 | if (p == nil) { 109 | ret allocate(new_size); 110 | } 111 | 112 | if (new_size == 0) { 113 | deallocate(p, old_size); 114 | ret nil; 115 | } 116 | 117 | val n: ptr = allocate(new_size); 118 | if (n == nil) { 119 | ret nil; 120 | } 121 | 122 | var to_copy: u64 = old_size; 123 | if (new_size < to_copy) { 124 | to_copy = new_size; 125 | } 126 | 127 | raw_copy(n, p, to_copy); 128 | deallocate(p, old_size); 129 | 130 | ret n; 131 | } 132 | } 133 | 134 | $if (OS == OS_WINDOWS) { 135 | pub fun allocate(size: u64) ptr { 136 | if (size == 0) { 137 | ret nil; 138 | } 139 | 140 | ret (sys.virt_alloc(size) :: ptr); 141 | } 142 | 143 | pub fun deallocate(p: ptr, size: u64) u8 { 144 | if (p == nil || size == 0) { 145 | ret 0; 146 | } 147 | 148 | val r: u32 = sys.virt_free((p :: *u8), size); 149 | if (r != 0) { 150 | ret 0; 151 | } 152 | 153 | ret 1; 154 | } 155 | 156 | pub fun reallocate(p: ptr, old_size: u64, new_size: u64) ptr { 157 | if (p == nil) { 158 | ret allocate(new_size); 159 | } 160 | 161 | if (new_size == 0) { 162 | deallocate(p, old_size); 163 | ret nil; 164 | } 165 | 166 | val n: ptr = allocate(new_size); 167 | if (n == nil) { 168 | ret nil; 169 | } 170 | 171 | var to_copy: u64 = old_size; 172 | if (new_size < to_copy) { 173 | to_copy = new_size; 174 | } 175 | 176 | raw_copy(n, p, to_copy); 177 | deallocate(p, old_size); 178 | 179 | ret n; 180 | } 181 | } 182 | 183 | pub fun alloc[T](count: u64) *T { 184 | if (count == 0) { 185 | ret nil; 186 | } 187 | 188 | val size: u64 = $size_of(T); 189 | val total: u64 = size * count; 190 | if (size != 0 && total / size != count) { 191 | ret nil; 192 | } 193 | 194 | ret (allocate(total) :: *T); 195 | } 196 | 197 | pub fun dealloc[T](p: *T, count: u64) u8 { 198 | if (p == nil || count == 0) { 199 | ret 0; 200 | } 201 | 202 | val size: u64 = $size_of(T); 203 | val total: u64 = size * count; 204 | ret deallocate((p :: ptr), total); 205 | } 206 | 207 | pub fun realloc[T](p: *T, old_count: u64, new_count: u64) *T { 208 | if (p == nil) { 209 | ret alloc[T](new_count); 210 | } 211 | 212 | if (new_count == 0) { 213 | dealloc[T](p, old_count); 214 | ret nil; 215 | } 216 | 217 | val size: u64 = $size_of(T); 218 | val old_total: u64 = size * old_count; 219 | val new_total: u64 = size * new_count; 220 | 221 | if (size != 0 && new_total / size != new_count) { 222 | ret nil; 223 | } 224 | 225 | ret (reallocate((p :: ptr), old_total, new_total) :: *T); 226 | } 227 | 228 | 229 | pub fun fill[T](p: *T, value: T, count: u64) { 230 | raw_fill((p :: ptr), (value :: u8), count * $size_of(T)); 231 | } 232 | 233 | pub fun copy[T](dst: *T, src: *T, count: u64) { 234 | raw_copy((dst :: ptr), (src :: ptr), count * $size_of(T)); 235 | } 236 | 237 | pub fun zero[T](p: *T, count: u64) { 238 | raw_zero((p :: ptr), count * $size_of(T)); 239 | } 240 | -------------------------------------------------------------------------------- /std/system/platform/darwin/sys.mach: -------------------------------------------------------------------------------- 1 | use std.system.platform.darwin.syscall; 2 | 3 | pub val SYS_READ: u64 = 0x2000003; 4 | pub val SYS_WRITE: u64 = 0x2000004; 5 | pub val SYS_OPEN: u64 = 0x2000005; 6 | pub val SYS_CLOSE: u64 = 0x2000006; 7 | pub val SYS_MMAP: u64 = 0x20000c5; 8 | pub val SYS_MUNMAP: u64 = 0x2000049; 9 | pub val SYS_OPENAT: u64 = 0x20001cf; 10 | pub val SYS_CLOCK_GETTIME: u64 = 0x2000126; 11 | 12 | pub val AT_FDCWD: i32 = -100; 13 | 14 | pub fun read(fd: i32, buf: *u8, count: u64) i64 { 15 | ret syscall3(SYS_READ, (fd :: u64), (buf :: u64), count); 16 | } 17 | 18 | pub fun write(fd: i32, buf: *u8, count: u64) i64 { 19 | ret syscall3(SYS_WRITE, (fd :: u64), (buf :: u64), count); 20 | } 21 | 22 | pub fun open(path: *u8, flags: i32, mode: i32) i64 { 23 | ret syscall3(SYS_OPEN, (path :: u64), (flags :: u64), (mode :: u64)); 24 | } 25 | 26 | pub fun openat(dirfd: i32, path: *u8, flags: i32, mode: i32) i64 { 27 | ret syscall4(SYS_OPENAT, (dirfd :: u64), (path :: u64), (flags :: u64), (mode :: u64)); 28 | } 29 | 30 | pub fun close(fd: i32) i32 { 31 | val res: i64 = syscall1(SYS_CLOSE, (fd :: u64)); 32 | ret res :: i32; 33 | } 34 | 35 | pub fun mmap(addr: *u8, length: u64, prot: i32, flags: i32, fd: i32, offset: i64) *u8 { 36 | val res: i64 = syscall6( 37 | SYS_MMAP, 38 | (addr :: u64), 39 | length, 40 | (prot :: u64), 41 | (flags :: u64), 42 | (fd :: u64), 43 | (offset :: u64) 44 | ); 45 | 46 | if (res < 0) { 47 | ret nil; 48 | } 49 | 50 | ret (res :: u64) :: *u8; 51 | } 52 | 53 | pub fun munmap(addr: *u8, length: u64) i32 { 54 | val res: i64 = syscall2(SYS_MUNMAP, (addr :: u64), length); 55 | if (res < 0) { 56 | ret -1; 57 | } 58 | ret 0; 59 | } 60 | 61 | pub fun clock_gettime(clock_id: i32, ts: *u8) i32 { 62 | val res: i64 = syscall2(SYS_CLOCK_GETTIME, (clock_id :: u64), (ts :: u64)); 63 | 64 | if (res < 0) { 65 | ret -1; 66 | } 67 | 68 | ret 0; 69 | } 70 | -------------------------------------------------------------------------------- /std/system/platform/darwin/syscall.mach: -------------------------------------------------------------------------------- 1 | asm { 2 | .text 3 | .globl syscall0 4 | syscall0: 5 | mov %rdi, %rax 6 | syscall 7 | ret 8 | 9 | .globl syscall1 10 | syscall1: 11 | mov %rdi, %rax 12 | mov %rsi, %rdi 13 | syscall 14 | ret 15 | 16 | .globl syscall2 17 | syscall2: 18 | mov %rdi, %rax 19 | mov %rsi, %rdi 20 | mov %rdx, %rsi 21 | syscall 22 | ret 23 | 24 | .globl syscall3 25 | syscall3: 26 | mov %rdi, %rax 27 | mov %rsi, %rdi 28 | mov %rdx, %rsi 29 | mov %rcx, %rdx 30 | syscall 31 | ret 32 | 33 | .globl syscall4 34 | syscall4: 35 | mov %rdi, %rax 36 | mov %rsi, %rdi 37 | mov %rdx, %rsi 38 | mov %rcx, %rdx 39 | mov %r8, %r10 40 | syscall 41 | ret 42 | 43 | .globl syscall5 44 | syscall5: 45 | mov %rdi, %rax 46 | mov %rsi, %rdi 47 | mov %rdx, %rsi 48 | mov %rcx, %rdx 49 | mov %r8, %r10 50 | mov %r9, %r8 51 | syscall 52 | ret 53 | 54 | .globl syscall6 55 | syscall6: 56 | mov %rdi, %rax 57 | mov %rsi, %rdi 58 | mov %rdx, %rsi 59 | mov %rcx, %rdx 60 | mov %r8, %r10 61 | mov %r9, %r8 62 | mov 8(%rsp), %r9 63 | syscall 64 | ret 65 | }; 66 | 67 | pub ext "syscall0" syscall0: fun(u64) i64; 68 | pub ext "syscall1" syscall1: fun(u64, u64) i64; 69 | pub ext "syscall2" syscall2: fun(u64, u64, u64) i64; 70 | pub ext "syscall3" syscall3: fun(u64, u64, u64, u64) i64; 71 | pub ext "syscall4" syscall4: fun(u64, u64, u64, u64, u64) i64; 72 | pub ext "syscall5" syscall5: fun(u64, u64, u64, u64, u64, u64) i64; 73 | pub ext "syscall6" syscall6: fun(u64, u64, u64, u64, u64, u64, u64) i64; 74 | -------------------------------------------------------------------------------- /std/system/platform/linux/sys.mach: -------------------------------------------------------------------------------- 1 | use std.system.platform.linux.syscall; 2 | 3 | pub val SYS_READ: u64 = 0; 4 | pub val SYS_WRITE: u64 = 1; 5 | pub val SYS_OPENAT: u64 = 257; 6 | pub val SYS_CLOSE: u64 = 3; 7 | pub val SYS_MMAP: u64 = 9; 8 | pub val SYS_MUNMAP: u64 = 11; 9 | pub val SYS_CLOCK_GETTIME: u64 = 228; 10 | 11 | pub val AT_FDCWD: i32 = -100; 12 | 13 | fun sext_i32(value: i32) u64 { 14 | ret (value::i64)::u64; 15 | } 16 | 17 | pub fun read(fd: i32, buf: *u8, count: u64) i64 { 18 | ret syscall3(SYS_READ, sext_i32(fd), (buf::u64), count); 19 | } 20 | 21 | pub fun write(fd: i32, buf: *u8, count: u64) i64 { 22 | ret syscall3(SYS_WRITE, sext_i32(fd), (buf::u64), count); 23 | } 24 | 25 | pub fun openat(dirfd: i32, path: *u8, flags: i32, mode: i32) i64 { 26 | ret syscall4(SYS_OPENAT, sext_i32(dirfd), (path::u64), (flags::u64), (mode::u64)); 27 | } 28 | 29 | pub fun close(fd: i32) i64 { 30 | ret syscall1(SYS_CLOSE, sext_i32(fd)); 31 | } 32 | 33 | pub fun mmap(addr: *u8, length: u64, prot: i32, flags: i32, fd: i32, offset: i64) *u8 { 34 | val res: i64 = syscall6( 35 | SYS_MMAP, 36 | (addr::u64), 37 | length, 38 | (prot::u64), 39 | (flags::u64), 40 | sext_i32(fd), 41 | (offset::u64) 42 | ); 43 | 44 | if (res < 0) { 45 | ret nil; 46 | } 47 | 48 | ret (res::u64)::*u8; 49 | } 50 | 51 | pub fun munmap(addr: *u8, length: u64) i32 { 52 | val res: i64 = syscall2(SYS_MUNMAP, (addr::u64), length); 53 | 54 | if (res < 0) { 55 | ret -1; 56 | } 57 | 58 | ret 0; 59 | } 60 | 61 | pub fun clock_gettime(clock_id: i32, ts: *u8) i32 { 62 | val res: i64 = syscall2(SYS_CLOCK_GETTIME, (clock_id::u64), (ts::u64)); 63 | 64 | if (res < 0) { 65 | ret -1; 66 | } 67 | 68 | ret (res::i32); 69 | } 70 | -------------------------------------------------------------------------------- /std/system/platform/linux/syscall.mach: -------------------------------------------------------------------------------- 1 | asm { 2 | .text 3 | .globl syscall0 4 | syscall0: 5 | mov %rdi, %rax 6 | syscall 7 | ret 8 | 9 | .globl syscall1 10 | syscall1: 11 | mov %rdi, %rax 12 | mov %rsi, %rdi 13 | syscall 14 | ret 15 | 16 | .globl syscall2 17 | syscall2: 18 | mov %rdi, %rax 19 | mov %rsi, %rdi 20 | mov %rdx, %rsi 21 | syscall 22 | ret 23 | 24 | .globl syscall3 25 | syscall3: 26 | mov %rdi, %rax 27 | mov %rsi, %rdi 28 | mov %rdx, %rsi 29 | mov %rcx, %rdx 30 | syscall 31 | ret 32 | 33 | .globl syscall4 34 | syscall4: 35 | mov %rdi, %rax 36 | mov %rsi, %rdi 37 | mov %rdx, %rsi 38 | mov %rcx, %rdx 39 | mov %r8, %r10 40 | syscall 41 | ret 42 | 43 | .globl syscall5 44 | syscall5: 45 | mov %rdi, %rax 46 | mov %rsi, %rdi 47 | mov %rdx, %rsi 48 | mov %rcx, %rdx 49 | mov %r8, %r10 50 | mov %r9, %r8 51 | syscall 52 | ret 53 | 54 | .globl syscall6 55 | syscall6: 56 | mov %rdi, %rax 57 | mov %rsi, %rdi 58 | mov %rdx, %rsi 59 | mov %rcx, %rdx 60 | mov %r8, %r10 61 | mov %r9, %r8 62 | mov 8(%rsp), %r9 63 | syscall 64 | ret 65 | }; 66 | 67 | pub ext "syscall0" syscall0: fun(u64) i64; 68 | pub ext "syscall1" syscall1: fun(u64, u64) i64; 69 | pub ext "syscall2" syscall2: fun(u64, u64, u64) i64; 70 | pub ext "syscall3" syscall3: fun(u64, u64, u64, u64) i64; 71 | pub ext "syscall4" syscall4: fun(u64, u64, u64, u64, u64) i64; 72 | pub ext "syscall5" syscall5: fun(u64, u64, u64, u64, u64, u64) i64; 73 | pub ext "syscall6" syscall6: fun(u64, u64, u64, u64, u64, u64, u64) i64; 74 | -------------------------------------------------------------------------------- /std/system/platform/windows/sys.mach: -------------------------------------------------------------------------------- 1 | use target; 2 | 3 | pub val MEM_COMMIT: u32 = 0x00001000; 4 | pub val MEM_RESERVE: u32 = 0x00002000; 5 | pub val MEM_RELEASE: u32 = 0x00008000; 6 | pub val PAGE_READWRITE: u32 = 0x00000004; 7 | 8 | pub val GENERIC_READ: u32 = 0x80000000; 9 | pub val GENERIC_WRITE: u32 = 0x40000000; 10 | 11 | pub val FILE_SHARE_READ: u32 = 0x00000001; 12 | pub val FILE_SHARE_WRITE: u32 = 0x00000002; 13 | 14 | pub val OPEN_EXISTING: u32 = 3; 15 | pub val CREATE_ALWAYS: u32 = 2; 16 | pub val FILE_ATTRIBUTE_NORMAL: u32 = 0x00000080; 17 | 18 | pub val STD_INPUT_HANDLE: i32 = -10; 19 | pub val STD_OUTPUT_HANDLE: i32 = -11; 20 | pub val STD_ERROR_HANDLE: i32 = -12; 21 | 22 | ext "C:VirtualAlloc" win32_virtual_alloc: fun(*u8, u64, u32, u32) *u8; 23 | ext "C:VirtualFree" win32_virtual_free: fun(*u8, u64, u32) u32; 24 | ext "C:CreateFileW" win32_create_file_w: fun(*u16, u32, u32, *u8, u32, u32, *u8) *u8; 25 | ext "C:ReadFile" win32_read_file: fun(*u8, *u8, u32, *u32, *u8) u32; 26 | ext "C:WriteFile" win32_write_file: fun(*u8, *u8, u32, *u32, *u8) u32; 27 | ext "C:CloseHandle" win32_close_handle: fun(*u8) u32; 28 | ext "C:GetFileSizeEx" win32_get_file_size_ex: fun(*u8, *i64) u32; 29 | ext "C:GetStdHandle" win32_get_std_handle: fun(u32) *u8; 30 | ext "C:GetEnvironmentStringsW" win32_get_environment_strings_w: fun() *u16; 31 | ext "C:FreeEnvironmentStringsW" win32_free_environment_strings_w: fun(*u16) u32; 32 | ext "C:QueryPerformanceCounter" win32_query_performance_counter: fun(*i64) u32; 33 | ext "C:QueryPerformanceFrequency" win32_query_performance_frequency: fun(*i64) u32; 34 | 35 | pub fun virt_alloc(size: u64) *u8 { 36 | if (size == 0) { 37 | ret nil; 38 | } 39 | 40 | ret win32_virtual_alloc(nil, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 41 | } 42 | 43 | pub fun virt_free(ptr: *u8, size: u64) u32 { 44 | if (ptr == nil) { 45 | ret 0; 46 | } 47 | 48 | # VirtualFree ignores size when MEM_RELEASE is used; pass 0 per WinAPI requirements. 49 | ret win32_virtual_free(ptr, 0, MEM_RELEASE); 50 | } 51 | 52 | pub fun create_file_read(path: *u16) *u8 { 53 | if (path == nil) { 54 | ret nil; 55 | } 56 | 57 | ret win32_create_file_w( 58 | path, 59 | GENERIC_READ, 60 | FILE_SHARE_READ | FILE_SHARE_WRITE, 61 | nil, 62 | OPEN_EXISTING, 63 | FILE_ATTRIBUTE_NORMAL, 64 | nil 65 | ); 66 | } 67 | 68 | pub fun create_file_write(path: *u16, truncate: u8) *u8 { 69 | if (path == nil) { 70 | ret nil; 71 | } 72 | 73 | var disposition: u32 = OPEN_EXISTING; 74 | if (truncate != 0) { 75 | disposition = CREATE_ALWAYS; 76 | } 77 | 78 | ret win32_create_file_w( 79 | path, 80 | GENERIC_WRITE, 81 | FILE_SHARE_READ | FILE_SHARE_WRITE, 82 | nil, 83 | disposition, 84 | FILE_ATTRIBUTE_NORMAL, 85 | nil 86 | ); 87 | } 88 | 89 | pub fun read_file(handle: *u8, buffer: *u8, size: u32, read: *u32) u32 { 90 | if (read != nil) { 91 | @read = 0; 92 | } 93 | 94 | if (handle == nil || buffer == nil) { 95 | ret 0; 96 | } 97 | 98 | var out_ptr: *u32 = read; 99 | if (read == nil) { 100 | out_ptr = nil; 101 | } 102 | 103 | ret win32_read_file(handle, buffer, size, out_ptr, nil); 104 | } 105 | 106 | pub fun write_file(handle: *u8, buffer: *u8, size: u32, written: *u32) u32 { 107 | if (written != nil) { 108 | @written = 0; 109 | } 110 | 111 | if (handle == nil || buffer == nil) { 112 | ret 0; 113 | } 114 | 115 | var out_ptr: *u32 = written; 116 | if (written == nil) { 117 | out_ptr = nil; 118 | } 119 | 120 | ret win32_write_file(handle, buffer, size, out_ptr, nil); 121 | } 122 | 123 | pub fun close_handle(handle: *u8) u32 { 124 | if (handle == nil) { 125 | ret 0; 126 | } 127 | 128 | ret win32_close_handle(handle); 129 | } 130 | 131 | pub fun get_file_size(handle: *u8, out: *i64) u32 { 132 | if (out != nil) { 133 | @out = 0; 134 | } 135 | 136 | if (handle == nil) { 137 | ret 0; 138 | } 139 | 140 | var size: i64 = 0; 141 | val ok: u32 = win32_get_file_size_ex(handle, (?size) :: *i64); 142 | if (ok == 0) { 143 | ret 0; 144 | } 145 | 146 | if (out != nil) { 147 | @out = size; 148 | } 149 | 150 | ret ok; 151 | } 152 | 153 | pub fun get_std_handle(which: i32) *u8 { 154 | ret win32_get_std_handle((which :: u32)); 155 | } 156 | 157 | pub fun free_environment_strings(ptr: *u16) u32 { 158 | if (ptr == nil) { 159 | ret 1; 160 | } 161 | 162 | ret win32_free_environment_strings_w(ptr); 163 | } 164 | 165 | pub fun get_environment_strings() *u16 { 166 | ret win32_get_environment_strings_w(); 167 | } 168 | 169 | pub fun query_performance_counter(out: *i64) u32 { 170 | if (out != nil) { 171 | @out = 0; 172 | } 173 | 174 | if (out == nil) { 175 | ret 0; 176 | } 177 | 178 | ret win32_query_performance_counter(out); 179 | } 180 | 181 | pub fun query_performance_frequency(out: *i64) u32 { 182 | if (out != nil) { 183 | @out = 0; 184 | } 185 | 186 | if (out == nil) { 187 | ret 0; 188 | } 189 | 190 | ret win32_query_performance_frequency(out); 191 | } 192 | -------------------------------------------------------------------------------- /std/system/time.mach: -------------------------------------------------------------------------------- 1 | $if (OS == OS_LINUX) { 2 | use sys: std.system.platform.linux.sys; 3 | } 4 | 5 | $if (OS == OS_DARWIN) { 6 | use sys: std.system.platform.darwin.sys; 7 | } 8 | 9 | $if (OS == OS_WINDOWS) { 10 | use sys: std.system.platform.windows.sys; 11 | } 12 | 13 | $if (OS != OS_WINDOWS) { 14 | rec Timespec { 15 | tv_sec: i64; 16 | tv_nsec: i64; 17 | } 18 | } 19 | 20 | pub val SECONDS: u64 = 1000000000; 21 | pub val MILLISECONDS: u64 = 1000000; 22 | pub val MICROSECONDS: u64 = 1000; 23 | pub val NANOSECONDS: u64 = 1; 24 | 25 | $if (OS == OS_LINUX) { 26 | 27 | pub val SYSTEM_CLOCK_MONOTONIC: i32 = 1; 28 | 29 | pub fun now() u64 { 30 | var ts: Timespec; 31 | ts.tv_sec = 0; 32 | ts.tv_nsec = 0; 33 | 34 | val r: i32 = sys.clock_gettime(SYSTEM_CLOCK_MONOTONIC, (?ts) :: *u8); 35 | if (r != 0) { 36 | ret 0; 37 | } 38 | 39 | ret (ts.tv_sec :: u64) * SECONDS + (ts.tv_nsec :: u64); 40 | } 41 | } 42 | 43 | $if (OS == OS_DARWIN) { 44 | 45 | pub val SYSTEM_CLOCK_MONOTONIC: i32 = 6; 46 | 47 | pub fun now() u64 { 48 | var ts: Timespec; 49 | ts.tv_sec = 0; 50 | ts.tv_nsec = 0; 51 | 52 | val r: i32 = sys.clock_gettime(SYSTEM_CLOCK_MONOTONIC, (?ts) :: *u8); 53 | if (r != 0) { 54 | ret 0; 55 | } 56 | 57 | ret (ts.tv_sec :: u64) * SECONDS + (ts.tv_nsec :: u64); 58 | } 59 | } 60 | 61 | $if (OS == OS_WINDOWS) { 62 | 63 | pub val SYSTEM_CLOCK_MONOTONIC: i32 = 1; 64 | 65 | var win_perf_freq: u64; 66 | var win_perf_ready: u8; 67 | 68 | fun ensure_win_perf() u8 { 69 | if (win_perf_ready != 0) { 70 | ret 1; 71 | } 72 | 73 | var freq: i64 = 0; 74 | val ok: u32 = sys.query_performance_frequency((?freq) :: *i64); 75 | if (ok == 0 || freq <= 0) { 76 | ret 0; 77 | } 78 | 79 | win_perf_freq = freq :: u64; 80 | win_perf_ready = 1; 81 | ret 1; 82 | } 83 | 84 | pub fun now() u64 { 85 | if (ensure_win_perf() == 0) { 86 | ret 0; 87 | } 88 | 89 | var counter: i64 = 0; 90 | val ok: u32 = sys.query_performance_counter((?counter) :: *i64); 91 | if (ok == 0 || counter < 0) { 92 | ret 0; 93 | } 94 | 95 | val value: u64 = counter :: u64; 96 | ret (value * SECONDS) / win_perf_freq; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /std/text/ascii.mach: -------------------------------------------------------------------------------- 1 | # ascii helpers 2 | pub fun is_space(b: u8) u8 { 3 | ret (b == 9 || b == 10 || b == 11 || b == 12 || b == 13 || b == 32); 4 | } 5 | 6 | pub fun is_digit(b: u8) u8 { 7 | ret (b >= '0' && b <= '9'); 8 | } 9 | 10 | pub fun is_alpha(b: u8) u8 { 11 | ret ((b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')); 12 | } 13 | 14 | pub fun is_alnum(b: u8) u8 { 15 | ret (is_alpha(b) || is_digit(b)); 16 | } 17 | 18 | pub fun to_lower(b: u8) u8 { 19 | if (b >= 'A' && b <= 'Z') { 20 | ret b + 32; 21 | } 22 | 23 | ret b; 24 | } 25 | 26 | pub fun to_upper(b: u8) u8 { 27 | if (b >= 'a' && b <= 'z') { 28 | ret b - 32; 29 | } 30 | 31 | ret b; 32 | } 33 | 34 | 35 | pub fun digit_val(b: u8) i32 { 36 | if (b >= '0' && b <= '9') { 37 | ret (b - '0')::i32; 38 | } 39 | 40 | val lo: u8 = to_lower(b); 41 | if (lo >= 'a' && lo <= 'z') { 42 | ret ((lo - 'a')::i32) + 10; 43 | } 44 | 45 | ret -1; 46 | } 47 | -------------------------------------------------------------------------------- /std/text/parse.mach: -------------------------------------------------------------------------------- 1 | use std.text.ascii; 2 | 3 | # integer parsing helpers (subset) 4 | pub fun parse_u64_dec(s: []u8) u64 { 5 | var i: u64 = 0; 6 | var v: u64 = 0; 7 | for (i < s.len) { 8 | val d: i32 = digit_val(s[i]); 9 | 10 | if (d < 0) { brk; } 11 | if (d > 9) { brk; } 12 | 13 | v = v * 10 + (d::u64); 14 | i = i + 1; 15 | } 16 | 17 | ret v; 18 | } 19 | 20 | pub fun parse_u64_hex(s: []u8) u64 { 21 | var i: u64 = 0; 22 | var v: u64 = 0; 23 | for (i < s.len) { 24 | val d: i32 = digit_val(s[i]); 25 | 26 | if (d < 0) { brk; } 27 | if (d > 15) { brk; } 28 | 29 | v = (v << 4) | (d::u64); 30 | i = i + 1; 31 | } 32 | 33 | ret v; 34 | } 35 | -------------------------------------------------------------------------------- /std/types/bool.mach: -------------------------------------------------------------------------------- 1 | # This is the first file created for the standard library. Cherish it. 2 | # 20251007 3 | pub def bool: u8; 4 | 5 | pub val true: bool = 1; 6 | pub val false: bool = 0; 7 | -------------------------------------------------------------------------------- /std/types/list.mach: -------------------------------------------------------------------------------- 1 | use std.types.bool; 2 | use std.types.result; 3 | use std.types.option; 4 | use mem: std.system.memory; 5 | 6 | pub rec List[T] { 7 | data: *T; 8 | len: u64; 9 | cap: u64; 10 | } 11 | 12 | pub fun list_new[T](cap: u64) Result[List[T], string] { 13 | if (cap == 0) { 14 | ret err[List[T], string]("capacity must be greater than zero"); 15 | } 16 | 17 | val data: *T = mem.alloc[T](cap); 18 | if (data == nil) { 19 | ret err[List[T], string]("failed to allocate memory for list"); 20 | } 21 | 22 | var list: List[T]; 23 | list.data = data; 24 | list.len = 0; 25 | list.cap = cap; 26 | ret ok[List[T], string](list); 27 | } 28 | 29 | pub fun list_from_slice[T](slice: []T) Result[List[T], string] { 30 | if (slice.len == 0 || slice.data == nil) { 31 | ret err[List[T], string]("slice is empty"); 32 | } 33 | 34 | val data: *T = mem.alloc[T](slice.len); 35 | if (data == nil) { 36 | ret err[List[T], string]("failed to allocate memory for list"); 37 | } 38 | 39 | mem.raw_copy((data :: ptr), (slice.data :: ptr), slice.len * $size_of(T)); 40 | var list: List[T] = List{ data: data, len: slice.len, cap: slice.len }; 41 | ret ok[List[T], string](list); 42 | } 43 | 44 | pub fun List[T].as_slice(this: List[T]) []T { 45 | ret []T{ this.data, this.len }; 46 | } 47 | 48 | pub fun List[T].is_empty(this: List[T]) bool { 49 | ret this.len == 0; 50 | } 51 | 52 | pub fun List[T].reserve(this: *List[T], additional: u64) Option[string] { 53 | val required: u64 = this.len + additional; 54 | if (required <= this.cap) { 55 | ret none[string](); 56 | } 57 | 58 | if (required < this.len) { 59 | ret none[string](); 60 | } 61 | 62 | var new_cap: u64 = this.cap; 63 | if (new_cap == 0) { new_cap = 8; } 64 | 65 | for (new_cap < required) { 66 | if (new_cap >= (0xffff_ffff_ffff_ffff / 2)) { 67 | new_cap = required; 68 | brk; 69 | } 70 | new_cap = new_cap * 2; 71 | } 72 | 73 | val new_data: *T = mem.realloc[T](this.data, this.cap, new_cap); 74 | if (new_data == nil) { 75 | ret some[string]("failed to reserve additional capacity"); 76 | } 77 | 78 | this.data = new_data; 79 | this.cap = new_cap; 80 | 81 | ret none[string](); 82 | } 83 | 84 | pub fun List[T].ensure_capacity(this: *List[T], min_capacity: u64) Option[string] { 85 | if (min_capacity <= this.cap) { 86 | ret none[string](); 87 | } 88 | 89 | ret this.reserve(min_capacity - this.cap); 90 | } 91 | 92 | pub fun List[T].shrink_to_fit(this: *List[T]) Option[string] { 93 | if (this.cap == this.len) { 94 | ret none[string](); 95 | } 96 | 97 | if (this.len == 0) { 98 | if (this.data != nil && this.cap > 0) { 99 | mem.dealloc[T](this.data, this.cap); 100 | } 101 | this.data = nil; 102 | this.len = 0; 103 | this.cap = 0; 104 | ret none[string](); 105 | } 106 | 107 | val new_data: *T = mem.realloc[T](this.data, this.cap, this.len); 108 | if (new_data == nil) { 109 | ret some[string]("failed to shrink to fit"); 110 | } 111 | 112 | this.data = new_data; 113 | this.cap = this.len; 114 | 115 | ret none[string](); 116 | } 117 | 118 | pub fun List[T].push(this: *List[T], item: T) Option[string] { 119 | if (this.len >= this.cap) { 120 | val res: Option[string] = this.reserve(1); 121 | if (res.is_some()) { 122 | ret res; 123 | } 124 | } 125 | 126 | @(this.data + this.len) = item; 127 | this.len = this.len + 1; 128 | 129 | ret none[string](); 130 | } 131 | 132 | pub fun List[T].pop(this: *List[T]) Option[T] { 133 | if (this.len == 0) { 134 | ret none[T](); 135 | } 136 | 137 | this.len = this.len - 1; 138 | 139 | ret some[T](@(this.data + this.len)); 140 | } 141 | 142 | pub fun List[T].append(this: *List[T], slice: []T) Option[string] { 143 | if (slice.len == 0 || slice.data == nil) { 144 | ret none[string](); 145 | } 146 | 147 | val res: Option[string] = this.reserve(slice.len); 148 | if (res.is_some()) { 149 | ret res; 150 | } 151 | 152 | if (this.len + slice.len < this.len) { 153 | ret some[string]("length overflow"); 154 | } 155 | 156 | if (this.len + slice.len > this.cap) { 157 | ret some[string]("capacity overflow"); 158 | } 159 | 160 | val dst: ptr = ((this.data + this.len) :: ptr); 161 | val src: ptr = (slice.data :: ptr); 162 | mem.raw_copy(dst, src, slice.len * $size_of(T)); 163 | 164 | this.len = this.len + slice.len; 165 | 166 | ret none[string](); 167 | } 168 | 169 | pub fun List[T].clear(this: *List[T]) { 170 | this.len = 0; 171 | } 172 | 173 | pub fun List[T].free(this: *List[T]) { 174 | if (this.data != nil && this.cap > 0) { 175 | mem.dealloc[T](this.data, this.cap); 176 | } 177 | this.data = nil; 178 | this.len = 0; 179 | this.cap = 0; 180 | } 181 | 182 | pub fun List[T].get(this: List[T], index: u64) Option[T] { 183 | if (index >= this.len) { 184 | ret none[T](); 185 | } 186 | 187 | ret some[T](@(this.data + index)); 188 | } 189 | 190 | pub fun List[T].set(this: *List[T], index: u64, item: T) Option[string] { 191 | if (index >= this.len) { 192 | ret some[string]("index out of bounds"); 193 | } 194 | 195 | @(this.data + index) = item; 196 | 197 | ret none[string](); 198 | } 199 | -------------------------------------------------------------------------------- /std/types/option.mach: -------------------------------------------------------------------------------- 1 | use std.types.bool; 2 | 3 | pub rec Option[T] { 4 | is_some: bool; 5 | value: T; 6 | } 7 | 8 | pub fun Option[T].unwrap(this: Option[T]) T { 9 | ret this.value; 10 | } 11 | 12 | pub fun Option[T].is_none(this: Option[T]) bool { 13 | ret this.is_some == false; 14 | } 15 | 16 | pub fun Option[T].is_some(this: Option[T]) bool { 17 | ret this.is_some == true; 18 | } 19 | 20 | pub fun some[T](value: T) Option[T] { 21 | var opt: Option[T]; 22 | opt.is_some = true; 23 | opt.value = value; 24 | ret opt; 25 | } 26 | 27 | pub fun none[T]() Option[T] { 28 | var opt: Option[T]; 29 | opt.is_some = false; 30 | ret opt; 31 | } 32 | -------------------------------------------------------------------------------- /std/types/result.mach: -------------------------------------------------------------------------------- 1 | use std.types.bool; 2 | 3 | pub rec Result[T, E] { 4 | tag: u32; 5 | data: uni { 6 | ok_value: T; 7 | err_value: E; 8 | }; 9 | } 10 | 11 | pub val TAG_OK: u32 = 0; 12 | pub val TAG_ERR: u32 = 1; 13 | 14 | pub fun Result[T, E].is_ok(this: Result[T, E]) bool { 15 | ret this.tag == TAG_OK; 16 | } 17 | 18 | pub fun Result[T, E].is_err(this: Result[T, E]) bool { 19 | ret this.tag == TAG_ERR; 20 | } 21 | 22 | pub fun Result[T, E].unwrap_ok(this: Result[T, E]) T { 23 | ret this.data.ok_value; 24 | } 25 | 26 | pub fun Result[T, E].unwrap_err(this: Result[T, E]) E { 27 | ret this.data.err_value; 28 | } 29 | 30 | pub fun ok[T, E](value: T) Result[T, E] { 31 | var res: Result[T, E]; 32 | res.tag = TAG_OK; 33 | res.data.ok_value = value; 34 | ret res; 35 | } 36 | 37 | pub fun err[T, E](value: E) Result[T, E] { 38 | var res: Result[T, E]; 39 | res.tag = TAG_ERR; 40 | res.data.err_value = value; 41 | ret res; 42 | } 43 | --------------------------------------------------------------------------------