From 55fc5790e43ac356787ffd1744201ec5c4152f05 Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 4 Feb 2026 01:34:30 +0200 Subject: [PATCH] so... jai :D --- .gitignore | 4 + LICENSE | 21 + build.zig | 90 + build.zig.zon | 81 + editors/vscode/.gitignore | 2 + editors/vscode/.vscodeignore | 3 + editors/vscode/language-configuration.json | 22 + editors/vscode/package-lock.json | 121 + editors/vscode/package.json | 68 + editors/vscode/src/extension.ts | 41 + editors/vscode/sx-lang-0.0.1.vsix | Bin 0 -> 466401 bytes editors/vscode/syntaxes/sx.tmLanguage.json | 208 + editors/vscode/tsconfig.json | 15 + examples/01-basic.sx | 5 + examples/02-stdout.sx | 4 + examples/03-structs.sx | 22 + examples/04-shadow.sx | 20 + examples/05-run.sx | 20 + examples/06-generic.sx | 17 + examples/07-defer.sx | 11 + examples/08-types.sx | 43 + examples/09-import.sx | 16 + examples/10-generic-struct.sx | 91 + examples/11-vector-math.sx | 28 + examples/12-meta.sx | 12 + examples/13-code.sx | 9 + examples/14-demo.sx | 15 + examples/15-while.sx | 50 + examples/16-union.sx | 48 + examples/17-lambda.sx | 9 + examples/18-conditions.sx | 23 + examples/19-varargs.sx | 35 + examples/20-any-varargs.sx | 47 + examples/21-categories.sx | 19 + examples/22-anytype.sx | 11 + examples/modules/math.sx | 17 + examples/modules/std.sx | 220 + examples/vision.sx | 98 + llvm_shim.c | 17 + readme.md | 40 + specs.md | 853 ++++ src/ast.zig | 326 ++ src/builtins.zig | 25 + src/codegen.zig | 4999 ++++++++++++++++++++ src/comptime.zig | 1753 +++++++ src/core.zig | 110 + src/errors.zig | 96 + src/imports.zig | 150 + src/lexer.zig | 403 ++ src/llvm_api.zig | 54 + src/lsp/document.zig | 48 + src/lsp/server.zig | 1776 +++++++ src/lsp/transport.zig | 75 + src/lsp/types.zig | 331 ++ src/main.zig | 158 + src/parser.zig | 1573 ++++++ src/root.zig | 19 + src/sema.zig | 1006 ++++ src/token.zig | 175 + src/types.zig | 323 ++ 60 files changed, 15876 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 build.zig create mode 100644 build.zig.zon create mode 100644 editors/vscode/.gitignore create mode 100644 editors/vscode/.vscodeignore create mode 100644 editors/vscode/language-configuration.json create mode 100644 editors/vscode/package-lock.json create mode 100644 editors/vscode/package.json create mode 100644 editors/vscode/src/extension.ts create mode 100644 editors/vscode/sx-lang-0.0.1.vsix create mode 100644 editors/vscode/syntaxes/sx.tmLanguage.json create mode 100644 editors/vscode/tsconfig.json create mode 100644 examples/01-basic.sx create mode 100644 examples/02-stdout.sx create mode 100644 examples/03-structs.sx create mode 100644 examples/04-shadow.sx create mode 100644 examples/05-run.sx create mode 100644 examples/06-generic.sx create mode 100644 examples/07-defer.sx create mode 100644 examples/08-types.sx create mode 100644 examples/09-import.sx create mode 100644 examples/10-generic-struct.sx create mode 100644 examples/11-vector-math.sx create mode 100644 examples/12-meta.sx create mode 100644 examples/13-code.sx create mode 100644 examples/14-demo.sx create mode 100644 examples/15-while.sx create mode 100644 examples/16-union.sx create mode 100644 examples/17-lambda.sx create mode 100644 examples/18-conditions.sx create mode 100644 examples/19-varargs.sx create mode 100644 examples/20-any-varargs.sx create mode 100644 examples/21-categories.sx create mode 100644 examples/22-anytype.sx create mode 100644 examples/modules/math.sx create mode 100644 examples/modules/std.sx create mode 100644 examples/vision.sx create mode 100644 llvm_shim.c create mode 100644 readme.md create mode 100644 specs.md create mode 100644 src/ast.zig create mode 100644 src/builtins.zig create mode 100644 src/codegen.zig create mode 100644 src/comptime.zig create mode 100644 src/core.zig create mode 100644 src/errors.zig create mode 100644 src/imports.zig create mode 100644 src/lexer.zig create mode 100644 src/llvm_api.zig create mode 100644 src/lsp/document.zig create mode 100644 src/lsp/server.zig create mode 100644 src/lsp/transport.zig create mode 100644 src/lsp/types.zig create mode 100644 src/main.zig create mode 100644 src/parser.zig create mode 100644 src/root.zig create mode 100644 src/sema.zig create mode 100644 src/token.zig create mode 100644 src/types.zig diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee4c0e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.zig-cache +zig-out +.DS_Store +.vscode/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4585f62 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 agra + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..e95400d --- /dev/null +++ b/build.zig @@ -0,0 +1,90 @@ +const std = @import("std"); +const math = @import("math"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const static_llvm = b.option(bool, "static-llvm", "Statically link LLVM (self-contained binary, no LLVM needed at runtime)") orelse false; + const llvm_prefix = b.option([]const u8, "llvm-prefix", "Path to LLVM installation") orelse "/opt/homebrew/opt/llvm@18"; + + const include_dir = b.fmt("{s}/include", .{llvm_prefix}); + const lib_dir = b.fmt("{s}/lib", .{llvm_prefix}); + const llvm_config = b.fmt("{s}/bin/llvm-config", .{llvm_prefix}); + + const mod = b.addModule("sx", .{ + .root_source_file = b.path("src/root.zig"), + .target = target, + .optimize = optimize, + }); + + mod.addSystemIncludePath(.{ .cwd_relative = include_dir }); + mod.addLibraryPath(.{ .cwd_relative = lib_dir }); + mod.link_libc = true; + mod.addCSourceFile(.{ + .file = b.path("llvm_shim.c"), + .flags = &.{b.fmt("-I{s}", .{include_dir})}, + }); + + if (static_llvm) { + // Query llvm-config for the static libraries needed + const libs_raw = std.mem.trim(u8, b.run(&.{ llvm_config, "--libs", "--link-static" }), " \t\n\r"); + var libs_it = std.mem.tokenizeAny(u8, libs_raw, " \t\n\r"); + while (libs_it.next()) |flag| { + if (flag.len > 2 and std.mem.startsWith(u8, flag, "-l")) { + mod.linkSystemLibrary(flag[2..], .{ .preferred_link_mode = .static }); + } + } + + // System libraries LLVM depends on (zlib, zstd, curses, etc.) + const syslibs_raw = std.mem.trim(u8, b.run(&.{ llvm_config, "--system-libs", "--link-static" }), " \t\n\r"); + var syslibs_it = std.mem.tokenizeAny(u8, syslibs_raw, " \t\n\r"); + while (syslibs_it.next()) |flag| { + if (flag.len > 2 and std.mem.startsWith(u8, flag, "-l")) { + mod.linkSystemLibrary(flag[2..], .{}); + } + } + + // LLVM is C++ — link the C++ standard library + mod.link_libcpp = true; + } else { + mod.linkSystemLibrary("LLVM-18", .{}); + } + + const exe = b.addExecutable(.{ + .name = "sx", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/main.zig"), + .target = target, + .optimize = optimize, + .imports = &.{ + .{ .name = "sx", .module = mod }, + }, + }), + }); + + b.installArtifact(exe); + + const run_step = b.step("run", "Run the app"); + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + + const mod_tests = b.addTest(.{ + .root_module = mod, + }); + const run_mod_tests = b.addRunArtifact(mod_tests); + + const exe_tests = b.addTest(.{ + .root_module = exe.root_module, + }); + const run_exe_tests = b.addRunArtifact(exe_tests); + + const test_step = b.step("test", "Run tests"); + test_step.dependOn(&run_mod_tests.step); + test_step.dependOn(&run_exe_tests.step); + +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..e6256b6 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,81 @@ +.{ + // This is the default name used by packages depending on this one. For + // example, when a user runs `zig fetch --save `, this field is used + // as the key in the `dependencies` table. Although the user can choose a + // different name, most users will stick with this provided value. + // + // It is redundant to include "zig" in this name because it is already + // within the Zig package namespace. + .name = .so, + // This is a [Semantic Version](https://semver.org/). + // In a future version of Zig it will be used for package deduplication. + .version = "0.0.0", + // Together with name, this represents a globally unique package + // identifier. This field is generated by the Zig toolchain when the + // package is first created, and then *never changes*. This allows + // unambiguous detection of one package being an updated version of + // another. + // + // When forking a Zig project, this id should be regenerated (delete the + // field and run `zig build`) if the upstream project is still maintained. + // Otherwise, the fork is *hostile*, attempting to take control over the + // original project's identity. Thus it is recommended to leave the comment + // on the following line intact, so that it shows up in code reviews that + // modify the field. + .fingerprint = 0x98c64403d9494683, // Changing this has security and trust implications. + // Tracks the earliest Zig version that the package considers to be a + // supported use case. + .minimum_zig_version = "0.16.0-dev.2290+200fb7c2a", + // This field is optional. + // Each dependency must either provide a `url` and `hash`, or a `path`. + // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. + // Once all dependencies are fetched, `zig build` no longer requires + // internet connectivity. + .dependencies = .{ + // See `zig fetch --save ` for a command-line interface for adding dependencies. + //.example = .{ + // // When updating this field to a new URL, be sure to delete the corresponding + // // `hash`, otherwise you are communicating that you expect to find the old hash at + // // the new URL. If the contents of a URL change this will result in a hash mismatch + // // which will prevent zig from using it. + // .url = "https://example.com/foo.tar.gz", + // + // // This is computed from the file contents of the directory of files that is + // // obtained after fetching `url` and applying the inclusion rules given by + // // `paths`. + // // + // // This field is the source of truth; packages do not come from a `url`; they + // // come from a `hash`. `url` is just one of many possible mirrors for how to + // // obtain a package matching this `hash`. + // // + // // Uses the [multihash](https://multiformats.io/multihash/) format. + // .hash = "...", + // + // // When this is provided, the package is found in a directory relative to the + // // build root. In this case the package's hash is irrelevant and therefore not + // // computed. This field and `url` are mutually exclusive. + // .path = "foo", + // + // // When this is set to `true`, a package is declared to be lazily + // // fetched. This makes the dependency only get fetched if it is + // // actually used. + // .lazy = false, + //}, + }, + // Specifies the set of files and directories that are included in this package. + // Only files and directories listed here are included in the `hash` that + // is computed for this package. Only files listed here will remain on disk + // when using the zig package manager. As a rule of thumb, one should list + // files required for compilation plus any license(s). + // Paths are relative to the build root. Use the empty string (`""`) to refer to + // the build root itself. + // A directory listed here means that all files within, recursively, are included. + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + // For example... + //"LICENSE", + //"README.md", + }, +} diff --git a/editors/vscode/.gitignore b/editors/vscode/.gitignore new file mode 100644 index 0000000..72aae85 --- /dev/null +++ b/editors/vscode/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +out/ diff --git a/editors/vscode/.vscodeignore b/editors/vscode/.vscodeignore new file mode 100644 index 0000000..6bcd6ab --- /dev/null +++ b/editors/vscode/.vscodeignore @@ -0,0 +1,3 @@ +src/ +tsconfig.json +.gitignore diff --git a/editors/vscode/language-configuration.json b/editors/vscode/language-configuration.json new file mode 100644 index 0000000..423ddfd --- /dev/null +++ b/editors/vscode/language-configuration.json @@ -0,0 +1,22 @@ +{ + "comments": { + "lineComment": "//" + }, + "brackets": [ + ["(", ")"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "(", "close": ")" }, + { "open": "\"", "close": "\"", "notIn": ["string"] } + ], + "surroundingPairs": [ + { "open": "{", "close": "}" }, + { "open": "(", "close": ")" }, + { "open": "\"", "close": "\"" } + ], + "indentationRules": { + "increaseIndentPattern": "\\{\\s*$", + "decreaseIndentPattern": "^\\s*\\}" + } +} diff --git a/editors/vscode/package-lock.json b/editors/vscode/package-lock.json new file mode 100644 index 0000000..5b24002 --- /dev/null +++ b/editors/vscode/package-lock.json @@ -0,0 +1,121 @@ +{ + "name": "sx-lang", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "sx-lang", + "version": "0.0.1", + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/vscode": "^1.75.0", + "typescript": "^5.0.0" + }, + "engines": { + "vscode": "^1.75.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.108.1", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.108.1.tgz", + "integrity": "sha512-DerV0BbSzt87TbrqmZ7lRDIYaMiqvP8tmJTzW2p49ZBVtGUnGAu2RGQd1Wv4XMzEVUpaHbsemVM5nfuQJj7H6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "license": "MIT", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + } + } +} diff --git a/editors/vscode/package.json b/editors/vscode/package.json new file mode 100644 index 0000000..c80051f --- /dev/null +++ b/editors/vscode/package.json @@ -0,0 +1,68 @@ +{ + "name": "sx-lang", + "displayName": "sx", + "description": "Language support for the sx programming language", + "version": "0.0.1", + "publisher": "swipelab", + "engines": { + "vscode": "^1.75.0" + }, + "categories": [ + "Programming Languages" + ], + "main": "./out/extension.js", + "contributes": { + "languages": [ + { + "id": "sx", + "aliases": [ + "sx" + ], + "extensions": [ + ".sx" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "sx", + "scopeName": "source.sx", + "path": "./syntaxes/sx.tmLanguage.json" + } + ], + "configuration": { + "title": "sx", + "properties": { + "sx.lspPath": { + "type": "string", + "default": "sx-lsp", + "description": "Path to the sx-lsp binary" + } + } + }, + "configurationDefaults": { + "editor.tokenColorCustomizations": { + "textMateRules": [ + { + "scope": "punctuation.definition.template-expression", + "settings": { + "foreground": "#E5C07B" + } + } + ] + } + } + }, + "scripts": { + "build": "tsc -p .", + "watch": "tsc -watch -p ." + }, + "dependencies": { + "vscode-languageclient": "^9.0.1" + }, + "devDependencies": { + "@types/vscode": "^1.75.0", + "typescript": "^5.0.0" + } +} diff --git a/editors/vscode/src/extension.ts b/editors/vscode/src/extension.ts new file mode 100644 index 0000000..ff63a48 --- /dev/null +++ b/editors/vscode/src/extension.ts @@ -0,0 +1,41 @@ +import { + workspace, + ExtensionContext, +} from "vscode"; +import { + LanguageClient, + LanguageClientOptions, + ServerOptions, +} from "vscode-languageclient/node"; + +let client: LanguageClient; + +export function activate(context: ExtensionContext) { + const config = workspace.getConfiguration("sx"); + const lspPath = config.get("lspPath", "sx-lsp"); + + const serverOptions: ServerOptions = { + command: lspPath, + args: ["lsp"], + }; + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: "file", language: "sx" }], + }; + + client = new LanguageClient( + "sx-lsp", + "sx Language Server", + serverOptions, + clientOptions + ); + + client.start(); +} + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined; + } + return client.stop(); +} diff --git a/editors/vscode/sx-lang-0.0.1.vsix b/editors/vscode/sx-lang-0.0.1.vsix new file mode 100644 index 0000000000000000000000000000000000000000..9a01694bcf04e3049a4f8f1ec27baca7d5442b08 GIT binary patch literal 466401 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�s0QkY7xsv5tUQ{`2W{8|i_YLzeJf#Vp1!EWS{ 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

$%9CU2W?+z`HUnn(b}*V~@56 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%0Dh$8KMsIVdB94(;YGR;+%$+ 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%0Dmpnt`eaNw4-pLyMzSY)#-o0|o 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 Ds!#JV literal 0 HcmV?d00001 diff --git a/editors/vscode/syntaxes/sx.tmLanguage.json b/editors/vscode/syntaxes/sx.tmLanguage.json new file mode 100644 index 0000000..e3b915c --- /dev/null +++ b/editors/vscode/syntaxes/sx.tmLanguage.json @@ -0,0 +1,208 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "sx", + "scopeName": "source.sx", + "patterns": [ + { "include": "#comments" }, + { "include": "#strings" }, + { "include": "#directives" }, + { "include": "#keywords" }, + { "include": "#types" }, + { "include": "#type-declaration" }, + { "include": "#type-annotation" }, + { "include": "#constants" }, + { "include": "#numbers" }, + { "include": "#operators" }, + { "include": "#function-declaration" }, + { "include": "#enum-literal" }, + { "include": "#identifiers" } + ], + "repository": { + "comments": { + "patterns": [ + { + "name": "comment.line.double-slash.sx", + "match": "//.*$" + } + ] + }, + "strings": { + "patterns": [ + { + "begin": "\"", + "end": "\"", + "beginCaptures": { + "0": { "name": "punctuation.definition.string.begin.sx" } + }, + "endCaptures": { + "0": { "name": "punctuation.definition.string.end.sx" } + }, + "patterns": [ + { + "name": "constant.character.escape.sx", + "match": "\\\\[ntr\"\\\\{}]" + }, + { + "begin": "\\{", + "end": "\\}", + "beginCaptures": { + "0": { "name": "punctuation.definition.template-expression.begin.sx" } + }, + "endCaptures": { + "0": { "name": "punctuation.definition.template-expression.end.sx" } + }, + "patterns": [ + { "include": "$self" } + ] + }, + { + "name": "string.quoted.double.sx", + "match": "[^\"\\\\{}]+" + } + ] + } + ] + }, + "directives": { + "patterns": [ + { + "name": "keyword.other.directive.sx", + "match": "#run" + } + ] + }, + "keywords": { + "patterns": [ + { + "name": "keyword.control.sx", + "match": "\\b(if|else|then|return|case|break|defer)\\b" + }, + { + "name": "keyword.other.sx", + "match": "\\b(enum|struct)\\b" + }, + { + "name": "keyword.operator.cast.sx", + "match": "\\bxx\\b" + } + ] + }, + "types": { + "patterns": [ + { + "name": "storage.type.sx", + "match": "\\b(s[0-9]+|u[0-9]+|f32|f64|bool|string)\\b" + } + ] + }, + "type-declaration": { + "patterns": [ + { + "match": "([A-Z][a-zA-Z0-9_]*)\\s*(::)\\s*(?=struct\\b|enum\\b)", + "captures": { + "1": { "name": "entity.name.type.sx" }, + "2": { "name": "keyword.operator.declaration.sx" } + } + } + ] + }, + "type-annotation": { + "patterns": [ + { + "match": "(?|=>" + }, + { + "name": "keyword.operator.comparison.sx", + "match": "==|!=|<=|>=" + }, + { + "name": "keyword.operator.assignment.sx", + "match": "[+\\-*/]=" + }, + { + "name": "keyword.operator.sx", + "match": "[+\\-*/=<>!]" + } + ] + }, + "function-declaration": { + "patterns": [ + { + "match": "([a-zA-Z_][a-zA-Z0-9_]*)\\s*(::)\\s*(?=\\(|\\{)", + "captures": { + "1": { "name": "entity.name.function.sx" }, + "2": { "name": "keyword.operator.declaration.sx" } + } + } + ] + }, + "enum-literal": { + "patterns": [ + { + "match": "\\.([a-zA-Z_][a-zA-Z0-9_]*)", + "captures": { + "1": { "name": "variable.other.enummember.sx" } + } + } + ] + }, + "identifiers": { + "patterns": [ + { + "name": "variable.other.generic-type.sx", + "match": "\\$([a-zA-Z_][a-zA-Z0-9_]*)", + "captures": { + "1": { "name": "entity.name.type.parameter.sx" } + } + }, + { + "match": "\\b(io)\\b", + "name": "support.module.sx" + } + ] + } + } +} diff --git a/editors/vscode/tsconfig.json b/editors/vscode/tsconfig.json new file mode 100644 index 0000000..e89a992 --- /dev/null +++ b/editors/vscode/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "outDir": "out", + "rootDir": "src", + "lib": ["ES2020"], + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src"], + "exclude": ["node_modules"] +} diff --git a/examples/01-basic.sx b/examples/01-basic.sx new file mode 100644 index 0000000..69dc250 --- /dev/null +++ b/examples/01-basic.sx @@ -0,0 +1,5 @@ +#import "modules/std.sx"; + +main :: () -> s32 { + if false then 40 else 42; +} \ No newline at end of file diff --git a/examples/02-stdout.sx b/examples/02-stdout.sx new file mode 100644 index 0000000..f484c9f --- /dev/null +++ b/examples/02-stdout.sx @@ -0,0 +1,4 @@ +#import "modules/std.sx"; +main :: () { + print("Hello\n"); +} \ No newline at end of file diff --git a/examples/03-structs.sx b/examples/03-structs.sx new file mode 100644 index 0000000..7acca06 --- /dev/null +++ b/examples/03-structs.sx @@ -0,0 +1,22 @@ +#import "modules/std.sx"; +Vec4 :: struct { + x, y, z, w: f32; +} + +main :: () { + v1 : Vec4 = .{ 1, 2, 3, 0}; + v2 := Vec4.{ 4, 1, 1, 3}; + v3 := Vec4.{ w=0, x=2, y=3, z=4}; + z := 5.0; // z is f32 + w := 6.0; // w is f32 + v4 := Vec4.{ y=3, x=9, w, z}; + + v4.y = 0; + print("v1: {}\nv2: {}\nv3: {}\nv4: {}\n", v1, v2, v3, v4); +} + +// ** stdout ** +//v1: Vec4{x:1.0, y:2.0, z:3.0, w:0.0} +//v2: Vec4{x:4.0, y:1.0, z:1.0, w:3.0} +//v3: Vec4{x:2.0, y:3.0, z:4.0, w:0.0} +//v4: Vec4{x:9.0, y:3.0, z:5.0, w:6.0} diff --git a/examples/04-shadow.sx b/examples/04-shadow.sx new file mode 100644 index 0000000..fd5a2e7 --- /dev/null +++ b/examples/04-shadow.sx @@ -0,0 +1,20 @@ +#import "modules/std.sx"; +main :: () -> s32 { + x := 42; + { + print("scope opened\n"); + defer print("scope closed\n"); + + // define a inner variable x shadowing the one define in the outer scope(s) + x:= 6; + print("scoped x: {}\n", x); //expect 6 + } + print("main x: {}\n", x); //expect 42 +} + +// ** stdout ** +// scope opened +// scoped x: 6 +// scope closed +// main x: 42 +// diff --git a/examples/05-run.sx b/examples/05-run.sx new file mode 100644 index 0000000..2ef6afc --- /dev/null +++ b/examples/05-run.sx @@ -0,0 +1,20 @@ +#import "modules/std.sx"; +// this will bake x to be 7 as a global constant +x :: #run compute(5); + +compute :: (v: s32) -> s32 => v + 2; + +main :: () { + //test + y :: #run compute(7); + c :: 2; + print("hello {}\n", x + y * c); +} + +#run main(); + +// ** stdout after build ** +// hello 25 + +// ** stdout after run ** +// hello 25 diff --git a/examples/06-generic.sx b/examples/06-generic.sx new file mode 100644 index 0000000..20e9d53 --- /dev/null +++ b/examples/06-generic.sx @@ -0,0 +1,17 @@ +#import "modules/std.sx"; +sum :: (a:$T, b:T) -> T { + return a + b; +} + +main :: () { + x:=sum(2,3); + + print("sum: {}\n", x); + print("sum: {}\n", sum(40,2)); + print("sum: {}\n", sum(40,2.5)); +} + +// ** stdout ** +// sum: 42 +// sum: 42.500000 +// diff --git a/examples/07-defer.sx b/examples/07-defer.sx new file mode 100644 index 0000000..4502c8c --- /dev/null +++ b/examples/07-defer.sx @@ -0,0 +1,11 @@ +#import "modules/std.sx"; +main :: () -> s32 { + defer print("still here\n"); + return 42; +} + +// ** exit code ** +// 42 +// ** stdout ** +// still here +// \ No newline at end of file diff --git a/examples/08-types.sx b/examples/08-types.sx new file mode 100644 index 0000000..8d420c0 --- /dev/null +++ b/examples/08-types.sx @@ -0,0 +1,43 @@ +#import "modules/std.sx"; +SPECIAL_VALUE :u8: 42; + +resolve :: (x: u8) -> s32 { + return 12 + x; +} + +Foo :: struct { + a : u2; // this will have 0 as default + b : u8 = SPECIAL_VALUE; + c : u8 = ---; // default for c is undefined + d : u8 = #run xx resolve(5); // converts s32 to u8 +} + +main :: () { + a : Foo; // default value of 0 + print("a 0 : {}\n", a); + a.a = 1; + // a.c is still undefined at this point + a.c = 8; + print("a 1 : {}\n", a); + + large: f64 = 5989.5; + b : Foo = ---; // undefined + b.a = 1; + b.c = xx large; // converts f64 to u8 + // expect stdout : "b: Foo{a:1, b: 42, c: 7, d: 12}" + print("b: {}", b); + print("\n"); + + f := Pack.{1,0,3,5,9,100,3.5}; + print("{}\n", f); +} + +Pack :: struct { + a: u1; + b: u2; + c: u8; + d: u32; + f: u64; + v: s32; + x: f32; +} diff --git a/examples/09-import.sx b/examples/09-import.sx new file mode 100644 index 0000000..7ca61db --- /dev/null +++ b/examples/09-import.sx @@ -0,0 +1,16 @@ +std :: #import "modules/std.sx"; + +//flat +#import "modules/math.sx"; + +main :: () -> s32 { + { + defer std.print("after hello"); + //expect stdout : hello there + std.print("hello there"); + } + + v:= std.Vector(3,f32).[1,2,3]; + + std.print("\n{}\n", v); +} diff --git a/examples/10-generic-struct.sx b/examples/10-generic-struct.sx new file mode 100644 index 0000000..fc22a80 --- /dev/null +++ b/examples/10-generic-struct.sx @@ -0,0 +1,91 @@ +#import "modules/std.sx"; + +Vec :: struct($N: u32, $T:Type) { + // (LLVM Vector) + // Vector is a Builtin Type + data: Vector(N,T); +} + +Complex :: ($T:Type) -> Type { + return struct { + value: T; + //..inject + count: u32; + }; +} + +Vec3 :: Vec(3, f32); + +vec3 :: (x:f32, y:f32, z:f32) -> Vector(3,f32) { + .[x, y, z]; +} + +Foo :: Complex(u32); + +main :: () { + v1 := Vec3.{data = .[1,3,2]}; + print("v1: {}\n", v1); + //stdout: Vec(3,f32){data: [1.0, 3.0, 2.0]} + // + + v2 := vec3(1,3,2); + print("v2: {}\n", v2); + //stdout: [1.0, 3.0, 2.0] + // + + // [N x T] (LLVM Array) + buffer : [5]f32 = .[0, 2, 3.5, 4, 0]; + print("buff: {}\n", buffer); + //stdout: [0.0, 2.0, 3.5, 4.0, 0.0] + // + + comp : Foo = .{value = 42, count = 1}; + print("comp: {}\n", comp); + //stdout: Foo{value: 42, count: 1} + // + + // Vector arithmetic + v3 := vec3(3,2,1); + add := v2 + v3; + print("add: {}\n", add); + + // Element access + v2x := v2.x; + print("v2.x: {}\n", v2x); + + // Index access + v2i := v2[1]; + print("v2[1]: {}\n", v2i); + + // Scalar broadcast + scaled := v2 * 2.0; + print("scaled: {}\n", scaled); + + // Negation + neg := -v2; + print("neg: {}\n", neg); + + // sqrt + s := sqrt(9.0); + print("sqrt(9): {}\n", s); + + // inline generic type + Sx :: (user: $T) -> Type { + return union { + counter: s32; + user: T; + }; + } + + sx := Sx(f32).user(0.5); + print("{}\n", sx); + + print("{}\n", size_of(f32)); + print("{}\n", size_of(Sx(f32))); + print("{}\n", size_of(Foo)); + print("{}\n", size_of(Complex)); + + + size:= size_of(Sx); + print("{}\n", size); +} diff --git a/examples/11-vector-math.sx b/examples/11-vector-math.sx new file mode 100644 index 0000000..19da380 --- /dev/null +++ b/examples/11-vector-math.sx @@ -0,0 +1,28 @@ +#import "modules/std.sx"; +math :: #import "modules/std/math.sx"; + +vec3 :: (x:f32, y:f32, z:f32) -> Vector(3,f32) { + .[x, y, z]; +} + +main :: () { + a := vec3(1, 0, 0); + b := vec3(0, 1, 0); + + // dot product + d := math.dot(a, b); + print("dot: {}\n", d); + + // cross product + cr := math.cross(a, b); + print("cross: {}\n", cr); + + // length + v := vec3(3, 4, 0); + len := math.length(v); + print("length: {}\n", len); + + // normalize + n := math.normalize(v); + print("norm: {}\n", n); +} diff --git a/examples/12-meta.sx b/examples/12-meta.sx new file mode 100644 index 0000000..b8601c6 --- /dev/null +++ b/examples/12-meta.sx @@ -0,0 +1,12 @@ +#import "modules/std.sx"; +#import "modules/math.sx"; + +main :: () { + x:Type = f64; + v:f64 = 3.2; + print("{}\n", x); + print("{}\n", v); + + x= Vec4; + print("{}\n", x); +} diff --git a/examples/13-code.sx b/examples/13-code.sx new file mode 100644 index 0000000..7488446 --- /dev/null +++ b/examples/13-code.sx @@ -0,0 +1,9 @@ +#import "modules/std.sx"; + +generate::() -> string { + return "print(\"hello from the other side\n\");"; +} + +main :: () { + #insert #run generate(); +} \ No newline at end of file diff --git a/examples/14-demo.sx b/examples/14-demo.sx new file mode 100644 index 0000000..e1f1814 --- /dev/null +++ b/examples/14-demo.sx @@ -0,0 +1,15 @@ +std :: #import "modules/std.sx"; + +vec3 :: (x:f32, y:f32, z:f32) -> std.Vector(3, f32) { + .[x,y,z]; +} + +main :: () { + v1 := vec3(1,0,0); + v2 := vec3(0,0,1); + s := 0.5; + + sum := (v1 - v2);// math.cross(v1, v2); + + std.print("{}\n", sum); +} diff --git a/examples/15-while.sx b/examples/15-while.sx new file mode 100644 index 0000000..cdf9049 --- /dev/null +++ b/examples/15-while.sx @@ -0,0 +1,50 @@ +#import "modules/std.sx"; + +sumOf10 :: () -> s32 { + i:= 1; + s:=0; + while i <= 10 { + s+=i; + i+=1; + } + s; +} + +someSum :: #run sumOf10(); + +main :: { + // Basic while loop: count to 5 + i := 0; + while i < 5 { + i += 1; + } + print("count: {}\n", i); + + // While with break + x := 1; + while x < 100 { + if x == 12 { + break; + } + x += 1; + } + print("break at: {}\n", x); + + // While with continue: sum odd numbers 1-9 + sum := 0; + j := 0; + while j < 10 { + j += 1; + // Skip even numbers + if j == 2 { continue; } + if j == 4 { continue; } + if j == 6 { continue; } + if j == 8 { continue; } + if j == 10 { continue; } + sum += j; + } + print("sum of odd 1-9: {}\n", sum); + + print("sum {}", someSum); + +} diff --git a/examples/16-union.sx b/examples/16-union.sx new file mode 100644 index 0000000..bb14a86 --- /dev/null +++ b/examples/16-union.sx @@ -0,0 +1,48 @@ +#import "modules/std.sx"; + +Shape :: union { + circle: f32; + rect: s32; + none; +} + +main :: () { + // Construction with .variant(payload) + s :Shape = .circle(3.14); + print("circle: {}\n", s); + + // Payload access + r := s.circle; + print("radius: {}\n", r); + + // Void variant via enum literal + s = .none; + print("none: {}\n", s); + + // Reassign with payload + s = .rect(42); + print("rect: {}\n", s); + + // Explicit prefix construction + sh :Shape = Shape.circle(2.71); + print("sh: {}\n", sh); + + // Field access on second union variable + sh2 :Shape = .rect(10); + val := sh2.rect; + print("rect val: {}\n", val); + + // Match on union + if sh2 == { + case .circle: print("matched circle\n"); + case .rect: print("matched rect\n"); + case .none: print("matched none\n"); + } + + cs := if sh2 == { + case .circle: 1; + case .rect: 2; + case .none: 3; + } + print("case : {}", cs); +} diff --git a/examples/17-lambda.sx b/examples/17-lambda.sx new file mode 100644 index 0000000..ab10547 --- /dev/null +++ b/examples/17-lambda.sx @@ -0,0 +1,9 @@ +#import "modules/std.sx"; + +main :: () { + fx :: (s:s3) -> s3 { + s; + } + + print("{}\n", fx(133)); +} diff --git a/examples/18-conditions.sx b/examples/18-conditions.sx new file mode 100644 index 0000000..f6bdfd2 --- /dev/null +++ b/examples/18-conditions.sx @@ -0,0 +1,23 @@ +#import "modules/std.sx"; + +main :: () { + x:= 32; + y:= 40; + + if 0 <= x <= 100 and 0 <= y <= 100 { + print("contained"); + } + + if 0 <= x <= 100 and 0 <= y <= 100 { + print("contained"); + } + + + if 1000 > x > -100 and 0 <= y <= 100 { + print("contained"); + } + + if 1000 > x >= -100 and 0 <= y <= 100 { + print("contained"); + } +} diff --git a/examples/19-varargs.sx b/examples/19-varargs.sx new file mode 100644 index 0000000..ccac756 --- /dev/null +++ b/examples/19-varargs.sx @@ -0,0 +1,35 @@ +#import "modules/std.sx"; + +sum :: (args: ..s32) -> s32 { + result := 0; + for args { + result = result + it; + } + result; +} + +print_all :: (args: ..s32) { + for args { + write(int_to_string(it)); + write(" "); + } + write("\n"); +} + +main :: () -> s32 { + write(int_to_string(sum(10, 20, 30))); + write("\n"); + + print_all(1, 2, 3, 4, 5); + + arr : [3]s32 = .[10, 20, 30]; + write(int_to_string(sum(..arr))); + write("\n"); + + for arr { + write(int_to_string(it)); + write(" "); + } + write("\n"); + 0; +} diff --git a/examples/20-any-varargs.sx b/examples/20-any-varargs.sx new file mode 100644 index 0000000..41d278c --- /dev/null +++ b/examples/20-any-varargs.sx @@ -0,0 +1,47 @@ +#import "modules/std.sx"; + +Point :: struct { + x: s32; + y: s32; +} + +// Print all arguments — accepts any type, dispatches via type-switch +print_any :: (args: ..Any) { + for args { + type := type_of(it); + if type == { + case int: write(int_to_string(cast(s32) it)); + case string: write(cast(string) it); + case bool: write(bool_to_string(cast(bool) it)); + case float: write(float_to_string(cast(f64) it)); + case Point: { + p := cast(Point) it; + write("("); + write(int_to_string(p.x)); + write(","); + write(int_to_string(p.y)); + write(")"); + } + } + write(" "); + } + write("\n"); +} + +count :: (args: ..Any) -> s32 { + args.len; +} + +main :: () -> s32 { + print_any(42, "hello", true, 3.14); + + // Test with struct + p := Point.{ x=10, y=20 }; + print_any("point:", p, 99); + + // Test count + write(int_to_string(count(1, 2, 3))); + write("\n"); + + 0; +} diff --git a/examples/21-categories.sx b/examples/21-categories.sx new file mode 100644 index 0000000..c2a5672 --- /dev/null +++ b/examples/21-categories.sx @@ -0,0 +1,19 @@ +#import "modules/std.sx"; + +Point :: struct { + x, y: s32; +} + +Color :: struct { + r, g, b: s32; +} + +main :: () { + p := Point.{10, 20}; + c := Color.{255, 128, 0}; + print("p: {}\n", p); + print("c: {}\n", c); + print("n: {}\n", 42); + print("s: {}\n", "hello"); + print("b: {}\n", true); +} diff --git a/examples/22-anytype.sx b/examples/22-anytype.sx new file mode 100644 index 0000000..55cd709 --- /dev/null +++ b/examples/22-anytype.sx @@ -0,0 +1,11 @@ +#import "modules/std.sx"; + +main :: { + i := 0; + while i < 10 { + i+=1; + if i == 2 then continue; + if i == 5 then break; + } + print("{}\n", i); +} \ No newline at end of file diff --git a/examples/modules/math.sx b/examples/modules/math.sx new file mode 100644 index 0000000..8266786 --- /dev/null +++ b/examples/modules/math.sx @@ -0,0 +1,17 @@ +#import "std.sx"; + +dot :: (a: Vector(3,f32), b: Vector(3,f32)) -> f32 { + return a.x*b.x + a.y*b.y + a.z*b.z; +} + +cross :: (a: Vector(3,f32), b: Vector(3,f32)) -> Vector(3,f32) { + .[a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x]; +} + +length :: (v: Vector(3,f32)) -> f32 { + return sqrt(dot(v, v)); +} + +normalize :: (v: Vector(3,f32)) -> Vector(3,f32) { + return v / length(v); +} \ No newline at end of file diff --git a/examples/modules/std.sx b/examples/modules/std.sx new file mode 100644 index 0000000..79e729c --- /dev/null +++ b/examples/modules/std.sx @@ -0,0 +1,220 @@ +Vector :: ($N: int, $T: Type) -> Type #builtin; +write :: (str: string) -> void #builtin; +sqrt :: (x: $T) -> T #builtin; +size_of :: ($T: Type) -> s32 #builtin; +alloc :: (size: s32) -> string #builtin; +type_of :: (val: $T) -> Type #builtin; +type_name :: ($T: Type) -> string #builtin; +field_count :: ($T: Type) -> s32 #builtin; +field_name :: ($T: Type, idx: s32) -> string #builtin; +field_value :: (s: $T, idx: s32) -> Any #builtin; + +int_to_string :: (n: s32) -> string { + if n == 0 { return "0"; } + neg := n < 0; + v := if neg then 0 - n else n; + tmp := v; + len := 0; + while tmp > 0 { len += 1; tmp = tmp / 10; } + total := if neg then len + 1 else len; + buf := alloc(total); + i := total - 1; + while v > 0 { + buf[i] = (v % 10) + 48; + v = v / 10; + i -= 1; + } + if neg { buf[0] = 45; } + buf; +} + +bool_to_string :: (b: bool) -> string { + if b then "true" else "false"; +} + +float_to_string :: (f: f64) -> string { + neg := f < 0.0; + v := if neg then 0.0 - f else f; + int_part := cast(s32) v; + frac := cast(s32) ((v - cast(f64) int_part) * 1000000.0); + if frac < 0 { frac = 0 - frac; } + istr := int_to_string(int_part); + fstr := int_to_string(frac); + il := istr.len; + fl := fstr.len; + prefix := if neg then 1 else 0; + total := prefix + il + 1 + 6; + buf := alloc(total); + pos := 0; + if neg { buf[0] = 45; pos = 1; } + i := 0; + while i < il { buf[pos] = istr[i]; pos += 1; i += 1; } + buf[pos] = 46; + pos += 1; + pad := 6 - fl; + j := 0; + while j < pad { buf[pos] = 48; pos += 1; j += 1; } + k := 0; + while k < fl { buf[pos] = fstr[k]; pos += 1; k += 1; } + buf; +} + +concat :: (a: string, b: string) -> string { + al := a.len; + bl := b.len; + buf := alloc(al + bl); + i := 0; + while i < al { buf[i] = a[i]; i += 1; } + j := 0; + while j < bl { buf[al + j] = b[j]; j += 1; } + buf; +} + +substr :: (s: string, start: s32, len: s32) -> string { + buf := alloc(len); + i := 0; + while i < len { + buf[i] = s[start + i]; + i += 1; + } + buf; +} + +struct_to_string :: (s: $T) -> string { + result := concat(type_name(T), "{"); + i := 0; + while i < field_count(T) { + if i > 0 { result = concat(result, ", "); } + result = concat(result, field_name(T, i)); + result = concat(result, ": "); + result = concat(result, any_to_string(field_value(s, i))); + i += 1; + } + concat(result, "}"); +} + +enum_to_string :: (e: $T) -> string { + concat(".", field_name(T, cast(s32) e)); +} + +vector_to_string :: (v: $T) -> string { + result := "["; + i := 0; + while i < field_count(T) { + if i > 0 { result = concat(result, ", "); } + result = concat(result, any_to_string(field_value(v, i))); + i += 1; + } + concat(result, "]"); +} + +array_to_string :: (a: $T) -> string { + result := "["; + i := 0; + while i < field_count(T) { + if i > 0 { result = concat(result, ", "); } + result = concat(result, any_to_string(field_value(a, i))); + i += 1; + } + concat(result, "]"); +} + +union_to_string :: (u: $T) -> string { + tag := cast(s32) u; + result := concat(".", field_name(T, tag)); + payload := field_value(u, tag); + pstr := any_to_string(payload); + if pstr.len > 0 { + result = concat(result, concat("(", concat(pstr, ")"))); + } + result; +} + +any_to_string :: (val: Any) -> string { + result := ""; + type := type_of(val); + if type == { + case void: result = ""; + case int: result = int_to_string(xx val); + case string: { s : string = xx val; result = s; } + case bool: result = bool_to_string(xx val); + case float: result = float_to_string(xx val); + case struct: result = struct_to_string(cast(type) val); + case enum: result = enum_to_string(cast(type) val); + case vector: result = vector_to_string(cast(type) val); + case array: result = array_to_string(cast(type) val); + case union: result = union_to_string(cast(type) val); + } + result; +} + +build_print :: (fmt: string) -> string { + code := "result := \"\"; "; + seg_start := 0; + i := 0; + arg_idx := 0; + while i < fmt.len { + if fmt[i] == 123 { + if i + 1 < fmt.len { + if fmt[i + 1] == 125 { + if i > seg_start { + code = concat(code, "result = concat(result, substr(fmt, "); + code = concat(code, int_to_string(seg_start)); + code = concat(code, ", "); + code = concat(code, int_to_string(i - seg_start)); + code = concat(code, ")); "); + } + code = concat(code, "result = concat(result, any_to_string(args["); + code = concat(code, int_to_string(arg_idx)); + code = concat(code, "])); "); + arg_idx += 1; + i += 2; + seg_start = i; + } else if fmt[i + 1] == 123 { + code = concat(code, "result = concat(result, substr(fmt, "); + code = concat(code, int_to_string(seg_start)); + code = concat(code, ", "); + code = concat(code, int_to_string(i - seg_start + 1)); + code = concat(code, ")); "); + i += 2; + seg_start = i; + } else { + i += 1; + } + } else { + i += 1; + } + } else if fmt[i] == 125 { + if i + 1 < fmt.len { + if fmt[i + 1] == 125 { + code = concat(code, "result = concat(result, substr(fmt, "); + code = concat(code, int_to_string(seg_start)); + code = concat(code, ", "); + code = concat(code, int_to_string(i - seg_start + 1)); + code = concat(code, ")); "); + i += 2; + seg_start = i; + } else { + i += 1; + } + } else { + i += 1; + } + } else { + i += 1; + } + } + if seg_start < fmt.len { + code = concat(code, "result = concat(result, substr(fmt, "); + code = concat(code, int_to_string(seg_start)); + code = concat(code, ", "); + code = concat(code, int_to_string(fmt.len - seg_start)); + code = concat(code, ")); "); + } + code = concat(code, "write(result);"); + code; +} + +print :: ($fmt: string, args: ..Any) { + #insert build_print(fmt); +} diff --git a/examples/vision.sx b/examples/vision.sx new file mode 100644 index 0000000..71b2031 --- /dev/null +++ b/examples/vision.sx @@ -0,0 +1,98 @@ +main :: { + // imagine a game loop + while(running) { + render_ui(build_menu); + } +} + +build_menu :: (ctx: ViewContext) -> View { + // use ctx to allocate some state at the for Menu ViewContext + state : MenuState = ctx.state(MenuState); + + // named args + HStack(ctx, + children = .[ + Button(ctx, + label = "Up", + onTap = ctx.callback(goUp, state), + ), + ScrollView(ctx, + LazyVStack(ctx, + builder = ctx.callback(build_menu_entry, state), + ), + ) + ], + ); +} + +build_menu_entry :: (ctx: *ViewContext, index: s32, state: *MenuState) -> View { + entry := state.entries[index]; + is_selected := index == state.selected_index; + icon := if entry.is_dir then "[D]" else " "; + Button(ctx, + label = concat(icon, " ", entry.name), + on_tap = ctx.callback(menu_go, state, index), + ); +} + +ViewContext :: struct { + //TBD +} + +MenuState :: struct { + current_path: string; + entries: List(MenuEntry); + error_message: string; +} + +MenuEntry :: struct { + name: string; + is_dir: bool; +} + +menu_go_up :: (state: *MenuState) { + parent := fs.path.dirname(state.current_path) else return; + // this frees the current path & copies parent to be owned by MenuState + state.current_path = parent; + menu_refresh(state); +} + +menu_go :: (state: *MenuState, s32 index) { + entry := state.entries[index] else return; + state.current_path := concat(state.current_path, "/", entry.name); + menu_refresh(state); +} + +menu_refresh :: (state: *MenuState) { + // this could retain the capacity + state.entries.clear(); + // this would basically create a copy of the empty string :( + state.error_message = ""; + + // ... multi return params vs Generic Result to deal with exceptions + // ... nullable + dir := io.Dir.open(state.current_path); + if !dir { + state.error_message = "failed to open"; + return; + }; + defer dir.close(); + + for iter.iterate() { + entries.append(.{it.name, it.kind == .Directory}); + } +} + +HStackState :: struct { + spacing: f32 = 8; + alignment: VerticalAlignment = .center; + padding: f32 = 0; + background: ?Color; + corner_radius: f32 = 0, +} + +HStack :: (ctx: ViewContext, children: []View) -> View { + data := ctx.alloc(HStackState); + data.* = .{}; +} + diff --git a/llvm_shim.c b/llvm_shim.c new file mode 100644 index 0000000..c0fd238 --- /dev/null +++ b/llvm_shim.c @@ -0,0 +1,17 @@ +#include +#include +#include +#include + +void sx_llvm_init_all_targets(void) { + LLVMInitializeAllTargetInfos(); + LLVMInitializeAllTargets(); + LLVMInitializeAllTargetMCs(); + LLVMInitializeAllAsmPrinters(); + LLVMInitializeAllAsmParsers(); +} + +void sx_llvm_init_native_target(void) { + LLVMInitializeNativeTarget(); + LLVMInitializeNativeAsmPrinter(); +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..63e74d8 --- /dev/null +++ b/readme.md @@ -0,0 +1,40 @@ +# sx + +*** HIGHLY EXPERIMENTAL *** DON'T USE *** + +This experiment is trying to answer a few questions: + +Q: Can we have an system language to build declarative ui ? + + +NOTE: +> i hope you have memory... currently it doesn't free anything :D + +## Building + +Requires **Zig 0.16+** and **LLVM 19**. + +```sh +zig build +``` + +## Usage + +```sh +# compile to binary +sx build examples/06-generic.sx + +# compile and run +sx run examples/06-generic.sx + +# emit LLVM IR +sx ir examples/06-generic.sx + +# start the language server +sx lsp +``` + +## Acknowledgments + +- [Jonathan Blow](https://en.wikipedia.org/wiki/Jonathan_Blow) — for Jai, the language that inspired this one +- [Andrew Kelley](https://andrewkelley.me) — for Zig, which made this compiler a joy to write diff --git a/specs.md b/specs.md new file mode 100644 index 0000000..9a7bfc6 --- /dev/null +++ b/specs.md @@ -0,0 +1,853 @@ +# sx language specification + +## 1. Lexical Structure + +### Comments +Line comments start with `//` and extend to end of line. +```sx +// this is a comment +``` + +### Identifiers +- Lowercase or mixed-case for variables, functions: `x`, `compute`, `main` +- UPPER_SNAKE_CASE for constants: `SOME_INT`, `SOME_STR` +- PascalCase for types: `Foo` + +### Literals + +| Kind | Examples | Type | +|-----------|---------------------|---------| +| Integer | `0`, `42`, `0xFF`, `0b1010` | `s32` | +| Float | `0.3`, `0.9` | `f32` | +| String | `"Hello"`, `"z: {z}"` | `string` | +| Boolean | `true`, `false` | `bool` | +| Enum | `.variant1` | inferred from context | +| Undefined | `---` | context-dependent | + +### Keywords +`if`, `else`, `then`, `while`, `break`, `continue`, `true`, `false`, `enum`, `struct`, `union`, `case`, `return`, `defer`, `xx`, `and`, `or` + +### Operators + +| Operator | Meaning | +|----------|------------------| +| `+` | addition | +| `-` | subtraction / negation | +| `*` | multiplication | +| `/` | division | +| `==` | equality | +| `!=` | inequality | +| `<` | less than | +| `>` | greater than | +| `<=` | less or equal | +| `>=` | greater or equal | +| `and` | logical AND (short-circuit) | +| `or` | logical OR (short-circuit) | +| `+=` | add-assign | +| `-=` | sub-assign | +| `*=` | mul-assign | +| `/=` | div-assign | + +### Delimiters and Punctuation + +| Token | Meaning | +|--------|--------------------------------------| +| `::` | constant binding / definition | +| `:=` | variable binding (mutable, inferred) | +| `:` | type annotation | +| `=` | assignment (in typed var decl) | +| `;` | statement terminator | +| `,` | separator | +| `.` | field access / enum literal prefix | +| `->` | return type annotation | +| `=>` | lambda arrow | +| `$` | generic type parameter introduction | +| `---` | undefined value | +| `()` | grouping / params | +| `{}` | blocks / bodies | + +--- + +## 2. Type System + +### Primitive Types +- `s1`..`s64` — signed integers (1 to 64 bits). `s32` is the default for integer literals. +- `u1`..`u64` — unsigned integers (1 to 64 bits). +- `f32` — 32-bit floating point +- `f64` — 64-bit floating point +- `bool` — boolean (`true` / `false`) +- `string` — string of characters +- `Any` — type-erased value, represented as `{ i32, i64 }` (type tag + payload). Used for variadic arguments and runtime type dispatch. +- `Type` — compile-time type value. At runtime, represented as an `i32` type tag (same tag space as `Any`). + +### Enum Types +User-defined sum types with named variants. +```sx +Foo :: enum { + variant1; + variant2; +} +``` +Variants are referenced with dot-prefix syntax: `.variant1` + +### Struct Types +User-defined product types with named fields. +```sx +Vec4 :: struct { + x, y, z, w: f32; +} +``` +Fields are declared as `name1, name2: type;` (comma-separated names sharing a type, semicolon-terminated). + +#### Field Defaults +Fields may have default values. Fields without an explicit default have a zero-value default. `---` marks a field as explicitly undefined. +```sx +Foo :: struct { + a : u2; // default is 0 + b : u8 = 42; // default is 42 + c : u8 = ---; // default is undefined +} +``` + +#### Struct Literals +```sx +// Positional (with type annotation — type inferred from annotation) +v1 : Vec4 = .{ 1, 2, 3, 0 }; + +// Positional (with type prefix) +v2 := Vec4.{ 4, 1, 1, 3 }; + +// Named fields (any order) +v3 := Vec4.{ w=0, x=2, y=3, z=4 }; + +// Mixed named + shorthand (bare identifier = field name matches variable name) +z := 5.0; +w := 6.0; +v4 := Vec4.{ y=3, x=9, w, z }; +``` + +#### Field Access and Assignment +```sx +v1.x // read field x of struct v1 +v1.x = 3.0; // assign to field x of struct v1 +``` + +#### Struct Interpolation +Struct values in string interpolation print as `TypeName{field:value, ...}`: +```sx +print("{v1}"); // Vec4{x:1.0, y:2.0, z:3.0, w:0.0} +``` + +### Union Types (Tagged Unions) +Sum types where each variant can carry typed data or be void. Internally represented as `{ i32, [max_payload_size x i8] }`. + +#### Declaration +```sx +Shape :: union { + circle: f32; // typed variant + rect: s32; // typed variant + none; // void variant +} +``` + +#### Construction +```sx +s :Shape = .circle(3.14); // inferred from context +s = .none; // void variant (enum literal syntax) +s = Shape.rect(42); // explicit prefix +``` + +#### Payload Access +```sx +r := s.circle; // load payload as f32 (undefined behavior if wrong variant active) +``` + +#### Pattern Matching +```sx +if s == { + case .circle: print("circle\n"); + case .rect: print("rect\n"); + case .none: print("none\n"); +} +``` + +#### Union Interpolation +Union values in string interpolation print as ``: +```sx +print("{s}"); // +``` + +### Array Types +Fixed-size arrays with element type and length. +```sx +buffer : [5]f32 = .[0, 2, 3.5, 4, 0]; +val := buffer[2]; // 3.5 +``` + +### Vector Types (SIMD) +LLVM SIMD vectors, parameterized by length and element type. +```sx +v := vec3(1, 3, 2); // Vector(3, f32) +``` + +**Arithmetic**: Element-wise `+`, `-`, `*`, `/` on vectors of same dimensions. +```sx +add := v1 + v2; // element-wise addition +``` + +**Scalar broadcast**: Scalar operands are broadcast to match the vector. +```sx +scaled := v * 2.0; // [2.0, 6.0, 4.0] +``` + +**Negation**: Unary `-` negates each element. +```sx +neg := -v; // [-1.0, -3.0, -2.0] +``` + +**Element access**: `.x`, `.y`, `.z`, `.w` (aliases `.r`, `.g`, `.b`, `.a`) extract single components. +```sx +v.x // first element +v.z // third element +``` + +**Index access**: `v[i]` extracts by index. +```sx +v[0] // first element +``` + +**Built-in `sqrt`**: Calls LLVM `llvm.sqrt.f32`/`.f64` intrinsic. +```sx +s := sqrt(9.0); // 3.0 +``` + +### Function Types +Expressed as `(param_types) -> return_type`. +A function with no return type annotation returns void. +```sx +// type is (s32) -> s32 +compute :: (x: s32) -> s32 { x * x; } + +// type is () -> void +main :: () { } +``` + +### Type Aliases +A name bound to an existing type. +```sx +SOME_TYPE :: f64; +``` + +### Generic Functions (Monomorphization) +Functions can be parameterized over types using `$T` syntax. The `$` prefix introduces a type parameter; subsequent uses of the name reference it. +```sx +sum :: (a: $T, b: T) -> T { + return a + b; +} +``` +- `$T` in a parameter type **introduces** type parameter `T` +- Bare `T` (without `$`) **references** the introduced type parameter +- At call sites, type arguments are **inferred** from actual argument types: + ```sx + sum(40, 2) // T = s32 + sum(1.5, 2.5) // T = f32 + ``` +- Each unique set of concrete types produces a **separate specialized function** (monomorphization) +- Multiple type parameters are supported: `(a: $T, b: $U) -> T` + +### Variadic Functions +Functions can accept a variable number of arguments using `..Type` syntax: +```sx +print :: (fmt: string, args: ..Any) { ... } +``` +- `..Any` means zero or more arguments, each boxed into `Any` (type tag + payload) +- The variadic parameter must be the last parameter +- At call sites, variadic arguments are automatically boxed: `print("x={}, y={}\n", x, y)` +- Inside the function body, `args` is accessed as a slice-like sequence + +### Type Inference +- `::` bindings infer type from the right-hand side +- `:=` bindings infer type from the right-hand side +- Explicit annotation overrides inference: `NAME : f64 : 0.9;` +- Integer literals default to `s32` +- Float literals default to `f32` +- Enum literals (`.variant`) infer their enum type from context (expected type) + +### Type Conversions + +**Implicit (widening)** — allowed without annotation: +- Integer to wider integer of same signedness (`u8` → `u16`, `s8` → `s32`) +- Unsigned to strictly wider signed (`u8` → `s16`) +- Any integer to any float (`u8` → `f32`, `s32` → `f64`) +- Float to wider float (`f32` → `f64`) +- Integer and float literals can convert to any numeric type implicitly + +**Explicit (narrowing)** — requires `xx` prefix: +- Integer to narrower integer (`s32` → `u8`) +- Signed to unsigned (`s32` → `u32`) +- Float to narrower float (`f64` → `f32`) +- Float to any integer (`f64` → `u16`) +- Unsigned to signed of same or narrower width (`u8` → `s8`) + +The `xx` prefix operator marks an expression for auto-conversion to the expected type from context (assignment, declaration, argument, return): +```sx +large: f64 = 5999.5; +x : u16 = xx large; // f64 → u16 +d : u8 = #run xx resolve(5); // s32 → u8 at compile time +``` + +Using `xx` outside a typed context (where the target type is known) is a compile error. + +--- + +## 3. Declarations + +### Constant Binding (immutable) + +```sx +// inferred type +NAME :: value; + +// explicit type +NAME : type : value; +``` + +The `::` operator creates an immutable binding. The value is evaluated at compile time when possible. + +Examples: +```sx +SOME_INT :: 0; // s32 +SOME_STR :: "Hello"; // string +SOME_FLOAT :: 0.3; // f32 +SOME_DOUBLE : f64 : 0.9; // f64 (explicit) +SOME_FUNC :: () => 42; // () -> s32 +SOME_TYPE :: f64; // type alias +``` + +### Variable Binding (mutable) + +```sx +// inferred type +name := value; + +// explicit type +name : type = value; + +// default-initialized (type required) +name : type; + +// undefined (type required) +name : type = ---; +``` + +The `:=` operator creates a mutable binding. The type is inferred unless explicitly annotated. + +`name : type;` initializes using the type's defaults: zero for primitives, per-field defaults for structs (see Field Defaults). + +`name : type = ---;` leaves the value undefined (uninitialized memory). Reading before writing is undefined behavior. + +Examples: +```sx +x := 42; // s32, mutable +x := if true then 1 else 2; +z : Foo = .variant2; // Foo, mutable, explicit type +a : Foo; // Foo, default-initialized (a=0, b=42, c=undef) +b : Foo = ---; // Foo, entirely undefined +``` + +### Function Definition + +```sx +name :: (params) -> return_type { + body +} +``` + +- Parameters: `name: type` separated by commas +- Return type: `-> type` (omit for void) +- Body: block of statements; last expression is the implicit return value +- No `return` keyword needed (last expression = return value) + +Examples: +```sx +compute :: (x: s32) -> s32 { + x * x; +} + +main :: () { + // void return, no -> annotation +} + +// Bare-block shorthand (equivalent to no-arg void function): +main :: { + // same as main :: () { ... } +} +``` + +### Enum Definition + +```sx +Name :: enum { + variant1; + variant2; +} +``` + +Defines a new enum type with the given variants. Trailing comma is allowed. + +--- + +## 4. Expressions + +Everything in `sx` is expression-oriented where possible. + +### Operator Precedence + +| Prec | Operators | Notes | +|------|-----------|-------| +| 6 (highest) | `*`, `/` | multiplication, division | +| 5 | `+`, `-` | addition, subtraction | +| 4 | `<`, `<=`, `>`, `>=`, `==`, `!=` | comparisons (chainable) | +| 2 | `and` | logical AND (short-circuit) | +| 1 (lowest) | `or` | logical OR (short-circuit) | + +### Arithmetic +Standard infix: `+`, `-`, `*`, `/` with usual precedence (`*`/`/` before `+`/`-`). +```sx +x * x +x + 2 +``` + +### Chained Comparisons +Comparison operators can be chained. Each operand is evaluated exactly once. +```sx +0 <= x <= 100 // equivalent to: 0 <= x and x <= 100 +1000 > x >= -100 // equivalent to: 1000 > x and x >= -100 +a == b == c // equivalent to: a == b and b == c +``` +Mixed operators are allowed: `a < b <= c > d` means `a < b and b <= c and c > d`. + +### Logical Operators +`and` and `or` are short-circuit boolean operators. The right operand is not evaluated if the left operand determines the result. +```sx +if 0 <= x <= 100 and 0 <= y <= 100 { + print("contained"); +} +``` + +### If Expression (inline form) +```sx +if condition then consequent else alternate +``` +Both branches are single expressions. The whole form produces a value. +```sx +x := if true then 1 else 2; +``` +The `else` branch is optional. Without it, the form is a statement (no value): +```sx +if i == 2 then continue; +if done then break; +if err then return; +``` + +### If Expression (block form) +```sx +if condition { + stmts +} else { + stmts +} +``` +Each branch is a block. The last expression in each block is the branch's value. Can be used inline within other expressions: +```sx +y := x + if false { + 7; +} else { + 12; +}; +``` + +### Pattern Matching +```sx +if subject == { + case pattern: body + case pattern: body + else: body // optional default arm +} +``` +Matches `subject` against each `case`. Patterns can be: +- **Enum literals**: `.variant` — matches a specific enum variant. +- **Integer/bool literals**: `42`, `true` — matches a specific value. +- **Type categories**: `struct`, `enum`, `union` — matches all types in that category (used with `type_of` values). + +`break` exits a case arm without producing a value. The optional `else:` arm matches when no `case` pattern matches. +```sx +if z == { + case .variant1: break; + case .variant2: + print("z: {z}"); + else: + print("unknown"); +} +``` + +#### Type Category Matching +When switching on a `Type` value (from `type_of`), category keywords match all registered types of that category: +```sx +type := type_of(val); +if type == { + case int: result = int_to_string(xx val); + case struct: result = struct_to_string(cast(type) val); + case enum: result = enum_to_string(cast(type) val); +} +``` +Available categories: `int`, `float`, `bool`, `string`, `struct`, `enum`, `union`. + +Inside a category arm, `cast(type) val` performs **runtime generic dispatch**: the compiler generates a switch over all types in the category, monomorphizing the callee for each concrete type. + +### While Loop +```sx +while condition { + body +} +``` +Repeats `body` as long as `condition` is true. `break;` exits the loop. `continue;` skips to the next iteration. +```sx +i := 0; +while i < 10 { + i += 1; + if i == 5 { continue; } + if i == 8 { break; } + print("{i}\n"); +} +``` + +### For Loop +```sx +for iterable { + // `it` is the current element + // `it_index` is the current index (s32) + print("{it}\n"); +} +``` +Iterates over arrays and slices. The loop body has two implicit variables: +- `it` — the current element value +- `it_index` — the current index (s32, starting at 0) + +`break;` exits the loop. `continue;` skips to the next iteration. +```sx +arr : [5]s32 = .[1, 2, 3, 4, 5]; +for arr { + if it_index == 2 { continue; } + print("{it}\n"); +} +``` + +### Lambda +```sx +(params) => expr +(params) -> return_type => expr +``` +Anonymous function. Produces a function value. Supports the same parameter features as named functions: `$` generic type params, `..` variadic params, and optional return type annotation. +```sx +SOME_FUNC :: () => 42; // () -> s32 +double :: (x: $T) -> T => x + x; // generic lambda with return type +``` + +### Function Call +```sx +callee(args) +``` +```sx +compute(6) +print("hello") +``` + +### Field Access +```sx +object.field +``` +Used for module access (`std.print`) and struct member access. + +### Enum Literal +```sx +.variant_name +``` +The enum type is inferred from context (expected type from declaration or parameter). + +### String Interpolation +Curly braces inside string literals interpolate expressions: +```sx +"z: {z}" +``` +The expression inside `{}` is evaluated and formatted according to its type: +- `s32` — decimal integer +- `f64` — decimal float +- `string` — as-is + +--- + +## 5. Statements + +Statements are terminated by `;`. + +- **Declaration**: `name :: value;` / `name := value;` +- **Assignment**: `name = value;` / `name += value;` (and other compound assignments). Also supports field targets: `obj.field = value;` +- **Expression statement**: `expr;` — evaluates the expression (last in a block = return value) +- **Return**: `return expr;` — returns from the enclosing function with the given value. `return;` returns void. +- **Break**: `break;` — exits a match arm or while loop +- **Continue**: `continue;` — skips to the next iteration of a while loop +- **Defer**: `defer expr;` — defers execution of `expr` until the enclosing block exits (LIFO order) + +--- + +## 6. Blocks, Scoping, and Implicit Returns + +A block `{ ... }` contains zero or more statements. The last expression in a block is its value (implicit return). + +In function bodies, the last expression becomes the return value: +```sx +compute :: (x: s32) -> s32 { + x * x; // this is returned +} +``` + +### Scope Blocks + +Bare blocks can be used as statements to introduce a new lexical scope. Variables declared inside a scope block are local to that block. No trailing `;` is required. + +```sx +main :: { + x := 42; + { + x := 6; // shadows outer x + print("inner: {x}"); // prints 6 + } + print("outer: {x}"); // prints 42 +} +``` + +### Variable Shadowing + +A variable declaration (`name :=`) inside an inner scope shadows any variable with the same name from outer scopes. The outer variable is restored when the inner scope exits. + +### Defer + +`defer expr;` schedules `expr` to execute when the enclosing scope block exits. Multiple defers in the same scope execute in reverse order (LIFO). + +```sx +{ + defer print("second"); + defer print("first"); +} +// prints: first, then second +``` + +--- + +## 7. Built-in Functions + +Built-in functions are declared in `std.sx` with the `#builtin` suffix, which tells the compiler to generate the implementation internally rather than looking for a function body. + +### I/O +- `write(str: string) -> void` — write a string to standard output +- `print(fmt: string, args: ..Any)` — formatted print. Parses `{}` placeholders in the format string and substitutes arguments. When all argument types are statically known, the compiler specializes the call at compile time (no `Any` boxing). + +### Math +- `sqrt(x: $T) -> T` — square root (maps to LLVM intrinsic) + +### Memory +- `alloc(size: s32) -> string` — allocate `size` bytes of memory, returned as a string slice +- `size_of($T: Type) -> s32` — size of type `T` in bytes + +### Type Introspection +- `type_of(val: $T) -> Type` — returns the runtime type tag of a value +- `type_name($T: Type) -> string` — returns the name of type `T` as a string (e.g., `"Point"`) +- `field_count($T: Type) -> s32` — returns the number of fields (struct), variants (enum), or elements (vector) in type `T` +- `field_name($T: Type, idx: s32) -> string` — returns the name of the `idx`-th field (struct) or variant (enum) of type `T` +- `field_value(s: $T, idx: s32) -> Any` — returns the `idx`-th field (struct) or element (vector) of `s`, boxed as `Any` + +### Type Conversion +- `cast(Type) expr` — prefix operator that converts `expr` to `Type`. Examples: `cast(s32) 3.14`, `cast(f64) n`. When `Type` is a runtime `Type` value inside a type-category match arm, the compiler generates a dispatch switch over all types in the category, monomorphizing the callee for each concrete type. + +### Vectors +- `Vector($N: int, $T: Type) -> Type` — returns an LLVM vector type of `N` elements of type `T` + +--- + +## 8. Compile-time Evaluation + +### `#run` Directive + +`#run expr` evaluates `expr` at compile time using lazy JIT execution. It can appear in two contexts: + +**Compile-time constants** — bind a compile-time value to a name: +```sx +compute :: (x: s32) -> s32 { x * x; } +x :: #run compute(5); // x = 25, evaluated at compile time +``` + +Comptime globals are resolved lazily: the JIT executes only when the value is first referenced during code generation. Chained dependencies are resolved automatically. + +**Side effects** — execute code at compile time for its side effects: +```sx +#run print("compiling..."); +``` + +### `#insert` Directive + +`#insert expr;` evaluates `expr` at compile time to obtain a string, then parses and compiles that string as inline code at the insertion point. + +```sx +generate :: () -> string { + return "print(\"hello from the other side\");"; +} + +main :: () { + #insert #run generate(); + // equivalent to: print("hello from the other side"); +} +``` + +The inserted string must contain valid `sx` statements (including semicolons). The statements are parsed and compiled in the same scope as the `#insert` site. + +--- + +## 9. Modules / Imports + +### `#import` Directive + +The `#import` directive brings declarations from another `.sx` file into the current file. Paths are resolved relative to the importing file's directory. + +**Flat import** — splices all declarations from the imported file into the current scope: +```sx +#import "modules/std/math.sx"; +``` + +**Namespaced import** — wraps all declarations under a namespace name: +```sx +std :: #import "modules/std.sx"; +``` + +Namespaced declarations are accessed with dot notation: +```sx +std.print("hello"); +``` + +### Import Resolution + +- Imports are resolved after parsing and before code generation. +- Paths are relative to the directory of the file containing the `#import`. +- Nested imports are supported (imported files may themselves contain `#import`). +- Circular imports are detected and silently skipped (each file is imported at most once). +- Generic functions in namespaced imports are supported (e.g., `std.mul(5, 2)` where `mul` is generic). + +### Intra-module References + +Functions within a namespaced import can call each other without the namespace prefix. When generating code for a namespaced module, unresolved function names are automatically tried with the namespace prefix. + +### Example + +```sx +// modules/std/math.sx +mul :: (base: $T, exp: T) -> T { base * exp; } + +// modules/std/std.sx +print :: (str: string) -> void #builtin; + +// main.sx +std :: #import "modules/std.sx"; +#import "modules/std/math.sx"; + +main :: () -> s32 { + std.print("hello there"); + mul(5, 2); +} +``` + +--- + +## 10. Program Structure + +A program is a sequence of top-level declarations and `#import` directives. Execution begins at `main`. + +```sx +main :: () { + // entry point +} +``` + +`main` takes no arguments and returns void. The process exit code is 0 unless otherwise specified. + +--- + +## 11. Grammar (informal) + +``` +program = top_level* +top_level = decl | import_decl +import_decl = '#import' STRING ';' + | IDENT '::' '#import' STRING ';' +decl = const_decl | var_decl | fn_decl | enum_decl | struct_decl +const_decl = IDENT '::' expr ';' + | IDENT ':' type ':' expr ';' +var_decl = IDENT ':=' expr ';' + | IDENT ':' type '=' expr ';' + | IDENT ':' type ';' +fn_decl = IDENT '::' '(' params? ')' ('->' type)? block + | IDENT '::' block +enum_decl = IDENT '::' 'enum' '{' (IDENT ';')* '}' +struct_decl = IDENT '::' 'struct' '{' field_group* '}' +field_group = IDENT (',' IDENT)* ':' type ('=' expr)? ';' +params = param (',' param)* +param = IDENT ':' type +block = '{' stmt* '}' +stmt = decl | assignment ';' | return_stmt | defer_stmt | insert_stmt + | break_stmt | continue_stmt | expr ';' +return_stmt = 'return' expr? ';' +break_stmt = 'break' ';' +continue_stmt = 'continue' ';' +defer_stmt = 'defer' expr ';' +insert_stmt = '#insert' expr ';' +assignment = lvalue ('=' | '+=' | '-=' | '*=' | '/=') expr +lvalue = IDENT | postfix '.' IDENT +expr = if_expr | match_expr | while_expr | for_expr | lambda | binary +while_expr = 'while' expr block +for_expr = 'for' expr block +binary = unary (binop unary)* +unary = ('-' | '!' | 'xx' | 'cast' '(' type ')') postfix + | postfix +postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')* +primary = INT | HEX_INT | BIN_INT | FLOAT | STRING | BOOL | IDENT | '---' + | '.' IDENT | '.' '{' field_init_list '}' + | '(' expr ')' | block | '#run' expr +field_init_list = field_init (',' field_init)* +field_init = IDENT '=' expr | IDENT | expr +if_expr = 'if' expr 'then' expr ('else' expr)? + | 'if' expr block ('else' block)? +match_expr = 'if' expr '==' '{' case_arm* else_arm? '}' +case_arm = 'case' pattern ':' (stmt* | 'break' ';') +else_arm = 'else' ':' stmt* +pattern = '.' IDENT | INT | BOOL | IDENT +lambda = '(' params? ')' ('->' type)? '=>' expr +args = expr (',' expr)* +type = '$' IDENT | 's32' | 'f32' | 'f64' | 'bool' | 'string' + | 'Any' | 'Type' | '..' type | '[' expr ']' type | IDENT +``` + +--- + +## 12. Open Questions + +These are inferred gaps — things not shown in the readme that need decisions: + +- **`return`**: Both `return expr;` and implicit return (last expression) are supported. +- **Else in match**: Is there a default/else arm in pattern matching? +- **Nested functions**: Can functions be defined inside other functions? +- **Mutability of params**: Are function parameters immutable by default? +- **Array/list types**: Not shown — deferred. +- **Struct types**: Implemented — named struct types with positional/named/shorthand literals. +- **Imports/modules**: `#import` directive supports flat and namespaced imports (see Section 8). +- **Operator overloading**: Not shown — presumably no. +- **Semicolons**: Required on all statements? What about the last expression in a block? +- **Top-level expressions**: Are bare expressions allowed at the top level or only declarations? diff --git a/src/ast.zig b/src/ast.zig new file mode 100644 index 0000000..b1ee246 --- /dev/null +++ b/src/ast.zig @@ -0,0 +1,326 @@ +const std = @import("std"); + +pub const Span = struct { + start: u32, + end: u32, +}; + +pub const Node = struct { + span: Span, + data: Data, + + pub const Data = union(enum) { + root: Root, + fn_decl: FnDecl, + block: Block, + int_literal: IntLiteral, + float_literal: FloatLiteral, + bool_literal: BoolLiteral, + string_literal: StringLiteral, + identifier: Identifier, + enum_literal: EnumLiteral, + binary_op: BinaryOp, + chained_comparison: ChainedComparison, + unary_op: UnaryOp, + call: Call, + field_access: FieldAccess, + if_expr: IfExpr, + match_expr: MatchExpr, + match_arm: MatchArm, + const_decl: ConstDecl, + var_decl: VarDecl, + assignment: Assignment, + enum_decl: EnumDecl, + struct_decl: StructDecl, + struct_literal: StructLiteral, + union_decl: UnionDecl, + union_literal: UnionLiteral, + lambda: Lambda, + type_expr: TypeExpr, + param: Param, + defer_stmt: DeferStmt, + comptime_expr: ComptimeExpr, + insert_expr: InsertExpr, + return_stmt: ReturnStmt, + import_decl: ImportDecl, + namespace_decl: NamespaceDecl, + array_type_expr: ArrayTypeExpr, + array_literal: ArrayLiteral, + parameterized_type_expr: ParameterizedTypeExpr, + index_expr: IndexExpr, + while_expr: WhileExpr, + for_expr: ForExpr, + spread_expr: SpreadExpr, + break_expr: void, + continue_expr: void, + undef_literal: void, + builtin_expr: void, + + pub fn declName(self: Data) ?[]const u8 { + return switch (self) { + .fn_decl => |d| d.name, + .const_decl => |d| d.name, + .var_decl => |d| d.name, + .enum_decl => |d| d.name, + .struct_decl => |d| d.name, + .union_decl => |d| d.name, + .namespace_decl => |d| d.name, + else => null, + }; + } + }; +}; + +pub const Root = struct { + decls: []const *Node, +}; + +pub const FnDecl = struct { + name: []const u8, + params: []const Param, + return_type: ?*Node, + body: *Node, + type_params: []const StructTypeParam = &.{}, +}; + +pub const Param = struct { + name: []const u8, + name_span: Span, + type_expr: *Node, + is_variadic: bool = false, + is_comptime: bool = false, +}; + +pub const Block = struct { + stmts: []const *Node, +}; + +pub const IntLiteral = struct { + value: i64, +}; + +pub const FloatLiteral = struct { + value: f64, +}; + +pub const BoolLiteral = struct { + value: bool, +}; + +pub const StringLiteral = struct { + raw: []const u8, +}; + +pub const Identifier = struct { + name: []const u8, +}; + +pub const EnumLiteral = struct { + name: []const u8, // without the leading dot +}; + +pub const BinaryOp = struct { + op: Op, + lhs: *Node, + rhs: *Node, + + pub const Op = enum { + add, + sub, + mul, + div, + mod, + eq, + neq, + lt, + lte, + gt, + gte, + and_op, + or_op, + }; +}; + +pub const ChainedComparison = struct { + operands: []const *Node, + ops: []const BinaryOp.Op, +}; + +pub const UnaryOp = struct { + op: Op, + operand: *Node, + + pub const Op = enum { + negate, + not, + xx, + }; +}; + +pub const Call = struct { + callee: *Node, + args: []const *Node, +}; + +pub const FieldAccess = struct { + object: *Node, + field: []const u8, +}; + +pub const IfExpr = struct { + condition: *Node, + then_branch: *Node, + else_branch: ?*Node, + is_inline: bool, // true for `if cond then a else b` +}; + +pub const MatchExpr = struct { + subject: *Node, + arms: []const MatchArm, +}; + +pub const MatchArm = struct { + pattern: ?*Node, // null = else (default) arm + body: *Node, + is_break: bool, +}; + +pub const ConstDecl = struct { + name: []const u8, + type_annotation: ?*Node, + value: *Node, +}; + +pub const VarDecl = struct { + name: []const u8, + type_annotation: ?*Node, + value: ?*Node, +}; + +pub const Assignment = struct { + target: *Node, + op: Op, + value: *Node, + + pub const Op = enum { + assign, + add_assign, + sub_assign, + mul_assign, + div_assign, + mod_assign, + }; +}; + +pub const EnumDecl = struct { + name: []const u8, + variants: []const []const u8, +}; + +pub const StructTypeParam = struct { + name: []const u8, // e.g. "N" or "T" (without $) + constraint: *Node, // type_expr: "u32" for value param, "Type" for type param +}; + +pub const StructDecl = struct { + name: []const u8, + field_names: []const []const u8, + field_types: []const *Node, // type_expr nodes + field_defaults: []const ?*Node, // default value per field, null if none + type_params: []const StructTypeParam = &.{}, +}; + +pub const StructFieldInit = struct { + name: ?[]const u8, // null for positional, non-null for named/shorthand + value: *Node, +}; + +pub const StructLiteral = struct { + struct_name: ?[]const u8, // null for anonymous `.{ ... }` + type_expr: ?*Node = null, // for GenericType(args).{ ... } + field_inits: []const StructFieldInit, +}; + +pub const Lambda = struct { + params: []const Param, + return_type: ?*Node, + body: *Node, + type_params: []const StructTypeParam = &.{}, +}; + +pub const TypeExpr = struct { + name: []const u8, + is_generic: bool = false, +}; + +pub const DeferStmt = struct { + expr: *Node, +}; + +pub const ComptimeExpr = struct { + expr: *Node, +}; + +pub const InsertExpr = struct { + expr: *Node, +}; + +pub const ReturnStmt = struct { + value: ?*Node, +}; + +pub const ImportDecl = struct { + path: []const u8, + name: ?[]const u8, +}; + +pub const ArrayTypeExpr = struct { + length: *Node, // int_literal for the size + element_type: *Node, // type_expr for the element type +}; + +pub const ArrayLiteral = struct { + elements: []const *Node, + type_expr: ?*Node = null, +}; + +pub const ParameterizedTypeExpr = struct { + name: []const u8, // e.g. "Vector", or later generic struct names + args: []const *Node, // e.g. [int_literal(3), type_expr("f32")] +}; + +pub const IndexExpr = struct { + object: *Node, + index: *Node, +}; + +pub const WhileExpr = struct { + condition: *Node, + body: *Node, +}; + +pub const ForExpr = struct { + iterable: *Node, + body: *Node, +}; + +pub const SpreadExpr = struct { + operand: *Node, +}; + +pub const UnionDecl = struct { + name: []const u8, + variant_names: []const []const u8, + variant_types: []const ?*Node, // null for void variants +}; + +pub const UnionLiteral = struct { + union_name: ?[]const u8, // null for anonymous `.variant(expr)` + variant_name: []const u8, + payload: ?*Node, // null for void variants +}; + +pub const NamespaceDecl = struct { + name: []const u8, + decls: []const *Node, +}; diff --git a/src/builtins.zig b/src/builtins.zig new file mode 100644 index 0000000..f6028a8 --- /dev/null +++ b/src/builtins.zig @@ -0,0 +1,25 @@ +const llvm = @import("llvm_api.zig"); +const c = llvm.c; + +pub const Builtins = struct { + printf_fn: c.LLVMValueRef, + calloc_fn: c.LLVMValueRef, + + pub fn init(module: c.LLVMModuleRef, ctx: c.LLVMContextRef) Builtins { + const ptr_type = c.LLVMPointerTypeInContext(ctx, 0); + const i64_type = c.LLVMInt64TypeInContext(ctx); + const i32_type = c.LLVMInt32TypeInContext(ctx); + + // Declare: int printf(const char*, ...) + var printf_params = [_]c.LLVMTypeRef{ptr_type}; + const printf_type = c.LLVMFunctionType(i32_type, &printf_params, 1, 1); + const printf_fn = c.LLVMAddFunction(module, "printf", printf_type); + + // Declare: void* calloc(size_t count, size_t size) + var calloc_params = [_]c.LLVMTypeRef{ i64_type, i64_type }; + const calloc_type = c.LLVMFunctionType(ptr_type, &calloc_params, 2, 0); + const calloc_fn = c.LLVMAddFunction(module, "calloc", calloc_type); + + return .{ .printf_fn = printf_fn, .calloc_fn = calloc_fn }; + } +}; diff --git a/src/codegen.zig b/src/codegen.zig new file mode 100644 index 0000000..d9c1381 --- /dev/null +++ b/src/codegen.zig @@ -0,0 +1,4999 @@ +const std = @import("std"); +const ast = @import("ast.zig"); +const Node = ast.Node; +const Span = ast.Span; +const llvm = @import("llvm_api.zig"); +const c = llvm.c; +const types = @import("types.zig"); +const Type = types.Type; +const Builtins = @import("builtins.zig").Builtins; +const Parser = @import("parser.zig").Parser; +const errors = @import("errors.zig"); +const sema = @import("sema.zig"); +const comptime_mod = @import("comptime.zig"); + +pub const CodeGen = struct { + context: c.LLVMContextRef, + module: c.LLVMModuleRef, + builder: c.LLVMBuilderRef, + allocator: std.mem.Allocator, + + // Symbol table: maps variable names to their alloca pointers + named_values: std.StringHashMap(NamedValue), + // Enum type registry: maps enum name to variant list + enum_types: std.StringHashMap([]const []const u8), + // Type alias registry: maps alias name to target type name + type_aliases: std.StringHashMap([]const u8), + // Struct type registry: maps struct name to field info + LLVM type + struct_types: std.StringHashMap(StructInfo), + // Union type registry: maps union name to variant info + LLVM type + union_types: std.StringHashMap(UnionInfo), + // Built-in functions (printf, etc.) + builtins: ?Builtins, + // Current function being generated (for alloca insertion) + current_function: c.LLVMValueRef, + // Return type of the current function being generated + current_return_type: Type = .void_type, + // Scope save stack: each entry records shadowed names to restore on scope exit + scope_saves: std.ArrayList(std.ArrayList(ScopeEntry)), + // Defer stack: parallel to scope_saves, each entry holds deferred expressions + defer_stack: std.ArrayList(std.ArrayList(*Node)), + // Compile-time globals: maps name to global variable info for #run results + comptime_globals: std.StringHashMap(ComptimeGlobal), + // Top-level #run expressions for side effects only + comptime_side_effects: std.ArrayList(*Node), + // Generic function templates: maps name to AST for deferred monomorphization + generic_templates: std.StringHashMap(GenericTemplate), + // Instantiated generic functions: maps mangled name to LLVM function + generic_instances: std.StringHashMap(c.LLVMValueRef), + // Active type parameter bindings during generic instantiation (null when not instantiating) + type_param_bindings: ?std.StringHashMap(Type) = null, + // Active value parameter bindings during generic struct instantiation + value_param_bindings: ?std.StringHashMap(i64) = null, + // Active comptime param AST nodes during generic function instantiation (for #insert substitution) + comptime_param_nodes: ?std.StringHashMap(*Node) = null, + // Generic struct templates: maps name to AST for deferred instantiation + generic_struct_templates: std.StringHashMap(GenericStructTemplate), + // Known namespace names (for import resolution) + namespaces: std.StringHashMap(void), + // Functions declared with #builtin (only available when imported) + builtin_functions: std.StringHashMap(void), + // Active namespace during body generation of imported modules + current_namespace: ?[]const u8 = null, + // Diagnostics list (optional, for structured error reporting) + diagnostics: ?*errors.DiagnosticList = null, + // Current source span (set at genExpr/genStmt/genExprAsType entry) + current_span: Span = .{ .start = 0, .end = 0 }, + // Loop context: break/continue target basic blocks (null when not in a loop) + loop_break_bb: c.LLVMBasicBlockRef = null, + loop_continue_bb: c.LLVMBasicBlockRef = null, + // Sema result (optional, for type-aware comptime evaluation) + sema_result: ?*const sema.SemaResult = null, + // Root declarations from the AST (for VM on-demand function compilation) + root_decls: []const *Node = &.{}, + // Cached LLVM struct type for string slices {ptr, i32} + string_struct_type: c.LLVMTypeRef = null, + // Cached LLVM struct type for Any {i32 tag, i64 value} + any_struct_type: c.LLVMTypeRef = null, + // Dynamic type ID assignment for Any tags (named types get unique IDs starting from 7) + any_type_id_map: std.StringHashMap(u64), + next_any_type_id: u64 = 7, + // Cache of auto-generated to_string functions for complex types + // Variadic function info: maps function name to variadic metadata + variadic_functions: std.StringHashMap(VariadicInfo), + // Enriched Any type entries: maps type name to tag + category + sx type + any_type_entries: std.StringHashMap(AnyTypeEntry), + // Current match arm type entries (set during category match arm body generation) + current_match_tags: ?[]const u64 = null, + + const TypeCategory = enum { + struct_cat, + enum_cat, + union_cat, + vector_cat, + array_cat, + slice_cat, + }; + + const AnyTypeEntry = struct { + tag_id: u64, + category: TypeCategory, + sx_type: Type, + }; + + const VariadicInfo = struct { + fixed_param_count: u32, // number of non-variadic params + element_type_name: []const u8, // element type of the variadic slice (e.g. "s32") + }; + + const GenericTemplate = struct { + fd: ast.FnDecl, + }; + + const GenericStructTemplate = struct { + sd: ast.StructDecl, + }; + + const ComptimeGlobal = struct { + global: c.LLVMValueRef, // LLVM global variable + ty: Type, // sx type + expr: *Node, // the inner expression to JIT-evaluate + is_resolved: bool = false, // true if initializer already set (no JIT needed) + }; + + const StructInfo = struct { + field_names: []const []const u8, + field_types: []const Type, + field_defaults: []const ?*Node, + llvm_type: c.LLVMTypeRef, + display_name: ?[]const u8 = null, // pretty name for generic instances + }; + + const UnionInfo = struct { + variant_names: []const []const u8, + variant_types: []const Type, // void_type for void variants + llvm_type: c.LLVMTypeRef, // { i32, [max_payload_size x i8] } + max_payload_size: u64, + }; + + // Scope stack entry: records what a name mapped to before being shadowed + const ScopeEntry = struct { + name: []const u8, + prev: ?NamedValue, // null = name didn't exist before this scope + }; + + const NamedValue = struct { + ptr: c.LLVMValueRef, // alloca pointer + ty: Type, // sx type + }; + + pub fn init(allocator: std.mem.Allocator, module_name: [*:0]const u8) CodeGen { + const ctx = c.LLVMContextCreate(); + const module = c.LLVMModuleCreateWithNameInContext(module_name, ctx); + const builder = c.LLVMCreateBuilderInContext(ctx); + return .{ + .context = ctx, + .module = module, + .builder = builder, + .allocator = allocator, + .named_values = std.StringHashMap(NamedValue).init(allocator), + .enum_types = std.StringHashMap([]const []const u8).init(allocator), + .type_aliases = std.StringHashMap([]const u8).init(allocator), + .struct_types = std.StringHashMap(StructInfo).init(allocator), + .union_types = std.StringHashMap(UnionInfo).init(allocator), + .builtins = null, + .current_function = null, + .scope_saves = std.ArrayList(std.ArrayList(ScopeEntry)).empty, + .defer_stack = std.ArrayList(std.ArrayList(*Node)).empty, + .comptime_globals = std.StringHashMap(ComptimeGlobal).init(allocator), + .comptime_side_effects = std.ArrayList(*Node).empty, + .generic_templates = std.StringHashMap(GenericTemplate).init(allocator), + .generic_instances = std.StringHashMap(c.LLVMValueRef).init(allocator), + .generic_struct_templates = std.StringHashMap(GenericStructTemplate).init(allocator), + .namespaces = std.StringHashMap(void).init(allocator), + .builtin_functions = std.StringHashMap(void).init(allocator), + .variadic_functions = std.StringHashMap(VariadicInfo).init(allocator), + .any_type_id_map = std.StringHashMap(u64).init(allocator), + .any_type_entries = std.StringHashMap(AnyTypeEntry).init(allocator), + }; + } + + pub fn deinit(self: *CodeGen) void { + self.named_values.deinit(); + self.enum_types.deinit(); + self.type_aliases.deinit(); + self.struct_types.deinit(); + self.union_types.deinit(); + self.comptime_globals.deinit(); + self.generic_templates.deinit(); + self.generic_instances.deinit(); + self.generic_struct_templates.deinit(); + self.namespaces.deinit(); + self.builtin_functions.deinit(); + self.variadic_functions.deinit(); + self.any_type_id_map.deinit(); + self.any_type_entries.deinit(); + c.LLVMDisposeBuilder(self.builder); + c.LLVMDisposeModule(self.module); + c.LLVMContextDispose(self.context); + } + + fn emitError(self: *CodeGen, msg: []const u8) error{CodeGenError} { + if (self.diagnostics) |diags| diags.add(.err, msg, self.current_span); + return error.CodeGenError; + } + + fn emitErrorFmt(self: *CodeGen, comptime fmt: []const u8, args: anytype) error{CodeGenError} { + if (self.diagnostics) |diags| diags.addFmt(.err, self.current_span, fmt, args); + return error.CodeGenError; + } + + pub fn typeToLLVM(self: *CodeGen, ty: Type) c.LLVMTypeRef { + return switch (ty) { + .signed => |w| c.LLVMIntTypeInContext(self.context, w), + .unsigned => |w| c.LLVMIntTypeInContext(self.context, w), + .f32 => c.LLVMFloatTypeInContext(self.context), + .f64 => c.LLVMDoubleTypeInContext(self.context), + .void_type => c.LLVMVoidTypeInContext(self.context), + .boolean => c.LLVMInt1TypeInContext(self.context), + .string_type, .slice_type => self.getStringStructType(), // slices use same {ptr, i32} layout + .enum_type => c.LLVMInt32TypeInContext(self.context), + .struct_type => |name| if (self.struct_types.get(name)) |info| info.llvm_type else unreachable, + .union_type => |name| if (self.union_types.get(name)) |info| info.llvm_type else unreachable, + .array_type => |info| { + const elem_ty = Type.fromName(info.element_name) orelse unreachable; + return c.LLVMArrayType2(self.typeToLLVM(elem_ty), info.length); + }, + .vector_type => |info| { + const elem_ty = Type.fromName(info.element_name) orelse unreachable; + return c.LLVMVectorType(self.typeToLLVM(elem_ty), info.length); + }, + .any_type => self.getAnyStructType(), + .meta_type => c.LLVMPointerTypeInContext(self.context, 0), + }; + } + + fn getAnyStructType(self: *CodeGen) c.LLVMTypeRef { + if (self.any_struct_type) |t| return t; + var field_types = [_]c.LLVMTypeRef{ + c.LLVMInt32TypeInContext(self.context), // type tag + c.LLVMInt64TypeInContext(self.context), // value (fits all primitives) + }; + self.any_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); + return self.any_struct_type.?; + } + + /// Type tag constants for Any type (builtins: 0-6, Type: 10, named types: 7+ dynamic) + const ANY_TAG_VOID: u64 = 0; + const ANY_TAG_BOOL: u64 = 1; + const ANY_TAG_S32: u64 = 2; + const ANY_TAG_S64: u64 = 3; + const ANY_TAG_F32: u64 = 4; + const ANY_TAG_F64: u64 = 5; + const ANY_TAG_STRING: u64 = 6; + const ANY_TAG_TYPE: u64 = 10; + + /// Get or assign a unique type ID for a named type (struct, enum, union, vector, array). + /// IDs start at 7 and are assigned dynamically per compilation. + /// Also populates `any_type_entries` with category and type info. + fn getAnyTypeId(self: *CodeGen, name: []const u8, sx_type: Type) !u64 { + const gop = try self.any_type_id_map.getOrPut(name); + if (!gop.found_existing) { + gop.value_ptr.* = self.next_any_type_id; + self.next_any_type_id += 1; + // Skip over reserved slot 10 (ANY_TAG_TYPE) + if (self.next_any_type_id == ANY_TAG_TYPE) self.next_any_type_id += 1; + + // Determine category from the sx type + const category: TypeCategory = switch (sx_type) { + .struct_type => .struct_cat, + .enum_type => .enum_cat, + .union_type => .union_cat, + .vector_type => .vector_cat, + .array_type => .array_cat, + .slice_type => .slice_cat, + else => .struct_cat, // fallback + }; + try self.any_type_entries.put(name, .{ + .tag_id = gop.value_ptr.*, + .category = category, + .sx_type = sx_type, + }); + } + return gop.value_ptr.*; + } + + /// Build an Any value { tag: i32, value: i64 } from a typed LLVM value. + /// Small values (ints, floats, bools, enums) are stored inline in the i64. + /// Complex values (strings, structs, unions) are stored via pointer (alloca + ptr-to-int). + fn buildAnyValue(self: *CodeGen, val: c.LLVMValueRef, ty: Type) !c.LLVMValueRef { + const any_ty = self.getAnyStructType(); + const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i64_ty = c.LLVMInt64TypeInContext(self.context); + const undef = c.LLVMGetUndef(any_ty); + + // Determine tag + const tag: u64 = switch (ty) { + .void_type => ANY_TAG_VOID, + .boolean => ANY_TAG_BOOL, + .signed => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, + .unsigned => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, + .f32 => ANY_TAG_F32, + .f64 => ANY_TAG_F64, + .string_type => ANY_TAG_STRING, + .struct_type => |name| try self.getAnyTypeId(name, ty), + .enum_type => |name| try self.getAnyTypeId(name, ty), + .union_type => |name| try self.getAnyTypeId(name, ty), + .vector_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ info.length, info.element_name }), ty), + .array_type => |info| try self.getAnyTypeId(try std.fmt.allocPrint(self.allocator, "[{d}]{s}", .{ info.length, info.element_name }), ty), + .meta_type => ANY_TAG_TYPE, + else => ANY_TAG_S32, + }; + const tag_val = c.LLVMConstInt(i32_ty, tag, 0); + const with_tag = c.LLVMBuildInsertValue(self.builder, undef, tag_val, 0, "any_tag"); + + // Convert value to i64 + const val_as_i64 = switch (ty) { + .boolean => c.LLVMBuildZExt(self.builder, val, i64_ty, "any_bool"), + .signed => |w| if (w <= 32) + c.LLVMBuildSExt(self.builder, val, i64_ty, "any_int") + else + val, + .unsigned => |w| if (w <= 32) + c.LLVMBuildZExt(self.builder, val, i64_ty, "any_uint") + else + val, + .f32 => blk: { + // f32 -> f64 -> bitcast to i64 + const as_f64 = c.LLVMBuildFPExt(self.builder, val, c.LLVMDoubleTypeInContext(self.context), "f32_to_f64"); + break :blk c.LLVMBuildBitCast(self.builder, as_f64, i64_ty, "any_f32"); + }, + .f64 => c.LLVMBuildBitCast(self.builder, val, i64_ty, "any_f64"), + .string_type => blk: { + // String is {ptr, i32} — store to alloca, pass alloca as i64 + const str_alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), "any_str_tmp"); + _ = c.LLVMBuildStore(self.builder, val, str_alloca); + break :blk c.LLVMBuildPtrToInt(self.builder, str_alloca, i64_ty, "any_str"); + }, + .struct_type => |sname| blk: { + // Struct — store to alloca, pass pointer as i64 + const info = self.struct_types.get(sname) orelse + return c.LLVMGetUndef(any_ty); + const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "any_struct_tmp"); + _ = c.LLVMBuildStore(self.builder, val, alloca); + break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_struct"); + }, + .enum_type => blk: { + // Enum is i32 tag — extend to i64 + break :blk c.LLVMBuildZExt(self.builder, val, i64_ty, "any_enum"); + }, + .union_type => |uname| blk: { + // Union — store to alloca, pass pointer as i64 + const info = self.union_types.get(uname) orelse + return c.LLVMGetUndef(any_ty); + const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "any_union_tmp"); + _ = c.LLVMBuildStore(self.builder, val, alloca); + break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_union"); + }, + .vector_type, .array_type => blk: { + // Vector/Array — store to alloca, pass pointer as i64 + const llvm_ty = self.typeToLLVM(ty); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, "any_vec_tmp"); + _ = c.LLVMBuildStore(self.builder, val, alloca); + break :blk c.LLVMBuildPtrToInt(self.builder, alloca, i64_ty, "any_vec"); + }, + .meta_type => blk: { + // Meta type is a pointer (global string) — convert via ptrtoint + break :blk c.LLVMBuildPtrToInt(self.builder, val, i64_ty, "any_type"); + }, + else => c.LLVMBuildSExt(self.builder, val, i64_ty, "any_val"), + }; + return c.LLVMBuildInsertValue(self.builder, with_tag, val_as_i64, 1, "any_value"); + } + + fn getStringStructType(self: *CodeGen) c.LLVMTypeRef { + if (self.string_struct_type) |t| return t; + var field_types = [_]c.LLVMTypeRef{ + c.LLVMPointerTypeInContext(self.context, 0), // ptr + c.LLVMInt32TypeInContext(self.context), // len + }; + self.string_struct_type = c.LLVMStructTypeInContext(self.context, &field_types, 2, 0); + return self.string_struct_type.?; + } + + /// Build a string slice {ptr, len} from a raw pointer and a constant length. + fn buildStringSlice(self: *CodeGen, ptr: c.LLVMValueRef, len: u32) c.LLVMValueRef { + const str_ty = self.getStringStructType(); + const undef = c.LLVMGetUndef(str_ty); + const with_ptr = c.LLVMBuildInsertValue(self.builder, undef, ptr, 0, "str_ptr"); + const len_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), len, 0); + return c.LLVMBuildInsertValue(self.builder, with_ptr, len_val, 1, "str_slice"); + } + + /// Build a string slice {ptr, len} from a raw pointer and a runtime length value. + fn buildStringSliceRT(self: *CodeGen, ptr: c.LLVMValueRef, len_val: c.LLVMValueRef) c.LLVMValueRef { + const str_ty = self.getStringStructType(); + const undef = c.LLVMGetUndef(str_ty); + const with_ptr = c.LLVMBuildInsertValue(self.builder, undef, ptr, 0, "str_ptr"); + return c.LLVMBuildInsertValue(self.builder, with_ptr, len_val, 1, "str_slice"); + } + + fn pushScope(self: *CodeGen) !void { + try self.scope_saves.append(self.allocator, std.ArrayList(ScopeEntry).empty); + try self.defer_stack.append(self.allocator, std.ArrayList(*Node).empty); + } + + fn popScope(self: *CodeGen) !void { + // 1. Execute deferred expressions in LIFO order + if (self.defer_stack.items.len > 0) { + const defers = self.defer_stack.items[self.defer_stack.items.len - 1]; + var i: usize = defers.items.len; + while (i > 0) { + i -= 1; + _ = try self.genExpr(defers.items[i]); + } + _ = self.defer_stack.pop(); + } + + // 2. Restore shadowed variables + if (self.scope_saves.items.len > 0) { + const saves = self.scope_saves.items[self.scope_saves.items.len - 1]; + // Restore in reverse order + var i: usize = saves.items.len; + while (i > 0) { + i -= 1; + const entry = saves.items[i]; + if (entry.prev) |prev| { + self.named_values.putAssumeCapacity(entry.name, prev); + } else { + _ = self.named_values.remove(entry.name); + } + } + _ = self.scope_saves.pop(); + } + } + + /// Emit all pending deferred expressions from all active scopes (LIFO order, + /// innermost scope first). Does NOT pop the stacks — used before `return` + /// so that popScope() can still clean up the data structures later. + fn emitAllDefers(self: *CodeGen) !void { + var i: usize = self.defer_stack.items.len; + while (i > 0) { + i -= 1; + const defers = self.defer_stack.items[i]; + var j: usize = defers.items.len; + while (j > 0) { + j -= 1; + _ = try self.genExpr(defers.items[j]); + } + } + } + + fn saveShadowed(self: *CodeGen, name: []const u8) !void { + if (self.scope_saves.items.len == 0) return; + const top = &self.scope_saves.items[self.scope_saves.items.len - 1]; + const prev = self.named_values.get(name); + try top.append(self.allocator, .{ .name = name, .prev = prev }); + } + + pub fn generate(self: *CodeGen, root: *Node) !void { + if (root.data != .root) return self.emitError("expected root node for code generation"); + // Store root decls for VM on-demand function compilation + self.root_decls = root.data.root.decls; + // Initialize built-in function declarations (printf, etc.) + self.builtins = Builtins.init(self.module, self.context); + + // Pass 1: Register all declarations (signatures only, no bodies) + for (root.data.root.decls) |decl| { + switch (decl.data) { + .fn_decl => |fd| { + if (fd.body.data == .builtin_expr) { + try self.builtin_functions.put(fd.name, {}); + } else if (fd.type_params.len > 0) { + try self.generic_templates.put(fd.name, .{ .fd = fd }); + } else { + try self.registerFnDecl(fd); + } + }, + .enum_decl => |ed| { + try self.enum_types.put(ed.name, ed.variants); + _ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name }); + }, + .struct_decl => |sd| try self.registerStructType(sd), + .union_decl => |ud| try self.registerUnionType(ud), + .const_decl => |cd| { + if (cd.value.data == .builtin_expr) { + // #builtin constant — skip codegen + } else if (cd.value.data == .lambda) { + try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); + } else if (cd.value.data == .type_expr) { + try self.type_aliases.put(cd.name, cd.value.data.type_expr.name); + } else if (cd.value.data == .call) { + // Check if this is a generic struct or type function instantiation + const callee_name = if (cd.value.data.call.callee.data == .identifier) + cd.value.data.call.callee.data.identifier.name + else + null; + if (callee_name) |cn| { + if (self.generic_struct_templates.get(cn)) |tmpl| { + // Generic struct instantiation: Vec3 :: Vec(3, f32); + const result_ty = try self.instantiateGenericStruct(cn, tmpl, cd.value.data.call.args); + if (result_ty.isStruct()) { + try self.type_aliases.put(cd.name, result_ty.struct_type); + } + } else if (self.generic_templates.get(cn)) |tmpl| { + // Type-returning function: Foo :: Complex(u32); + const result_ty = try self.instantiateTypeFunction(cd.name, cn, tmpl, cd.value.data.call.args); + if (result_ty.isStruct()) { + try self.type_aliases.put(cd.name, result_ty.struct_type); + } else if (result_ty.isUnion()) { + try self.type_aliases.put(cd.name, result_ty.union_type); + } + } else { + try self.registerTopLevelConstant(cd); + } + } else { + try self.registerTopLevelConstant(cd); + } + } else if (cd.value.data == .comptime_expr) { + // Use explicit type annotation if available + const ct_type_override: ?Type = if (cd.type_annotation) |te| Type.fromTypeExpr(te) else null; + try self.registerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, ct_type_override); + } else { + // Top-level value constant (e.g., SPECIAL_VALUE :u8: 42;) + try self.registerTopLevelConstant(cd); + } + }, + .comptime_expr => |ct| { + try self.comptime_side_effects.append(self.allocator, ct.expr); + }, + .namespace_decl => |ns| { + try self.registerNamespace(ns); + }, + else => {}, + } + } + + // Pre-register all known types for Any type ID assignment + { + var it = self.struct_types.iterator(); + while (it.next()) |entry| { + _ = try self.getAnyTypeId(entry.key_ptr.*, .{ .struct_type = entry.key_ptr.* }); + } + } + { + var it = self.enum_types.iterator(); + while (it.next()) |entry| { + _ = try self.getAnyTypeId(entry.key_ptr.*, .{ .enum_type = entry.key_ptr.* }); + } + } + { + var it = self.union_types.iterator(); + while (it.next()) |entry| { + _ = try self.getAnyTypeId(entry.key_ptr.*, .{ .union_type = entry.key_ptr.* }); + } + } + + // Pass 2: Generate all function bodies + for (root.data.root.decls) |decl| { + switch (decl.data) { + .fn_decl => |fd| { + if (fd.body.data == .builtin_expr) { + // skip + } else if (fd.type_params.len == 0) { + try self.genFnBody(fd); + } + }, + .const_decl => |cd| { + if (cd.value.data == .lambda) { + try self.genLambdaBody(cd.name, cd.value.data.lambda); + } + }, + .namespace_decl => |ns| { + try self.genNamespaceBodies(ns); + }, + else => {}, + } + } + + // Execute comptime side effects via bytecode VM (e.g., #run main();) + for (self.comptime_side_effects.items) |expr| { + _ = try self.comptimeEval(expr, .void_type); + } + } + + /// Evaluate a comptime expression using the bytecode VM. + /// No LLVM state save/restore needed — the VM operates independently. + fn comptimeEval(self: *CodeGen, expr: *Node, expected_type: Type) !comptime_mod.Value { + _ = expected_type; // VM infers types from values; expected_type used by caller for LLVM conversion + + var compiler = comptime_mod.Compiler.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self); + const chunk = compiler.compile(expr) catch |err| { + return self.emitErrorFmt("comptime compilation failed: {s}", .{@errorName(err)}); + }; + + var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self); + return vm.execute(&chunk) catch |err| { + return self.emitErrorFmt("comptime execution failed: {s}", .{@errorName(err)}); + }; + } + + /// Substitute comptime param identifiers in an AST expression with their literal nodes. + /// Used before comptimeEval in #insert to resolve comptime function params. + fn substituteComptimeNodes(self: *CodeGen, node: *Node) !*Node { + const cpn = self.comptime_param_nodes orelse return node; + + // Direct identifier match + if (node.data == .identifier) { + if (cpn.get(node.data.identifier.name)) |replacement| { + return replacement; + } + } + + // Recurse into call arguments + if (node.data == .call) { + var new_args = try self.allocator.alloc(*Node, node.data.call.args.len); + var changed = false; + for (node.data.call.args, 0..) |arg, i| { + new_args[i] = try self.substituteComptimeNodes(arg); + if (new_args[i] != arg) changed = true; + } + if (changed) { + const new_node = try self.allocator.create(Node); + new_node.* = .{ + .span = node.span, + .data = .{ .call = .{ + .callee = node.data.call.callee, + .args = new_args, + } }, + }; + return new_node; + } + } + + // Recurse into binary ops + if (node.data == .binary_op) { + const new_lhs = try self.substituteComptimeNodes(node.data.binary_op.lhs); + const new_rhs = try self.substituteComptimeNodes(node.data.binary_op.rhs); + if (new_lhs != node.data.binary_op.lhs or new_rhs != node.data.binary_op.rhs) { + const new_node = try self.allocator.create(Node); + new_node.* = .{ + .span = node.span, + .data = .{ .binary_op = .{ + .op = node.data.binary_op.op, + .lhs = new_lhs, + .rhs = new_rhs, + } }, + }; + return new_node; + } + } + + return node; + } + + /// Convert a comptime VM Value to an LLVM constant value. + fn comptimeValueToLLVM(self: *CodeGen, value: comptime_mod.Value, ty: Type) c.LLVMValueRef { + return switch (value) { + .int_val => |v| c.LLVMConstInt(self.typeToLLVM(ty), @bitCast(v), 0), + .float_val => |v| c.LLVMConstReal(c.LLVMDoubleTypeInContext(self.context), v), + .float32_val => |v| c.LLVMConstReal(c.LLVMFloatTypeInContext(self.context), @as(f64, v)), + .bool_val => |v| c.LLVMConstInt(c.LLVMInt1TypeInContext(self.context), if (v) 1 else 0, 0), + .string_val => |v| blk: { + const z = self.allocator.dupeZ(u8, v) catch unreachable; + const ptr = c.LLVMBuildGlobalStringPtr(self.builder, z.ptr, "comptime_str"); + break :blk self.buildStringSlice(ptr, @intCast(v.len)); + }, + .void_val => c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + .struct_val, .array_val, .type_val, .function_val => unreachable, + }; + } + + /// Lazily resolve a comptime global by evaluating its expression via bytecode VM. + fn resolveComptimeGlobal(self: *CodeGen, ct: *ComptimeGlobal) !void { + const result = try self.comptimeEval(ct.expr, ct.ty); + const const_val = self.comptimeValueToLLVM(result, ct.ty); + c.LLVMSetInitializer(ct.global, const_val); + c.LLVMSetGlobalConstant(ct.global, 1); + ct.is_resolved = true; + } + + fn resolveType(self: *CodeGen, type_node: ?*Node) Type { + if (type_node) |tn| { + if (Type.fromTypeExpr(tn)) |t| return t; + // Array type: [N]T + if (tn.data == .array_type_expr) { + const ate = tn.data.array_type_expr; + const length: u32 = @intCast(ate.length.data.int_literal.value); + const elem_type = self.resolveType(ate.element_type); + const elem_name = elem_type.displayName(self.allocator) catch unreachable; + return .{ .array_type = .{ .element_name = elem_name, .length = length } }; + } + // Parameterized type: Vector(N, T) or generic struct instantiation + if (tn.data == .parameterized_type_expr) { + const pte = tn.data.parameterized_type_expr; + // Direct lookup (unqualified names from flat imports) + if (self.builtin_functions.contains(pte.name)) { + if (self.resolveBuiltinType(pte.name, pte.args)) |ty| return ty; + } + if (self.generic_struct_templates.get(pte.name)) |tmpl| { + return self.instantiateGenericStruct(pte.name, tmpl, pte.args) catch .void_type; + } + // Progressive namespace resolution for dotted names (e.g. "std.Vector") + if (std.mem.indexOfScalar(u8, pte.name, '.')) |dot| { + const ns = pte.name[0..dot]; + if (self.namespaces.contains(ns)) { + // Namespace verified — look up qualified name in registries + if (self.builtin_functions.contains(pte.name)) { + if (self.resolveBuiltinType(pte.name, pte.args)) |ty| return ty; + } + if (self.generic_struct_templates.get(pte.name)) |tmpl| { + return self.instantiateGenericStruct(pte.name, tmpl, pte.args) catch .void_type; + } + } + } + if (self.diagnostics) |diags| diags.addFmt(.err, tn.span, "unresolved type '{s}'", .{pte.name}); + return .void_type; + } + // Call expression as type: Vec(3, f32) → generic struct/type function instantiation + if (tn.data == .call) { + if (tn.data.call.callee.data == .identifier) { + const name = tn.data.call.callee.data.identifier.name; + if (self.generic_struct_templates.get(name)) |tmpl| { + return self.instantiateGenericStruct(name, tmpl, tn.data.call.args) catch .void_type; + } + if (self.generic_templates.get(name)) |tmpl| { + return self.instantiateTypeFunction(name, name, tmpl, tn.data.call.args) catch .void_type; + } + } + return .void_type; + } + // Check type parameter bindings (during generic instantiation) + if (tn.data == .type_expr or tn.data == .identifier) { + const name = if (tn.data == .type_expr) tn.data.type_expr.name else tn.data.identifier.name; + // Try primitive type name first + if (Type.fromName(name)) |t| return t; + if (self.type_param_bindings) |bindings| { + if (bindings.get(name)) |t| return t; + } + // Check type aliases + if (self.type_aliases.get(name)) |target| { + if (Type.fromName(target)) |t| return t; + if (self.struct_types.contains(target)) return .{ .struct_type = target }; + if (self.union_types.contains(target)) return .{ .union_type = target }; + } + // Check enum types + if (self.enum_types.contains(name)) return .{ .enum_type = name }; + // Check struct types + if (self.struct_types.contains(name)) return .{ .struct_type = name }; + // Check union types + if (self.union_types.contains(name)) return .{ .union_type = name }; + } + return .void_type; + } + return .void_type; + } + + /// Resolve a value argument to an integer — handles int_literal and identifier referencing value_param_bindings. + fn resolveValueArg(self: *CodeGen, node: *Node) i64 { + if (node.data == .int_literal) return node.data.int_literal.value; + if (node.data == .identifier or node.data == .type_expr) { + const name = if (node.data == .identifier) node.data.identifier.name else node.data.type_expr.name; + if (self.value_param_bindings) |bindings| { + if (bindings.get(name)) |val| return val; + } + } + return 0; + } + + /// Instantiate a generic struct template with concrete arguments. + /// Returns the struct_type for the instantiated struct (possibly cached). + fn instantiateGenericStruct(self: *CodeGen, template_name: []const u8, tmpl: GenericStructTemplate, args: []const *Node) !Type { + const sd = tmpl.sd; + + // Build bindings from template params + args + var type_bindings = std.StringHashMap(Type).init(self.allocator); + var val_bindings = std.StringHashMap(i64).init(self.allocator); + + for (sd.type_params, 0..) |tp, i| { + if (i >= args.len) return self.emitErrorFmt("generic struct '{s}' expects {d} type arguments, got {d}", .{ template_name, sd.type_params.len, args.len }); + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (std.mem.eql(u8, constraint_name, "Type")) { + // Type parameter: resolve arg as type + const resolved = self.resolveType(args[i]); + try type_bindings.put(tp.name, resolved); + } else { + // Value parameter: resolve arg as integer + const val = self.resolveValueArg(args[i]); + try val_bindings.put(tp.name, val); + } + } + + // Build mangled name: Vec__3_f32 + var mangle_buf = std.ArrayList(u8).empty; + try mangle_buf.appendSlice(self.allocator, template_name); + try mangle_buf.appendSlice(self.allocator, "__"); + for (sd.type_params, 0..) |tp, i| { + if (i > 0) try mangle_buf.append(self.allocator, '_'); + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (std.mem.eql(u8, constraint_name, "Type")) { + if (type_bindings.get(tp.name)) |ty| { + const dn = ty.displayName(self.allocator) catch "?"; + try mangle_buf.appendSlice(self.allocator, dn); + } + } else { + if (val_bindings.get(tp.name)) |val| { + var tmp: [20]u8 = undefined; + const s = std.fmt.bufPrint(&tmp, "{d}", .{val}) catch "0"; + try mangle_buf.appendSlice(self.allocator, s); + } + } + } + const mangled_name = try mangle_buf.toOwnedSlice(self.allocator); + + // Check if already instantiated + if (self.struct_types.contains(mangled_name)) { + return .{ .struct_type = mangled_name }; + } + + // Instantiate: resolve field types with bindings active + const saved_type_bindings = self.type_param_bindings; + const saved_value_bindings = self.value_param_bindings; + self.type_param_bindings = type_bindings; + self.value_param_bindings = val_bindings; + defer { + self.type_param_bindings = saved_type_bindings; + self.value_param_bindings = saved_value_bindings; + } + + var field_sx_types = std.ArrayList(Type).empty; + var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + + for (sd.field_types) |ft| { + const sx_ty = self.resolveType(ft); + try field_sx_types.append(self.allocator, sx_ty); + try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); + } + + const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); + const name_z = try self.allocator.dupeZ(u8, mangled_name); + const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); + + var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); + for (sd.field_defaults, 0..) |fd, i| { + resolved_defaults[i] = fd; + } + + // Build pretty display name: Vec(3,f32) + var display_buf = std.ArrayList(u8).empty; + try display_buf.appendSlice(self.allocator, template_name); + try display_buf.append(self.allocator, '('); + for (sd.type_params, 0..) |tp, i| { + if (i > 0) try display_buf.appendSlice(self.allocator, ","); + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (std.mem.eql(u8, constraint_name, "Type")) { + if (type_bindings.get(tp.name)) |ty| { + const dn = ty.displayName(self.allocator) catch "?"; + try display_buf.appendSlice(self.allocator, dn); + } + } else { + if (val_bindings.get(tp.name)) |val| { + var tmp: [20]u8 = undefined; + const s = std.fmt.bufPrint(&tmp, "{d}", .{val}) catch "0"; + try display_buf.appendSlice(self.allocator, s); + } + } + } + try display_buf.append(self.allocator, ')'); + const display_name = try display_buf.toOwnedSlice(self.allocator); + + try self.struct_types.put(mangled_name, .{ + .field_names = sd.field_names, + .field_types = try field_sx_types.toOwnedSlice(self.allocator), + .field_defaults = resolved_defaults, + .llvm_type = struct_ty, + .display_name = display_name, + }); + _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); + + return .{ .struct_type = mangled_name }; + } + + /// Instantiate a type-returning function (e.g. Complex(u32)) by walking the body AST + /// to find `return struct { ... }` or `return union { ... }` and registering with bindings active. + fn instantiateTypeFunction(self: *CodeGen, alias_name: []const u8, template_name: []const u8, tmpl: GenericTemplate, args: []const *Node) !Type { + const fd = tmpl.fd; + + // Build type bindings from params + args + var type_bindings = std.StringHashMap(Type).init(self.allocator); + for (fd.type_params, 0..) |tp, i| { + if (i >= args.len) return self.emitErrorFmt("type function '{s}' expects {d} type arguments, got {d}", .{ template_name, fd.type_params.len, args.len }); + const resolved = self.resolveType(args[i]); + try type_bindings.put(tp.name, resolved); + } + + // Activate bindings + const saved_type_bindings = self.type_param_bindings; + self.type_param_bindings = type_bindings; + defer self.type_param_bindings = saved_type_bindings; + + // Build mangled name from template + args + var mangle_buf = std.ArrayList(u8).empty; + try mangle_buf.appendSlice(self.allocator, template_name); + try mangle_buf.appendSlice(self.allocator, "__"); + for (fd.type_params, 0..) |tp, i| { + if (i > 0) try mangle_buf.append(self.allocator, '_'); + if (type_bindings.get(tp.name)) |ty| { + const dn = ty.displayName(self.allocator) catch "?"; + try mangle_buf.appendSlice(self.allocator, dn); + } + } + const mangled_name = try mangle_buf.toOwnedSlice(self.allocator); + + // Try struct first + if (self.findStructInBody(fd.body)) |struct_decl| { + if (self.struct_types.contains(mangled_name)) { + return .{ .struct_type = mangled_name }; + } + return self.registerInstantiatedStruct(mangled_name, alias_name, struct_decl); + } + + // Try union + if (self.findUnionInBody(fd.body)) |union_decl| { + if (self.union_types.contains(mangled_name)) { + return .{ .union_type = mangled_name }; + } + return self.registerInstantiatedUnion(mangled_name, union_decl); + } + + return self.emitErrorFmt("type function '{s}' does not return a struct or union", .{template_name}); + } + + fn registerInstantiatedStruct(self: *CodeGen, mangled_name: []const u8, alias_name: []const u8, struct_decl: ast.StructDecl) !Type { + var field_sx_types = std.ArrayList(Type).empty; + var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + + for (struct_decl.field_types) |ft| { + const sx_ty = self.resolveType(ft); + try field_sx_types.append(self.allocator, sx_ty); + try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); + } + + const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); + const name_z = try self.allocator.dupeZ(u8, mangled_name); + const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); + + var resolved_defaults = try self.allocator.alloc(?*Node, struct_decl.field_defaults.len); + for (struct_decl.field_defaults, 0..) |fd_def, i| { + resolved_defaults[i] = fd_def; + } + + var display_buf = std.ArrayList(u8).empty; + try display_buf.appendSlice(self.allocator, alias_name); + const display_name = try display_buf.toOwnedSlice(self.allocator); + + try self.struct_types.put(mangled_name, .{ + .field_names = struct_decl.field_names, + .field_types = try field_sx_types.toOwnedSlice(self.allocator), + .field_defaults = resolved_defaults, + .llvm_type = struct_ty, + .display_name = display_name, + }); + _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); + + return .{ .struct_type = mangled_name }; + } + + fn registerInstantiatedUnion(self: *CodeGen, mangled_name: []const u8, union_decl: ast.UnionDecl) !Type { + var variant_sx_types = std.ArrayList(Type).empty; + var max_payload_size: u64 = 0; + const data_layout = c.LLVMGetModuleDataLayout(self.module); + + for (union_decl.variant_types) |vt| { + if (vt) |type_node| { + const sx_ty = self.resolveType(type_node); + try variant_sx_types.append(self.allocator, sx_ty); + const llvm_ty = self.typeToLLVM(sx_ty); + const size = c.LLVMStoreSizeOfType(data_layout, llvm_ty); + if (size > max_payload_size) max_payload_size = size; + } else { + try variant_sx_types.append(self.allocator, .void_type); + } + } + + const name_z = try self.allocator.dupeZ(u8, mangled_name); + const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i8_ty = c.LLVMInt8TypeInContext(self.context); + const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); + var fields = [2]c.LLVMTypeRef{ i32_ty, payload_array_ty }; + c.LLVMStructSetBody(union_ty, &fields, 2, 0); + + try self.union_types.put(mangled_name, .{ + .variant_names = union_decl.variant_names, + .variant_types = try variant_sx_types.toOwnedSlice(self.allocator), + .llvm_type = union_ty, + .max_payload_size = max_payload_size, + }); + _ = try self.getAnyTypeId(mangled_name, .{ .union_type = mangled_name }); + + return .{ .union_type = mangled_name }; + } + + /// Walk an AST body to find a struct declaration (from `return struct { ... }` or bare struct expr). + fn findStructInBody(_: *CodeGen, body: *Node) ?ast.StructDecl { + if (body.data == .struct_decl) return body.data.struct_decl; + if (body.data == .block) { + for (body.data.block.stmts) |stmt| { + if (stmt.data == .return_stmt) { + if (stmt.data.return_stmt.value) |val| { + if (val.data == .struct_decl) return val.data.struct_decl; + } + } + if (stmt.data == .struct_decl) return stmt.data.struct_decl; + } + } + return null; + } + + /// Walk an AST body to find a union declaration (from `return union { ... }` or bare union expr). + fn findUnionInBody(_: *CodeGen, body: *Node) ?ast.UnionDecl { + if (body.data == .union_decl) return body.data.union_decl; + if (body.data == .block) { + for (body.data.block.stmts) |stmt| { + if (stmt.data == .return_stmt) { + if (stmt.data.return_stmt.value) |val| { + if (val.data == .union_decl) return val.data.union_decl; + } + } + if (stmt.data == .union_decl) return stmt.data.union_decl; + } + } + return null; + } + + fn buildFnType(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8) !c.LLVMTypeRef { + const ret_sx_type = self.resolveType(return_type); + const is_main = std.mem.eql(u8, name, "main"); + const ret_llvm_type = if (is_main) + c.LLVMInt32TypeInContext(self.context) + else + self.typeToLLVM(ret_sx_type); + + var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + for (params) |param| { + if (param.is_comptime) continue; + if (param.is_variadic) { + // Variadic param becomes a slice {ptr, i32} in the LLVM signature + try param_llvm_types.append(self.allocator, self.getStringStructType()); + } else { + const sx_ty = self.resolveType(param.type_expr); + if (sx_ty == .void_type) return self.emitErrorFmt("parameter '{s}' in function '{s}' has unresolved type", .{ param.name, name }); + try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); + } + } + const params_slice = try param_llvm_types.toOwnedSlice(self.allocator); + + return c.LLVMFunctionType( + ret_llvm_type, + if (params_slice.len > 0) params_slice.ptr else null, + @intCast(params_slice.len), + 0, + ); + } + + fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl) !void { + return self.registerFnDeclAs(fd, fd.name); + } + + fn registerFnDeclAs(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void { + const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name); + const name_z = try self.allocator.dupeZ(u8, llvm_name); + _ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); + // Track variadic function info for call site packing + for (fd.params, 0..) |param, i| { + if (param.is_variadic) { + const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; + try self.variadic_functions.put(llvm_name, .{ + .fixed_param_count = @intCast(i), + .element_type_name = elem_name, + }); + break; + } + } + } + + fn registerNamespace(self: *CodeGen, ns: ast.NamespaceDecl) !void { + try self.namespaces.put(ns.name, {}); + for (ns.decls) |decl| { + switch (decl.data) { + .fn_decl => |fd| { + if (fd.body.data == .builtin_expr) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); + try self.builtin_functions.put(qualified, {}); + continue; + } + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); + if (fd.type_params.len > 0) { + try self.generic_templates.put(qualified, .{ .fd = fd }); + } else { + try self.registerFnDeclAs(fd, qualified); + } + }, + .enum_decl => |ed| { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); + try self.enum_types.put(qualified, ed.variants); + }, + .struct_decl => |sd| try self.registerStructType(sd), + .union_decl => |ud| try self.registerUnionType(ud), + .const_decl => |cd| { + if (cd.value.data == .builtin_expr) { + // #builtin constant in namespace — skip codegen + } else if (cd.value.data == .lambda) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); + try self.registerLambdaAsFunction(qualified, cd.value.data.lambda); + } else if (cd.value.data == .type_expr) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); + try self.type_aliases.put(qualified, cd.value.data.type_expr.name); + } + }, + else => {}, + } + } + } + + fn genNamespaceBodies(self: *CodeGen, ns: ast.NamespaceDecl) !void { + const saved_ns = self.current_namespace; + self.current_namespace = ns.name; + defer self.current_namespace = saved_ns; + + for (ns.decls) |decl| { + switch (decl.data) { + .fn_decl => |fd| { + if (fd.body.data == .builtin_expr) { + // skip + } else if (fd.type_params.len == 0) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); + try self.genFnBodyAs(fd, qualified); + } + }, + .const_decl => |cd| { + if (cd.value.data == .lambda) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); + try self.genLambdaBody(qualified, cd.value.data.lambda); + } + }, + else => {}, + } + } + } + + fn inferComptimeReturnType(self: *CodeGen, expr: *Node) Type { + // xx: see through to inner expression + if (expr.data == .unary_op and expr.data.unary_op.op == .xx) { + return self.inferComptimeReturnType(expr.data.unary_op.operand); + } + // For function calls, look up the registered function's return type + if (expr.data == .call) { + if (self.resolveCalleeName(expr.data.call)) |callee_name| { + const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(32); + const callee_fn = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr) orelse return Type.s(32); + const fn_type = c.LLVMGlobalGetValueType(callee_fn); + const ret_llvm = c.LLVMGetReturnType(fn_type); + return self.llvmTypeToSxType(ret_llvm); + } + } + return self.inferType(expr); + } + + /// Map an LLVM type back to a sx Type + fn llvmTypeToSxType(self: *CodeGen, llvm_ty: c.LLVMTypeRef) Type { + if (llvm_ty == c.LLVMInt1TypeInContext(self.context)) return .boolean; + if (llvm_ty == c.LLVMInt8TypeInContext(self.context)) return Type.s(8); + if (llvm_ty == c.LLVMInt16TypeInContext(self.context)) return Type.s(16); + if (llvm_ty == c.LLVMInt32TypeInContext(self.context)) return Type.s(32); + if (llvm_ty == c.LLVMInt64TypeInContext(self.context)) return Type.s(64); + if (llvm_ty == c.LLVMFloatTypeInContext(self.context)) return .f32; + if (llvm_ty == c.LLVMDoubleTypeInContext(self.context)) return .f64; + if (llvm_ty == c.LLVMVoidTypeInContext(self.context)) return .void_type; + if (llvm_ty == self.getStringStructType()) return .string_type; + if (self.any_struct_type != null and llvm_ty == self.any_struct_type.?) return .any_type; + if (llvm_ty == c.LLVMPointerTypeInContext(self.context, 0)) return .string_type; // raw ptr fallback (meta_type) + // Handle arbitrary-width integer types (e.g. i3, i7, i12) + if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMIntegerTypeKind) { + const width = c.LLVMGetIntTypeWidth(llvm_ty); + if (width > 0 and width <= 64) return Type.s(@intCast(width)); + } + // Check for named struct types + if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMStructTypeKind) { + const name_ptr = c.LLVMGetStructName(llvm_ty); + if (name_ptr != null) { + const name = std.mem.span(name_ptr); + if (self.struct_types.contains(name)) return .{ .struct_type = name }; + if (self.union_types.contains(name)) return .{ .union_type = name }; + } + } + // Check for array types + if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMArrayTypeKind) { + const elem_llvm = c.LLVMGetElementType(llvm_ty); + const length: u32 = @intCast(c.LLVMGetArrayLength2(llvm_ty)); + const elem_ty = self.llvmTypeToSxType(elem_llvm); + const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(32); + return .{ .array_type = .{ .element_name = elem_name, .length = length } }; + } + // Check for vector types + if (c.LLVMGetTypeKind(llvm_ty) == c.LLVMVectorTypeKind) { + const elem_llvm = c.LLVMGetElementType(llvm_ty); + const length = c.LLVMGetVectorSize(llvm_ty); + const elem_ty = self.llvmTypeToSxType(elem_llvm); + const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(32); + return .{ .vector_type = .{ .element_name = elem_name, .length = length } }; + } + return Type.s(32); + } + + fn registerComptimeGlobal(self: *CodeGen, name: []const u8, expr: *Node, type_override: ?Type) !void { + const ty = type_override orelse self.inferComptimeReturnType(expr); + if (ty == .void_type) return self.emitErrorFmt("cannot infer type for comptime global '{s}'", .{name}); + + const llvm_ty = self.typeToLLVM(ty); + const name_z = try self.allocator.dupeZ(u8, name); + const global = c.LLVMAddGlobal(self.module, llvm_ty, name_z.ptr); + c.LLVMSetInitializer(global, c.LLVMConstInt(llvm_ty, 0, 0)); + + try self.comptime_globals.put(name, .{ .global = global, .ty = ty, .expr = expr }); + } + + /// Evaluate a simple constant expression to an LLVM constant value. + /// Returns null for expressions that can't be constant-folded at registration time. + fn evalConstant(self: *CodeGen, node: *Node, target_ty: Type) ?c.LLVMValueRef { + const llvm_ty = self.typeToLLVM(target_ty); + switch (node.data) { + .int_literal => |lit| { + return c.LLVMConstInt(llvm_ty, @bitCast(@as(i64, lit.value)), 0); + }, + .float_literal => |lit| { + return c.LLVMConstReal(llvm_ty, lit.value); + }, + .bool_literal => |lit| { + return c.LLVMConstInt(llvm_ty, if (lit.value) 1 else 0, 0); + }, + else => return null, + } + } + + /// Register a top-level value constant (e.g., `SPECIAL_VALUE :u8: 42;`) as an LLVM global. + fn registerTopLevelConstant(self: *CodeGen, cd: ast.ConstDecl) !void { + const ta = cd.type_annotation orelse return; // need explicit type for top-level constants + const sx_ty = self.resolveType(ta); + if (sx_ty == .void_type) return; + + const const_val = self.evalConstant(cd.value, sx_ty) orelse return; + + const name_z = try self.allocator.dupeZ(u8, cd.name); + const global = c.LLVMAddGlobal(self.module, self.typeToLLVM(sx_ty), name_z.ptr); + c.LLVMSetInitializer(global, const_val); + c.LLVMSetGlobalConstant(global, 1); + + try self.comptime_globals.put(cd.name, .{ + .global = global, + .ty = sx_ty, + .expr = cd.value, + .is_resolved = true, + }); + } + + fn genFnBody(self: *CodeGen, fd: ast.FnDecl) !void { + return self.genFnBodyAs(fd, fd.name); + } + + fn genFnBodyAs(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void { + self.named_values.clearRetainingCapacity(); + + const ret_sx_type = self.resolveType(fd.return_type); + const is_main = std.mem.eql(u8, llvm_name, "main"); + const ret_llvm_type = if (is_main) + c.LLVMInt32TypeInContext(self.context) + else + self.typeToLLVM(ret_sx_type); + + self.current_return_type = if (is_main) Type.s(32) else ret_sx_type; + + const name_z = try self.allocator.dupeZ(u8, llvm_name); + const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse return self.emitErrorFmt("function '{s}' not found in LLVM module", .{llvm_name}); + self.current_function = function; + + const entry = c.LLVMAppendBasicBlockInContext(self.context, function, "entry"); + c.LLVMPositionBuilderAtEnd(self.builder, entry); + + // Create allocas for parameters and store incoming values + for (fd.params, 0..) |param, i| { + const sx_ty = if (param.is_variadic) blk: { + const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; + break :blk Type{ .slice_type = .{ .element_name = elem_name } }; + } else self.resolveType(param.type_expr); + if (sx_ty == .void_type) return self.emitErrorFmt("parameter '{s}' has unresolved type", .{param.name}); + const llvm_ty = self.typeToLLVM(sx_ty); + const param_name_z = try self.allocator.dupeZ(u8, param.name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); + const param_val = c.LLVMGetParam(function, @intCast(i)); + _ = c.LLVMBuildStore(self.builder, param_val, alloca); + try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + } + + // Push function-level scope so that function-body defers are tracked + try self.pushScope(); + + // Generate body + const body = fd.body; + if (body.data != .block) return self.emitError("function body must be a block"); + + var last_val: c.LLVMValueRef = null; + for (body.data.block.stmts) |stmt| { + last_val = try self.genStmt(stmt); + } + + // Return — skip if current block already has a terminator (from explicit return) + const current_bb = c.LLVMGetInsertBlock(self.builder); + if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { + // Implicit return path: pop scope (executes defers) then return + try self.popScope(); + // Check if last_val is void-typed (e.g. call to void-returning function) + const effective_last_val: ?c.LLVMValueRef = if (last_val) |val| + (if (c.LLVMTypeOf(val) == c.LLVMVoidTypeInContext(self.context)) null else val) + else + null; + + if (ret_sx_type == .void_type and !is_main) { + _ = c.LLVMBuildRetVoid(self.builder); + } else if (effective_last_val) |val| { + if (ret_sx_type.isStruct()) { + // Struct implicit return: val is an alloca pointer, load the value + const sname = ret_sx_type.struct_type; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, val, "retval"); + _ = c.LLVMBuildRet(self.builder, loaded); + } else { + const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); + const ret_val = self.convertValue(val, src_ty, self.current_return_type); + _ = c.LLVMBuildRet(self.builder, ret_val); + } + } else if (is_main) { + _ = c.LLVMBuildRet(self.builder, c.LLVMConstInt(ret_llvm_type, 0, 0)); + } else if (ret_sx_type != .void_type) { + _ = c.LLVMBuildUnreachable(self.builder); + } else { + _ = c.LLVMBuildRetVoid(self.builder); + } + } else { + // Explicit return already emitted defers; just clean up scope stacks + if (self.defer_stack.items.len > 0) _ = self.defer_stack.pop(); + if (self.scope_saves.items.len > 0) _ = self.scope_saves.pop(); + } + } + + fn registerLambdaAsFunction(self: *CodeGen, name: []const u8, lambda: ast.Lambda) !void { + const ret_sx_type = self.inferType(lambda.body); + const ret_llvm_type = self.typeToLLVM(ret_sx_type); + + var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + for (lambda.params) |param| { + const sx_ty = self.resolveType(param.type_expr); + try param_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); + } + const params_slice = try param_llvm_types.toOwnedSlice(self.allocator); + + const fn_type = c.LLVMFunctionType( + ret_llvm_type, + if (params_slice.len > 0) params_slice.ptr else null, + @intCast(params_slice.len), + 0, + ); + + const name_z = try self.allocator.dupeZ(u8, name); + _ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type); + } + + fn genLambdaBody(self: *CodeGen, name: []const u8, lambda: ast.Lambda) !void { + self.named_values.clearRetainingCapacity(); + + const ret_sx_type = self.inferType(lambda.body); + self.current_return_type = ret_sx_type; + + const name_z = try self.allocator.dupeZ(u8, name); + const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse return self.emitErrorFmt("lambda '{s}' not found in LLVM module", .{name}); + self.current_function = function; + + const entry = c.LLVMAppendBasicBlockInContext(self.context, function, "entry"); + c.LLVMPositionBuilderAtEnd(self.builder, entry); + + for (lambda.params, 0..) |param, i| { + const sx_ty = self.resolveType(param.type_expr); + const llvm_ty = self.typeToLLVM(sx_ty); + const param_name_z = try self.allocator.dupeZ(u8, param.name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); + const param_val = c.LLVMGetParam(function, @intCast(i)); + _ = c.LLVMBuildStore(self.builder, param_val, alloca); + try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + } + + const ret_val = try self.genExpr(lambda.body); + if (ret_val) |val| { + const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); + const converted = self.convertValue(val, src_ty, ret_sx_type); + _ = c.LLVMBuildRet(self.builder, converted); + } else { + _ = c.LLVMBuildRetVoid(self.builder); + } + } + + fn genStmt(self: *CodeGen, node: *Node) !c.LLVMValueRef { + self.current_span = node.span; + switch (node.data) { + .var_decl => |vd| { + return self.genVarDecl(vd); + }, + .const_decl => |cd| { + return self.genConstDecl(cd); + }, + .fn_decl => |fd| { + // Local declaration inside a function body + if (fd.type_params.len > 0) { + // Generic template / type function: register for lazy instantiation + try self.generic_templates.put(fd.name, .{ .fd = fd }); + } else { + // Non-generic local function + // Save outer function state + const saved_fn = self.current_function; + const saved_bb = c.LLVMGetInsertBlock(self.builder); + const saved_ret = self.current_return_type; + const saved_named = self.named_values; + self.named_values = std.StringHashMap(NamedValue).init(self.allocator); + + // Register with correct types (null return_type = void) + try self.registerFnDeclAs(fd, fd.name); + + // Generate body inline + const ret_sx_type = self.resolveType(fd.return_type); + self.current_return_type = ret_sx_type; + const name_z = try self.allocator.dupeZ(u8, fd.name); + const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse + return self.emitErrorFmt("local function '{s}' not found", .{fd.name}); + self.current_function = function; + const entry = c.LLVMAppendBasicBlockInContext(self.context, function, "entry"); + c.LLVMPositionBuilderAtEnd(self.builder, entry); + + for (fd.params, 0..) |param, i| { + const sx_ty = self.resolveType(param.type_expr); + const llvm_ty = self.typeToLLVM(sx_ty); + const param_name_z = try self.allocator.dupeZ(u8, param.name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); + const param_val = c.LLVMGetParam(function, @intCast(i)); + _ = c.LLVMBuildStore(self.builder, param_val, alloca); + try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + } + + var last_val: c.LLVMValueRef = null; + if (fd.body.data == .block) { + for (fd.body.data.block.stmts) |stmt| { + last_val = try self.genStmt(stmt); + } + } else { + last_val = try self.genExpr(fd.body); + } + + const current_bb2 = c.LLVMGetInsertBlock(self.builder); + if (c.LLVMGetBasicBlockTerminator(current_bb2) == null) { + if (ret_sx_type == .void_type) { + _ = c.LLVMBuildRetVoid(self.builder); + } else if (last_val) |val| { + const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); + const converted = self.convertValue(val, src_ty, ret_sx_type); + _ = c.LLVMBuildRet(self.builder, converted); + } else { + _ = c.LLVMBuildRetVoid(self.builder); + } + } + + // Restore outer function state + self.named_values = saved_named; + self.current_return_type = saved_ret; + self.current_function = saved_fn; + c.LLVMPositionBuilderAtEnd(self.builder, saved_bb); + } + return null; + }, + .struct_decl => |sd| { + try self.registerStructType(sd); + return null; + }, + .union_decl => |ud| { + try self.registerUnionType(ud); + return null; + }, + .assignment => |asgn| { + return self.genAssignment(asgn); + }, + .return_stmt => |rs| { + // Evaluate return value first, then emit all defers, then return + if (rs.value) |val_node| { + const raw_val = try self.genExpr(val_node); + if (self.current_return_type.isStruct()) { + // Struct return: raw_val is an alloca pointer, load the value + const sname = self.current_return_type.struct_type; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, raw_val, "retval"); + try self.emitAllDefers(); + _ = c.LLVMBuildRet(self.builder, loaded); + } else { + const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(raw_val)); + const val = self.convertValue(raw_val, src_ty, self.current_return_type); + try self.emitAllDefers(); + _ = c.LLVMBuildRet(self.builder, val); + } + } else { + try self.emitAllDefers(); + _ = c.LLVMBuildRetVoid(self.builder); + } + // Create a dead basic block for any subsequent instructions + const dead_bb = c.LLVMAppendBasicBlockInContext(self.context, self.current_function, "after_ret"); + c.LLVMPositionBuilderAtEnd(self.builder, dead_bb); + return null; + }, + .defer_stmt => |ds| { + // Don't generate now — push onto current defer list for later execution + if (self.defer_stack.items.len > 0) { + const top = &self.defer_stack.items[self.defer_stack.items.len - 1]; + try top.append(self.allocator, ds.expr); + } + return null; + }, + .insert_expr => |ins| { + // Substitute comptime param nodes before evaluation (e.g., replace $fmt identifier with literal) + const expr = if (self.comptime_param_nodes != null) + try self.substituteComptimeNodes(ins.expr) + else + ins.expr; + // Evaluate the inner expression via bytecode VM to get a string, parse it, generate inline + const result = try self.comptimeEval(expr, .string_type); + const code_z = try self.allocator.dupeZ(u8, result.string_val); + var parser = Parser.init(self.allocator, code_z); + var last_val: c.LLVMValueRef = null; + while (parser.current.tag != .eof) { + const stmt = try parser.parseStmt(); + last_val = try self.genStmt(stmt); + } + return last_val; + }, + else => { + return self.genExpr(node); + }, + } + } + + fn genVarDecl(self: *CodeGen, vd: ast.VarDecl) !c.LLVMValueRef { + // Meta type variable: x := f64 or x := Vec4 → runtime string holding the type name + if (vd.value) |val| { + const meta_name = self.asTypeName(val); + if (meta_name) |raw_name| { + const type_name = self.resolveDisplayName(raw_name); + const name_z = try self.allocator.dupeZ(u8, vd.name); + const ptr_ty = c.LLVMPointerTypeInContext(self.context, 0); + const alloca = c.LLVMBuildAlloca(self.builder, ptr_ty, name_z.ptr); + const str_val = c.LLVMBuildGlobalStringPtr(self.builder, type_name.ptr, "type_name"); + _ = c.LLVMBuildStore(self.builder, str_val, alloca); + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = .{ .meta_type = .{ .name = raw_name } } }); + return null; + } + } + + var sx_ty: Type = Type.s(32); + + if (vd.type_annotation) |ta| { + sx_ty = self.resolveType(ta); + } else if (vd.value) |val| { + // Infer type from value + if (val.data == .struct_literal) { + const sl = val.data.struct_literal; + if (sl.struct_name) |name| { + sx_ty = .{ .struct_type = name }; + } else if (sl.type_expr) |te| { + sx_ty = self.resolveType(te); + } else { + return self.emitError("cannot infer struct type from untyped struct literal"); + } + } else { + sx_ty = self.inferType(val); + } + } else { + return self.emitErrorFmt("variable '{s}' has no type annotation and no initializer", .{vd.name}); + } + + // Struct-typed variable + if (sx_ty.isStruct()) { + // Resolve type aliases (e.g. Vec3 -> Vec__3_f32) + const sname = self.type_aliases.get(sx_ty.struct_type) orelse sx_ty.struct_type; + sx_ty = .{ .struct_type = sname }; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + const name_z = try self.allocator.dupeZ(u8, vd.name); + const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr); + + if (vd.value == null) { + // Default-init: per-field defaults or zero + try self.genStructDefaultInit(alloca, info); + } else if (vd.value.?.data == .undef_literal) { + // Undef-init: entire struct is undefined + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(info.llvm_type), alloca); + } else if (vd.value.?.data == .struct_literal) { + // Struct literal codegen returns an alloca — use it directly instead + const lit_alloca = try self.genStructLiteral(vd.value.?.data.struct_literal, sname); + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = lit_alloca, .ty = sx_ty }); + return null; + } else if (vd.value.?.data == .call) { + // Function call returning a struct — result is a value, store to alloca + const val = try self.genExpr(vd.value.?); + _ = c.LLVMBuildStore(self.builder, val, alloca); + } else { + // General expression (xx cast, identifier, etc.) — evaluate as target type + const val = try self.genExprAsType(vd.value.?, sx_ty); + _ = c.LLVMBuildStore(self.builder, val, alloca); + } + + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = sx_ty }); + return null; + } + + // Union-typed variable + if (sx_ty.isUnion()) { + const uname = self.type_aliases.get(sx_ty.union_type) orelse sx_ty.union_type; + sx_ty = .{ .union_type = uname }; + const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union type '{s}'", .{uname}); + const name_z = try self.allocator.dupeZ(u8, vd.name); + const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr); + + if (vd.value == null) { + // Zero-init: tag=0, payload zeroed + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(info.llvm_type), alloca); + } else if (vd.value.?.data == .undef_literal) { + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(info.llvm_type), alloca); + } else if (vd.value.?.data == .union_literal) { + const lit_alloca = try self.genUnionLiteral(vd.value.?.data.union_literal, uname); + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = lit_alloca, .ty = sx_ty }); + return null; + } else if (vd.value.?.data == .enum_literal) { + // Void variant: .none assigned to union variable + const ul = ast.UnionLiteral{ + .union_name = uname, + .variant_name = vd.value.?.data.enum_literal.name, + .payload = null, + }; + const lit_alloca = try self.genUnionLiteral(ul, uname); + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = lit_alloca, .ty = sx_ty }); + return null; + } else if (vd.value.?.data == .call) { + // Call returning a union (e.g., Shape.circle(3.14)) — genExpr returns alloca + const result_alloca = try self.genExpr(vd.value.?); + const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, result_alloca, "union_load"); + _ = c.LLVMBuildStore(self.builder, loaded, alloca); + } else { + // Other expression — try genExprAsType + const result_alloca = try self.genExprAsType(vd.value.?, sx_ty); + const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, result_alloca, "union_load"); + _ = c.LLVMBuildStore(self.builder, loaded, alloca); + } + + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = sx_ty }); + return null; + } + + // Array-typed variable + if (sx_ty.isArray()) { + const arr_info = sx_ty.array_type; + const llvm_arr_ty = self.typeToLLVM(sx_ty); + const arr_name_z = try self.allocator.dupeZ(u8, vd.name); + const arr_alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, arr_name_z.ptr); + + if (vd.value == null) { + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_arr_ty), arr_alloca); + } else if (vd.value.?.data == .undef_literal) { + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(llvm_arr_ty), arr_alloca); + } else if (vd.value.?.data == .array_literal) { + const al = vd.value.?.data.array_literal; + const elem_sx_ty = Type.fromName(arr_info.element_name) orelse return self.emitErrorFmt("unknown array element type '{s}'", .{arr_info.element_name}); + const elem_llvm_ty = self.typeToLLVM(elem_sx_ty); + const len = @min(al.elements.len, arr_info.length); + for (0..len) |i| { + const val = try self.genExprAsType(al.elements[i], elem_sx_ty); + var indices = [_]c.LLVMValueRef{ + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(i), 0), + }; + const gep = c.LLVMBuildGEP2(self.builder, llvm_arr_ty, arr_alloca, &indices, 2, "arr_elem"); + _ = c.LLVMBuildStore(self.builder, val, gep); + } + // Zero-init remaining elements + for (len..arr_info.length) |i| { + var indices = [_]c.LLVMValueRef{ + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(i), 0), + }; + const gep = c.LLVMBuildGEP2(self.builder, llvm_arr_ty, arr_alloca, &indices, 2, "arr_elem"); + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(elem_llvm_ty), gep); + } + } else { + return self.emitErrorFmt("unsupported initializer for array variable '{s}'", .{vd.name}); + } + + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = arr_alloca, .ty = sx_ty }); + return null; + } + + // Vector-typed variable + if (sx_ty.isVector()) { + const llvm_vec_ty = self.typeToLLVM(sx_ty); + const vec_name_z = try self.allocator.dupeZ(u8, vd.name); + const vec_alloca = c.LLVMBuildAlloca(self.builder, llvm_vec_ty, vec_name_z.ptr); + + if (vd.value == null) { + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_vec_ty), vec_alloca); + } else if (vd.value.?.data == .undef_literal) { + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(llvm_vec_ty), vec_alloca); + } else if (vd.value.?.data == .array_literal) { + const vec_val = try self.genVectorLiteral(vd.value.?.data.array_literal, sx_ty); + _ = c.LLVMBuildStore(self.builder, vec_val, vec_alloca); + } else { + // Expression (e.g. function call) returning a vector + const val = try self.genExpr(vd.value.?); + _ = c.LLVMBuildStore(self.builder, val, vec_alloca); + } + + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = vec_alloca, .ty = sx_ty }); + return null; + } + + // Guard: void type cannot be allocated (would crash LLVM) + if (sx_ty == .void_type) { + return self.emitErrorFmt("cannot declare variable '{s}' with void type", .{vd.name}); + } + + // Non-struct types + const llvm_ty = self.typeToLLVM(sx_ty); + const name_z = try self.allocator.dupeZ(u8, vd.name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, name_z.ptr); + + if (vd.value == null) { + // Default-init: zero + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(llvm_ty), alloca); + } else if (vd.value.?.data == .undef_literal) { + // Undef-init + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(llvm_ty), alloca); + } else { + const val = vd.value.?; + const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null; + const init_val = if (val.data == .enum_literal and enum_name != null) + self.genEnumLiteral(val.data.enum_literal.name, enum_name.?) + else if (vd.type_annotation != null) + try self.genExprAsType(val, sx_ty) + else + try self.genExpr(val); + _ = c.LLVMBuildStore(self.builder, init_val, alloca); + } + + try self.saveShadowed(vd.name); + try self.named_values.put(vd.name, .{ .ptr = alloca, .ty = sx_ty }); + return null; + } + + fn genStructDefaultInit(self: *CodeGen, alloca: c.LLVMValueRef, info: StructInfo) !void { + for (info.field_names, 0..) |_, fi| { + const ft = info.field_types[fi]; + const ft_llvm = self.typeToLLVM(ft); + const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, @intCast(fi), "dinit"); + + if (info.field_defaults.len > fi and info.field_defaults[fi] != null) { + const default_node = info.field_defaults[fi].?; + if (default_node.data == .undef_literal) { + // Field default is --- → store undef + _ = c.LLVMBuildStore(self.builder, c.LLVMGetUndef(ft_llvm), gep); + } else { + // Field has expression default → evaluate and convert + const val = try self.genExprAsType(default_node, ft); + _ = c.LLVMBuildStore(self.builder, val, gep); + } + } else { + // No default → zero + _ = c.LLVMBuildStore(self.builder, c.LLVMConstNull(ft_llvm), gep); + } + } + } + + fn genConstDecl(self: *CodeGen, cd: ast.ConstDecl) !c.LLVMValueRef { + // Compile-time evaluation: register as comptime global for JIT + if (cd.value.data == .comptime_expr) { + const ct_type_override: ?Type = if (cd.type_annotation) |te| Type.fromTypeExpr(te) else null; + try self.registerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, ct_type_override); + return null; + } + + var sx_ty: Type = Type.s(32); + + if (cd.type_annotation) |ta| { + sx_ty = self.resolveType(ta); + } else { + sx_ty = self.inferType(cd.value); + } + + // Union-typed constant: delegate to genExprAsType which handles enum_literal + union_literal + if (sx_ty.isUnion()) { + const val = try self.genExprAsType(cd.value, sx_ty); + try self.saveShadowed(cd.name); + try self.named_values.put(cd.name, .{ .ptr = val, .ty = sx_ty }); + return null; + } + + const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null; + const init_val = if (cd.value.data == .enum_literal and enum_name != null) + self.genEnumLiteral(cd.value.data.enum_literal.name, enum_name.?) + else + try self.genExpr(cd.value); + + const llvm_ty = self.typeToLLVM(sx_ty); + const name_z = try self.allocator.dupeZ(u8, cd.name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, name_z.ptr); + _ = c.LLVMBuildStore(self.builder, init_val, alloca); + try self.saveShadowed(cd.name); + try self.named_values.put(cd.name, .{ .ptr = alloca, .ty = sx_ty }); + return null; + } + + fn genAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { + // Field assignment: expr.field = value; + if (asgn.target.data == .field_access) { + return self.genFieldAssignment(asgn); + } + + // Index assignment: expr[i] = value; + if (asgn.target.data == .index_expr) { + return self.genIndexAssignment(asgn); + } + + // Target must be an identifier + if (asgn.target.data != .identifier) return self.emitError("assignment target must be a variable"); + const name = asgn.target.data.identifier.name; + const entry = self.named_values.get(name) orelse { + return self.emitErrorFmt("undefined variable '{s}'", .{name}); + }; + + // Meta type reassignment: x = Vec4 or x = f64 + if (entry.ty == .meta_type and asgn.op == .assign) { + if (self.asTypeName(asgn.value)) |raw_name| { + const type_name = self.resolveDisplayName(raw_name); + const str_val = c.LLVMBuildGlobalStringPtr(self.builder, type_name.ptr, "type_name"); + _ = c.LLVMBuildStore(self.builder, str_val, entry.ptr); + if (self.named_values.getPtr(name)) |entry_ptr| { + entry_ptr.ty = .{ .meta_type = .{ .name = raw_name } }; + } + return null; + } + } + + // Union reassignment: s = .circle(3.14) or s = .none + if (entry.ty.isUnion() and asgn.op == .assign) { + const new_alloca = try self.genExprAsType(asgn.value, entry.ty); + // Copy from new alloca to existing alloca + const info = self.union_types.get(entry.ty.union_type).?; + const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, new_alloca, "union_load"); + _ = c.LLVMBuildStore(self.builder, loaded, entry.ptr); + return null; + } + + const new_val = try self.genExpr(asgn.value); + const llvm_ty = self.typeToLLVM(entry.ty); + + const store_val = switch (asgn.op) { + .assign => new_val, + .add_assign => blk: { + const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); + break :blk if (entry.ty.isFloat()) + c.LLVMBuildFAdd(self.builder, cur, new_val, "addtmp") + else + c.LLVMBuildAdd(self.builder, cur, new_val, "addtmp"); + }, + .sub_assign => blk: { + const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); + break :blk if (entry.ty.isFloat()) + c.LLVMBuildFSub(self.builder, cur, new_val, "subtmp") + else + c.LLVMBuildSub(self.builder, cur, new_val, "subtmp"); + }, + .mul_assign => blk: { + const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); + break :blk if (entry.ty.isFloat()) + c.LLVMBuildFMul(self.builder, cur, new_val, "multmp") + else + c.LLVMBuildMul(self.builder, cur, new_val, "multmp"); + }, + .div_assign => blk: { + const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); + break :blk if (entry.ty.isFloat()) + c.LLVMBuildFDiv(self.builder, cur, new_val, "divtmp") + else if (entry.ty.isUnsigned()) + c.LLVMBuildUDiv(self.builder, cur, new_val, "divtmp") + else + c.LLVMBuildSDiv(self.builder, cur, new_val, "divtmp"); + }, + .mod_assign => blk: { + const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); + break :blk if (entry.ty.isFloat()) + c.LLVMBuildFRem(self.builder, cur, new_val, "modtmp") + else if (entry.ty.isUnsigned()) + c.LLVMBuildURem(self.builder, cur, new_val, "modtmp") + else + c.LLVMBuildSRem(self.builder, cur, new_val, "modtmp"); + }, + }; + + _ = c.LLVMBuildStore(self.builder, store_val, entry.ptr); + return null; + } + + fn genFieldAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { + const fa = asgn.target.data.field_access; + // Object must be an identifier for now + if (fa.object.data != .identifier) return self.emitError("field assignment target must be a variable"); + const obj_name = fa.object.data.identifier.name; + const entry = self.named_values.get(obj_name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{obj_name}); + if (!entry.ty.isStruct()) return self.emitErrorFmt("field access on non-struct variable '{s}'", .{obj_name}); + + const sname = entry.ty.struct_type; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + const fi = self.findFieldIndex(info, fa.field) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fa.field, sname }); + const field_ty = info.field_types[fi]; + + const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, @intCast(fi), "fassign"); + + // Generate RHS and convert to field type + const rhs = try self.genExprAsType(asgn.value, field_ty); + + if (asgn.op == .assign) { + _ = c.LLVMBuildStore(self.builder, rhs, gep); + } else { + // Compound assignment on struct field + const field_llvm_ty = self.typeToLLVM(field_ty); + const cur = c.LLVMBuildLoad2(self.builder, field_llvm_ty, gep, "fcur"); + const store_val = switch (asgn.op) { + .add_assign => if (field_ty.isFloat()) + c.LLVMBuildFAdd(self.builder, cur, rhs, "faddtmp") + else + c.LLVMBuildAdd(self.builder, cur, rhs, "faddtmp"), + .sub_assign => if (field_ty.isFloat()) + c.LLVMBuildFSub(self.builder, cur, rhs, "fsubtmp") + else + c.LLVMBuildSub(self.builder, cur, rhs, "fsubtmp"), + .mul_assign => if (field_ty.isFloat()) + c.LLVMBuildFMul(self.builder, cur, rhs, "fmultmp") + else + c.LLVMBuildMul(self.builder, cur, rhs, "fmultmp"), + .div_assign => if (field_ty.isFloat()) + c.LLVMBuildFDiv(self.builder, cur, rhs, "fdivtmp") + else if (field_ty.isUnsigned()) + c.LLVMBuildUDiv(self.builder, cur, rhs, "fdivtmp") + else + c.LLVMBuildSDiv(self.builder, cur, rhs, "fdivtmp"), + .mod_assign => if (field_ty.isFloat()) + c.LLVMBuildFRem(self.builder, cur, rhs, "fmodtmp") + else if (field_ty.isUnsigned()) + c.LLVMBuildURem(self.builder, cur, rhs, "fmodtmp") + else + c.LLVMBuildSRem(self.builder, cur, rhs, "fmodtmp"), + .assign => unreachable, + }; + _ = c.LLVMBuildStore(self.builder, store_val, gep); + } + return null; + } + + fn genIndexAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { + const ie = asgn.target.data.index_expr; + const obj_ty = self.inferType(ie.object); + if (obj_ty == .string_type) { + // String index assignment: s[i] = c + const str_val = try self.genExpr(ie.object); + const ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str_ptr"); + const idx = try self.genExpr(ie.index); + const val = try self.genExpr(asgn.value); + const i8_type = c.LLVMInt8TypeInContext(self.context); + var gep_indices = [_]c.LLVMValueRef{idx}; + const gep_ptr = c.LLVMBuildGEP2(self.builder, i8_type, ptr, &gep_indices, 1, "stridx"); + const byte_val = c.LLVMBuildTrunc(self.builder, val, i8_type, "trunc_byte"); + _ = c.LLVMBuildStore(self.builder, byte_val, gep_ptr); + return null; + } + if (obj_ty.isArray()) { + if (ie.object.data == .identifier) { + if (self.named_values.get(ie.object.data.identifier.name)) |entry| { + const arr_info = obj_ty.array_type; + const elem_ty = Type.fromName(arr_info.element_name) orelse return self.emitError("unknown array element type"); + const idx = try self.genExpr(ie.index); + const val = try self.genExpr(asgn.value); + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + var indices = [_]c.LLVMValueRef{ zero, idx }; + const gep_ptr = c.LLVMBuildGEP2(self.builder, self.typeToLLVM(obj_ty), entry.ptr, &indices, 2, "arridx"); + _ = c.LLVMBuildStore(self.builder, val, gep_ptr); + _ = elem_ty; + return null; + } + } + } + return self.emitError("index assignment requires a string or array target"); + } + + fn unescapeString(allocator: std.mem.Allocator, raw: []const u8) ![]u8 { + var result = try allocator.alloc(u8, raw.len); + var i: usize = 0; + var j: usize = 0; + while (i < raw.len) { + if (raw[i] == '\\' and i + 1 < raw.len) { + i += 1; + switch (raw[i]) { + 'n' => { + result[j] = '\n'; + }, + 't' => { + result[j] = '\t'; + }, + 'r' => { + result[j] = '\r'; + }, + '\\' => { + result[j] = '\\'; + }, + '"' => { + result[j] = '"'; + }, + '0' => { + result[j] = 0; + }, + else => { + result[j] = raw[i]; + }, + } + j += 1; + i += 1; + } else { + result[j] = raw[i]; + j += 1; + i += 1; + } + } + return result[0..j]; + } + + fn genExpr(self: *CodeGen, node: *Node) anyerror!c.LLVMValueRef { + self.current_span = node.span; + switch (node.data) { + .int_literal => |lit| { + const i32_type = c.LLVMInt32TypeInContext(self.context); + return c.LLVMConstInt(i32_type, @bitCast(@as(i64, lit.value)), 0); + }, + .float_literal => |lit| { + const f32_type = c.LLVMFloatTypeInContext(self.context); + return c.LLVMConstReal(f32_type, lit.value); + }, + .bool_literal => |lit| { + const i1_type = c.LLVMInt1TypeInContext(self.context); + return c.LLVMConstInt(i1_type, if (lit.value) 1 else 0, 0); + }, + .string_literal => |lit| { + const unescaped = try unescapeString(self.allocator, lit.raw); + const str_z = try self.allocator.dupeZ(u8, unescaped); + const ptr = c.LLVMBuildGlobalStringPtr(self.builder, str_z.ptr, "str"); + return self.buildStringSlice(ptr, @intCast(unescaped.len)); + }, + .identifier => |ident| { + if (self.named_values.get(ident.name)) |entry| { + const llvm_ty = self.typeToLLVM(entry.ty); + return c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "loadtmp"); + } + // Fall back to comptime globals (lazy resolution) + if (self.comptime_globals.getPtr(ident.name)) |ct| { + if (!ct.is_resolved) { + try self.resolveComptimeGlobal(ct); + } + const llvm_ty = self.typeToLLVM(ct.ty); + return c.LLVMBuildLoad2(self.builder, llvm_ty, ct.global, "ct_load"); + } + return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name}); + }, + .binary_op => |binop| { + if (binop.op == .and_op) return self.genAndOp(binop); + if (binop.op == .or_op) return self.genOrOp(binop); + const lhs_ty = self.inferType(binop.lhs); + const rhs_ty = self.inferType(binop.rhs); + const result_type = Type.widen(lhs_ty, rhs_ty); + const lhs = try self.genExprAsType(binop.lhs, result_type); + const rhs = try self.genExprAsType(binop.rhs, result_type); + return self.genBinaryOp(binop.op, lhs, rhs, result_type); + }, + .chained_comparison => |chain| { + return self.genChainedComparison(chain); + }, + .unary_op => |unop| { + if (unop.op == .xx) { + // xx requires a target type context (assignment, declaration, argument, return) + return self.emitError("'xx' cast requires a target type context"); + } + const operand = try self.genExpr(unop.operand); + return switch (unop.op) { + .negate => blk: { + const operand_ty = self.inferType(unop.operand); + if (operand_ty.isVector()) { + const elem_ty = operand_ty.vectorElementType(); + break :blk if (elem_ty != null and elem_ty.?.isFloat()) + c.LLVMBuildFNeg(self.builder, operand, "vnegtmp") + else + c.LLVMBuildNeg(self.builder, operand, "vnegtmp"); + } + break :blk if (self.exprIsFloat(unop.operand)) + c.LLVMBuildFNeg(self.builder, operand, "negtmp") + else + c.LLVMBuildNeg(self.builder, operand, "negtmp"); + }, + .not => c.LLVMBuildNot(self.builder, operand, "nottmp"), + .xx => unreachable, + }; + }, + .struct_literal => |sl| { + const ctx_name: ?[]const u8 = if (self.current_return_type.isStruct()) self.current_return_type.struct_type else null; + return self.genStructLiteral(sl, ctx_name); + }, + .union_literal => |ul| { + return self.genUnionLiteral(ul, null); + }, + .array_literal => |al| { + // If current return type is vector, build as vector SSA value + if (self.current_return_type.isVector()) { + return self.genVectorLiteral(al, self.current_return_type); + } + return self.genArrayLiteral(al, null); + }, + .field_access => |fa| { + return self.genFieldAccess(fa); + }, + .index_expr => |ie| { + return self.genIndexExpr(ie); + }, + .call => |call_node| { + return self.genCall(call_node); + }, + .if_expr => |ie| { + return self.genIfExpr(ie); + }, + .match_expr => |me| { + return self.genMatchExpr(me); + }, + .while_expr => |we| { + return self.genWhileExpr(we); + }, + .for_expr => |fe| { + return self.genForExpr(fe); + }, + .break_expr => { + if (self.loop_break_bb) |break_bb| { + _ = c.LLVMBuildBr(self.builder, break_bb); + const dead_bb = c.LLVMAppendBasicBlockInContext(self.context, self.current_function, "after_break"); + c.LLVMPositionBuilderAtEnd(self.builder, dead_bb); + return null; + } + return self.emitError("'break' outside of loop"); + }, + .continue_expr => { + if (self.loop_continue_bb) |continue_bb| { + _ = c.LLVMBuildBr(self.builder, continue_bb); + const dead_bb = c.LLVMAppendBasicBlockInContext(self.context, self.current_function, "after_continue"); + c.LLVMPositionBuilderAtEnd(self.builder, dead_bb); + return null; + } + return self.emitError("'continue' outside of loop"); + }, + .block => |blk| { + try self.pushScope(); + var last_val: c.LLVMValueRef = null; + for (blk.stmts) |stmt| { + last_val = try self.genStmt(stmt); + } + try self.popScope(); + return last_val; + }, + .var_decl => |vd| { + return self.genVarDecl(vd); + }, + .const_decl => |cd| { + return self.genConstDecl(cd); + }, + .assignment => |asgn| { + return self.genAssignment(asgn); + }, + .return_stmt => |rs| { + if (rs.value) |val_node| { + const raw_val = try self.genExpr(val_node); + const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(raw_val)); + const val = self.convertValue(raw_val, src_ty, self.current_return_type); + try self.emitAllDefers(); + _ = c.LLVMBuildRet(self.builder, val); + } else { + try self.emitAllDefers(); + _ = c.LLVMBuildRetVoid(self.builder); + } + const dead_bb = c.LLVMAppendBasicBlockInContext(self.context, self.current_function, "after_ret"); + c.LLVMPositionBuilderAtEnd(self.builder, dead_bb); + return null; + }, + .comptime_expr => |ct| { + return self.genExpr(ct.expr); + }, + else => return self.emitError("unsupported expression"), + } + } + + fn registerStructType(self: *CodeGen, sd: ast.StructDecl) !void { + // Generic struct: store as template instead of registering now + if (sd.type_params.len > 0) { + try self.generic_struct_templates.put(sd.name, .{ .sd = sd }); + return; + } + + var field_sx_types = std.ArrayList(Type).empty; + var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + + for (sd.field_types) |ft| { + const sx_ty = self.resolveType(ft); + try field_sx_types.append(self.allocator, sx_ty); + try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); + } + + const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); + const name_z = try self.allocator.dupeZ(u8, sd.name); + const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); + + // Process field defaults: replace #run expressions with comptime global references + var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); + for (sd.field_defaults, 0..) |fd, i| { + if (fd != null and fd.?.data == .comptime_expr) { + // Register as anonymous comptime global for JIT evaluation + const synthetic_name = try std.fmt.allocPrint(self.allocator, "__struct_{s}_field_{d}", .{ sd.name, i }); + const field_type_override: ?Type = if (i < field_sx_types.items.len) field_sx_types.items[i] else null; + try self.registerComptimeGlobal(synthetic_name, fd.?.data.comptime_expr.expr, field_type_override); + // Replace with identifier node referencing the comptime global + const id_node = try self.allocator.create(Node); + id_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = synthetic_name } } }; + resolved_defaults[i] = id_node; + } else { + resolved_defaults[i] = fd; + } + } + + try self.struct_types.put(sd.name, .{ + .field_names = sd.field_names, + .field_types = try field_sx_types.toOwnedSlice(self.allocator), + .field_defaults = resolved_defaults, + .llvm_type = struct_ty, + }); + _ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name }); + } + + fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { + var variant_sx_types = std.ArrayList(Type).empty; + var max_payload_size: u64 = 0; + const data_layout = c.LLVMGetModuleDataLayout(self.module); + + for (ud.variant_types) |vt| { + if (vt) |type_node| { + const sx_ty = self.resolveType(type_node); + try variant_sx_types.append(self.allocator, sx_ty); + const llvm_ty = self.typeToLLVM(sx_ty); + const size = c.LLVMStoreSizeOfType(data_layout, llvm_ty); + if (size > max_payload_size) max_payload_size = size; + } else { + try variant_sx_types.append(self.allocator, .void_type); + } + } + + // Union LLVM type: { i32, [max_payload_size x i8] } + const name_z = try self.allocator.dupeZ(u8, ud.name); + const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i8_ty = c.LLVMInt8TypeInContext(self.context); + const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); + var fields = [2]c.LLVMTypeRef{ i32_ty, payload_array_ty }; + c.LLVMStructSetBody(union_ty, &fields, 2, 0); + + try self.union_types.put(ud.name, .{ + .variant_names = ud.variant_names, + .variant_types = try variant_sx_types.toOwnedSlice(self.allocator), + .llvm_type = union_ty, + .max_payload_size = max_payload_size, + }); + _ = try self.getAnyTypeId(ud.name, .{ .union_type = ud.name }); + } + + fn genUnionLiteral(self: *CodeGen, ul: ast.UnionLiteral, expected_union_name: ?[]const u8) !c.LLVMValueRef { + const uname = ul.union_name orelse expected_union_name orelse + (if (self.current_return_type.isUnion()) self.current_return_type.union_type else null) orelse + return self.emitError("cannot infer union type for literal"); + const resolved_name = self.type_aliases.get(uname) orelse uname; + const info = self.union_types.get(resolved_name) orelse return self.emitErrorFmt("unknown union type '{s}'", .{resolved_name}); + + // Find variant index + var variant_idx: ?u32 = null; + for (info.variant_names, 0..) |vn, i| { + if (std.mem.eql(u8, vn, ul.variant_name)) { + variant_idx = @intCast(i); + break; + } + } + const idx = variant_idx orelse return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ ul.variant_name, resolved_name }); + + // Alloca union + const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_tmp"); + const i32_ty = c.LLVMInt32TypeInContext(self.context); + + // Store tag (field 0) + const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, 0, "tag"); + _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_ty, idx, 0), tag_gep); + + // Store payload (field 1) if not void + if (ul.payload) |payload_node| { + const variant_ty = info.variant_types[idx]; + if (variant_ty != .void_type) { + const payload_val = try self.genExprAsType(payload_node, variant_ty); + const payload_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, 1, "payload"); + const payload_llvm_ty = self.typeToLLVM(variant_ty); + // Bitcast payload area to the variant's type pointer and store + if (variant_ty.isStruct()) { + // Struct payload: load from alloca, store to payload area + const struct_val = c.LLVMBuildLoad2(self.builder, payload_llvm_ty, payload_val, "struct_load"); + _ = c.LLVMBuildStore(self.builder, struct_val, payload_gep); + } else { + _ = c.LLVMBuildStore(self.builder, payload_val, payload_gep); + } + } + } + + return alloca; + } + + fn genStructLiteral(self: *CodeGen, sl: ast.StructLiteral, expected_struct_name: ?[]const u8) !c.LLVMValueRef { + const raw_name = sl.struct_name orelse blk: { + if (sl.type_expr) |te| { + const ty = self.resolveType(te); + if (ty.isStruct()) break :blk ty.struct_type; + } + break :blk expected_struct_name orelse return self.emitError("cannot infer struct type for literal"); + }; + // Resolve type aliases (e.g. Vec3 -> Vec__3_f32) + const sname = self.type_aliases.get(raw_name) orelse raw_name; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + + // Alloca the struct and default-init all fields (zero or declared defaults) + const name_z = try self.allocator.dupeZ(u8, sname); + const alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, name_z.ptr); + try self.genStructDefaultInit(alloca, info); + + // Determine if this is named or positional mode + var has_named = false; + for (sl.field_inits) |fi| { + if (fi.name != null) { + has_named = true; + break; + } + } + + if (has_named) { + // Named/shorthand mode: map by field name + for (sl.field_inits) |fi| { + const fname = fi.name orelse { + // Positional field mixed with named — treat as identifier shorthand + if (fi.value.data == .identifier) { + const idx = self.findFieldIndex(info, fi.value.data.identifier.name) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fi.value.data.identifier.name, sname }); + const val = try self.genExprAsType(fi.value, info.field_types[idx]); + const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, @intCast(idx), "field"); + _ = c.LLVMBuildStore(self.builder, val, gep); + continue; + } + return self.emitError("mixed positional and named fields in struct literal"); + }; + const idx = self.findFieldIndex(info, fname) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fname, sname }); + const val = try self.genExprAsType(fi.value, info.field_types[idx]); + const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, @intCast(idx), "field"); + _ = c.LLVMBuildStore(self.builder, val, gep); + } + } else { + // Positional mode: assign in order + for (sl.field_inits, 0..) |fi, i| { + if (i >= info.field_names.len) return self.emitErrorFmt("too many fields in struct literal (expected {d})", .{info.field_names.len}); + const val = try self.genExprAsType(fi.value, info.field_types[i]); + const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, alloca, @intCast(i), "field"); + _ = c.LLVMBuildStore(self.builder, val, gep); + } + } + + return alloca; + } + + /// Generate an array literal as an alloca with elements stored via GEP. + /// If target_ty is provided, elements are converted to the array's element type. + /// Otherwise, element type is inferred from the first element. + fn genArrayLiteral(self: *CodeGen, al: ast.ArrayLiteral, target_ty_opt: ?Type) !c.LLVMValueRef { + const arr_ty: Type = target_ty_opt orelse blk: { + // Infer from first element + if (al.elements.len == 0) return self.emitError("cannot infer type of empty array literal"); + const elem_ty = self.inferType(al.elements[0]); + const elem_name = try elem_ty.displayName(self.allocator); + break :blk .{ .array_type = .{ .element_name = elem_name, .length = @intCast(al.elements.len) } }; + }; + const arr_info = arr_ty.array_type; + const elem_sx_ty = Type.fromName(arr_info.element_name) orelse return self.emitErrorFmt("unknown array element type '{s}'", .{arr_info.element_name}); + const llvm_arr_ty = self.typeToLLVM(arr_ty); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_arr_ty, "arr"); + + const len = @min(al.elements.len, arr_info.length); + for (0..len) |i| { + const val = try self.genExprAsType(al.elements[i], elem_sx_ty); + var indices = [_]c.LLVMValueRef{ + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(i), 0), + }; + const gep = c.LLVMBuildGEP2(self.builder, llvm_arr_ty, alloca, &indices, 2, "arr_elem"); + _ = c.LLVMBuildStore(self.builder, val, gep); + } + return alloca; + } + + fn genVectorLiteral(self: *CodeGen, al: ast.ArrayLiteral, vec_ty: Type) !c.LLVMValueRef { + const vec_info = vec_ty.vector_type; + const elem_sx_ty = Type.fromName(vec_info.element_name) orelse return self.emitErrorFmt("unknown vector element type '{s}'", .{vec_info.element_name}); + const llvm_vec_ty = self.typeToLLVM(vec_ty); + var vec_val = c.LLVMGetUndef(llvm_vec_ty); + + const len = @min(al.elements.len, vec_info.length); + for (0..len) |i| { + const elem_val = try self.genExprAsType(al.elements[i], elem_sx_ty); + const idx = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(i), 0); + vec_val = c.LLVMBuildInsertElement(self.builder, vec_val, elem_val, idx, "vec_ins"); + } + return vec_val; + } + + fn broadcastScalar(self: *CodeGen, scalar: c.LLVMValueRef, vec_ty: Type) c.LLVMValueRef { + const vec_info = vec_ty.vector_type; + const llvm_vec_ty = self.typeToLLVM(vec_ty); + // Insert scalar at index 0 of undef vector + var vec = c.LLVMGetUndef(llvm_vec_ty); + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + vec = c.LLVMBuildInsertElement(self.builder, vec, scalar, zero, "splat_ins"); + // Shuffle with zeroinitializer mask to broadcast element 0 to all lanes + const mask_ty = c.LLVMVectorType(c.LLVMInt32TypeInContext(self.context), vec_info.length); + const mask = c.LLVMConstNull(mask_ty); + return c.LLVMBuildShuffleVector(self.builder, vec, c.LLVMGetUndef(llvm_vec_ty), mask, "splat"); + } + + fn genExprAsType(self: *CodeGen, node: *Node, target_ty: Type) !c.LLVMValueRef { + self.current_span = node.span; + // xx prefix: unwrap and convert freely (explicit cast) + if (node.data == .unary_op and node.data.unary_op.op == .xx) { + const inner = node.data.unary_op.operand; + const val = try self.genExpr(inner); + const src_ty = self.inferType(inner); + return self.convertValue(val, src_ty, target_ty); + } + + // Enum literal assigned to union type: construct tag-only (void variant) union + if (node.data == .enum_literal and target_ty.isUnion()) { + const ul = ast.UnionLiteral{ + .union_name = null, + .variant_name = node.data.enum_literal.name, + .payload = null, + }; + return self.genUnionLiteral(ul, target_ty.union_type); + } + + // Union literal with target union type: pass context + if (node.data == .union_literal and target_ty.isUnion()) { + return self.genUnionLiteral(node.data.union_literal, target_ty.union_type); + } + + // Array literal with target array type: generate with element conversion + if (node.data == .array_literal and target_ty.isArray()) { + return self.genArrayLiteral(node.data.array_literal, target_ty); + } + + // Array literal with target vector type: build via undef + InsertElement + if (node.data == .array_literal and target_ty.isVector()) { + return self.genVectorLiteral(node.data.array_literal, target_ty); + } + + const val = try self.genExpr(node); + const src_ty = self.inferType(node); + + // Scalar to vector broadcast + if (target_ty.isVector() and !src_ty.isVector()) { + const elem_ty = target_ty.vectorElementType() orelse return self.emitError("cannot determine vector element type"); + const converted = self.convertValue(val, src_ty, elem_ty); + return self.broadcastScalar(converted, target_ty); + } + + // Literals are exempt from narrowing checks + if (node.data == .int_literal or node.data == .float_literal) { + return self.convertValue(val, src_ty, target_ty); + } + + // Check for narrowing conversion + if (!src_ty.isImplicitlyConvertibleTo(target_ty)) { + // Narrowing without xx — compile error + return self.emitErrorFmt("narrowing conversion from '{s}' to '{s}' requires explicit 'xx' cast", .{ + src_ty.displayName(self.allocator) catch "?", + target_ty.displayName(self.allocator) catch "?", + }); + } + + return self.convertValue(val, src_ty, target_ty); + } + + /// Convert an LLVM value from src_ty to target_ty, emitting appropriate casts. + fn convertValue(self: *CodeGen, val: c.LLVMValueRef, src_ty: Type, target_ty: Type) c.LLVMValueRef { + // Same type → return as-is + if (std.meta.eql(src_ty, target_ty)) return val; + + const target_llvm = self.typeToLLVM(target_ty); + + // Any → concrete type: extract the i64 value and convert + if (src_ty.isAny()) { + const i64_val = c.LLVMBuildExtractValue(self.builder, val, 1, "any_extract"); + if (target_ty.isInt()) { + if (target_ty.bitWidth() < 64) { + return c.LLVMBuildTrunc(self.builder, i64_val, target_llvm, "any_to_int"); + } + return i64_val; + } + if (target_ty == .boolean) { + return c.LLVMBuildTrunc(self.builder, i64_val, c.LLVMInt1TypeInContext(self.context), "any_to_bool"); + } + if (target_ty == .f64) { + return c.LLVMBuildBitCast(self.builder, i64_val, c.LLVMDoubleTypeInContext(self.context), "any_to_f64"); + } + if (target_ty == .f32) { + const as_f64 = c.LLVMBuildBitCast(self.builder, i64_val, c.LLVMDoubleTypeInContext(self.context), "any_f64_tmp"); + return c.LLVMBuildFPTrunc(self.builder, as_f64, c.LLVMFloatTypeInContext(self.context), "any_to_f32"); + } + if (target_ty == .string_type) { + // i64 is a pointer to {ptr, i32} on the stack + const ptr = c.LLVMBuildIntToPtr(self.builder, i64_val, c.LLVMPointerTypeInContext(self.context, 0), "any_str_ptr"); + return c.LLVMBuildLoad2(self.builder, self.getStringStructType(), ptr, "any_to_str"); + } + if (target_ty.isStruct()) { + const sname = target_ty.struct_type; + if (self.struct_types.get(sname)) |info| { + const ptr = c.LLVMBuildIntToPtr(self.builder, i64_val, c.LLVMPointerTypeInContext(self.context, 0), "any_struct_ptr"); + return c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_struct"); + } + } + if (target_ty.isEnum()) { + return c.LLVMBuildTrunc(self.builder, i64_val, c.LLVMInt32TypeInContext(self.context), "any_to_enum"); + } + if (target_ty.isUnion()) { + const uname = target_ty.union_type; + if (self.union_types.get(uname)) |info| { + const ptr = c.LLVMBuildIntToPtr(self.builder, i64_val, c.LLVMPointerTypeInContext(self.context, 0), "any_union_ptr"); + return c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_union"); + } + } + return i64_val; + } + + // Float → float conversions + if (src_ty.isFloat() and target_ty.isFloat()) { + if (target_ty.bitWidth() > src_ty.bitWidth()) { + return c.LLVMBuildFPExt(self.builder, val, target_llvm, "fext"); + } else { + return c.LLVMBuildFPTrunc(self.builder, val, target_llvm, "ftrunc"); + } + } + + // Int → float conversions + if (src_ty.isInt() and target_ty.isFloat()) { + if (src_ty.isSigned()) { + return c.LLVMBuildSIToFP(self.builder, val, target_llvm, "sitofp"); + } else { + return c.LLVMBuildUIToFP(self.builder, val, target_llvm, "uitofp"); + } + } + + // Float → int conversions + if (src_ty.isFloat() and target_ty.isInt()) { + if (target_ty.isSigned()) { + return c.LLVMBuildFPToSI(self.builder, val, target_llvm, "fptosi"); + } else { + return c.LLVMBuildFPToUI(self.builder, val, target_llvm, "fptoui"); + } + } + + // Union → int: extract the tag field (index 0) + if (src_ty.isUnion() and target_ty.isInt()) { + const uname = src_ty.union_type; + if (self.union_types.get(uname)) |info| { + const tmp = c.LLVMBuildAlloca(self.builder, info.llvm_type, "union_cast"); + _ = c.LLVMBuildStore(self.builder, val, tmp); + const tag_ptr = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, tmp, 0, "tag_ptr"); + const tag_val = c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_ptr, "tag_val"); + if (target_ty.bitWidth() == 32) return tag_val; + if (target_ty.bitWidth() > 32) return c.LLVMBuildSExt(self.builder, tag_val, target_llvm, "tag_ext"); + return c.LLVMBuildTrunc(self.builder, tag_val, target_llvm, "tag_trunc"); + } + } + + // Int → int conversions + if (src_ty.isInt() and target_ty.isInt()) { + const sw = src_ty.bitWidth(); + const tw = target_ty.bitWidth(); + if (tw > sw) { + // Extend — use SExt if source is signed, ZExt if unsigned + if (src_ty.isSigned()) { + return c.LLVMBuildSExt(self.builder, val, target_llvm, "sext"); + } else { + return c.LLVMBuildZExt(self.builder, val, target_llvm, "zext"); + } + } else if (tw < sw) { + // Truncate + return c.LLVMBuildTrunc(self.builder, val, target_llvm, "trunc"); + } + // Same width, different signedness — no-op (bit pattern is the same) + return val; + } + + return val; + } + + fn findFieldIndex(_: *CodeGen, info: StructInfo, name: []const u8) ?usize { + for (info.field_names, 0..) |fn_name, i| { + if (std.mem.eql(u8, fn_name, name)) return i; + } + return null; + } + + fn componentToIndex(ch: u8) ?u32 { + return switch (ch) { + 'x', 'r', 'u' => 0, + 'y', 'g', 'v' => 1, + 'z', 'b' => 2, + 'w', 'a' => 3, + else => null, + }; + } + + fn genSqrt(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("sqrt expects exactly 1 argument"); + const arg_val = try self.genExpr(call_node.args[0]); + const arg_ty = self.inferType(call_node.args[0]); + + // Pick the right LLVM intrinsic based on float type + const intrinsic_name: [*c]const u8 = if (std.meta.eql(arg_ty, Type.f64)) "llvm.sqrt.f64" else "llvm.sqrt.f32"; + const llvm_float_ty = if (std.meta.eql(arg_ty, Type.f64)) + c.LLVMDoubleTypeInContext(self.context) + else + c.LLVMFloatTypeInContext(self.context); + + // Get or declare the intrinsic + var intrinsic_fn = c.LLVMGetNamedFunction(self.module, intrinsic_name); + if (intrinsic_fn == null) { + var param_types = [_]c.LLVMTypeRef{llvm_float_ty}; + const fn_type = c.LLVMFunctionType(llvm_float_ty, ¶m_types, 1, 0); + intrinsic_fn = c.LLVMAddFunction(self.module, intrinsic_name, fn_type); + } + + var args = [_]c.LLVMValueRef{arg_val}; + return c.LLVMBuildCall2( + self.builder, + c.LLVMGlobalGetValueType(intrinsic_fn.?), + intrinsic_fn.?, + &args, + 1, + "sqrt", + ); + } + + fn genSizeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("size_of expects exactly 1 argument"); + const ty = self.resolveType(call_node.args[0]); + if (std.meta.eql(ty, Type.void_type)) { + return c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + } + const llvm_ty = self.typeToLLVM(ty); + const data_layout = c.LLVMGetModuleDataLayout(self.module); + const size = c.LLVMStoreSizeOfType(data_layout, llvm_ty); + return c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), size, 0); + } + + fn genTypeOf(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("type_of expects exactly 1 argument"); + const arg = call_node.args[0]; + const arg_ty = self.inferType(arg); + const i32_ty = c.LLVMInt32TypeInContext(self.context); + + // For Any values: extract the runtime tag (field 0) + if (arg_ty.isAny()) { + const val = try self.genExpr(arg); + return c.LLVMBuildExtractValue(self.builder, val, 0, "type_of"); + } + + // For known types: return the constant tag value + const tag: u64 = switch (arg_ty) { + .void_type => ANY_TAG_VOID, + .boolean => ANY_TAG_BOOL, + .signed => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, + .unsigned => |w| if (w <= 32) ANY_TAG_S32 else ANY_TAG_S64, + .f32 => ANY_TAG_F32, + .f64 => ANY_TAG_F64, + .string_type => ANY_TAG_STRING, + .struct_type => |name| try self.getAnyTypeId(name, arg_ty), + .enum_type => |name| try self.getAnyTypeId(name, arg_ty), + .union_type => |name| try self.getAnyTypeId(name, arg_ty), + .meta_type => ANY_TAG_TYPE, + else => ANY_TAG_S32, + }; + return c.LLVMConstInt(i32_ty, tag, 0); + } + + fn genTypeName(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("type_name expects exactly 1 argument"); + const ty = self.resolveType(call_node.args[0]); + const name = try ty.displayName(self.allocator); + return self.buildConstStr(name); + } + + fn genFieldCount(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 1) return self.emitError("field_count expects exactly 1 argument"); + const ty = self.resolveType(call_node.args[0]); + const i32_ty = c.LLVMInt32TypeInContext(self.context); + if (ty.isStruct()) { + const info = self.struct_types.get(ty.struct_type) orelse + return self.emitErrorFmt("unknown struct type '{s}'", .{ty.struct_type}); + return c.LLVMConstInt(i32_ty, info.field_names.len, 0); + } + if (ty.isEnum()) { + const variants = self.enum_types.get(ty.enum_type) orelse + return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); + return c.LLVMConstInt(i32_ty, variants.len, 0); + } + if (ty.isVector()) { + return c.LLVMConstInt(i32_ty, ty.vector_type.length, 0); + } + if (ty.isUnion()) { + const info = self.union_types.get(ty.union_type) orelse + return self.emitErrorFmt("unknown union type '{s}'", .{ty.union_type}); + return c.LLVMConstInt(i32_ty, info.variant_names.len, 0); + } + if (ty.isArray()) { + return c.LLVMConstInt(i32_ty, ty.array_type.length, 0); + } + return self.emitError("field_count requires a struct, enum, vector, union, or array type"); + } + + fn genFieldName(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 2) return self.emitError("field_name expects 2 arguments: field_name(T, idx)"); + const ty = self.resolveType(call_node.args[0]); + + // Get the name list and type key + const names: []const []const u8, const type_key: []const u8 = if (ty.isStruct()) blk: { + const info = self.struct_types.get(ty.struct_type) orelse + return self.emitErrorFmt("unknown struct type '{s}'", .{ty.struct_type}); + break :blk .{ info.field_names, ty.struct_type }; + } else if (ty.isEnum()) blk: { + const variants = self.enum_types.get(ty.enum_type) orelse + return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); + break :blk .{ variants, ty.enum_type }; + } else if (ty.isUnion()) blk: { + const info = self.union_types.get(ty.union_type) orelse + return self.emitErrorFmt("unknown union type '{s}'", .{ty.union_type}); + break :blk .{ info.variant_names, ty.union_type }; + } else return self.emitError("field_name requires a struct, enum, or union type"); + + // Build a global array of string slices + const n = names.len; + const str_ty = self.getStringStructType(); + const arr_ty = c.LLVMArrayType2(str_ty, n); + + const vals = try self.allocator.alloc(c.LLVMValueRef, n); + for (names, 0..) |name, i| { + vals[i] = self.buildConstStrGlobal(name); + } + const arr_init = c.LLVMConstArray2(str_ty, vals.ptr, @intCast(n)); + const global_name = try self.allocator.dupeZ(u8, try std.fmt.allocPrint(self.allocator, "field_names.{s}", .{type_key})); + var global = c.LLVMGetNamedGlobal(self.module, global_name.ptr); + if (global == null) { + global = c.LLVMAddGlobal(self.module, arr_ty, global_name.ptr); + c.LLVMSetInitializer(global, arr_init); + c.LLVMSetGlobalConstant(global, 1); + c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); + } + + // GEP into the array with runtime index + const idx = try self.genExpr(call_node.args[1]); + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + var indices = [_]c.LLVMValueRef{ zero, idx }; + const elem_ptr = c.LLVMBuildGEP2(self.builder, arr_ty, global, &indices, 2, "field_name_ptr"); + return c.LLVMBuildLoad2(self.builder, str_ty, elem_ptr, "field_name"); + } + + fn genFieldValue(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 2) return self.emitError("field_value expects 2 arguments: field_value(s, idx)"); + + const val = try self.genExpr(call_node.args[0]); + const val_ty = self.inferType(call_node.args[0]); + + // Vector: extractelement + box as Any + if (val_ty.isVector()) { + const info = val_ty.vector_type; + const elem_ty = Type.fromName(info.element_name) orelse + return self.emitErrorFmt("unknown vector element type '{s}'", .{info.element_name}); + const idx = try self.genExpr(call_node.args[1]); + const elem = c.LLVMBuildExtractElement(self.builder, val, idx, "vec_elem"); + return self.buildAnyValue(elem, elem_ty); + } + + // Union: switch over tag, extract payload with correct type + if (val_ty.isUnion()) { + const uinfo = self.union_types.get(val_ty.union_type) orelse + return self.emitErrorFmt("unknown union type '{s}'", .{val_ty.union_type}); + + const union_alloca = c.LLVMBuildAlloca(self.builder, uinfo.llvm_type, "fv_union"); + _ = c.LLVMBuildStore(self.builder, val, union_alloca); + + // Read tag (field 0) + const tag_ptr = c.LLVMBuildStructGEP2(self.builder, uinfo.llvm_type, union_alloca, 0, "fv_tag_ptr"); + const tag_val = c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_ptr, "fv_tag"); + const payload_ptr = c.LLVMBuildStructGEP2(self.builder, uinfo.llvm_type, union_alloca, 1, "fv_payload_ptr"); + + const n = uinfo.variant_names.len; + const function = self.current_function; + const any_ty = self.getAnyStructType(); + const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_merge"); + const default_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_default"); + const sw = c.LLVMBuildSwitch(self.builder, tag_val, default_bb, @intCast(n)); + + var phi_vals = std.ArrayList(c.LLVMValueRef).empty; + var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; + + for (uinfo.variant_types, 0..) |vty, vi| { + const case_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_ucase"); + c.LLVMAddCase(sw, c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(vi), 0), case_bb); + c.LLVMPositionBuilderAtEnd(self.builder, case_bb); + + const any_val = if (vty == .void_type) blk: { + // Void variant: return Any with void tag + const undef = c.LLVMGetUndef(any_ty); + const void_tag = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), ANY_TAG_VOID, 0); + const with_tag = c.LLVMBuildInsertValue(self.builder, undef, void_tag, 0, "void_tag"); + const zero_val = c.LLVMConstInt(c.LLVMInt64TypeInContext(self.context), 0, 0); + break :blk c.LLVMBuildInsertValue(self.builder, with_tag, zero_val, 1, "void_any"); + } else blk: { + const payload = c.LLVMBuildLoad2(self.builder, self.typeToLLVM(vty), payload_ptr, "fv_payload"); + break :blk try self.buildAnyValue(payload, vty); + }; + try phi_vals.append(self.allocator, any_val); + try phi_bbs.append(self.allocator, c.LLVMGetInsertBlock(self.builder)); + _ = c.LLVMBuildBr(self.builder, merge_bb); + } + + // Default: undef + c.LLVMPositionBuilderAtEnd(self.builder, default_bb); + try phi_vals.append(self.allocator, c.LLVMGetUndef(any_ty)); + try phi_bbs.append(self.allocator, default_bb); + _ = c.LLVMBuildBr(self.builder, merge_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); + const vals_slice = try phi_vals.toOwnedSlice(self.allocator); + const bbs_slice = try phi_bbs.toOwnedSlice(self.allocator); + const phi = c.LLVMBuildPhi(self.builder, any_ty, "fv_uresult"); + c.LLVMAddIncoming(phi, vals_slice.ptr, bbs_slice.ptr, @intCast(vals_slice.len)); + return phi; + } + + // Array: GEP + load + box as Any + if (val_ty.isArray()) { + const ainfo = val_ty.array_type; + const elem_ty = Type.fromName(ainfo.element_name) orelse + return self.emitErrorFmt("unknown array element type '{s}'", .{ainfo.element_name}); + const arr_llvm_ty = self.typeToLLVM(val_ty); + const elem_llvm_ty = self.typeToLLVM(elem_ty); + const arr_alloca = c.LLVMBuildAlloca(self.builder, arr_llvm_ty, "fv_arr"); + _ = c.LLVMBuildStore(self.builder, val, arr_alloca); + const idx = try self.genExpr(call_node.args[1]); + var gep_indices = [_]c.LLVMValueRef{ + c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), + idx, + }; + const elem_ptr = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, arr_alloca, &gep_indices, 2, "fv_aelem"); + const elem = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, elem_ptr, "fv_aeval"); + return self.buildAnyValue(elem, elem_ty); + } + + // Struct: switch over field indices + const struct_val = val; + const struct_ty = val_ty; + if (!struct_ty.isStruct()) return self.emitError("field_value requires a struct, vector, union, or array value"); + + const info = self.struct_types.get(struct_ty.struct_type) orelse + return self.emitErrorFmt("unknown struct type '{s}'", .{struct_ty.struct_type}); + + const idx = try self.genExpr(call_node.args[1]); + const n = info.field_names.len; + + // Store struct to alloca BEFORE the switch (switch is a terminator) + const struct_alloca = c.LLVMBuildAlloca(self.builder, info.llvm_type, "fv_struct"); + _ = c.LLVMBuildStore(self.builder, struct_val, struct_alloca); + + // Generate switch on idx with N cases + const function = self.current_function; + const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_merge"); + const default_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_default"); + const sw = c.LLVMBuildSwitch(self.builder, idx, default_bb, @intCast(n)); + + const any_ty = self.getAnyStructType(); + var phi_vals = std.ArrayList(c.LLVMValueRef).empty; + var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; + + for (0..n) |i| { + const case_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "fv_case"); + const case_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), i, 0); + c.LLVMAddCase(sw, case_val, case_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, case_bb); + // Extract field i via GEP + load + const field_ptr = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, struct_alloca, @intCast(i), "fv_field_ptr"); + const field_llvm_ty = c.LLVMStructGetTypeAtIndex(info.llvm_type, @intCast(i)); + const field_val = c.LLVMBuildLoad2(self.builder, field_llvm_ty, field_ptr, "fv_field"); + const any_val = try self.buildAnyValue(field_val, info.field_types[i]); + try phi_vals.append(self.allocator, any_val); + try phi_bbs.append(self.allocator, c.LLVMGetInsertBlock(self.builder)); + _ = c.LLVMBuildBr(self.builder, merge_bb); + } + + // Default: return undef Any + c.LLVMPositionBuilderAtEnd(self.builder, default_bb); + try phi_vals.append(self.allocator, c.LLVMGetUndef(any_ty)); + try phi_bbs.append(self.allocator, default_bb); + _ = c.LLVMBuildBr(self.builder, merge_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); + const vals_slice = try phi_vals.toOwnedSlice(self.allocator); + const bbs_slice = try phi_bbs.toOwnedSlice(self.allocator); + const phi = c.LLVMBuildPhi(self.builder, any_ty, "fv_result"); + c.LLVMAddIncoming(phi, vals_slice.ptr, bbs_slice.ptr, @intCast(vals_slice.len)); + return phi; + } + + fn genCast(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + if (call_node.args.len != 2) return self.emitError("cast expects: cast(Type) expr"); + const target_ty = self.resolveType(call_node.args[0]); + const src_ty = self.inferType(call_node.args[1]); + const val = try self.genExpr(call_node.args[1]); + return self.convertValue(val, src_ty, target_ty); + } + + fn genAlloc(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { + if (args.len != 1) return self.emitError("alloc expects exactly 1 argument: alloc(size)"); + const builtins = self.builtins orelse return self.emitError("builtins not available (missing #builtin import)"); + const size_val = try self.genExpr(args[0]); + const i64_type = c.LLVMInt64TypeInContext(self.context); + // Extend size to i64 for calloc + const size_i64 = c.LLVMBuildSExt(self.builder, size_val, i64_type, "size64"); + // calloc(size + 1, 1) — extra byte for null terminator + const one_i64 = c.LLVMConstInt(i64_type, 1, 0); + const size_plus_one = c.LLVMBuildAdd(self.builder, size_i64, one_i64, "szp1"); + const calloc_fn = builtins.calloc_fn; + const calloc_ty = c.LLVMGlobalGetValueType(calloc_fn); + var calloc_args = [_]c.LLVMValueRef{ size_plus_one, one_i64 }; + const ptr = c.LLVMBuildCall2(self.builder, calloc_ty, calloc_fn, &calloc_args, 2, "alloc_ptr"); + // Build string slice: {ptr, size} + return self.buildStringSliceRT(ptr, size_val); + } + + fn genVectorExtract(self: *CodeGen, vec_val: c.LLVMValueRef, field: []const u8) !c.LLVMValueRef { + if (field.len == 1) { + const idx_val = componentToIndex(field[0]) orelse return self.emitErrorFmt("invalid vector component '{c}'", .{field[0]}); + const idx = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), idx_val, 0); + return c.LLVMBuildExtractElement(self.builder, vec_val, idx, "comp"); + } + return self.emitErrorFmt("unsupported vector swizzle '{s}'", .{field}); + } + + fn genFieldAccess(self: *CodeGen, fa: ast.FieldAccess) !c.LLVMValueRef { + // Check if the object is a struct or vector variable + if (fa.object.data == .identifier) { + if (self.named_values.get(fa.object.data.identifier.name)) |entry| { + if (entry.ty.isStruct()) { + const sname = entry.ty.struct_type; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + const idx = self.findFieldIndex(info, fa.field) orelse return self.emitErrorFmt("no field '{s}' in struct '{s}'", .{ fa.field, sname }); + const gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, @intCast(idx), "field"); + return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(info.field_types[idx]), gep, "fieldval"); + } + if (entry.ty.isUnion()) { + const uname = entry.ty.union_type; + const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union type '{s}'", .{uname}); + // Find variant by name to determine payload type + var vidx: ?usize = null; + for (info.variant_names, 0..) |vn, i| { + if (std.mem.eql(u8, vn, fa.field)) { + vidx = i; + break; + } + } + const idx = vidx orelse return self.emitErrorFmt("no variant '{s}' in union '{s}'", .{ fa.field, uname }); + const variant_ty = info.variant_types[idx]; + if (variant_ty == .void_type) return self.emitErrorFmt("cannot access payload of void variant '{s}'", .{fa.field}); + // GEP to field 1 (payload area), load as variant type + const payload_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, 1, "payload"); + return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(variant_ty), payload_gep, "union_payload"); + } + if (entry.ty.isVector()) { + const vec_val = c.LLVMBuildLoad2(self.builder, self.typeToLLVM(entry.ty), entry.ptr, "vec_load"); + return self.genVectorExtract(vec_val, fa.field); + } + if (entry.ty == .string_type) { + const str_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "str_load"); + if (std.mem.eql(u8, fa.field, "len")) { + return c.LLVMBuildExtractValue(self.builder, str_val, 1, "str_len"); + } + if (std.mem.eql(u8, fa.field, "ptr")) { + return c.LLVMBuildExtractValue(self.builder, str_val, 0, "str_ptr"); + } + return self.emitErrorFmt("no field '{s}' on string (available: .len, .ptr)", .{fa.field}); + } + if (entry.ty.isSlice()) { + const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "slice_load"); + if (std.mem.eql(u8, fa.field, "len")) { + return c.LLVMBuildExtractValue(self.builder, slice_val, 1, "slice_len"); + } + if (std.mem.eql(u8, fa.field, "ptr")) { + return c.LLVMBuildExtractValue(self.builder, slice_val, 0, "slice_ptr"); + } + return self.emitErrorFmt("no field '{s}' on slice (available: .len, .ptr)", .{fa.field}); + } + if (entry.ty.isAny()) { + const any_val = c.LLVMBuildLoad2(self.builder, self.getAnyStructType(), entry.ptr, "any_load"); + if (std.mem.eql(u8, fa.field, "tag")) { + return c.LLVMBuildExtractValue(self.builder, any_val, 0, "any_tag"); + } + if (std.mem.eql(u8, fa.field, "value")) { + return c.LLVMBuildExtractValue(self.builder, any_val, 1, "any_value"); + } + return self.emitErrorFmt("no field '{s}' on Any (available: .tag, .value)", .{fa.field}); + } + } + } + // Non-identifier object: evaluate expression and check type + const obj_val = try self.genExpr(fa.object); + const obj_ty = self.inferType(fa.object); + if (obj_ty.isVector()) { + return self.genVectorExtract(obj_val, fa.field); + } + if (obj_ty == .string_type) { + if (std.mem.eql(u8, fa.field, "len")) { + return c.LLVMBuildExtractValue(self.builder, obj_val, 1, "str_len"); + } + if (std.mem.eql(u8, fa.field, "ptr")) { + return c.LLVMBuildExtractValue(self.builder, obj_val, 0, "str_ptr"); + } + return self.emitErrorFmt("no field '{s}' on string (available: .len, .ptr)", .{fa.field}); + } + return self.emitError("field access on non-struct/non-vector expression"); + } + + fn genVectorComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef, vec_ty: Type, elem_ty: Type) c.LLVMValueRef { + const vec_info = vec_ty.vector_type; + const cmp = if (elem_ty.isFloat()) + (if (op == .eq) c.LLVMBuildFCmp(self.builder, c.LLVMRealOEQ, lhs, rhs, "vcmp") else c.LLVMBuildFCmp(self.builder, c.LLVMRealONE, lhs, rhs, "vcmp")) + else + (if (op == .eq) c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, lhs, rhs, "vcmp") else c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs, rhs, "vcmp")); + // Reduce: extract each i1 and AND (eq) or OR (neq) + var result = c.LLVMBuildExtractElement(self.builder, cmp, c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0), "cmp0"); + for (1..vec_info.length) |i| { + const elem = c.LLVMBuildExtractElement(self.builder, cmp, c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(i), 0), "cmpi"); + result = if (op == .eq) + c.LLVMBuildAnd(self.builder, result, elem, "andcmp") + else + c.LLVMBuildOr(self.builder, result, elem, "orcmp"); + } + return result; + } + + fn genIndexExpr(self: *CodeGen, ie: ast.IndexExpr) !c.LLVMValueRef { + const obj_ty = self.inferType(ie.object); + if (obj_ty.isVector()) { + const vec_val = try self.genExpr(ie.object); + const idx = try self.genExpr(ie.index); + return c.LLVMBuildExtractElement(self.builder, vec_val, idx, "vidx"); + } + if (obj_ty.isArray()) { + // Array index: load from GEP + if (ie.object.data == .identifier) { + if (self.named_values.get(ie.object.data.identifier.name)) |entry| { + const arr_info = obj_ty.array_type; + const elem_ty = Type.fromName(arr_info.element_name) orelse return self.emitErrorFmt("unknown array element type '{s}'", .{arr_info.element_name}); + const idx = try self.genExpr(ie.index); + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + var indices = [_]c.LLVMValueRef{ zero, idx }; + const gep = c.LLVMBuildGEP2(self.builder, self.typeToLLVM(obj_ty), entry.ptr, &indices, 2, "arridx"); + return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(elem_ty), gep, "arrval"); + } + } + } + if (obj_ty == .string_type) { + // String indexing: extract ptr from slice, GEP + load i8 + zext to i32 + const str_val = try self.genExpr(ie.object); + const ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str_ptr"); + const idx = try self.genExpr(ie.index); + const i8_type = c.LLVMInt8TypeInContext(self.context); + var gep_indices = [_]c.LLVMValueRef{idx}; + const gep = c.LLVMBuildGEP2(self.builder, i8_type, ptr, &gep_indices, 1, "stridx"); + const byte = c.LLVMBuildLoad2(self.builder, i8_type, gep, "byte"); + return c.LLVMBuildZExt(self.builder, byte, c.LLVMInt32TypeInContext(self.context), "char"); + } + if (obj_ty.isSlice()) { + // Slice indexing: extract ptr, GEP with element type, load + const slice_info = obj_ty.slice_type; + const elem_ty = Type.fromName(slice_info.element_name) orelse return self.emitErrorFmt("unknown slice element type '{s}'", .{slice_info.element_name}); + const elem_llvm_ty = self.typeToLLVM(elem_ty); + // For identifier objects, load the slice from alloca + if (ie.object.data == .identifier) { + if (self.named_values.get(ie.object.data.identifier.name)) |entry| { + const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "slice_load"); + const ptr = c.LLVMBuildExtractValue(self.builder, slice_val, 0, "slice_ptr"); + const idx = try self.genExpr(ie.index); + var gep_indices = [_]c.LLVMValueRef{idx}; + const gep = c.LLVMBuildGEP2(self.builder, elem_llvm_ty, ptr, &gep_indices, 1, "sliceidx"); + return c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "sliceval"); + } + } + // Fallback for non-identifier slice expressions + const slice_val = try self.genExpr(ie.object); + const ptr = c.LLVMBuildExtractValue(self.builder, slice_val, 0, "slice_ptr"); + const idx = try self.genExpr(ie.index); + var gep_indices = [_]c.LLVMValueRef{idx}; + const gep = c.LLVMBuildGEP2(self.builder, elem_llvm_ty, ptr, &gep_indices, 1, "sliceidx"); + return c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "sliceval"); + } + return self.emitError("index expression requires an array, vector, string, or slice"); + } + + fn genBinaryOp(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef, result_type: Type) c.LLVMValueRef { + // Vector types: dispatch based on element type (LLVM does element-wise automatically) + if (result_type.isVector()) { + const elem_ty = result_type.vectorElementType() orelse return lhs; + if (op == .eq or op == .neq) { + return self.genVectorComparison(op, lhs, rhs, result_type, elem_ty); + } + if (elem_ty.isFloat()) { + return switch (op) { + .add => c.LLVMBuildFAdd(self.builder, lhs, rhs, "vaddtmp"), + .sub => c.LLVMBuildFSub(self.builder, lhs, rhs, "vsubtmp"), + .mul => c.LLVMBuildFMul(self.builder, lhs, rhs, "vmultmp"), + .div => c.LLVMBuildFDiv(self.builder, lhs, rhs, "vdivtmp"), + .mod => c.LLVMBuildFRem(self.builder, lhs, rhs, "vmodtmp"), + else => lhs, + }; + } else if (elem_ty.isUnsigned()) { + return switch (op) { + .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "vaddtmp"), + .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "vsubtmp"), + .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "vmultmp"), + .div => c.LLVMBuildUDiv(self.builder, lhs, rhs, "vdivtmp"), + .mod => c.LLVMBuildURem(self.builder, lhs, rhs, "vmodtmp"), + else => lhs, + }; + } else { + return switch (op) { + .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "vaddtmp"), + .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "vsubtmp"), + .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "vmultmp"), + .div => c.LLVMBuildSDiv(self.builder, lhs, rhs, "vdivtmp"), + .mod => c.LLVMBuildSRem(self.builder, lhs, rhs, "vmodtmp"), + else => lhs, + }; + } + } + if (result_type.isFloat()) { + return switch (op) { + .add => c.LLVMBuildFAdd(self.builder, lhs, rhs, "addtmp"), + .sub => c.LLVMBuildFSub(self.builder, lhs, rhs, "subtmp"), + .mul => c.LLVMBuildFMul(self.builder, lhs, rhs, "multmp"), + .div => c.LLVMBuildFDiv(self.builder, lhs, rhs, "divtmp"), + .mod => c.LLVMBuildFRem(self.builder, lhs, rhs, "modtmp"), + .eq => c.LLVMBuildFCmp(self.builder, c.LLVMRealOEQ, lhs, rhs, "eqtmp"), + .neq => c.LLVMBuildFCmp(self.builder, c.LLVMRealONE, lhs, rhs, "neqtmp"), + .lt => c.LLVMBuildFCmp(self.builder, c.LLVMRealOLT, lhs, rhs, "lttmp"), + .lte => c.LLVMBuildFCmp(self.builder, c.LLVMRealOLE, lhs, rhs, "letmp"), + .gt => c.LLVMBuildFCmp(self.builder, c.LLVMRealOGT, lhs, rhs, "gttmp"), + .gte => c.LLVMBuildFCmp(self.builder, c.LLVMRealOGE, lhs, rhs, "getmp"), + .and_op, .or_op => unreachable, + }; + } else if (result_type.isUnsigned()) { + return switch (op) { + .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "addtmp"), + .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "subtmp"), + .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "multmp"), + .div => c.LLVMBuildUDiv(self.builder, lhs, rhs, "divtmp"), + .mod => c.LLVMBuildURem(self.builder, lhs, rhs, "modtmp"), + .eq => c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, lhs, rhs, "eqtmp"), + .neq => c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs, rhs, "neqtmp"), + .lt => c.LLVMBuildICmp(self.builder, c.LLVMIntULT, lhs, rhs, "lttmp"), + .lte => c.LLVMBuildICmp(self.builder, c.LLVMIntULE, lhs, rhs, "letmp"), + .gt => c.LLVMBuildICmp(self.builder, c.LLVMIntUGT, lhs, rhs, "gttmp"), + .gte => c.LLVMBuildICmp(self.builder, c.LLVMIntUGE, lhs, rhs, "getmp"), + .and_op, .or_op => unreachable, + }; + } else { + // signed int (default) + return switch (op) { + .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "addtmp"), + .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "subtmp"), + .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "multmp"), + .div => c.LLVMBuildSDiv(self.builder, lhs, rhs, "divtmp"), + .mod => c.LLVMBuildSRem(self.builder, lhs, rhs, "modtmp"), + .eq => c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, lhs, rhs, "eqtmp"), + .neq => c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs, rhs, "neqtmp"), + .lt => c.LLVMBuildICmp(self.builder, c.LLVMIntSLT, lhs, rhs, "lttmp"), + .lte => c.LLVMBuildICmp(self.builder, c.LLVMIntSLE, lhs, rhs, "letmp"), + .gt => c.LLVMBuildICmp(self.builder, c.LLVMIntSGT, lhs, rhs, "gttmp"), + .gte => c.LLVMBuildICmp(self.builder, c.LLVMIntSGE, lhs, rhs, "getmp"), + .and_op, .or_op => unreachable, + }; + } + } + + fn genAndOp(self: *CodeGen, binop: ast.BinaryOp) !c.LLVMValueRef { + const function = self.current_function; + const i1_type = c.LLVMInt1TypeInContext(self.context); + + var lhs_val = try self.genExpr(binop.lhs); + if (c.LLVMTypeOf(lhs_val) != i1_type) { + lhs_val = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs_val, c.LLVMConstInt(c.LLVMTypeOf(lhs_val), 0, 0), "tobool"); + } + const lhs_bb = c.LLVMGetInsertBlock(self.builder); + + const rhs_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "and.rhs"); + const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "and.merge"); + + _ = c.LLVMBuildCondBr(self.builder, lhs_val, rhs_bb, merge_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, rhs_bb); + var rhs_val = try self.genExpr(binop.rhs); + if (c.LLVMTypeOf(rhs_val) != i1_type) { + rhs_val = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, rhs_val, c.LLVMConstInt(c.LLVMTypeOf(rhs_val), 0, 0), "tobool"); + } + const rhs_end_bb = c.LLVMGetInsertBlock(self.builder); + _ = c.LLVMBuildBr(self.builder, merge_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); + const phi = c.LLVMBuildPhi(self.builder, i1_type, "and.result"); + var vals = [2]c.LLVMValueRef{ c.LLVMConstInt(i1_type, 0, 0), rhs_val }; + var blocks = [2]c.LLVMBasicBlockRef{ lhs_bb, rhs_end_bb }; + c.LLVMAddIncoming(phi, &vals, &blocks, 2); + + return phi; + } + + fn genOrOp(self: *CodeGen, binop: ast.BinaryOp) !c.LLVMValueRef { + const function = self.current_function; + const i1_type = c.LLVMInt1TypeInContext(self.context); + + var lhs_val = try self.genExpr(binop.lhs); + if (c.LLVMTypeOf(lhs_val) != i1_type) { + lhs_val = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs_val, c.LLVMConstInt(c.LLVMTypeOf(lhs_val), 0, 0), "tobool"); + } + const lhs_bb = c.LLVMGetInsertBlock(self.builder); + + const rhs_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "or.rhs"); + const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "or.merge"); + + _ = c.LLVMBuildCondBr(self.builder, lhs_val, merge_bb, rhs_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, rhs_bb); + var rhs_val = try self.genExpr(binop.rhs); + if (c.LLVMTypeOf(rhs_val) != i1_type) { + rhs_val = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, rhs_val, c.LLVMConstInt(c.LLVMTypeOf(rhs_val), 0, 0), "tobool"); + } + const rhs_end_bb = c.LLVMGetInsertBlock(self.builder); + _ = c.LLVMBuildBr(self.builder, merge_bb); + + c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); + const phi = c.LLVMBuildPhi(self.builder, i1_type, "or.result"); + var vals = [2]c.LLVMValueRef{ c.LLVMConstInt(i1_type, 1, 0), rhs_val }; + var blocks = [2]c.LLVMBasicBlockRef{ lhs_bb, rhs_end_bb }; + c.LLVMAddIncoming(phi, &vals, &blocks, 2); + + return phi; + } + + fn genChainedComparison(self: *CodeGen, chain: ast.ChainedComparison) !c.LLVMValueRef { + // Evaluate all operands exactly once + var operand_vals = std.ArrayList(c.LLVMValueRef).empty; + for (chain.operands) |operand| { + const val = try self.genExpr(operand); + try operand_vals.append(self.allocator, val); + } + + // Compare pairwise and AND results together + var result: c.LLVMValueRef = undefined; + for (chain.ops, 0..) |op, i| { + const lhs_ty = self.inferType(chain.operands[i]); + const rhs_ty = self.inferType(chain.operands[i + 1]); + const cmp_type = Type.widen(lhs_ty, rhs_ty); + + const lhs_conv = self.convertValue(operand_vals.items[i], lhs_ty, cmp_type); + const rhs_conv = self.convertValue(operand_vals.items[i + 1], rhs_ty, cmp_type); + + const cmp = self.genBinaryOp(op, lhs_conv, rhs_conv, cmp_type); + + if (i == 0) { + result = cmp; + } else { + result = c.LLVMBuildAnd(self.builder, result, cmp, "chain.and"); + } + } + + return result; + } + + fn genCall(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { + // Handle union construction: Shape.variant(payload) + if (call_node.callee.data == .field_access) { + const fa = call_node.callee.data.field_access; + // Resolve the object to a type name (identifier, call, or field_access chain) + const resolved_type: ?Type = blk: { + if (fa.object.data == .identifier) { + const name = self.type_aliases.get(fa.object.data.identifier.name) orelse fa.object.data.identifier.name; + if (self.union_types.contains(name)) break :blk .{ .union_type = name }; + if (self.struct_types.contains(name)) break :blk .{ .struct_type = name }; + } else { + const ty = self.resolveType(fa.object); + if (ty.isUnion() or ty.isStruct()) break :blk ty; + } + break :blk null; + }; + if (resolved_type) |rty| { + if (rty.isUnion()) { + const type_name = rty.union_type; + const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null; + const ul = ast.UnionLiteral{ + .union_name = type_name, + .variant_name = fa.field, + .payload = payload_node, + }; + return self.genUnionLiteral(ul, type_name); + } + } + } + + // Handle namespaced calls: namespace.func(args) + if (call_node.callee.data == .field_access) { + const fa = call_node.callee.data.field_access; + if (fa.object.data == .identifier) { + const ns_name = fa.object.data.identifier.name; + if (self.namespaces.contains(ns_name)) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns_name, fa.field }); + return self.genCallByName(qualified, call_node); + } + } + } + + // Resolve callee — must be an identifier + if (call_node.callee.data != .identifier) return self.emitError("callee must be an identifier"); + const callee_name = call_node.callee.data.identifier.name; + return self.genCallByName(callee_name, call_node); + } + + fn genCallByName(self: *CodeGen, callee_name: []const u8, call_node: ast.Call) !c.LLVMValueRef { + // Check if this is a generic function call + if (self.generic_templates.get(callee_name)) |template| { + return self.genGenericCall(callee_name, template, call_node); + } + // Intra-namespace fallback for generic templates + if (self.current_namespace) |ns| { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); + if (self.generic_templates.get(qualified)) |template| { + return self.genGenericCall(qualified, template, call_node); + } + } + + // Check for #builtin function (only available when imported) + if (self.builtin_functions.contains(callee_name)) { + return self.dispatchBuiltin(callee_name, call_node); + } + // Intra-namespace fallback for builtins + if (self.current_namespace) |ns| { + const qualified_builtin = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); + if (self.builtin_functions.contains(qualified_builtin)) { + return self.dispatchBuiltin(qualified_builtin, call_node); + } + } + + // Compiler intrinsics (always available, no #builtin declaration needed) + if (std.mem.eql(u8, callee_name, "sqrt")) { + return self.genSqrt(call_node); + } + if (std.mem.eql(u8, callee_name, "cast")) { + return self.genCast(call_node); + } + + const name_z = try self.allocator.dupeZ(u8, callee_name); + var callee_fn = c.LLVMGetNamedFunction(self.module, name_z.ptr); + // Intra-namespace fallback: try qualified name + if (callee_fn == null) { + if (self.current_namespace) |ns| { + const qualified2 = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); + const qualified_z = try self.allocator.dupeZ(u8, qualified2); + callee_fn = c.LLVMGetNamedFunction(self.module, qualified_z.ptr); + } + } + if (callee_fn == null) return self.emitErrorFmt("undefined function '{s}'", .{callee_name}); + + // Get function type (opaque pointers: use LLVMGlobalGetValueType) + const fn_type = c.LLVMGlobalGetValueType(callee_fn.?); + + // Check if this is a variadic function call + const var_info = self.variadic_functions.get(callee_name) orelse blk: { + // Try qualified name lookup + if (self.current_namespace) |ns| { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }); + break :blk self.variadic_functions.get(qualified); + } + break :blk null; + }; + + // Generate arguments with type conversion to match parameter types + const num_params = c.LLVMCountParamTypes(fn_type); + var param_llvm_types: [64]c.LLVMTypeRef = undefined; + if (num_params > 0) { + c.LLVMGetParamTypes(fn_type, ¶m_llvm_types); + } + + var arg_vals = std.ArrayList(c.LLVMValueRef).empty; + + if (var_info) |vi| { + // Variadic call: generate fixed args, then pack remaining into slice + const fixed_count = vi.fixed_param_count; + // Generate fixed args + for (0..fixed_count) |i| { + if (i < call_node.args.len) { + const param_ty = self.llvmTypeToSxType(param_llvm_types[i]); + try arg_vals.append(self.allocator, try self.genExprAsType(call_node.args[i], param_ty)); + } + } + // Pack variadic args into a slice {ptr, len} + const elem_ty = Type.fromName(vi.element_type_name) orelse Type.s(32); + const elem_llvm_ty = self.typeToLLVM(elem_ty); + const var_arg_count = if (call_node.args.len > fixed_count) call_node.args.len - fixed_count else 0; + + // Check for spread operator: fn(..array) — single spread arg + if (var_arg_count == 1 and call_node.args[fixed_count].data == .spread_expr) { + const spread_operand = call_node.args[fixed_count].data.spread_expr.operand; + const spread_ty = self.inferType(spread_operand); + if (spread_ty.isArray()) { + // Spread an array: construct slice from array pointer + known length + const arr_info = spread_ty.array_type; + if (spread_operand.data == .identifier) { + if (self.named_values.get(spread_operand.data.identifier.name)) |entry| { + const arr_llvm_ty = self.typeToLLVM(spread_ty); + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + var ptr_indices = [_]c.LLVMValueRef{ zero, zero }; + const arr_ptr = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, entry.ptr, &ptr_indices, 2, "spread_ptr"); + const len_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), arr_info.length, 0); + const slice_val = self.buildStringSliceRT(arr_ptr, len_val); + try arg_vals.append(self.allocator, slice_val); + } else { + return self.emitError("spread operand not found"); + } + } else { + return self.emitError("spread operator requires a named variable"); + } + } else if (spread_ty.isSlice()) { + // Spread a slice: pass through as-is + const slice_val = try self.genExpr(spread_operand); + try arg_vals.append(self.allocator, slice_val); + } else { + return self.emitError("spread operator requires an array or slice"); + } + } else if (var_arg_count > 0) { + // Allocate array on stack: [N x elem_type] + const arr_ty = c.LLVMArrayType2(elem_llvm_ty, @intCast(var_arg_count)); + const arr_alloca = c.LLVMBuildAlloca(self.builder, arr_ty, "varargs_arr"); + // Store each variadic arg + for (0..var_arg_count) |vi_idx| { + const arg_val = if (elem_ty.isAny()) blk: { + // ..Any: wrap each arg in Any{tag, value} + const raw_val = try self.genExpr(call_node.args[fixed_count + vi_idx]); + const arg_ty = self.inferType(call_node.args[fixed_count + vi_idx]); + break :blk try self.buildAnyValue(raw_val, arg_ty); + } else try self.genExprAsType(call_node.args[fixed_count + vi_idx], elem_ty); + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + const idx_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(vi_idx), 0); + var indices = [_]c.LLVMValueRef{ zero, idx_val }; + const gep = c.LLVMBuildGEP2(self.builder, arr_ty, arr_alloca, &indices, 2, "vararg_elem"); + _ = c.LLVMBuildStore(self.builder, arg_val, gep); + } + // Build slice: {ptr, len} + const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + var ptr_indices = [_]c.LLVMValueRef{ zero, zero }; + const arr_ptr = c.LLVMBuildGEP2(self.builder, arr_ty, arr_alloca, &ptr_indices, 2, "varargs_ptr"); + const len_val = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), @intCast(var_arg_count), 0); + const slice_val = self.buildStringSliceRT(arr_ptr, len_val); + try arg_vals.append(self.allocator, slice_val); + } else { + // Zero variadic args: pass empty slice {null, 0} + const null_ptr = c.LLVMConstNull(c.LLVMPointerTypeInContext(self.context, 0)); + const zero_len = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); + const slice_val = self.buildStringSliceRT(null_ptr, zero_len); + try arg_vals.append(self.allocator, slice_val); + } + } else { + // Normal (non-variadic) call + for (call_node.args, 0..) |arg, i| { + if (i < num_params) { + const param_ty = self.llvmTypeToSxType(param_llvm_types[i]); + try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty)); + } else { + try arg_vals.append(self.allocator, try self.genExpr(arg)); + } + } + } + const args_slice = try arg_vals.toOwnedSlice(self.allocator); + + const ret_ty = c.LLVMGetReturnType(fn_type); + const call_name: [*c]const u8 = if (ret_ty == c.LLVMVoidTypeInContext(self.context)) "" else "calltmp"; + return c.LLVMBuildCall2( + self.builder, + fn_type, + callee_fn.?, + if (args_slice.len > 0) args_slice.ptr else null, + @intCast(args_slice.len), + call_name, + ); + } + + fn genGenericCall(self: *CodeGen, qualified_name: []const u8, template: GenericTemplate, call_node: ast.Call) !c.LLVMValueRef { + const fd = template.fd; + + // Check for runtime type dispatch: cast(runtime_type_var, any_val) as argument + if (self.current_match_tags) |match_tags| { + if (match_tags.len > 0) { + for (call_node.args) |arg| { + if (arg.data == .call) { + if (arg.data.call.callee.data == .identifier) { + const cast_name = arg.data.call.callee.data.identifier.name; + if (std.mem.eql(u8, cast_name, "cast") or std.mem.eql(u8, cast_name, "std.cast")) { + if (arg.data.call.args.len == 2) { + const type_arg = arg.data.call.args[0]; + // Check if first arg of cast is a runtime variable (not a type expression) + if (type_arg.data == .identifier) { + const name = type_arg.data.identifier.name; + // It's a runtime type if it's a named_value, not a type name + if (self.named_values.contains(name) and + Type.fromName(name) == null and + !self.struct_types.contains(name) and + !self.enum_types.contains(name) and + !self.union_types.contains(name) and + !self.type_aliases.contains(name)) + { + return self.genGenericCallWithRuntimeDispatch(template, call_node, match_tags); + } + } + } + } + } + } + } + } + } + + // Check for comptime value params + var has_comptime_values = false; + var comptime_nodes = std.StringHashMap(*Node).init(self.allocator); + for (fd.type_params) |tp| { + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (!std.mem.eql(u8, constraint_name, "Type")) { + // Value param — extract comptime value from call arg + has_comptime_values = true; + for (fd.params, 0..) |param, pi| { + if (std.mem.eql(u8, param.name, tp.name)) { + if (pi < call_node.args.len) { + try comptime_nodes.put(tp.name, @constCast(call_node.args[pi])); + } + break; + } + } + } + } + + // Normal generic call: Infer type bindings from arguments, widening across all args for the same type param + var bindings = std.StringHashMap(Type).init(self.allocator); + for (fd.params, 0..) |param, i| { + if (param.is_comptime) continue; + if (param.type_expr.data == .type_expr) { + const type_name = param.type_expr.data.type_expr.name; + // Check if this type name is a type parameter + for (fd.type_params) |tp| { + if (std.mem.eql(u8, tp.name, type_name)) { + if (i < call_node.args.len) { + const arg_ty = self.inferType(call_node.args[i]); + if (bindings.get(type_name)) |existing| { + // Widen to the broader type to avoid data loss + try bindings.put(type_name, Type.widen(existing, arg_ty)); + } else { + try bindings.put(type_name, arg_ty); + } + } + break; + } + } + } + } + + if (has_comptime_values) { + return self.genComptimeCall(qualified_name, fd, call_node, bindings, comptime_nodes); + } + + // Generate mangled name + const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings); + + // Check cache + const callee_fn = if (self.generic_instances.get(mangled)) |cached| + cached + else + try self.instantiateGeneric(fd, bindings, mangled); + + // Generate arguments with type conversion to match parameter types + self.type_param_bindings = bindings; + var arg_vals = std.ArrayList(c.LLVMValueRef).empty; + for (call_node.args, 0..) |arg, i| { + if (i < fd.params.len) { + const param_ty = self.resolveType(fd.params[i].type_expr); + try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty)); + } else { + try arg_vals.append(self.allocator, try self.genExpr(arg)); + } + } + self.type_param_bindings = null; + const args_slice = try arg_vals.toOwnedSlice(self.allocator); + + const fn_type = c.LLVMGlobalGetValueType(callee_fn); + return c.LLVMBuildCall2( + self.builder, + fn_type, + callee_fn, + if (args_slice.len > 0) args_slice.ptr else null, + @intCast(args_slice.len), + "calltmp", + ); + } + + /// Generate a call to a generic function with comptime value parameters. + /// Instantiates the function with the specific comptime values, then delegates to genCallByName + /// with the mangled name and adjusted args (comptime args removed). + fn genComptimeCall( + self: *CodeGen, + qualified_name: []const u8, + fd: ast.FnDecl, + call_node: ast.Call, + type_bindings: std.StringHashMap(Type), + comptime_nodes: std.StringHashMap(*Node), + ) !c.LLVMValueRef { + // Build mangled name including comptime values (use qualified name for namespace) + var buf = std.ArrayList(u8).empty; + try buf.appendSlice(self.allocator, qualified_name); + try buf.appendSlice(self.allocator, "__"); + for (fd.type_params, 0..) |tp, i| { + if (i > 0) try buf.append(self.allocator, '_'); + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (std.mem.eql(u8, constraint_name, "Type")) { + // Type param — include type name + if (type_bindings.get(tp.name)) |ty| { + const name = try ty.displayName(self.allocator); + try buf.appendSlice(self.allocator, name); + } + } else { + // Value param — include hash of value + if (comptime_nodes.get(tp.name)) |node| { + if (node.data == .string_literal) { + const hash = std.hash.Wyhash.hash(0, node.data.string_literal.raw); + var hash_buf: [16]u8 = undefined; + const hash_str = std.fmt.bufPrint(&hash_buf, "{x}", .{hash}) catch "0"; + try buf.appendSlice(self.allocator, hash_str); + } else if (node.data == .int_literal) { + var int_buf: [20]u8 = undefined; + const int_str = std.fmt.bufPrint(&int_buf, "{d}", .{node.data.int_literal.value}) catch "0"; + try buf.appendSlice(self.allocator, int_str); + } + } + } + } + const mangled = try buf.toOwnedSlice(self.allocator); + + // Instantiate if not cached + if (!self.generic_instances.contains(mangled)) { + // Set comptime param nodes for #insert substitution + const saved_comptime_nodes = self.comptime_param_nodes; + self.comptime_param_nodes = comptime_nodes; + defer self.comptime_param_nodes = saved_comptime_nodes; + + // Set namespace context if the qualified name is namespaced (e.g. "std.print") + const saved_namespace = self.current_namespace; + if (std.mem.indexOfScalar(u8, qualified_name, '.')) |dot_pos| { + self.current_namespace = qualified_name[0..dot_pos]; + } + defer self.current_namespace = saved_namespace; + + _ = try self.instantiateGeneric(fd, type_bindings, mangled); + + // Register variadic info for the mangled function (adjusted for removed comptime params) + var comptime_before_variadic: u32 = 0; + for (fd.params) |param| { + if (param.is_variadic) break; + if (param.is_comptime) comptime_before_variadic += 1; + } + for (fd.params, 0..) |param, i| { + if (param.is_variadic) { + const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; + try self.variadic_functions.put(mangled, .{ + .fixed_param_count = @intCast(i - comptime_before_variadic), + .element_type_name = elem_name, + }); + break; + } + } + } + + // Build adjusted call args (skip comptime args) + var adjusted_args = std.ArrayList(*Node).empty; + for (call_node.args, 0..) |arg, i| { + if (i < fd.params.len and fd.params[i].is_comptime) continue; + try adjusted_args.append(self.allocator, @constCast(arg)); + } + const adjusted_args_slice = try adjusted_args.toOwnedSlice(self.allocator); + + const adjusted_call = ast.Call{ + .callee = call_node.callee, + .args = adjusted_args_slice, + }; + + // Call the instantiated function through normal path (handles variadic packing etc.) + return self.genCallByName(mangled, adjusted_call); + } + + /// Generate a generic function call with runtime type dispatch. + /// For each type tag in match_tags, monomorphize the generic function and dispatch via switch. + fn genGenericCallWithRuntimeDispatch( + self: *CodeGen, + template: GenericTemplate, + call_node: ast.Call, + match_tags: []const u64, + ) !c.LLVMValueRef { + const fd = template.fd; + + // Find the cast argument and extract the runtime type tag + any value source + var cast_arg_idx: usize = 0; + var type_tag_node: *Node = undefined; + var any_val_node: *Node = undefined; + for (call_node.args, 0..) |arg, i| { + if (arg.data == .call and arg.data.call.callee.data == .identifier) { + const name = arg.data.call.callee.data.identifier.name; + if ((std.mem.eql(u8, name, "cast") or std.mem.eql(u8, name, "std.cast")) and arg.data.call.args.len == 2) { + cast_arg_idx = i; + type_tag_node = arg.data.call.args[0]; + any_val_node = arg.data.call.args[1]; + break; + } + } + } + + // Generate the runtime type tag value and the Any value + const type_tag_val = try self.genExpr(type_tag_node); + const any_val = try self.genExpr(any_val_node); + // Generate non-cast arguments (evaluated once, before the switch) + var other_arg_vals = std.ArrayList(?c.LLVMValueRef).empty; + for (call_node.args, 0..) |arg, i| { + if (i == cast_arg_idx) { + try other_arg_vals.append(self.allocator, null); // placeholder + } else { + try other_arg_vals.append(self.allocator, try self.genExpr(arg)); + } + } + + // Extract Any value i64 BEFORE the switch (switch is a terminator, nothing can follow it in the same BB) + const any_i64 = c.LLVMBuildExtractValue(self.builder, any_val, 1, "any_payload"); + + // Build dispatch switch + const function = self.current_function; + const dispatch_merge = c.LLVMAppendBasicBlockInContext(self.context, function, "dispatch_merge"); + const dispatch_default = c.LLVMAppendBasicBlockInContext(self.context, function, "dispatch_default"); + const sw = c.LLVMBuildSwitch(self.builder, type_tag_val, dispatch_default, @intCast(match_tags.len)); + + // Determine return type from function signature + const ret_ty = if (fd.return_type) |rt| self.resolveType(rt) else Type.void_type; + // We'll use the first monomorphized function's return to determine LLVM type + var result_llvm_ty: c.LLVMTypeRef = null; + + var phi_vals = std.ArrayList(c.LLVMValueRef).empty; + var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; + + for (match_tags) |tag| { + // Find the AnyTypeEntry for this tag + var entry_type: ?Type = null; + var it = self.any_type_entries.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.tag_id == tag) { + entry_type = entry.value_ptr.sx_type; + break; + } + } + const sx_type = entry_type orelse continue; + + // Create case BB + const case_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "dispatch_case"); + c.LLVMAddCase(sw, c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), tag, 0), case_bb); + c.LLVMPositionBuilderAtEnd(self.builder, case_bb); + + // Convert Any payload to the concrete type + const concrete_val = try self.extractAnyToConcreteType(any_i64, sx_type); + + // Monomorphize the generic function with this type + var bindings = std.StringHashMap(Type).init(self.allocator); + // Bind the type parameter from the cast argument's position + if (cast_arg_idx < fd.params.len) { + if (fd.params[cast_arg_idx].type_expr.data == .type_expr) { + const tp_name = fd.params[cast_arg_idx].type_expr.data.type_expr.name; + for (fd.type_params) |tp| { + if (std.mem.eql(u8, tp.name, tp_name)) { + try bindings.put(tp.name, sx_type); + break; + } + } + } + } + + const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings); + const callee_fn = if (self.generic_instances.get(mangled)) |cached| + cached + else + try self.instantiateGeneric(fd, bindings, mangled); + + // Build argument list + self.type_param_bindings = bindings; + var arg_vals_list = std.ArrayList(c.LLVMValueRef).empty; + for (other_arg_vals.items, 0..) |maybe_val, ai| { + if (ai == cast_arg_idx) { + // Use the converted concrete value + try arg_vals_list.append(self.allocator, concrete_val); + } else if (maybe_val) |v| { + try arg_vals_list.append(self.allocator, v); + } + } + self.type_param_bindings = null; + + const args_slice = try arg_vals_list.toOwnedSlice(self.allocator); + const fn_type = c.LLVMGlobalGetValueType(callee_fn); + const call_result = c.LLVMBuildCall2( + self.builder, + fn_type, + callee_fn, + if (args_slice.len > 0) args_slice.ptr else null, + @intCast(args_slice.len), + if (ret_ty != .void_type) "dispatch_result" else "", + ); + + if (result_llvm_ty == null and ret_ty != .void_type) { + result_llvm_ty = c.LLVMTypeOf(call_result); + } + + if (ret_ty != .void_type) { + try phi_vals.append(self.allocator, call_result); + try phi_bbs.append(self.allocator, c.LLVMGetInsertBlock(self.builder)); + } + _ = c.LLVMBuildBr(self.builder, dispatch_merge); + } + + // Default case: return undef (should not be reached) + c.LLVMPositionBuilderAtEnd(self.builder, dispatch_default); + if (ret_ty != .void_type and result_llvm_ty != null) { + try phi_vals.append(self.allocator, c.LLVMGetUndef(result_llvm_ty.?)); + try phi_bbs.append(self.allocator, dispatch_default); + } + _ = c.LLVMBuildBr(self.builder, dispatch_merge); + + // Merge + c.LLVMPositionBuilderAtEnd(self.builder, dispatch_merge); + if (ret_ty != .void_type and result_llvm_ty != null) { + const vals_slice = try phi_vals.toOwnedSlice(self.allocator); + const bbs_slice = try phi_bbs.toOwnedSlice(self.allocator); + const phi = c.LLVMBuildPhi(self.builder, result_llvm_ty.?, "dispatch_phi"); + c.LLVMAddIncoming(phi, vals_slice.ptr, bbs_slice.ptr, @intCast(vals_slice.len)); + return phi; + } + + return null; + } + + /// Extract a concrete typed value from an Any i64 payload. + fn extractAnyToConcreteType(self: *CodeGen, any_i64: c.LLVMValueRef, sx_type: Type) !c.LLVMValueRef { + return switch (sx_type) { + .boolean => c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMInt1TypeInContext(self.context), "any_to_bool"), + .signed => |w| if (w <= 32) + c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMIntTypeInContext(self.context, w), "any_to_int") + else + any_i64, + .unsigned => |w| if (w <= 32) + c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMIntTypeInContext(self.context, w), "any_to_uint") + else + any_i64, + .f32 => blk: { + const as_f64 = c.LLVMBuildBitCast(self.builder, any_i64, c.LLVMDoubleTypeInContext(self.context), "i64_to_f64"); + break :blk c.LLVMBuildFPTrunc(self.builder, as_f64, c.LLVMFloatTypeInContext(self.context), "any_to_f32"); + }, + .f64 => c.LLVMBuildBitCast(self.builder, any_i64, c.LLVMDoubleTypeInContext(self.context), "any_to_f64"), + .string_type => blk: { + const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_str_ptr"); + break :blk c.LLVMBuildLoad2(self.builder, self.getStringStructType(), ptr, "any_to_str"); + }, + .struct_type => |sname| blk: { + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct '{s}'", .{sname}); + const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_struct_ptr"); + break :blk c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_struct"); + }, + .enum_type => c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMInt32TypeInContext(self.context), "any_to_enum"), + .union_type => |uname| blk: { + const info = self.union_types.get(uname) orelse return self.emitErrorFmt("unknown union '{s}'", .{uname}); + const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_union_ptr"); + break :blk c.LLVMBuildLoad2(self.builder, info.llvm_type, ptr, "any_to_union"); + }, + .vector_type, .array_type => blk: { + const llvm_ty = self.typeToLLVM(sx_type); + const ptr = c.LLVMBuildIntToPtr(self.builder, any_i64, c.LLVMPointerTypeInContext(self.context, 0), "any_to_vec_ptr"); + break :blk c.LLVMBuildLoad2(self.builder, llvm_ty, ptr, "any_to_vec"); + }, + else => c.LLVMBuildTrunc(self.builder, any_i64, c.LLVMInt32TypeInContext(self.context), "any_to_default"), + }; + } + + fn mangleGenericName(self: *CodeGen, base: []const u8, type_params: []const ast.StructTypeParam, bindings: std.StringHashMap(Type)) ![]const u8 { + var buf = std.ArrayList(u8).empty; + try buf.appendSlice(self.allocator, base); + try buf.appendSlice(self.allocator, "__"); + for (type_params, 0..) |tp, i| { + if (i > 0) try buf.append(self.allocator, '_'); + if (bindings.get(tp.name)) |ty| { + const name = try ty.displayName(self.allocator); + try buf.appendSlice(self.allocator, name); + } + } + return try buf.toOwnedSlice(self.allocator); + } + + fn instantiateGeneric(self: *CodeGen, fd: ast.FnDecl, bindings: std.StringHashMap(Type), mangled: []const u8) !c.LLVMValueRef { + // Save current codegen state + const saved_function = self.current_function; + const saved_return_type = self.current_return_type; + const saved_insert_bb = c.LLVMGetInsertBlock(self.builder); + + // Save named_values + var saved_named_values = std.StringHashMap(NamedValue).init(self.allocator); + var nv_iter = self.named_values.iterator(); + while (nv_iter.next()) |entry| { + try saved_named_values.put(entry.key_ptr.*, entry.value_ptr.*); + } + + // Save scope_saves and defer_stack — generic body must not pollute caller's scope tracking + const saved_scope_saves = self.scope_saves; + const saved_defer_stack = self.defer_stack; + self.scope_saves = std.ArrayList(std.ArrayList(ScopeEntry)).empty; + self.defer_stack = std.ArrayList(std.ArrayList(*Node)).empty; + + // Set type param bindings + self.type_param_bindings = bindings; + defer self.type_param_bindings = null; + + // Build the specialized function type + const fn_type = try self.buildFnType(fd.params, fd.return_type, mangled); + const mangled_z = try self.allocator.dupeZ(u8, mangled); + const function = c.LLVMAddFunction(self.module, mangled_z.ptr, fn_type); + + // Cache before generating body (in case of recursion) + try self.generic_instances.put(mangled, function); + + // Generate body + self.named_values.clearRetainingCapacity(); + self.current_function = function; + + const entry_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "entry"); + c.LLVMPositionBuilderAtEnd(self.builder, entry_bb); + + // Create allocas for parameters + var llvm_param_idx: u32 = 0; + for (fd.params) |param| { + if (param.is_comptime) { + // Comptime param: create a constant in named_values from the call-site value + if (self.comptime_param_nodes) |cpn| { + if (cpn.get(param.name)) |node| { + if (node.data == .string_literal) { + const raw = node.data.string_literal.raw; + const inner = if (raw.len >= 2 and raw[0] == '"' and raw[raw.len - 1] == '"') + raw[1 .. raw.len - 1] + else + raw; + const unescaped = try unescapeString(self.allocator, inner); + const str_val = self.buildConstStr(unescaped); + const param_name_z = try self.allocator.dupeZ(u8, param.name); + const alloca = c.LLVMBuildAlloca(self.builder, self.getStringStructType(), param_name_z.ptr); + _ = c.LLVMBuildStore(self.builder, str_val, alloca); + try self.named_values.put(param.name, .{ .ptr = alloca, .ty = .string_type }); + } else if (node.data == .int_literal) { + const ct_sx_ty = self.resolveType(param.type_expr); + const ct_llvm_ty = self.typeToLLVM(ct_sx_ty); + const const_val = c.LLVMConstInt(ct_llvm_ty, @bitCast(node.data.int_literal.value), 0); + const param_name_z = try self.allocator.dupeZ(u8, param.name); + const alloca = c.LLVMBuildAlloca(self.builder, ct_llvm_ty, param_name_z.ptr); + _ = c.LLVMBuildStore(self.builder, const_val, alloca); + try self.named_values.put(param.name, .{ .ptr = alloca, .ty = ct_sx_ty }); + } + } + } + continue; + } + // Variadic params: use slice_type (same as genFnBodyAs) + const sx_ty = if (param.is_variadic) blk: { + const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; + break :blk Type{ .slice_type = .{ .element_name = elem_name } }; + } else self.resolveType(param.type_expr); + const llvm_ty = self.typeToLLVM(sx_ty); + const param_name_z = try self.allocator.dupeZ(u8, param.name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); + const param_val = c.LLVMGetParam(function, llvm_param_idx); + _ = c.LLVMBuildStore(self.builder, param_val, alloca); + try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + llvm_param_idx += 1; + } + + // Generate body statements + const body = fd.body; + if (body.data != .block) return self.emitError("generic function body must be a block"); + + const ret_sx_type = self.resolveType(fd.return_type); + self.current_return_type = ret_sx_type; + + var last_val: c.LLVMValueRef = null; + for (body.data.block.stmts) |stmt| { + last_val = try self.genStmt(stmt); + } + + // Emit return if current block has no terminator + const current_bb = c.LLVMGetInsertBlock(self.builder); + if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { + if (ret_sx_type == .void_type) { + _ = c.LLVMBuildRetVoid(self.builder); + } else if (last_val) |val| { + if (ret_sx_type.isStruct()) { + const sname = ret_sx_type.struct_type; + const info = self.struct_types.get(sname) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{sname}); + const loaded = c.LLVMBuildLoad2(self.builder, info.llvm_type, val, "retval"); + _ = c.LLVMBuildRet(self.builder, loaded); + } else { + const src_ty = self.llvmTypeToSxType(c.LLVMTypeOf(val)); + const converted = self.convertValue(val, src_ty, ret_sx_type); + _ = c.LLVMBuildRet(self.builder, converted); + } + } else { + _ = c.LLVMBuildUnreachable(self.builder); + } + } + + // Restore codegen state + self.current_function = saved_function; + self.current_return_type = saved_return_type; + if (saved_insert_bb) |bb| { + c.LLVMPositionBuilderAtEnd(self.builder, bb); + } + self.named_values.clearRetainingCapacity(); + var restore_iter = saved_named_values.iterator(); + while (restore_iter.next()) |entry| { + try self.named_values.put(entry.key_ptr.*, entry.value_ptr.*); + } + saved_named_values.deinit(); + + // Restore scope_saves and defer_stack + self.scope_saves = saved_scope_saves; + self.defer_stack = saved_defer_stack; + + return function; + } + + fn genIfExpr(self: *CodeGen, if_expr: ast.IfExpr) !c.LLVMValueRef { + // Generate condition + var cond_val = try self.genExpr(if_expr.condition); + + // Ensure condition is i1 (bool) + const cond_type = c.LLVMTypeOf(cond_val); + const i1_type = c.LLVMInt1TypeInContext(self.context); + if (cond_type != i1_type) { + cond_val = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond_val, c.LLVMConstInt(cond_type, 0, 0), "tobool"); + } + + const function = self.current_function; + const has_else = if_expr.else_branch != null; + + var then_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "then"); + var else_bb: c.LLVMBasicBlockRef = if (has_else) + c.LLVMAppendBasicBlockInContext(self.context, function, "else") + else + null; + const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "merge"); + + const false_dest = if (has_else) else_bb else merge_bb; + _ = c.LLVMBuildCondBr(self.builder, cond_val, then_bb, false_dest); + + // Then branch + c.LLVMPositionBuilderAtEnd(self.builder, then_bb); + const then_val = try self.genExpr(if_expr.then_branch); + then_bb = c.LLVMGetInsertBlock(self.builder); // may have changed due to nested control flow + _ = c.LLVMBuildBr(self.builder, merge_bb); + + // Else branch + var else_val: c.LLVMValueRef = null; + if (if_expr.else_branch) |else_branch| { + c.LLVMPositionBuilderAtEnd(self.builder, else_bb); + else_val = try self.genExpr(else_branch); + else_bb = c.LLVMGetInsertBlock(self.builder); + _ = c.LLVMBuildBr(self.builder, merge_bb); + } + + // Merge block + c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); + + // PHI node if both branches produced values + if (then_val != null and else_val != null) { + const phi = c.LLVMBuildPhi(self.builder, c.LLVMTypeOf(then_val), "iftmp"); + var vals = [2]c.LLVMValueRef{ then_val, else_val }; + var blocks = [2]c.LLVMBasicBlockRef{ then_bb, else_bb }; + c.LLVMAddIncoming(phi, &vals, &blocks, 2); + return phi; + } + + return null; + } + + fn genWhileExpr(self: *CodeGen, while_expr: ast.WhileExpr) !c.LLVMValueRef { + const function = self.current_function; + + // Create basic blocks: condition, body, after + const cond_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "while.cond"); + const body_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "while.body"); + const after_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "while.after"); + + // Branch from current block to condition check + _ = c.LLVMBuildBr(self.builder, cond_bb); + + // Condition block + c.LLVMPositionBuilderAtEnd(self.builder, cond_bb); + var cond_val = try self.genExpr(while_expr.condition); + + // Ensure condition is i1 (bool) + const cond_type = c.LLVMTypeOf(cond_val); + const i1_type = c.LLVMInt1TypeInContext(self.context); + if (cond_type != i1_type) { + cond_val = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cond_val, c.LLVMConstInt(cond_type, 0, 0), "tobool"); + } + + _ = c.LLVMBuildCondBr(self.builder, cond_val, body_bb, after_bb); + + // Body block — save and set loop context for break/continue + c.LLVMPositionBuilderAtEnd(self.builder, body_bb); + const saved_break_bb = self.loop_break_bb; + const saved_continue_bb = self.loop_continue_bb; + self.loop_break_bb = after_bb; + self.loop_continue_bb = cond_bb; + + _ = try self.genExpr(while_expr.body); + + // Restore loop context + self.loop_break_bb = saved_break_bb; + self.loop_continue_bb = saved_continue_bb; + + // Branch back to condition (if not already terminated by break/return) + const current_bb = c.LLVMGetInsertBlock(self.builder); + if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { + _ = c.LLVMBuildBr(self.builder, cond_bb); + } + + // Position at after block + c.LLVMPositionBuilderAtEnd(self.builder, after_bb); + + return null; + } + + fn genForExpr(self: *CodeGen, for_expr: ast.ForExpr) !c.LLVMValueRef { + const function = self.current_function; + const i32_type = c.LLVMInt32TypeInContext(self.context); + + // Determine iterable type and get length + element access info + const iter_ty = self.inferType(for_expr.iterable); + var len_val: c.LLVMValueRef = undefined; + var elem_ty: Type = Type.s(32); + var iter_ptr: c.LLVMValueRef = undefined; // pointer to data + var is_slice = false; + + if (iter_ty.isSlice()) { + is_slice = true; + const info = iter_ty.slice_type; + elem_ty = Type.fromName(info.element_name) orelse Type.s(32); + // Load slice value from alloca + if (for_expr.iterable.data == .identifier) { + if (self.named_values.get(for_expr.iterable.data.identifier.name)) |entry| { + const slice_val = c.LLVMBuildLoad2(self.builder, self.getStringStructType(), entry.ptr, "for_slice"); + iter_ptr = c.LLVMBuildExtractValue(self.builder, slice_val, 0, "for_ptr"); + len_val = c.LLVMBuildExtractValue(self.builder, slice_val, 1, "for_len"); + } else return self.emitError("for: iterable not found"); + } else return self.emitError("for: slice iterable must be a variable"); + } else if (iter_ty.isArray()) { + const info = iter_ty.array_type; + elem_ty = Type.fromName(info.element_name) orelse Type.s(32); + len_val = c.LLVMConstInt(i32_type, info.length, 0); + // Get pointer to array + if (for_expr.iterable.data == .identifier) { + if (self.named_values.get(for_expr.iterable.data.identifier.name)) |entry| { + iter_ptr = entry.ptr; + } else return self.emitError("for: iterable not found"); + } else return self.emitError("for: array iterable must be a variable"); + } else { + return self.emitError("for loop requires a slice or array iterable"); + } + + const elem_llvm_ty = self.typeToLLVM(elem_ty); + + // Allocate it_index (s32) and it (element type) + const idx_alloca = c.LLVMBuildAlloca(self.builder, i32_type, "it_index"); + _ = c.LLVMBuildStore(self.builder, c.LLVMConstInt(i32_type, 0, 0), idx_alloca); + const it_alloca = c.LLVMBuildAlloca(self.builder, elem_llvm_ty, "it"); + + // Push scope and bind it, it_index + try self.pushScope(); + try self.named_values.put("it", .{ .ptr = it_alloca, .ty = elem_ty }); + try self.named_values.put("it_index", .{ .ptr = idx_alloca, .ty = Type.s(32) }); + + // Create basic blocks + const cond_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "for.cond"); + const body_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "for.body"); + const after_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "for.after"); + + _ = c.LLVMBuildBr(self.builder, cond_bb); + + // Condition: it_index < len + c.LLVMPositionBuilderAtEnd(self.builder, cond_bb); + const cur_idx = c.LLVMBuildLoad2(self.builder, i32_type, idx_alloca, "cur_idx"); + const cond_val = c.LLVMBuildICmp(self.builder, c.LLVMIntSLT, cur_idx, len_val, "for_cond"); + _ = c.LLVMBuildCondBr(self.builder, cond_val, body_bb, after_bb); + + // Body: load it = iterable[it_index], then execute body + c.LLVMPositionBuilderAtEnd(self.builder, body_bb); + const body_idx = c.LLVMBuildLoad2(self.builder, i32_type, idx_alloca, "body_idx"); + + if (is_slice) { + // Slice: GEP through data pointer + var gep_indices = [_]c.LLVMValueRef{body_idx}; + const gep = c.LLVMBuildGEP2(self.builder, elem_llvm_ty, iter_ptr, &gep_indices, 1, "for_elem"); + const elem_val = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "it_val"); + _ = c.LLVMBuildStore(self.builder, elem_val, it_alloca); + } else { + // Array: GEP with [0, idx] + const arr_llvm_ty = self.typeToLLVM(iter_ty); + const zero = c.LLVMConstInt(i32_type, 0, 0); + var indices = [_]c.LLVMValueRef{ zero, body_idx }; + const gep = c.LLVMBuildGEP2(self.builder, arr_llvm_ty, iter_ptr, &indices, 2, "for_elem"); + const elem_val = c.LLVMBuildLoad2(self.builder, elem_llvm_ty, gep, "it_val"); + _ = c.LLVMBuildStore(self.builder, elem_val, it_alloca); + } + + // Save and set loop context for break/continue + const saved_break_bb = self.loop_break_bb; + const saved_continue_bb = self.loop_continue_bb; + self.loop_break_bb = after_bb; + self.loop_continue_bb = cond_bb; + + _ = try self.genExpr(for_expr.body); + + self.loop_break_bb = saved_break_bb; + self.loop_continue_bb = saved_continue_bb; + + // Increment it_index + const current_bb = c.LLVMGetInsertBlock(self.builder); + if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { + const inc_idx = c.LLVMBuildLoad2(self.builder, i32_type, idx_alloca, "inc_idx"); + const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i32_type, 1, 0), "next_idx"); + _ = c.LLVMBuildStore(self.builder, next_idx, idx_alloca); + _ = c.LLVMBuildBr(self.builder, cond_bb); + } + + c.LLVMPositionBuilderAtEnd(self.builder, after_bb); + + try self.popScope(); + + return null; + } + + fn genEnumLiteral(self: *CodeGen, variant_name: []const u8, enum_type_name: []const u8) c.LLVMValueRef { + const i32_type = c.LLVMInt32TypeInContext(self.context); + const variants = self.enum_types.get(enum_type_name) orelse return c.LLVMConstInt(i32_type, 0, 0); + for (variants, 0..) |v, i| { + if (std.mem.eql(u8, v, variant_name)) { + return c.LLVMConstInt(i32_type, @intCast(i), 0); + } + } + return c.LLVMConstInt(i32_type, 0, 0); + } + + fn lookupVariantIndex(variants: ?[]const []const u8, name: []const u8) u64 { + if (variants) |vs| { + for (vs, 0..) |v, i| { + if (std.mem.eql(u8, v, name)) return i; + } + } + return 0; + } + + fn genMatchExpr(self: *CodeGen, match: ast.MatchExpr) !c.LLVMValueRef { + // Determine subject type for enum vs union dispatch + var enum_name: ?[]const u8 = null; + var union_name: ?[]const u8 = null; + if (match.subject.data == .identifier) { + if (self.named_values.get(match.subject.data.identifier.name)) |entry| { + if (entry.ty.isEnum()) enum_name = entry.ty.enum_type; + if (entry.ty.isUnion()) union_name = entry.ty.union_type; + } + } + + // Get the switch value: for unions, load the tag from field 0; for enums, use the value directly + const subject_val: c.LLVMValueRef = if (union_name != null) blk: { + // Union: load tag from field 0 of the alloca + const entry = self.named_values.get(match.subject.data.identifier.name).?; + const info = self.union_types.get(union_name.?).?; + const tag_gep = c.LLVMBuildStructGEP2(self.builder, info.llvm_type, entry.ptr, 0, "tag"); + break :blk c.LLVMBuildLoad2(self.builder, c.LLVMInt32TypeInContext(self.context), tag_gep, "tag_val"); + } else try self.genExpr(match.subject); + + const variants: ?[]const []const u8 = if (union_name) |un| + (if (self.union_types.get(un)) |info| info.variant_names else null) + else if (enum_name) |en| + self.enum_types.get(en) + else + null; + + const function = self.current_function; + const i32_type = c.LLVMInt32TypeInContext(self.context); + const merge_bb = c.LLVMAppendBasicBlockInContext(self.context, function, "match_end"); + + // Create case basic blocks + var case_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; + for (match.arms) |_| { + try case_bbs.append(self.allocator, c.LLVMAppendBasicBlockInContext(self.context, function, "case")); + } + + // Find else arm (null pattern) — use its BB as the switch default + var else_arm_idx: ?usize = null; + for (match.arms, 0..) |arm, i| { + if (arm.pattern == null) { + else_arm_idx = i; + break; + } + } + const default_bb = if (else_arm_idx) |idx| + case_bbs.items[idx] + else + c.LLVMAppendBasicBlockInContext(self.context, function, "match_default"); + + // Build switch instruction + const sw = c.LLVMBuildSwitch(self.builder, subject_val, default_bb, @intCast(match.arms.len)); + for (match.arms, 0..) |arm, i| { + const pat = arm.pattern orelse continue; // skip else arm + if (pat.data == .enum_literal) { + const idx = lookupVariantIndex(variants, pat.data.enum_literal.name); + const case_val = c.LLVMConstInt(i32_type, idx, 0); + c.LLVMAddCase(sw, case_val, case_bbs.items[i]); + } else if (pat.data == .type_expr) { + // Type-match: resolve type name to Any tag value(s) + const tag_values = try self.resolveTypeMatchTags(pat.data.type_expr.name); + for (tag_values) |tag| { + c.LLVMAddCase(sw, c.LLVMConstInt(i32_type, tag, 0), case_bbs.items[i]); + } + } else if (pat.data == .identifier) { + // Named type (struct/enum/union name) or category (int/float) + const tag_values = try self.resolveTypeMatchTags(pat.data.identifier.name); + for (tag_values) |tag| { + c.LLVMAddCase(sw, c.LLVMConstInt(i32_type, tag, 0), case_bbs.items[i]); + } + } + } + + // Generate arm bodies and collect PHI info + var phi_vals = std.ArrayList(c.LLVMValueRef).empty; + var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; + var has_value = false; + var value_type: c.LLVMTypeRef = null; + + // Pre-collect tag values for each arm (for runtime dispatch context) + var arm_tag_values = std.ArrayList([]const u64).empty; + for (match.arms) |arm| { + const tag_values: []const u64 = if (arm.pattern) |pat| blk: { + break :blk if (pat.data == .type_expr) + try self.resolveTypeMatchTags(pat.data.type_expr.name) + else if (pat.data == .identifier) + try self.resolveTypeMatchTags(pat.data.identifier.name) + else + &.{}; + } else &.{}; + try arm_tag_values.append(self.allocator, tag_values); + } + + for (match.arms, 0..) |arm, i| { + c.LLVMPositionBuilderAtEnd(self.builder, case_bbs.items[i]); + if (arm.is_break) { + _ = c.LLVMBuildBr(self.builder, merge_bb); + } else if (arm.pattern != null and arm_tag_values.items[i].len == 0 and + (arm.pattern.?.data == .identifier or arm.pattern.?.data == .type_expr)) + { + // Category/type arm with no matching types — BB is unreachable, skip body + _ = c.LLVMBuildBr(self.builder, merge_bb); + } else { + // Set match arm context for runtime type dispatch + const saved_match_tags = self.current_match_tags; + self.current_match_tags = arm_tag_values.items[i]; + const val = try self.genExpr(arm.body); + self.current_match_tags = saved_match_tags; + const bb = c.LLVMGetInsertBlock(self.builder); + _ = c.LLVMBuildBr(self.builder, merge_bb); + if (val != null and c.LLVMGetTypeKind(c.LLVMTypeOf(val)) != c.LLVMVoidTypeKind) { + has_value = true; + if (value_type == null) value_type = c.LLVMTypeOf(val); + try phi_vals.append(self.allocator, val); + try phi_bbs.append(self.allocator, bb); + } + } + } + + // Default block branches to merge (only if no else arm — else arm's body already generated above) + if (else_arm_idx == null) { + c.LLVMPositionBuilderAtEnd(self.builder, default_bb); + _ = c.LLVMBuildBr(self.builder, merge_bb); + } + + // Merge block + c.LLVMPositionBuilderAtEnd(self.builder, merge_bb); + + if (has_value and value_type != null) { + const undef_val = c.LLVMGetUndef(value_type); + // Add undef entries for break arms and default block + for (match.arms, 0..) |arm, i| { + if (arm.is_break) { + try phi_vals.append(self.allocator, undef_val); + try phi_bbs.append(self.allocator, case_bbs.items[i]); + } + } + if (else_arm_idx == null) { + try phi_vals.append(self.allocator, undef_val); + try phi_bbs.append(self.allocator, default_bb); + } + + const vals_slice = try phi_vals.toOwnedSlice(self.allocator); + const bbs_slice = try phi_bbs.toOwnedSlice(self.allocator); + const phi = c.LLVMBuildPhi(self.builder, value_type, "matchtmp"); + c.LLVMAddIncoming(phi, vals_slice.ptr, bbs_slice.ptr, @intCast(vals_slice.len)); + return phi; + } + + return null; + } + + /// Resolve a type name to one or more Any tag values for type-switch matching. + /// Categories: "int" matches s32+s64, "float" matches f32+f64. + /// Specific types: "s32", "f64", "string", "bool", "Type". + /// Named types: struct/enum/union names get dynamic IDs. + fn resolveTypeMatchTags(self: *CodeGen, name: []const u8) ![]const u64 { + // Category aliases + if (std.mem.eql(u8, name, "int")) { + const tags = try self.allocator.alloc(u64, 2); + tags[0] = ANY_TAG_S32; + tags[1] = ANY_TAG_S64; + return tags; + } + if (std.mem.eql(u8, name, "float")) { + const tags = try self.allocator.alloc(u64, 2); + tags[0] = ANY_TAG_F32; + tags[1] = ANY_TAG_F64; + return tags; + } + // Type category aliases: "struct", "enum", "union", "vector", "array", "slice" + const category: ?TypeCategory = if (std.mem.eql(u8, name, "struct")) + .struct_cat + else if (std.mem.eql(u8, name, "enum")) + .enum_cat + else if (std.mem.eql(u8, name, "union")) + .union_cat + else if (std.mem.eql(u8, name, "vector")) + .vector_cat + else if (std.mem.eql(u8, name, "array")) + .array_cat + else if (std.mem.eql(u8, name, "slice")) + .slice_cat + else + null; + if (category) |cat| { + var tag_list = std.ArrayList(u64).empty; + var it = self.any_type_entries.iterator(); + while (it.next()) |entry| { + if (entry.value_ptr.category == cat) { + try tag_list.append(self.allocator, entry.value_ptr.tag_id); + } + } + if (tag_list.items.len > 0) { + return try tag_list.toOwnedSlice(self.allocator); + } + // No types registered for this category — return empty slice + return &.{}; + } + // Specific builtin types + const single_tag: ?u64 = if (std.mem.eql(u8, name, "bool")) + ANY_TAG_BOOL + else if (std.mem.eql(u8, name, "s32")) + ANY_TAG_S32 + else if (std.mem.eql(u8, name, "s64")) + ANY_TAG_S64 + else if (std.mem.eql(u8, name, "f32")) + ANY_TAG_F32 + else if (std.mem.eql(u8, name, "f64")) + ANY_TAG_F64 + else if (std.mem.eql(u8, name, "string")) + ANY_TAG_STRING + else if (std.mem.eql(u8, name, "Type")) + ANY_TAG_TYPE + else if (std.mem.eql(u8, name, "void")) + ANY_TAG_VOID + else + null; + if (single_tag) |t| { + const tags = try self.allocator.alloc(u64, 1); + tags[0] = t; + return tags; + } + // Named type (struct/enum/union) — get dynamic ID + const sx_type: Type = if (self.struct_types.contains(name)) + .{ .struct_type = name } + else if (self.enum_types.contains(name)) + .{ .enum_type = name } + else if (self.union_types.contains(name)) + .{ .union_type = name } + else + .{ .struct_type = name }; // fallback + const id = try self.getAnyTypeId(name, sx_type); + const tags = try self.allocator.alloc(u64, 1); + tags[0] = id; + return tags; + } + + /// Resolve a callee node to a function name string for type inference. + /// Handles identifiers, namespaced calls, and intra-namespace fallback. + fn resolveCalleeName(self: *CodeGen, call_node: ast.Call) ?[]const u8 { + if (call_node.callee.data == .identifier) { + return call_node.callee.data.identifier.name; + } + if (call_node.callee.data == .field_access) { + const fa = call_node.callee.data.field_access; + if (fa.object.data == .identifier) { + if (self.namespaces.contains(fa.object.data.identifier.name)) { + return std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ fa.object.data.identifier.name, fa.field }) catch return null; + } + } + } + return null; + } + + /// Resolve a builtin parameterized type (e.g. Vector(3, f32)). + /// Strips namespace prefix to get base name, then dispatches. + fn resolveBuiltinType(self: *CodeGen, name: []const u8, args: []const *Node) ?Type { + const base = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name; + if (std.mem.eql(u8, base, "Vector")) { + if (args.len >= 2) { + const n: u32 = @intCast(self.resolveValueArg(args[0])); + const elem = self.resolveType(args[1]); + const elem_name = elem.displayName(self.allocator) catch return null; + const ty: Type = .{ .vector_type = .{ .element_name = elem_name, .length = n } }; + // Pre-register in any_type_entries so runtime dispatch knows about this type + const any_name = std.fmt.allocPrint(self.allocator, "vec[{d}]{s}", .{ n, elem_name }) catch return null; + _ = self.getAnyTypeId(any_name, ty) catch return null; + return ty; + } + } + return null; + } + + fn dispatchBuiltin(self: *CodeGen, name: []const u8, call_node: ast.Call) !c.LLVMValueRef { + // Extract base name (strip namespace prefix) + const base = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name; + if (std.mem.eql(u8, base, "write")) return self.genWriteCall(call_node.args); + if (std.mem.eql(u8, base, "sqrt")) return self.genSqrt(call_node); + if (std.mem.eql(u8, base, "size_of")) return self.genSizeOf(call_node); + if (std.mem.eql(u8, base, "cast")) return self.genCast(call_node); + if (std.mem.eql(u8, base, "alloc")) return self.genAlloc(call_node.args); + if (std.mem.eql(u8, base, "type_of")) return self.genTypeOf(call_node); + if (std.mem.eql(u8, base, "type_name")) return self.genTypeName(call_node); + if (std.mem.eql(u8, base, "field_count")) return self.genFieldCount(call_node); + if (std.mem.eql(u8, base, "field_name")) return self.genFieldName(call_node); + if (std.mem.eql(u8, base, "field_value")) return self.genFieldValue(call_node); + return self.emitErrorFmt("unknown builtin function '{s}'", .{name}); + } + + fn genWriteCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef { + if (args.len != 1) return self.emitError("write expects exactly 1 argument"); + const builtins = self.builtins orelse return self.emitError("builtins not available (missing #builtin import)"); + const val = try self.genExpr(args[0]); + // Extract ptr and len from string slice + const ptr = c.LLVMBuildExtractValue(self.builder, val, 0, "str_ptr"); + const len = c.LLVMBuildExtractValue(self.builder, val, 1, "str_len"); + const fmt = c.LLVMBuildGlobalStringPtr(self.builder, "%.*s", "write_fmt"); + const printf_fn = builtins.printf_fn; + const fn_type = c.LLVMGlobalGetValueType(printf_fn); + var call_args = [_]c.LLVMValueRef{ fmt, len, ptr }; + _ = c.LLVMBuildCall2(self.builder, fn_type, printf_fn, &call_args, 3, ""); + return null; + } + + /// Helper: build a constant string slice in the current function + fn buildConstStr(self: *CodeGen, s: []const u8) c.LLVMValueRef { + const sz = self.allocator.dupeZ(u8, s) catch unreachable; + const ptr = c.LLVMBuildGlobalStringPtr(self.builder, sz.ptr, "cstr"); + return self.buildStringSlice(ptr, @intCast(s.len)); + } + + /// Helper: build a constant string slice as a global constant (no builder needed). + fn buildConstStrGlobal(self: *CodeGen, s: []const u8) c.LLVMValueRef { + const sz = self.allocator.dupeZ(u8, s) catch unreachable; + const i32_ty = c.LLVMInt32TypeInContext(self.context); + const i8_ty = c.LLVMInt8TypeInContext(self.context); + // Create a global string constant + const str_const = c.LLVMConstStringInContext(self.context, sz.ptr, @intCast(s.len), 0); + const global_name = (self.allocator.dupeZ(u8, std.fmt.allocPrint(self.allocator, ".str.{s}", .{s}) catch unreachable)) catch unreachable; + var global = c.LLVMGetNamedGlobal(self.module, global_name.ptr); + if (global == null) { + const arr_ty = c.LLVMArrayType2(i8_ty, s.len + 1); + global = c.LLVMAddGlobal(self.module, arr_ty, global_name.ptr); + c.LLVMSetInitializer(global, str_const); + c.LLVMSetGlobalConstant(global, 1); + c.LLVMSetLinkage(global, c.LLVMPrivateLinkage); + } + // Build constant struct {ptr, i32} + var fields = [_]c.LLVMValueRef{ + c.LLVMConstBitCast(global.?, c.LLVMPointerTypeInContext(self.context, 0)), + c.LLVMConstInt(i32_ty, s.len, 0), + }; + return c.LLVMConstStructInContext(self.context, &fields, 2, 0); + } + + /// Check if a node refers to a type name. Returns the raw name or null. + fn asTypeName(self: *CodeGen, node: *const Node) ?[]const u8 { + if (node.data == .type_expr) return node.data.type_expr.name; + if (node.data == .identifier) { + const id = node.data.identifier.name; + if (self.resolveTypeName(id) != null) return id; + } + return null; + } + + /// Resolve a type name to its display string (null-terminated) for runtime use. + fn resolveDisplayName(self: *CodeGen, name: []const u8) [:0]const u8 { + // Type aliases → follow to target + if (self.type_aliases.get(name)) |target| { + if (self.struct_types.get(target)) |info| + return self.allocator.dupeZ(u8, info.display_name orelse target) catch unreachable; + return self.allocator.dupeZ(u8, target) catch unreachable; + } + // Struct types → use display name + if (self.struct_types.get(name)) |info| + return self.allocator.dupeZ(u8, info.display_name orelse name) catch unreachable; + // Primitive / enum / anything else → use as-is + return self.allocator.dupeZ(u8, name) catch unreachable; + } + + /// Resolve a name to a type display string, or null if not a type. + fn resolveTypeName(self: *CodeGen, name: []const u8) ?[]const u8 { + // Type aliases (e.g. x := f64, Vec3 :: Vec(3, f32)) — follow alias first + if (self.type_aliases.get(name)) |target| { + // Check if target is a struct with a display name + if (self.struct_types.get(target)) |info| return info.display_name orelse target; + return target; + } + // Primitive types + if (Type.fromName(name) != null) return name; + // Struct types + if (self.struct_types.get(name)) |info| return info.display_name orelse name; + // Enum types + if (self.enum_types.get(name) != null) return name; + // Union types + if (self.union_types.get(name) != null) return name; + return null; + } + + fn inferType(self: *CodeGen, node: *Node) Type { + return switch (node.data) { + .int_literal => Type.s(32), + .float_literal => .f32, + .bool_literal => .boolean, + .string_literal => .string_type, + .insert_expr => .void_type, + .comptime_expr => |ct| self.inferType(ct.expr), + .binary_op => |binop| { + switch (binop.op) { + .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op => return .boolean, + else => { + const lhs_ty = self.inferType(binop.lhs); + const rhs_ty = self.inferType(binop.rhs); + return Type.widen(lhs_ty, rhs_ty); + }, + } + }, + .chained_comparison => return .boolean, + .identifier => |ident| { + if (self.named_values.get(ident.name)) |entry| return entry.ty; + if (self.comptime_globals.get(ident.name)) |ct| return ct.ty; + return Type.s(32); + }, + .if_expr => |ie| { + return self.inferType(ie.then_branch); + }, + .block => |blk| { + if (blk.stmts.len > 0) { + return self.inferType(blk.stmts[blk.stmts.len - 1]); + } + return .void_type; + }, + .union_literal => |ul| { + if (ul.union_name) |uname| return .{ .union_type = uname }; + if (self.current_return_type.isUnion()) return self.current_return_type; + return .void_type; + }, + .enum_literal => { + if (self.current_return_type.isEnum()) return self.current_return_type; + if (self.current_return_type.isUnion()) return self.current_return_type; + return .{ .enum_type = "" }; + }, + .match_expr => |me| { + for (me.arms) |arm| { + if (!arm.is_break) return self.inferType(arm.body); + } + return .void_type; + }, + .call => |call_node| { + // Check for union literal pattern: Type.variant(payload) + if (call_node.callee.data == .field_access) { + const fa = call_node.callee.data.field_access; + const obj_ty = blk: { + if (fa.object.data == .identifier) { + const name = self.type_aliases.get(fa.object.data.identifier.name) orelse fa.object.data.identifier.name; + if (self.union_types.contains(name)) break :blk Type{ .union_type = name }; + } + const ty = self.resolveType(fa.object); + if (ty.isUnion()) break :blk ty; + break :blk @as(?Type, null); + }; + if (obj_ty) |uty| return uty; + } + const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(32); + const base_name = if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |idx| callee_name[idx + 1 ..] else callee_name; + // Built-in: sqrt returns same type as argument + if (std.mem.eql(u8, base_name, "sqrt")) { + if (call_node.args.len > 0) return self.inferType(call_node.args[0]); + return .f32; + } + // Built-in: size_of returns s32 + if (std.mem.eql(u8, base_name, "size_of")) return Type.s(32); + // Built-in: type_of returns s32 (type tag) + if (std.mem.eql(u8, base_name, "type_of")) return Type.s(32); + // Built-in: type_name returns string + if (std.mem.eql(u8, base_name, "type_name")) return .string_type; + // Built-in: field_count returns s32 + if (std.mem.eql(u8, base_name, "field_count")) return Type.s(32); + // Built-in: field_name returns string + if (std.mem.eql(u8, base_name, "field_name")) return .string_type; + // Built-in: field_value returns Any + if (std.mem.eql(u8, base_name, "field_value")) return .{ .any_type = {} }; + // Built-in: cast returns the target type (first arg) + if (std.mem.eql(u8, base_name, "cast")) { + if (call_node.args.len > 0) return self.resolveType(call_node.args[0]); + return Type.s(32); + } + // Built-in: alloc returns string + if (std.mem.eql(u8, base_name, "alloc")) return .string_type; + // Check generic templates — infer return type from widened bindings + const template = self.generic_templates.get(callee_name) orelse blk: { + // Intra-namespace fallback + if (self.current_namespace) |ns| { + const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(32); + break :blk self.generic_templates.get(qualified); + } + break :blk null; + }; + if (template) |tmpl| { + const gfd = tmpl.fd; + // Build widened type bindings from all call args + var inferred_bindings = std.StringHashMap(Type).init(self.allocator); + for (gfd.params, 0..) |param, pi| { + if (param.type_expr.data == .type_expr) { + for (gfd.type_params) |tp| { + if (std.mem.eql(u8, tp.name, param.type_expr.data.type_expr.name)) { + if (pi < call_node.args.len) { + const arg_ty = self.inferType(call_node.args[pi]); + if (inferred_bindings.get(tp.name)) |existing| { + inferred_bindings.put(tp.name, Type.widen(existing, arg_ty)) catch {}; + } else { + inferred_bindings.put(tp.name, arg_ty) catch {}; + } + } + break; + } + } + } + } + // Resolve return type from bindings + if (gfd.return_type) |rt| { + if (rt.data == .type_expr) { + if (inferred_bindings.get(rt.data.type_expr.name)) |bound_ty| { + return bound_ty; + } + } + // Try resolving as a concrete type (e.g. -> string, -> s32) + const resolved = self.resolveType(rt); + if (!std.meta.eql(resolved, Type.void_type)) return resolved; + } + return Type.s(32); + } + // Check non-generic LLVM functions + const callee_name_z = self.allocator.dupeZ(u8, callee_name) catch return Type.s(32); + var callee_fn_opt = c.LLVMGetNamedFunction(self.module, callee_name_z.ptr); + // Intra-namespace fallback + if (callee_fn_opt == null) { + if (self.current_namespace) |ns| { + const q = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, callee_name }) catch return Type.s(32); + const qz = self.allocator.dupeZ(u8, q) catch return Type.s(32); + callee_fn_opt = c.LLVMGetNamedFunction(self.module, qz.ptr); + } + } + if (callee_fn_opt) |callee_fn| { + const fn_type = c.LLVMGlobalGetValueType(callee_fn); + const ret_llvm = c.LLVMGetReturnType(fn_type); + return self.llvmTypeToSxType(ret_llvm); + } + return Type.s(32); + }, + .unary_op => |unop| { + return self.inferType(unop.operand); + }, + .field_access => |fa| { + const obj_ty = self.inferType(fa.object); + if (obj_ty == .string_type) { + if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "ptr")) return .string_type; + } + if (obj_ty.isSlice()) { + if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + } + if (obj_ty.isAny()) { + if (std.mem.eql(u8, fa.field, "tag")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "value")) return Type.s(64); + } + if (obj_ty.isVector()) { + return obj_ty.vectorElementType() orelse Type.s(32); + } + if (obj_ty.isStruct()) { + if (self.struct_types.get(obj_ty.struct_type)) |info| { + if (self.findFieldIndex(info, fa.field)) |idx| { + return info.field_types[idx]; + } + } + } + if (obj_ty.isUnion()) { + if (self.union_types.get(obj_ty.union_type)) |info| { + for (info.variant_names, 0..) |vn, i| { + if (std.mem.eql(u8, vn, fa.field)) { + return info.variant_types[i]; + } + } + } + } + return Type.s(32); + }, + .index_expr => |ie| { + const obj_ty = self.inferType(ie.object); + if (obj_ty.isVector()) { + return obj_ty.vectorElementType() orelse Type.s(32); + } + if (obj_ty.isArray()) { + return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(32); + } + if (obj_ty.isSlice()) { + return obj_ty.sliceElementType() orelse Type.s(32); + } + return Type.s(32); + }, + .array_literal => |al| { + if (al.elements.len == 0) return .void_type; + const elem_ty = self.inferType(al.elements[0]); + const elem_name = elem_ty.displayName(self.allocator) catch return Type.s(32); + return .{ .array_type = .{ .element_name = elem_name, .length = @intCast(al.elements.len) } }; + }, + .while_expr, .for_expr, .break_expr, .continue_expr => .void_type, + else => Type.s(32), + }; + } + + fn exprIsFloat(self: *CodeGen, node: *Node) bool { + return self.inferType(node).isFloat(); + } + + pub fn verify(self: *CodeGen) !void { + var err_msg: [*c]u8 = null; + if (c.LLVMVerifyModule(self.module, c.LLVMReturnStatusAction, &err_msg) != 0) { + defer c.LLVMDisposeMessage(err_msg); + const msg = std.mem.span(err_msg); + return self.emitErrorFmt("LLVM verification failed: {s}", .{msg}); + } + } + + pub fn printIR(self: *CodeGen) void { + const ir = c.LLVMPrintModuleToString(self.module); + defer c.LLVMDisposeMessage(ir); + const len = std.mem.len(ir); + std.debug.print("{s}\n", .{ir[0..len]}); + } + + pub fn emitObject(self: *CodeGen, output_path: [*:0]const u8) !void { + llvm.initAllTargets(); + + const triple = c.LLVMGetDefaultTargetTriple(); + defer c.LLVMDisposeMessage(triple); + + var target: c.LLVMTargetRef = null; + var err_msg: [*c]u8 = null; + + if (c.LLVMGetTargetFromTriple(triple, &target, &err_msg) != 0) { + defer c.LLVMDisposeMessage(err_msg); + const msg = std.mem.span(err_msg); + return self.emitErrorFmt("failed to get target: {s}", .{msg}); + } + + const tm = c.LLVMCreateTargetMachine( + target, + triple, + "generic", + "", + c.LLVMCodeGenLevelDefault, + c.LLVMRelocPIC, + c.LLVMCodeModelDefault, + ); + defer c.LLVMDisposeTargetMachine(tm); + + c.LLVMSetTarget(self.module, triple); + + var err_msg2: [*c]u8 = null; + if (c.LLVMTargetMachineEmitToFile(tm, self.module, output_path, c.LLVMObjectFile, &err_msg2) != 0) { + defer c.LLVMDisposeMessage(err_msg2); + const msg = std.mem.span(err_msg2); + return self.emitErrorFmt("failed to emit object file: {s}", .{msg}); + } + } + + pub fn link(io: std.Io, output_obj: []const u8, output_bin: []const u8) !void { + var child = std.process.spawn(io, .{ + .argv = &.{ "cc", output_obj, "-o", output_bin }, + }) catch return error.LinkError; + const result = child.wait(io) catch return error.LinkError; + if (result != .exited) return error.LinkError; + if (result.exited != 0) return error.LinkError; + } +}; diff --git a/src/comptime.zig b/src/comptime.zig new file mode 100644 index 0000000..67bc218 --- /dev/null +++ b/src/comptime.zig @@ -0,0 +1,1753 @@ +const std = @import("std"); +const types = @import("types.zig"); +const Type = types.Type; + +/// Runtime value for comptime evaluation. +/// Replaces codegen's JitResult with richer type support. +pub const Value = union(enum) { + int_val: i64, + float_val: f64, + float32_val: f32, + bool_val: bool, + string_val: []const u8, + void_val: void, + struct_val: StructValue, + array_val: ArrayValue, + type_val: Type, + function_val: FunctionVal, + + pub const StructValue = struct { + type_name: []const u8, + field_names: []const []const u8, + fields: []Value, + }; + + pub const ArrayValue = struct { + elements: []Value, + }; + + pub const FunctionVal = struct { + name: []const u8, + param_count: u8, + }; + + pub fn isInt(self: Value) bool { + return self == .int_val; + } + + pub fn isFloat(self: Value) bool { + return switch (self) { + .float_val, .float32_val => true, + else => false, + }; + } + + pub fn asInt(self: Value) ?i64 { + return switch (self) { + .int_val => |v| v, + .bool_val => |v| if (v) @as(i64, 1) else 0, + else => null, + }; + } + + pub fn asFloat(self: Value) ?f64 { + return switch (self) { + .float_val => |v| v, + .float32_val => |v| @floatCast(v), + .int_val => |v| @floatFromInt(v), + else => null, + }; + } + + pub fn format(self: Value, allocator: std.mem.Allocator) ![]const u8 { + return switch (self) { + .int_val => |v| std.fmt.allocPrint(allocator, "{d}", .{v}), + .float_val => |v| std.fmt.allocPrint(allocator, "{d}", .{v}), + .float32_val => |v| std.fmt.allocPrint(allocator, "{d}", .{v}), + .bool_val => |v| if (v) allocator.dupe(u8, "true") else allocator.dupe(u8, "false"), + .string_val => |v| allocator.dupe(u8, v), + .void_val => allocator.dupe(u8, "void"), + .type_val => |v| v.displayName(allocator), + .function_val => |v| std.fmt.allocPrint(allocator, "", .{v.name}), + .struct_val => |v| { + var buf: std.ArrayList(u8) = .empty; + try buf.appendSlice(allocator, v.type_name); + try buf.append(allocator, '{'); + for (v.fields, 0..) |fv, i| { + if (i > 0) try buf.appendSlice(allocator, ", "); + if (i < v.field_names.len) { + try buf.appendSlice(allocator, v.field_names[i]); + try buf.appendSlice(allocator, ": "); + } + const fs = try fv.format(allocator); + try buf.appendSlice(allocator, fs); + } + try buf.append(allocator, '}'); + return buf.items; + }, + .array_val => |v| { + var buf: std.ArrayList(u8) = .empty; + try buf.append(allocator, '['); + for (v.elements, 0..) |elem, i| { + if (i > 0) try buf.appendSlice(allocator, ", "); + const es = try elem.format(allocator); + try buf.appendSlice(allocator, es); + } + try buf.append(allocator, ']'); + return buf.items; + }, + }; + } +}; + +/// Bytecode instruction for the comptime VM. +pub const Instruction = union(enum) { + // Constants + push_int: i64, + push_float: f64, + push_f32: f32, + push_true, + push_false, + push_string: u32, // index into Chunk.strings + push_void, + push_type: Type, + push_function: FnRef, + + // Local variables + get_local: u16, // slot index in current frame + set_local: u16, + + // Global variables (resolved lazily from root_decls) + get_global: u32, // index into Chunk.strings for the global name + + // Arithmetic (type-dispatched at runtime via Value tag) + add, + sub, + mul, + div, + mod, + negate, + + // Comparison + eq, + neq, + lt, + lte, + gt, + gte, + + // Logic + not, + + // Type conversion + cast: CastInfo, + + // Control flow + jump: i32, // relative offset + jump_if_false: i32, + jump_if_true: i32, + pop, + dup, + + // Functions + call: CallInfo, + call_builtin: BuiltinCall, + ret, + ret_void, + + // Structs + make_struct: StructMake, + get_field: u16, + set_field: u16, + + // Arrays + make_array: u32, // element count on stack + get_index, + set_index, + + // Strings + concat, + format_to_string, // convert top-of-stack value to string representation + + pub const CastInfo = struct { to: ValueKind }; + pub const CallInfo = struct { func_name: []const u8, arg_count: u8 }; + pub const BuiltinCall = struct { id: BuiltinId, arg_count: u8 }; + pub const StructMake = struct { type_name: []const u8, field_count: u16, field_names: []const []const u8 }; + pub const FnRef = struct { name: []const u8, param_count: u8 }; +}; + +pub const ValueKind = enum { int, float, f32_k, bool_k, string }; + +pub const BuiltinId = enum { print, write, sqrt, size_of, cast, alloc }; + +/// A compiled function or expression — a flat sequence of instructions. +pub const Chunk = struct { + code: []const Instruction, + strings: []const []const u8, // string constant pool + local_count: u16, // number of local variable slots + name: []const u8, // function name (for debugging) +}; + +const ast = @import("ast.zig"); +const Node = ast.Node; +const sema = @import("sema.zig"); +const codegen_mod = @import("codegen.zig"); +const llvm = @import("llvm_api.zig"); + +/// Compute byte size of a Type. Uses LLVM data layout via codegen if available, +/// otherwise falls back to known sizes for primitive types. +fn sizeOfType(ty: Type, cg: ?*codegen_mod.CodeGen) u64 { + if (cg) |gen| { + if (std.meta.eql(ty, Type.void_type)) return 0; + const llvm_ty = gen.typeToLLVM(ty); + const data_layout = llvm.c.LLVMGetModuleDataLayout(gen.module); + return llvm.c.LLVMStoreSizeOfType(data_layout, llvm_ty); + } + // Fallback without codegen + return switch (ty) { + .signed, .unsigned => |w| (w + 7) / 8, + .f32 => 4, + .f64 => 8, + .boolean => 1, + .string_type => 8, + .void_type => 0, + .enum_type => 4, + else => 0, + }; +} + +/// Compiles AST expressions into bytecode Chunks. +pub const Compiler = struct { + allocator: std.mem.Allocator, + instructions: std.ArrayList(Instruction), + strings: std.ArrayList([]const u8), + locals: std.ArrayList(Local), + scope_depth: u16, + sema_result: ?*const sema.SemaResult, + root_decls: []const *Node, + codegen: ?*codegen_mod.CodeGen, + + // Loop context for break/continue + loop_start: ?usize = null, // instruction index of condition start (for continue) + break_patches: std.ArrayList(usize) = std.ArrayList(usize).empty, // indices of break jumps to patch + + const Local = struct { + name: []const u8, + depth: u16, + }; + + pub fn init(allocator: std.mem.Allocator, sema_result: ?*const sema.SemaResult, root_decls: []const *Node, cg: ?*codegen_mod.CodeGen) Compiler { + return .{ + .allocator = allocator, + .instructions = std.ArrayList(Instruction).empty, + .strings = std.ArrayList([]const u8).empty, + .locals = std.ArrayList(Local).empty, + .scope_depth = 0, + .sema_result = sema_result, + .root_decls = root_decls, + .codegen = cg, + }; + } + + pub fn compile(self: *Compiler, expr: *Node) !Chunk { + try self.compileNode(expr); + return .{ + .code = try self.instructions.toOwnedSlice(self.allocator), + .strings = try self.strings.toOwnedSlice(self.allocator), + .local_count = @intCast(self.locals.items.len), + .name = "", + }; + } + + pub fn compileFunction(self: *Compiler, fd: ast.FnDecl) !Chunk { + // Add params as locals + for (fd.params) |param| { + try self.locals.append(self.allocator, .{ .name = param.name, .depth = self.scope_depth }); + } + try self.compileNode(fd.body); + // Ensure there's a return at the end. + // If the function has a return type, emit `ret` (implicit return of last value). + // Otherwise emit `ret_void`. + const code = self.instructions.items; + if (code.len == 0 or (code[code.len - 1] != .ret and code[code.len - 1] != .ret_void)) { + const has_return_type = fd.return_type != null; + if (has_return_type) { + try self.emit(.ret); + } else { + try self.emit(.ret_void); + } + } + return .{ + .code = try self.instructions.toOwnedSlice(self.allocator), + .strings = try self.strings.toOwnedSlice(self.allocator), + .local_count = @intCast(self.locals.items.len), + .name = fd.name, + }; + } + + fn emit(self: *Compiler, instruction: Instruction) !void { + try self.instructions.append(self.allocator, instruction); + } + + fn addString(self: *Compiler, str: []const u8) !u32 { + const idx: u32 = @intCast(self.strings.items.len); + try self.strings.append(self.allocator, str); + return idx; + } + + fn resolveLocal(self: *Compiler, name: []const u8) ?u16 { + var i = self.locals.items.len; + while (i > 0) { + i -= 1; + if (std.mem.eql(u8, self.locals.items[i].name, name)) { + return @intCast(i); + } + } + return null; + } + + /// Process escape sequences in a raw string literal. + fn unescapeString(allocator: std.mem.Allocator, raw: []const u8) ![]u8 { + var result = try allocator.alloc(u8, raw.len); + var i: usize = 0; + var j: usize = 0; + while (i < raw.len) { + if (raw[i] == '\\' and i + 1 < raw.len) { + i += 1; + switch (raw[i]) { + 'n' => result[j] = '\n', + 't' => result[j] = '\t', + 'r' => result[j] = '\r', + '\\' => result[j] = '\\', + '"' => result[j] = '"', + '0' => result[j] = 0, + else => result[j] = raw[i], + } + j += 1; + i += 1; + } else { + result[j] = raw[i]; + j += 1; + i += 1; + } + } + return result[0..j]; + } + + /// Compile a string literal with escape sequences and interpolation support. + /// Handles `{expr}` patterns by parsing and compiling the inner expressions, + /// then concatenating all segments together. + /// + /// Strategy: emit each segment in order, and after each additional segment + /// (from the second one onward), emit a concat instruction to merge it with + /// the accumulated result so far. + fn compileStringLiteral(self: *Compiler, raw: []const u8) !void { + // String literals are plain text — {} is NOT interpolated here. + // String interpolation is handled by print() at the call site. + const unescaped = try unescapeString(self.allocator, raw); + const idx = try self.addString(unescaped); + try self.emit(.{ .push_string = idx }); + } + + fn compileNode(self: *Compiler, node: *Node) anyerror!void { + switch (node.data) { + .int_literal => |lit| { + try self.emit(.{ .push_int = lit.value }); + }, + .float_literal => |lit| { + try self.emit(.{ .push_float = lit.value }); + }, + .bool_literal => |lit| { + try self.emit(if (lit.value) .push_true else .push_false); + }, + .string_literal => |lit| { + try self.compileStringLiteral(lit.raw); + }, + .identifier => |ident| { + if (self.resolveLocal(ident.name)) |slot| { + try self.emit(.{ .get_local = slot }); + } else { + // Not a local — emit get_global to resolve lazily at runtime + const idx = try self.addString(ident.name); + try self.emit(.{ .get_global = idx }); + } + }, + .binary_op => |binop| { + if (binop.op == .and_op) { + // Short-circuit AND: LHS, dup, jump_if_false +N, pop, RHS + try self.compileNode(binop.lhs); + try self.emit(.dup); + const jump_idx = self.instructions.items.len; + try self.emit(.{ .jump_if_false = 0 }); + try self.emit(.pop); + try self.compileNode(binop.rhs); + self.instructions.items[jump_idx] = .{ + .jump_if_false = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(jump_idx)) - 1), + }; + } else if (binop.op == .or_op) { + // Short-circuit OR: LHS, dup, jump_if_true +N, pop, RHS + try self.compileNode(binop.lhs); + try self.emit(.dup); + const jump_idx = self.instructions.items.len; + try self.emit(.{ .jump_if_true = 0 }); + try self.emit(.pop); + try self.compileNode(binop.rhs); + self.instructions.items[jump_idx] = .{ + .jump_if_true = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(jump_idx)) - 1), + }; + } else { + try self.compileNode(binop.lhs); + try self.compileNode(binop.rhs); + try self.emit(switch (binop.op) { + .add => .add, + .sub => .sub, + .mul => .mul, + .div => .div, + .mod => .mod, + .eq => .eq, + .neq => .neq, + .lt => .lt, + .lte => .lte, + .gt => .gt, + .gte => .gte, + .and_op, .or_op => unreachable, + }); + } + }, + .chained_comparison => |chain| { + // Compile first pair + try self.compileNode(chain.operands[0]); + try self.compileNode(chain.operands[1]); + try self.emit(switch (chain.ops[0]) { + .lt => .lt, + .lte => .lte, + .gt => .gt, + .gte => .gte, + .eq => .eq, + .neq => .neq, + else => unreachable, + }); + // For each subsequent pair, short-circuit AND + var i: usize = 1; + while (i < chain.ops.len) : (i += 1) { + try self.emit(.dup); + const jump_idx = self.instructions.items.len; + try self.emit(.{ .jump_if_false = 0 }); + try self.emit(.pop); + try self.compileNode(chain.operands[i]); + try self.compileNode(chain.operands[i + 1]); + try self.emit(switch (chain.ops[i]) { + .lt => .lt, + .lte => .lte, + .gt => .gt, + .gte => .gte, + .eq => .eq, + .neq => .neq, + else => unreachable, + }); + self.instructions.items[jump_idx] = .{ + .jump_if_false = @intCast(@as(i64, @intCast(self.instructions.items.len)) - @as(i64, @intCast(jump_idx)) - 1), + }; + } + }, + .unary_op => |unop| { + try self.compileNode(unop.operand); + switch (unop.op) { + .negate => try self.emit(.negate), + .not => try self.emit(.not), + .xx => {}, // cast — handle later + } + }, + .comptime_expr => |ct| { + try self.compileNode(ct.expr); + }, + .block => |blk| { + self.scope_depth += 1; + const scope_start = self.locals.items.len; + for (blk.stmts) |stmt| { + try self.compileNode(stmt); + } + // Pop locals from this scope + while (self.locals.items.len > scope_start) { + _ = self.locals.pop(); + } + self.scope_depth -= 1; + }, + .var_decl => |vd| { + if (vd.value) |val| { + try self.compileNode(val); + } else { + try self.emit(.push_void); + } + const slot: u16 = @intCast(self.locals.items.len); + try self.locals.append(self.allocator, .{ .name = vd.name, .depth = self.scope_depth }); + try self.emit(.{ .set_local = slot }); + }, + .const_decl => |cd| { + try self.compileNode(cd.value); + const slot: u16 = @intCast(self.locals.items.len); + try self.locals.append(self.allocator, .{ .name = cd.name, .depth = self.scope_depth }); + try self.emit(.{ .set_local = slot }); + }, + .assignment => |asgn| { + if (asgn.target.data == .identifier) { + if (self.resolveLocal(asgn.target.data.identifier.name)) |slot| { + if (asgn.op != .assign) { + // Compound assignment: get current value, compile RHS, apply op, set + try self.emit(.{ .get_local = slot }); + try self.compileNode(asgn.value); + try self.emit(switch (asgn.op) { + .add_assign => .add, + .sub_assign => .sub, + .mul_assign => .mul, + .div_assign => .div, + .mod_assign => .mod, + .assign => unreachable, + }); + } else { + try self.compileNode(asgn.value); + } + try self.emit(.{ .set_local = slot }); + } else { + return error.UndefinedVariable; + } + } else if (asgn.target.data == .index_expr) { + // arr[i] = val → push arr, push idx, push val, set_index + const ie = asgn.target.data.index_expr; + try self.compileNode(ie.object); + try self.compileNode(ie.index); + if (asgn.op != .assign) { + // Compound: get current, apply op with RHS + try self.emit(.dup); // dup index + // We need the array and index for both get and set + // Stack: arr, idx — but we need arr[idx] for the compound op + // Simpler: just support simple assign for index targets + return error.UnsupportedExpression; + } + try self.compileNode(asgn.value); + try self.emit(.set_index); + // set_index pushes the modified container back; store it back if it's a local + if (ie.object.data == .identifier) { + if (self.resolveLocal(ie.object.data.identifier.name)) |slot| { + try self.emit(.{ .set_local = slot }); + } + } + } + }, + .return_stmt => |rs| { + if (rs.value) |val| { + try self.compileNode(val); + try self.emit(.ret); + } else { + try self.emit(.ret_void); + } + }, + .if_expr => |ie| { + try self.compileNode(ie.condition); + const jump_false_idx = self.instructions.items.len; + try self.emit(.{ .jump_if_false = 0 }); // placeholder + try self.compileNode(ie.then_branch); + if (ie.else_branch) |eb| { + const jump_end_idx = self.instructions.items.len; + try self.emit(.{ .jump = 0 }); // placeholder + // Patch jump_if_false to here + self.instructions.items[jump_false_idx] = .{ .jump_if_false = @intCast(self.instructions.items.len - jump_false_idx - 1) }; + try self.compileNode(eb); + // Patch jump to end + self.instructions.items[jump_end_idx] = .{ .jump = @intCast(self.instructions.items.len - jump_end_idx - 1) }; + } else { + // Patch jump_if_false to here + self.instructions.items[jump_false_idx] = .{ .jump_if_false = @intCast(self.instructions.items.len - jump_false_idx - 1) }; + } + }, + .call => |call_node| { + // Compile arguments + for (call_node.args) |arg| { + try self.compileNode(arg); + } + // Resolve callee name + const callee_name = if (call_node.callee.data == .identifier) + call_node.callee.data.identifier.name + else if (call_node.callee.data == .field_access) blk: { + const fa = call_node.callee.data.field_access; + if (fa.object.data == .identifier) { + break :blk fa.field; + } + break :blk null; + } else null; + + if (callee_name) |name| { + // Check if it's a builtin + const base = if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name; + if (std.mem.eql(u8, base, "print")) { + try self.emit(.{ .call_builtin = .{ .id = .print, .arg_count = @intCast(call_node.args.len) } }); + } else if (std.mem.eql(u8, base, "write")) { + try self.emit(.{ .call_builtin = .{ .id = .write, .arg_count = @intCast(call_node.args.len) } }); + } else if (std.mem.eql(u8, base, "sqrt")) { + try self.emit(.{ .call_builtin = .{ .id = .sqrt, .arg_count = @intCast(call_node.args.len) } }); + } else if (std.mem.eql(u8, base, "size_of")) { + try self.emit(.{ .call_builtin = .{ .id = .size_of, .arg_count = @intCast(call_node.args.len) } }); + } else if (std.mem.eql(u8, base, "cast")) { + try self.emit(.{ .call_builtin = .{ .id = .cast, .arg_count = @intCast(call_node.args.len) } }); + } else if (std.mem.eql(u8, base, "alloc")) { + try self.emit(.{ .call_builtin = .{ .id = .alloc, .arg_count = @intCast(call_node.args.len) } }); + } else { + try self.emit(.{ .call = .{ .func_name = name, .arg_count = @intCast(call_node.args.len) } }); + } + } else { + return error.InvalidCallee; + } + }, + .match_expr => |me| { + try self.compileNode(me.subject); + var end_jumps = std.ArrayList(usize).empty; + for (me.arms) |arm| { + if (arm.pattern) |pattern| { + try self.emit(.dup); // duplicate subject for comparison + try self.compileNode(pattern); + try self.emit(.eq); + const jump_next_idx = self.instructions.items.len; + try self.emit(.{ .jump_if_false = 0 }); // placeholder + try self.emit(.pop); // pop the subject copy + try self.compileNode(arm.body); + try end_jumps.append(self.allocator, self.instructions.items.len); + try self.emit(.{ .jump = 0 }); // placeholder jump to end + // Patch jump_if_false + self.instructions.items[jump_next_idx] = .{ .jump_if_false = @intCast(self.instructions.items.len - jump_next_idx - 1) }; + } else { + // else arm: unconditionally execute body + try self.emit(.pop); // pop the subject copy + try self.compileNode(arm.body); + try end_jumps.append(self.allocator, self.instructions.items.len); + try self.emit(.{ .jump = 0 }); // placeholder jump to end + } + } + try self.emit(.pop); // pop remaining subject + // Patch all end jumps + for (end_jumps.items) |idx| { + self.instructions.items[idx] = .{ .jump = @intCast(self.instructions.items.len - idx - 1) }; + } + }, + .struct_literal => |sl| { + for (sl.field_inits) |fi| { + try self.compileNode(fi.value); + } + const name = sl.struct_name orelse ""; + const fnames = try self.allocator.alloc([]const u8, sl.field_inits.len); + for (sl.field_inits, 0..) |fi, i| { + fnames[i] = fi.name orelse ""; + } + try self.emit(.{ .make_struct = .{ .type_name = name, .field_count = @intCast(sl.field_inits.len), .field_names = fnames } }); + }, + .field_access => |fa| { + try self.compileNode(fa.object); + // Check for string field access (.len, .ptr) + if (self.sema_result) |sr| { + const obj_ty = sr.type_map.get(fa.object); + if (obj_ty != null and obj_ty.? == .string_type) { + if (std.mem.eql(u8, fa.field, "len")) { + try self.emit(.{ .get_field = 1 }); // len is field 1 in {ptr, len} + return; + } else if (std.mem.eql(u8, fa.field, "ptr")) { + try self.emit(.{ .get_field = 0 }); // ptr is field 0 + return; + } + } + } + // Look up field index from sema struct_types + if (self.sema_result) |sr| { + // Infer the object type to find the struct name + const obj_ty = sr.type_map.get(fa.object); + if (obj_ty != null and obj_ty.?.isStruct()) { + if (sr.struct_types.get(obj_ty.?.struct_type)) |info| { + for (info.field_names, 0..) |fname, idx| { + if (std.mem.eql(u8, fname, fa.field)) { + try self.emit(.{ .get_field = @intCast(idx) }); + return; + } + } + } + } + } + // Fallback: use field name for well-known string fields + // (sema may not have type info for nodes in imported function bodies) + if (std.mem.eql(u8, fa.field, "len")) { + try self.emit(.{ .get_field = 1 }); + } else { + try self.emit(.{ .get_field = 0 }); + } + }, + .array_literal => |al| { + for (al.elements) |elem| { + try self.compileNode(elem); + } + try self.emit(.{ .make_array = @intCast(al.elements.len) }); + }, + .index_expr => |ie| { + try self.compileNode(ie.object); + try self.compileNode(ie.index); + try self.emit(.get_index); + }, + .type_expr => |te| { + const resolved = if (self.sema_result) |sr| + sr.type_map.get(node) orelse Type.fromName(te.name) orelse .void_type + else + Type.fromName(te.name) orelse .void_type; + try self.emit(.{ .push_type = resolved }); + }, + .enum_literal => |el| { + const idx = try self.addString(el.name); + try self.emit(.{ .push_string = idx }); + }, + .while_expr => |we| { + // Save outer loop context + const saved_loop_start = self.loop_start; + const saved_break_patches = self.break_patches; + self.break_patches = std.ArrayList(usize).empty; + + // Record condition start position + const condition_start = self.instructions.items.len; + self.loop_start = condition_start; + + // Compile condition + try self.compileNode(we.condition); + + // Jump past body if false + const jump_false_idx = self.instructions.items.len; + try self.emit(.{ .jump_if_false = 0 }); // placeholder + + // Compile body + try self.compileNode(we.body); + + // Jump back to condition + const back_offset = @as(i32, @intCast(condition_start)) - @as(i32, @intCast(self.instructions.items.len)) - 1; + try self.emit(.{ .jump = back_offset }); + + // Patch jump_if_false to after the loop + const after_loop = self.instructions.items.len; + self.instructions.items[jump_false_idx] = .{ .jump_if_false = @intCast(after_loop - jump_false_idx - 1) }; + + // Patch all break jumps to after the loop + for (self.break_patches.items) |patch_idx| { + self.instructions.items[patch_idx] = .{ .jump = @as(i32, @intCast(after_loop)) - @as(i32, @intCast(patch_idx)) - 1 }; + } + + // Restore outer loop context + self.loop_start = saved_loop_start; + self.break_patches = saved_break_patches; + }, + .break_expr => { + // Emit placeholder jump, record for patching + try self.break_patches.append(self.allocator, self.instructions.items.len); + try self.emit(.{ .jump = 0 }); // placeholder — patched when while ends + }, + .continue_expr => { + // Jump back to condition start + const target = self.loop_start orelse return error.UnsupportedExpression; + const offset = @as(i32, @intCast(target)) - @as(i32, @intCast(self.instructions.items.len)) - 1; + try self.emit(.{ .jump = offset }); + }, + .defer_stmt => {}, // defer not meaningful in comptime + .insert_expr => {}, // handled by codegen, not VM + else => { + return error.UnsupportedExpression; + }, + } + } +}; + +/// Stack-based virtual machine for comptime bytecode execution. +pub const VM = struct { + stack: [256]Value = undefined, + sp: u16 = 0, + frames: [64]CallFrame = undefined, + fp: u8 = 0, + functions: std.StringHashMap(Chunk), + globals: std.StringHashMap(Value), + allocator: std.mem.Allocator, + sema_result: ?*const sema.SemaResult, + root_decls: []const *Node, + codegen: ?*codegen_mod.CodeGen, + + pub const CallFrame = struct { + chunk: *const Chunk, + ip: u32, + base_slot: u16, + }; + + pub fn init(allocator: std.mem.Allocator, sema_result: ?*const sema.SemaResult, root_decls: []const *Node, cg: ?*codegen_mod.CodeGen) VM { + return .{ + .functions = std.StringHashMap(Chunk).init(allocator), + .globals = std.StringHashMap(Value).init(allocator), + .allocator = allocator, + .sema_result = sema_result, + .root_decls = root_decls, + .codegen = cg, + }; + } + + fn push(self: *VM, value: Value) !void { + if (self.sp >= 256) return error.StackOverflow; + self.stack[self.sp] = value; + self.sp += 1; + } + + fn pop(self: *VM) !Value { + if (self.sp == 0) return error.StackUnderflow; + self.sp -= 1; + return self.stack[self.sp]; + } + + fn peek(self: *VM) !Value { + if (self.sp == 0) return error.StackUnderflow; + return self.stack[self.sp - 1]; + } + + pub fn execute(self: *VM, chunk: *const Chunk) !Value { + // Set up initial frame + self.frames[0] = .{ .chunk = chunk, .ip = 0, .base_slot = 0 }; + self.fp = 1; + + return self.run(); + } + + fn run(self: *VM) !Value { + while (true) { + const frame = &self.frames[self.fp - 1]; + if (frame.ip >= frame.chunk.code.len) { + // End of chunk — return top of stack or void + if (self.sp > frame.base_slot) { + return self.pop(); + } + return .{ .void_val = {} }; + } + + const instruction = frame.chunk.code[frame.ip]; + frame.ip += 1; + + switch (instruction) { + // Constants + .push_int => |v| try self.push(.{ .int_val = v }), + .push_float => |v| try self.push(.{ .float_val = v }), + .push_f32 => |v| try self.push(.{ .float32_val = v }), + .push_true => try self.push(.{ .bool_val = true }), + .push_false => try self.push(.{ .bool_val = false }), + .push_string => |idx| { + if (idx < frame.chunk.strings.len) { + try self.push(.{ .string_val = frame.chunk.strings[idx] }); + } else { + try self.push(.{ .string_val = "" }); + } + }, + .push_void => try self.push(.{ .void_val = {} }), + .push_type => |t| try self.push(.{ .type_val = t }), + .push_function => |fr| try self.push(.{ .function_val = .{ .name = fr.name, .param_count = fr.param_count } }), + + // Stack ops + .pop => _ = try self.pop(), + .dup => { + const v = try self.peek(); + try self.push(v); + }, + + // Local variables + .get_local => |slot| { + const abs_slot = frame.base_slot + slot; + if (abs_slot < self.sp) { + try self.push(self.stack[abs_slot]); + } else { + try self.push(.{ .void_val = {} }); + } + }, + .set_local => |slot| { + const abs_slot = frame.base_slot + slot; + const val = try self.pop(); + // Grow stack if needed + while (self.sp <= abs_slot) { + self.stack[self.sp] = .{ .void_val = {} }; + self.sp += 1; + } + self.stack[abs_slot] = val; + }, + + // Global variables (lazily resolved from root_decls) + .get_global => |name_idx| { + const name = if (name_idx < frame.chunk.strings.len) frame.chunk.strings[name_idx] else return error.InvalidGlobal; + try self.push(try self.resolveGlobal(name)); + }, + + // Arithmetic + .add => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(try self.arith(a, b, .add_op)); + }, + .sub => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(try self.arith(a, b, .sub_op)); + }, + .mul => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(try self.arith(a, b, .mul_op)); + }, + .div => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(try self.arith(a, b, .div_op)); + }, + .mod => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(try self.arith(a, b, .mod_op)); + }, + .negate => { + const v = try self.pop(); + try self.push(switch (v) { + .int_val => |i| Value{ .int_val = -i }, + .float_val => |f| Value{ .float_val = -f }, + .float32_val => |f| Value{ .float32_val = -f }, + else => return error.TypeError, + }); + }, + + // Comparison + .eq => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(.{ .bool_val = self.valEqual(a, b) }); + }, + .neq => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(.{ .bool_val = !self.valEqual(a, b) }); + }, + .lt => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(.{ .bool_val = self.valLess(a, b) }); + }, + .lte => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(.{ .bool_val = self.valLess(a, b) or self.valEqual(a, b) }); + }, + .gt => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(.{ .bool_val = self.valLess(b, a) }); + }, + .gte => { + const b = try self.pop(); + const a = try self.pop(); + try self.push(.{ .bool_val = self.valLess(b, a) or self.valEqual(a, b) }); + }, + .not => { + const v = try self.pop(); + const b = switch (v) { + .bool_val => |bv| bv, + .int_val => |iv| iv != 0, + else => true, + }; + try self.push(.{ .bool_val = !b }); + }, + + // Control flow + .jump => |offset| { + frame.ip = @intCast(@as(i64, frame.ip) + offset); + }, + .jump_if_false => |offset| { + const v = try self.pop(); + const is_true = switch (v) { + .bool_val => |bv| bv, + .int_val => |iv| iv != 0, + else => true, + }; + if (!is_true) { + frame.ip = @intCast(@as(i64, frame.ip) + offset); + } + }, + .jump_if_true => |offset| { + const v = try self.pop(); + const is_true = switch (v) { + .bool_val => |bv| bv, + .int_val => |iv| iv != 0, + else => true, + }; + if (is_true) { + frame.ip = @intCast(@as(i64, frame.ip) + offset); + } + }, + + // Functions + .call => |ci| { + try self.callFunction(ci.func_name, ci.arg_count); + }, + .call_builtin => |bi| { + try self.callBuiltin(bi.id, bi.arg_count); + }, + .ret => { + const result = try self.pop(); + if (self.fp <= 1) return result; + // Pop frame + self.fp -= 1; + self.sp = frame.base_slot; + try self.push(result); + }, + .ret_void => { + if (self.fp <= 1) return .{ .void_val = {} }; + self.fp -= 1; + self.sp = frame.base_slot; + try self.push(.{ .void_val = {} }); + }, + + // Structs + .make_struct => |sm| { + const fields = try self.allocator.alloc(Value, sm.field_count); + var i: u16 = sm.field_count; + while (i > 0) { + i -= 1; + fields[i] = try self.pop(); + } + try self.push(.{ .struct_val = .{ .type_name = sm.type_name, .field_names = sm.field_names, .fields = fields } }); + }, + .get_field => |idx| { + const obj = try self.pop(); + if (obj == .struct_val) { + if (idx < obj.struct_val.fields.len) { + try self.push(obj.struct_val.fields[idx]); + } else { + try self.push(.{ .void_val = {} }); + } + } else if (obj == .string_val) { + // String slice: field 0 = ptr (return string itself), field 1 = len + if (idx == 1) { + try self.push(.{ .int_val = @intCast(obj.string_val.len) }); + } else { + try self.push(obj); // ptr → return string itself + } + } else { + return error.TypeError; + } + }, + .set_field => |idx| { + const val = try self.pop(); + const obj = try self.pop(); + if (obj == .struct_val) { + if (idx < obj.struct_val.fields.len) { + obj.struct_val.fields[idx] = val; + } + try self.push(obj); + } else { + return error.TypeError; + } + }, + + // Arrays + .make_array => |count| { + const elements = try self.allocator.alloc(Value, count); + var i: u32 = count; + while (i > 0) { + i -= 1; + elements[i] = try self.pop(); + } + try self.push(.{ .array_val = .{ .elements = elements } }); + }, + .get_index => { + const idx_val = try self.pop(); + const arr = try self.pop(); + if (arr == .array_val) { + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + if (idx < arr.array_val.elements.len) { + try self.push(arr.array_val.elements[idx]); + } else { + return error.IndexOutOfBounds; + } + } else if (arr == .string_val) { + // String indexing: return byte as int + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + if (idx < arr.string_val.len) { + try self.push(.{ .int_val = @intCast(arr.string_val[idx]) }); + } else { + return error.IndexOutOfBounds; + } + } else { + return error.TypeError; + } + }, + .set_index => { + const val = try self.pop(); + const idx_val = try self.pop(); + const arr = try self.pop(); + if (arr == .array_val) { + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + if (idx < arr.array_val.elements.len) { + arr.array_val.elements[idx] = val; + } + try self.push(arr); + } else if (arr == .string_val) { + // String index assignment: mutate byte + const idx: usize = @intCast(idx_val.asInt() orelse return error.TypeError); + const byte_val: u8 = @intCast(val.asInt() orelse return error.TypeError); + if (idx < arr.string_val.len) { + const mutable = @constCast(arr.string_val); + mutable[idx] = byte_val; + } + try self.push(arr); + } else { + return error.TypeError; + } + }, + + // Strings + .concat => { + const b = try self.pop(); + const a = try self.pop(); + const sa = try a.format(self.allocator); + const sb = try b.format(self.allocator); + const result = try std.fmt.allocPrint(self.allocator, "{s}{s}", .{ sa, sb }); + try self.push(.{ .string_val = result }); + }, + .format_to_string => { + const v = try self.pop(); + const s = try v.format(self.allocator); + try self.push(.{ .string_val = s }); + }, + + // Cast + .cast => { + // TODO: implement type casting + }, + } + } + } + + const ArithOp = enum { add_op, sub_op, mul_op, div_op, mod_op }; + + fn arith(self: *VM, a: Value, b: Value, op: ArithOp) !Value { + _ = self; + // Both int + if (a == .int_val and b == .int_val) { + const ai = a.int_val; + const bi = b.int_val; + return .{ .int_val = switch (op) { + .add_op => ai + bi, + .sub_op => ai - bi, + .mul_op => ai * bi, + .div_op => if (bi != 0) @divTrunc(ai, bi) else return error.DivisionByZero, + .mod_op => if (bi != 0) @rem(ai, bi) else return error.DivisionByZero, + } }; + } + + // Both f32 + if (a == .float32_val and b == .float32_val) { + const af = a.float32_val; + const bf = b.float32_val; + return .{ .float32_val = switch (op) { + .add_op => af + bf, + .sub_op => af - bf, + .mul_op => af * bf, + .div_op => af / bf, + .mod_op => @rem(af, bf), + } }; + } + + // Promote to f64 + const af = a.asFloat() orelse return error.TypeError; + const bf = b.asFloat() orelse return error.TypeError; + return .{ .float_val = switch (op) { + .add_op => af + bf, + .sub_op => af - bf, + .mul_op => af * bf, + .div_op => af / bf, + .mod_op => @rem(af, bf), + } }; + } + + fn valEqual(self: *VM, a: Value, b: Value) bool { + _ = self; + if (a == .int_val and b == .int_val) return a.int_val == b.int_val; + if (a == .bool_val and b == .bool_val) return a.bool_val == b.bool_val; + if (a == .string_val and b == .string_val) return std.mem.eql(u8, a.string_val, b.string_val); + // Float comparison + const af = a.asFloat(); + const bf = b.asFloat(); + if (af != null and bf != null) return af.? == bf.?; + return false; + } + + fn valLess(self: *VM, a: Value, b: Value) bool { + _ = self; + if (a == .int_val and b == .int_val) return a.int_val < b.int_val; + const af = a.asFloat(); + const bf = b.asFloat(); + if (af != null and bf != null) return af.? < bf.?; + return false; + } + + fn callFunction(self: *VM, name: []const u8, arg_count: u8) !void { + // Look up chunk in cache + if (self.functions.getPtr(name)) |ptr| { + return self.invokeChunk(ptr, arg_count); + } + + // On-demand compilation: find function AST in root_decls + for (self.root_decls) |decl| { + switch (decl.data) { + .fn_decl => |fd| { + if (std.mem.eql(u8, fd.name, name)) { + var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); + const chunk = try compiler.compileFunction(fd); + try self.functions.put(name, chunk); + if (self.functions.getPtr(name)) |ptr| { + return self.invokeChunk(ptr, arg_count); + } + } + }, + .namespace_decl => |ns| { + for (ns.decls) |d| { + if (d.data == .fn_decl) { + if (std.mem.eql(u8, d.data.fn_decl.name, name)) { + var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); + const chunk = try compiler.compileFunction(d.data.fn_decl); + try self.functions.put(name, chunk); + if (self.functions.getPtr(name)) |ptr| { + return self.invokeChunk(ptr, arg_count); + } + } + } + } + }, + else => {}, + } + } + + return error.UndefinedFunction; + } + + fn invokeChunk(self: *VM, chunk: *const Chunk, arg_count: u8) !void { + if (self.fp >= 64) return error.StackOverflow; + + // Args are on the stack. Set up new frame. + const base = self.sp - @as(u16, arg_count); + self.frames[self.fp] = .{ .chunk = chunk, .ip = 0, .base_slot = base }; + self.fp += 1; + } + + fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void { + switch (id) { + .write => { + // write(str) — raw string output + if (arg_count >= 1) { + const val = try self.pop(); + const str = try val.format(self.allocator); + std.debug.print("{s}", .{str}); + } + try self.push(.{ .void_val = {} }); + }, + .print => { + // print(fmt, args...) — positional {} formatting + if (arg_count == 0) { + try self.push(.{ .void_val = {} }); + } else if (arg_count == 1) { + // Single arg: just print it + const val = try self.pop(); + const str = try val.format(self.allocator); + std.debug.print("{s}", .{str}); + try self.push(.{ .void_val = {} }); + } else { + // Pop args in reverse order (stack is LIFO) + var vals = std.ArrayList(Value).empty; + var j: u8 = 0; + while (j < arg_count) : (j += 1) { + try vals.append(self.allocator, try self.pop()); + } + // vals[0] is last arg, vals[arg_count-1] is first (format string) + const fmt_val = vals.items[arg_count - 1]; + const fmt_str = try fmt_val.format(self.allocator); + + // Process format string with {} placeholders + var out = std.ArrayList(u8).empty; + var arg_idx: usize = 0; + var fi: usize = 0; + while (fi < fmt_str.len) { + if (fi + 1 < fmt_str.len and fmt_str[fi] == '{' and fmt_str[fi + 1] == '}') { + if (arg_idx < arg_count - 1) { + // vals are in reverse: vals[arg_count-2] is first value arg, vals[0] is last + const val_idx = arg_count - 2 - arg_idx; + const formatted = try vals.items[val_idx].format(self.allocator); + try out.appendSlice(self.allocator, formatted); + arg_idx += 1; + } + fi += 2; + } else if (fi + 1 < fmt_str.len and fmt_str[fi] == '{' and fmt_str[fi + 1] == '{') { + try out.append(self.allocator, '{'); + fi += 2; + } else if (fi + 1 < fmt_str.len and fmt_str[fi] == '}' and fmt_str[fi + 1] == '}') { + try out.append(self.allocator, '}'); + fi += 2; + } else { + try out.append(self.allocator, fmt_str[fi]); + fi += 1; + } + } + std.debug.print("{s}", .{out.items}); + try self.push(.{ .void_val = {} }); + } + }, + .sqrt => { + if (arg_count >= 1) { + const val = try self.pop(); + const f = val.asFloat() orelse return error.TypeError; + try self.push(.{ .float_val = @sqrt(f) }); + } else { + try self.push(.{ .float_val = 0.0 }); + } + }, + .size_of => { + if (arg_count >= 1) { + const val = try self.pop(); + if (val == .type_val) { + const size = sizeOfType(val.type_val, self.codegen); + try self.push(.{ .int_val = @intCast(size) }); + } else { + try self.push(.{ .int_val = 0 }); + } + } else { + try self.push(.{ .int_val = 0 }); + } + }, + .cast => { + // cast(Type, val) — explicit type conversion + if (arg_count >= 2) { + const val = try self.pop(); // second arg (value) + const type_arg = try self.pop(); // first arg (type) + const target_ty: Type = if (type_arg == .type_val) type_arg.type_val else .void_type; + // Convert based on target type + if (target_ty.isFloat()) { + // Target is float — convert from int or other float + switch (val) { + .int_val => |v| try self.push(.{ .float_val = @floatFromInt(v) }), + .float32_val => |v| try self.push(.{ .float_val = @as(f64, v) }), + .float_val => try self.push(val), + else => try self.push(val), + } + } else if (target_ty.isInt()) { + // Target is int — convert from float + switch (val) { + .float_val => |v| try self.push(.{ .int_val = @intFromFloat(v) }), + .float32_val => |v| try self.push(.{ .int_val = @intFromFloat(v) }), + .int_val => try self.push(val), + else => try self.push(val), + } + } else { + try self.push(val); // pass through + } + } else { + try self.push(.{ .int_val = 0 }); + } + }, + .alloc => { + // alloc(size) — allocate zeroed byte buffer, return as string + if (arg_count >= 1) { + const val = try self.pop(); + const size: usize = if (val.asInt()) |v| @intCast(@max(0, v)) else 0; + const buf = try self.allocator.alloc(u8, size); + @memset(buf, 0); + try self.push(.{ .string_val = buf }); + } else { + try self.push(.{ .string_val = "" }); + } + }, + } + } + + /// Resolve a global variable by name. Checks the globals cache first, + /// then searches root_decls for matching const_decl/var_decl and evaluates. + const VMError = error{ + CompileError, + UndefinedVariable, + UndefinedFunction, + InvalidGlobal, + InvalidCallee, + TypeError, + StackOverflow, + StackUnderflow, + IndexOutOfBounds, + DivisionByZero, + UnsupportedExpression, + OutOfMemory, + }; + + /// Evaluate a chunk in a fresh VM to avoid corrupting this VM's state. + fn evalInFreshVM(self: *VM, chunk: *const Chunk) VMError!Value { + var nested_vm = VM.init(self.allocator, self.sema_result, self.root_decls, self.codegen); + // Share the globals cache so nested evaluations see already-resolved globals + nested_vm.globals = self.globals; + const result = nested_vm.execute(chunk) catch return error.CompileError; + // Copy back any new globals that were resolved during nested evaluation + self.globals = nested_vm.globals; + return result; + } + + fn resolveGlobal(self: *VM, name: []const u8) VMError!Value { + // Check cache first + if (self.globals.get(name)) |val| return val; + + // Search root_decls for matching declaration + for (self.root_decls) |decl| { + switch (decl.data) { + .const_decl => |cd| { + if (std.mem.eql(u8, cd.name, name)) { + var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); + const chunk = compiler.compile(cd.value) catch return error.CompileError; + const result = self.evalInFreshVM(&chunk) catch return error.CompileError; + try self.globals.put(name, result); + return result; + } + }, + .var_decl => |vd| { + if (std.mem.eql(u8, vd.name, name)) { + if (vd.value) |val_expr| { + var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); + const chunk = compiler.compile(val_expr) catch return error.CompileError; + const result = self.evalInFreshVM(&chunk) catch return error.CompileError; + try self.globals.put(name, result); + return result; + } + return .{ .void_val = {} }; + } + }, + .namespace_decl => |ns| { + // Check inside namespace for matching declarations + for (ns.decls) |d| { + if (d.data == .const_decl and std.mem.eql(u8, d.data.const_decl.name, name)) { + var compiler = Compiler.init(self.allocator, self.sema_result, self.root_decls, self.codegen); + const chunk = compiler.compile(d.data.const_decl.value) catch return error.CompileError; + const result = self.evalInFreshVM(&chunk) catch return error.CompileError; + try self.globals.put(name, result); + return result; + } + } + }, + else => {}, + } + } + + // Check for struct/enum/union type declarations + for (self.root_decls) |decl| { + switch (decl.data) { + .struct_decl => |sd| { + if (std.mem.eql(u8, sd.name, name)) { + const val = Value{ .type_val = .{ .struct_type = name } }; + try self.globals.put(name, val); + return val; + } + }, + .enum_decl => |ed| { + if (std.mem.eql(u8, ed.name, name)) { + const val = Value{ .type_val = .{ .enum_type = name } }; + try self.globals.put(name, val); + return val; + } + }, + .union_decl => |ud| { + if (std.mem.eql(u8, ud.name, name)) { + const val = Value{ .type_val = .{ .union_type = name } }; + try self.globals.put(name, val); + return val; + } + }, + else => {}, + } + } + + // Check if it's a primitive type name (s32, f64, bool, etc.) + if (Type.fromName(name)) |ty| { + const val = Value{ .type_val = ty }; + try self.globals.put(name, val); + return val; + } + + return error.UndefinedVariable; + } +}; + +test "Value: basic operations" { + const a = Value{ .int_val = 42 }; + try std.testing.expect(a.isInt()); + try std.testing.expect(!a.isFloat()); + try std.testing.expectEqual(@as(i64, 42), a.asInt().?); + try std.testing.expectEqual(@as(f64, 42.0), a.asFloat().?); + + const b = Value{ .float_val = 3.14 }; + try std.testing.expect(!b.isInt()); + try std.testing.expect(b.isFloat()); + try std.testing.expectEqual(@as(f64, 3.14), b.asFloat().?); + + const c = Value{ .bool_val = true }; + try std.testing.expectEqual(@as(i64, 1), c.asInt().?); +} + +const parser_mod = @import("parser.zig"); + +fn compileAndRun(source: [:0]const u8) !Value { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var p = parser_mod.Parser.init(alloc, source); + const expr = try p.parseExpr(); + + var compiler = Compiler.init(alloc, null, &.{}, null); + const chunk = try compiler.compile(expr); + + var vm = VM.init(alloc, null, &.{}, null); + return vm.execute(&chunk); +} + +test "VM: 2 + 3 = 5" { + const result = try compileAndRun("2 + 3"); + try std.testing.expectEqual(@as(i64, 5), result.int_val); +} + +test "VM: arithmetic operations" { + // subtraction + const r1 = try compileAndRun("10 - 3"); + try std.testing.expectEqual(@as(i64, 7), r1.int_val); + + // multiplication + const r2 = try compileAndRun("6 * 7"); + try std.testing.expectEqual(@as(i64, 42), r2.int_val); + + // division + const r3 = try compileAndRun("20 / 4"); + try std.testing.expectEqual(@as(i64, 5), r3.int_val); + + // negation + const r4 = try compileAndRun("-42"); + try std.testing.expectEqual(@as(i64, -42), r4.int_val); +} + +test "VM: comparison operations" { + const r1 = try compileAndRun("3 == 3"); + try std.testing.expectEqual(true, r1.bool_val); + + const r2 = try compileAndRun("3 != 4"); + try std.testing.expectEqual(true, r2.bool_val); + + const r3 = try compileAndRun("2 < 5"); + try std.testing.expectEqual(true, r3.bool_val); + + const r4 = try compileAndRun("5 > 2"); + try std.testing.expectEqual(true, r4.bool_val); +} + +test "VM: boolean literals" { + const r1 = try compileAndRun("true"); + try std.testing.expectEqual(true, r1.bool_val); + + const r2 = try compileAndRun("false"); + try std.testing.expectEqual(false, r2.bool_val); + + const r3 = try compileAndRun("!false"); + try std.testing.expectEqual(true, r3.bool_val); +} + +test "VM: float arithmetic" { + const r1 = try compileAndRun("1.5 + 2.5"); + try std.testing.expectEqual(@as(f64, 4.0), r1.float_val); + + const r2 = try compileAndRun("3.0 * 2.0"); + try std.testing.expectEqual(@as(f64, 6.0), r2.float_val); +} + +test "VM: if expression" { + const r1 = try compileAndRun("if true then 1 else 2"); + try std.testing.expectEqual(@as(i64, 1), r1.int_val); + + const r2 = try compileAndRun("if false then 1 else 2"); + try std.testing.expectEqual(@as(i64, 2), r2.int_val); +} + +test "VM: block with variables" { + // Parse a block expression: { x := 5; y := x + 3; y; } + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Parse a block as a statement sequence + var p = parser_mod.Parser.init(alloc, "{ x := 5; y := x + 3; y; }"); + const expr = try p.parseExpr(); + + var compiler = Compiler.init(alloc, null, &.{}, null); + const chunk = try compiler.compile(expr); + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 8), result.int_val); +} + +test "VM: nested if with variables" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var p = parser_mod.Parser.init(alloc, "{ x := 10; if x > 5 then 1 else 0; }"); + const expr = try p.parseExpr(); + + var compiler = Compiler.init(alloc, null, &.{}, null); + const chunk = try compiler.compile(expr); + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 1), result.int_val); +} + +/// Helper to compile and run a full program, executing a specific expression +/// after all declarations are registered. +fn compileAndRunProgram(source: [:0]const u8, expr_source: [:0]const u8) !Value { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Parse the full program to get root decls + var prog_parser = parser_mod.Parser.init(alloc, source); + const root = try prog_parser.parse(); + const decls = root.data.root.decls; + + // Parse the expression to evaluate + var expr_parser = parser_mod.Parser.init(alloc, expr_source); + const expr = try expr_parser.parseExpr(); + + var compiler = Compiler.init(alloc, null, decls, null); + const chunk = try compiler.compile(expr); + + var vm = VM.init(alloc, null, decls, null); + return vm.execute(&chunk); +} + +test "VM: function call" { + const result = try compileAndRunProgram( + "add :: (a: s32, b: s32) -> s32 { a + b; }", + "add(2, 3)", + ); + try std.testing.expectEqual(@as(i64, 5), result.int_val); +} + +test "VM: nested function calls" { + const result = try compileAndRunProgram( + "double :: (x: s32) -> s32 { x * 2; } quad :: (x: s32) -> s32 { double(double(x)); }", + "quad(3)", + ); + try std.testing.expectEqual(@as(i64, 12), result.int_val); +} + +test "VM: match expression" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Match on integer value + var p = parser_mod.Parser.init(alloc, "{ x := 2; if x == { case 1: 10; case 2: 20; case 3: 30; } }"); + const expr = try p.parseExpr(); + + var compiler = Compiler.init(alloc, null, &.{}, null); + const chunk = try compiler.compile(expr); + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 20), result.int_val); +} + +test "VM: builtin sqrt" { + const result = try compileAndRun("sqrt(16.0)"); + try std.testing.expectEqual(@as(f64, 4.0), result.float_val); +} + +test "VM: struct literal and field access" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Manually build a chunk that creates a struct and reads field 1 + const code = [_]Instruction{ + .{ .push_int = 10 }, // field 0: x + .{ .push_int = 20 }, // field 1: y + .{ .make_struct = .{ .type_name = "Point", .field_count = 2, .field_names = &.{ "x", "y" } } }, + .{ .get_field = 1 }, // get y + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 0, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 20), result.int_val); +} + +test "VM: array literal and index" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + // Manually build: make_array([10, 20, 30]), get_index(1) + const code = [_]Instruction{ + .{ .push_int = 10 }, + .{ .push_int = 20 }, + .{ .push_int = 30 }, + .{ .make_array = 3 }, + .{ .push_int = 1 }, // index + .get_index, + }; + const chunk = Chunk{ + .code = &code, + .strings = &.{}, + .local_count = 0, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqual(@as(i64, 20), result.int_val); +} + +test "VM: string concat" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + const strings = [_][]const u8{ "hello ", "world" }; + const code = [_]Instruction{ + .{ .push_string = 0 }, + .{ .push_string = 1 }, + .concat, + }; + const chunk = Chunk{ + .code = &code, + .strings = &strings, + .local_count = 0, + .name = "", + }; + + var vm = VM.init(alloc, null, &.{}, null); + const result = try vm.execute(&chunk); + try std.testing.expectEqualStrings("hello world", result.string_val); +} + +test "VM: type value" { + const result = try compileAndRun("f64"); + try std.testing.expect(result == .type_val); +} + +test "VM: function with return statement" { + const result = try compileAndRunProgram( + "compute :: (x: s32) -> s32 { return x * x; }", + "compute(6)", + ); + try std.testing.expectEqual(@as(i64, 36), result.int_val); +} diff --git a/src/core.zig b/src/core.zig new file mode 100644 index 0000000..edd6756 --- /dev/null +++ b/src/core.zig @@ -0,0 +1,110 @@ +const std = @import("std"); +const ast = @import("ast.zig"); +const parser = @import("parser.zig"); +const imports = @import("imports.zig"); +const sema = @import("sema.zig"); +const codegen = @import("codegen.zig"); +const errors = @import("errors.zig"); +const Node = ast.Node; + +pub const Compilation = struct { + allocator: std.mem.Allocator, + io: std.Io, + file_path: []const u8, + source: [:0]const u8, + diagnostics: errors.DiagnosticList, + + // Pipeline results + root: ?*Node = null, + resolved_root: ?*Node = null, + import_sources: std.StringHashMap([:0]const u8), + sema_result: ?sema.SemaResult = null, + cg: ?codegen.CodeGen = null, + + pub fn init(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8, source: [:0]const u8) Compilation { + return .{ + .allocator = allocator, + .io = io, + .file_path = file_path, + .source = source, + .diagnostics = errors.DiagnosticList.init(allocator, source, file_path), + .import_sources = std.StringHashMap([:0]const u8).init(allocator), + }; + } + + pub fn deinit(self: *Compilation) void { + if (self.cg) |*cg| cg.deinit(); + self.diagnostics.deinit(); + } + + pub fn parse(self: *Compilation) !void { + var p = parser.Parser.init(self.allocator, self.source); + p.diagnostics = &self.diagnostics; + self.root = p.parse() catch return error.CompileError; + } + + pub fn resolveImports(self: *Compilation) !void { + const root = self.root orelse return error.CompileError; + var chain = std.StringHashMap(void).init(self.allocator); + var cache = imports.ModuleCache.init(self.allocator); + const base_dir = imports.dirName(self.file_path); + const mod = imports.resolveImports( + self.allocator, + self.io, + root, + base_dir, + self.file_path, + &chain, + &cache, + &self.import_sources, + &self.diagnostics, + ) catch return error.CompileError; + + // Build a root node from the resolved module's decls + const new_root = try self.allocator.create(Node); + new_root.* = .{ + .span = root.span, + .data = .{ .root = .{ .decls = mod.decls } }, + }; + self.resolved_root = new_root; + } + + pub fn analyze(self: *Compilation) !void { + const root = self.resolved_root orelse self.root orelse return error.CompileError; + var analyzer = sema.Analyzer.init(self.allocator); + self.sema_result = analyzer.analyze(root) catch return error.CompileError; + // Merge sema diagnostics into our list + if (self.sema_result) |sr| { + for (sr.diagnostics) |d| { + self.diagnostics.add(d.level, d.message, d.span); + } + } + } + + pub fn generateCode(self: *Compilation) !void { + const root = self.resolved_root orelse self.root orelse return error.CompileError; + var cg = codegen.CodeGen.init(self.allocator, "sx_module"); + cg.diagnostics = &self.diagnostics; + if (self.sema_result) |*sr| { + cg.sema_result = sr; + } + cg.generate(root) catch return error.CompileError; + self.cg = cg; + } + + pub fn renderErrors(self: *const Compilation) void { + for (self.diagnostics.items.items) |d| { + const level_str = switch (d.level) { + .err => "error", + .warn => "warning", + .note => "note", + }; + if (d.span) |span| { + const loc = errors.SourceLoc.compute(self.source, span.start); + std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ self.file_path, loc.line, loc.col, level_str, d.message }); + } else { + std.debug.print("{s}: {s}: {s}\n", .{ self.file_path, level_str, d.message }); + } + } + } +}; diff --git a/src/errors.zig b/src/errors.zig new file mode 100644 index 0000000..546a9ae --- /dev/null +++ b/src/errors.zig @@ -0,0 +1,96 @@ +const std = @import("std"); +const Span = @import("ast.zig").Span; + +pub const Level = enum { + err, + warn, + note, +}; + +pub const SourceLoc = struct { + line: u32, + col: u32, + + pub fn compute(source: []const u8, byte_offset: u32) SourceLoc { + var line: u32 = 1; + var col: u32 = 1; + for (source[0..byte_offset]) |c| { + if (c == '\n') { + line += 1; + col = 1; + } else { + col += 1; + } + } + return .{ .line = line, .col = col }; + } +}; + +pub const Diagnostic = struct { + level: Level, + message: []const u8, + span: ?Span, +}; + +pub const DiagnosticList = struct { + items: std.ArrayList(Diagnostic) = .empty, + allocator: std.mem.Allocator, + source: []const u8, + file_name: []const u8, + + pub fn init(allocator: std.mem.Allocator, source: []const u8, file_name: []const u8) DiagnosticList { + return .{ + .allocator = allocator, + .source = source, + .file_name = file_name, + }; + } + + pub fn deinit(self: *DiagnosticList) void { + self.items.deinit(self.allocator); + } + + pub fn add(self: *DiagnosticList, level: Level, message: []const u8, span: ?Span) void { + // Deduplicate: skip if same level+span+message already exists + for (self.items.items) |d| { + if (d.level == level and std.mem.eql(u8, d.message, message)) { + const a = d.span orelse continue; + const b = span orelse continue; + if (a.start == b.start and a.end == b.end) return; + } + } + self.items.append(self.allocator, .{ + .level = level, + .message = message, + .span = span, + }) catch {}; + } + + pub fn addFmt(self: *DiagnosticList, level: Level, span: ?Span, comptime fmt: []const u8, args: anytype) void { + const message = std.fmt.allocPrint(self.allocator, fmt, args) catch "diagnostic format error"; + self.add(level, message, span); + } + + pub fn hasErrors(self: *const DiagnosticList) bool { + for (self.items.items) |d| { + if (d.level == .err) return true; + } + return false; + } + + pub fn render(self: *const DiagnosticList, writer: anytype) !void { + for (self.items.items) |d| { + const level_str = switch (d.level) { + .err => "error", + .warn => "warning", + .note => "note", + }; + if (d.span) |span| { + const loc = SourceLoc.compute(self.source, span.start); + try writer.print("{s}:{d}:{d}: {s}: {s}\n", .{ self.file_name, loc.line, loc.col, level_str, d.message }); + } else { + try writer.print("{s}: {s}: {s}\n", .{ self.file_name, level_str, d.message }); + } + } + } +}; diff --git a/src/imports.zig b/src/imports.zig new file mode 100644 index 0000000..3c444d7 --- /dev/null +++ b/src/imports.zig @@ -0,0 +1,150 @@ +const std = @import("std"); +const ast = @import("ast.zig"); +const parser = @import("parser.zig"); +const errors = @import("errors.zig"); +const Node = ast.Node; + +pub fn dirName(path: []const u8) []const u8 { + var last_sep: usize = 0; + var found = false; + for (path, 0..) |ch, i| { + if (ch == '/') { + last_sep = i; + found = true; + } + } + return if (found) path[0..last_sep] else "."; +} + +/// A resolved module: the fully-resolved declarations of a single .sx file, +/// with its own scope tracking which names are defined. +pub const ResolvedModule = struct { + path: []const u8, + decls: []const *Node, + scope: std.StringHashMap(void), + + /// Try to add a declaration. Returns true if added, false if name already in scope. + pub fn addDecl(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), decl: *Node) !bool { + if (decl.data.declName()) |name| { + if (self.scope.contains(name)) return false; + try self.scope.put(name, {}); + } + try list.append(allocator, decl); + return true; + } + + /// Merge another module's decls as flat imports (skipping duplicates). + pub fn mergeFlat(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), other: ResolvedModule) !void { + for (other.decls) |decl| { + _ = try self.addDecl(allocator, list, decl); + } + } + + /// Add another module as a namespaced import. + pub fn addNamespace(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node), name: []const u8, other: ResolvedModule, span: ast.Span) !void { + const ns_node = try allocator.create(Node); + ns_node.* = .{ + .span = span, + .data = .{ .namespace_decl = .{ + .name = name, + .decls = other.decls, + } }, + }; + try self.scope.put(name, {}); + try list.append(allocator, ns_node); + } + + pub fn finalize(self: *ResolvedModule, allocator: std.mem.Allocator, list: *std.ArrayList(*Node)) !void { + self.decls = try list.toOwnedSlice(allocator); + } +}; + +/// Module cache: maps resolved file paths to their ResolvedModules. +pub const ModuleCache = std.StringHashMap(ResolvedModule); + +pub fn resolveImports( + allocator: std.mem.Allocator, + io: std.Io, + root: *Node, + base_dir: []const u8, + file_path: []const u8, + chain: *std.StringHashMap(void), + cache: *ModuleCache, + source_map: ?*std.StringHashMap([:0]const u8), + diagnostics: ?*errors.DiagnosticList, +) !ResolvedModule { + var mod = ResolvedModule{ + .path = file_path, + .decls = &.{}, + .scope = std.StringHashMap(void).init(allocator), + }; + + if (root.data != .root) { + mod.decls = &.{}; + return mod; + } + + var decl_list = std.ArrayList(*Node).empty; + + for (root.data.root.decls) |decl| { + if (decl.data != .import_decl) { + _ = try mod.addDecl(allocator, &decl_list, decl); + continue; + } + const imp = decl.data.import_decl; + + // Resolve path relative to base_dir + const resolved_path = if (std.mem.eql(u8, base_dir, ".")) + imp.path + else + try std.fmt.allocPrint(allocator, "{s}/{s}", .{ base_dir, imp.path }); + + // Circular import check — only along the current chain + if (chain.contains(resolved_path)) continue; + + // Resolve or retrieve the imported module + const imported_mod = if (cache.get(resolved_path)) |cached| + cached + else blk: { + // Read imported file + const imp_bytes = std.Io.Dir.readFileAlloc(.cwd(), io, resolved_path, allocator, .limited(10 * 1024 * 1024)) catch { + if (diagnostics) |diags| { + diags.addFmt(.err, decl.span, "cannot read import '{s}'", .{resolved_path}); + } + return error.ImportError; + }; + const imp_source = try allocator.dupeZ(u8, imp_bytes); + + if (source_map) |sm| { + sm.put(resolved_path, imp_source) catch {}; + } + + var p = parser.Parser.init(allocator, imp_source); + const imp_root = p.parse() catch { + if (diagnostics) |diags| { + diags.addFmt(.err, decl.span, "parse error in '{s}': {s}", .{ resolved_path, p.err_msg orelse "unknown" }); + } + return error.ImportError; + }; + + // Push onto chain before recursing, pop after + try chain.put(resolved_path, {}); + const imp_dir = dirName(resolved_path); + const result = try resolveImports(allocator, io, imp_root, imp_dir, resolved_path, chain, cache, source_map, diagnostics); + _ = chain.remove(resolved_path); + + // Cache + try cache.put(resolved_path, result); + break :blk result; + }; + + if (imp.name) |ns_name| { + try mod.addNamespace(allocator, &decl_list, ns_name, imported_mod, decl.span); + } else { + try mod.mergeFlat(allocator, &decl_list, imported_mod); + } + } + + try mod.finalize(allocator, &decl_list); + return mod; +} diff --git a/src/lexer.zig b/src/lexer.zig new file mode 100644 index 0000000..0c16247 --- /dev/null +++ b/src/lexer.zig @@ -0,0 +1,403 @@ +const std = @import("std"); +const Token = @import("token.zig").Token; +const Tag = @import("token.zig").Tag; +const getKeyword = @import("token.zig").getKeyword; + +pub const Lexer = struct { + source: [:0]const u8, + index: u32, + + pub fn init(source: [:0]const u8) Lexer { + return .{ .source = source, .index = 0 }; + } + + pub fn next(self: *Lexer) Token { + // Skip whitespace and comments + while (true) { + if (self.index >= self.source.len) { + return self.makeToken(.eof, self.index, self.index); + } + const c = self.source[self.index]; + if (c == ' ' or c == '\t' or c == '\n' or c == '\r') { + self.index += 1; + continue; + } + // Line comments + if (c == '/' and self.index + 1 < self.source.len and self.source[self.index + 1] == '/') { + while (self.index < self.source.len and self.source[self.index] != '\n') { + self.index += 1; + } + continue; + } + break; + } + + const start = self.index; + const c = self.source[start]; + + // Integer / float literals + if (isDigit(c)) { + return self.lexNumber(start); + } + + // Identifiers and keywords + if (isIdentStart(c)) { + return self.lexIdentifier(start); + } + + // String literals + if (c == '"') { + return self.lexString(start); + } + + // Directives: #import, #insert, #run + if (c == '#') { + if (self.source.len >= start + 7 and std.mem.eql(u8, self.source[start .. start + 7], "#import") and + (start + 7 >= self.source.len or !isIdentContinue(self.source[start + 7]))) + { + self.index = start + 7; + return self.makeToken(.hash_import, start, self.index); + } + if (self.source.len >= start + 7 and std.mem.eql(u8, self.source[start .. start + 7], "#insert") and + (start + 7 >= self.source.len or !isIdentContinue(self.source[start + 7]))) + { + self.index = start + 7; + return self.makeToken(.hash_insert, start, self.index); + } + if (self.source.len >= start + 4 and std.mem.eql(u8, self.source[start .. start + 4], "#run") and + (start + 4 >= self.source.len or !isIdentContinue(self.source[start + 4]))) + { + self.index = start + 4; + return self.makeToken(.hash_run, start, self.index); + } + if (self.source.len >= start + 8 and std.mem.eql(u8, self.source[start .. start + 8], "#builtin") and + (start + 8 >= self.source.len or !isIdentContinue(self.source[start + 8]))) + { + self.index = start + 8; + return self.makeToken(.hash_builtin, start, self.index); + } + self.index += 1; + return self.makeToken(.invalid, start, self.index); + } + + // Punctuation and operators + self.index += 1; + switch (c) { + ';' => return self.makeToken(.semicolon, start, self.index), + ',' => return self.makeToken(.comma, start, self.index), + '(' => return self.makeToken(.l_paren, start, self.index), + ')' => return self.makeToken(.r_paren, start, self.index), + '{' => return self.makeToken(.l_brace, start, self.index), + '}' => return self.makeToken(.r_brace, start, self.index), + '[' => return self.makeToken(.l_bracket, start, self.index), + ']' => return self.makeToken(.r_bracket, start, self.index), + '.' => { + if (self.peek() == '.') { + self.index += 1; + return self.makeToken(.dot_dot, start, self.index); + } + return self.makeToken(.dot, start, self.index); + }, + '$' => return self.makeToken(.dollar, start, self.index), + ':' => { + if (self.peek() == ':') { + self.index += 1; + return self.makeToken(.colon_colon, start, self.index); + } + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.colon_equal, start, self.index); + } + return self.makeToken(.colon, start, self.index); + }, + '=' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.equal_equal, start, self.index); + } + if (self.peek() == '>') { + self.index += 1; + return self.makeToken(.fat_arrow, start, self.index); + } + return self.makeToken(.equal, start, self.index); + }, + '+' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.plus_equal, start, self.index); + } + return self.makeToken(.plus, start, self.index); + }, + '-' => { + if (self.peek() == '-' and (self.index + 1) < self.source.len and self.source[self.index + 1] == '-') { + self.index += 2; + return self.makeToken(.triple_minus, start, self.index); + } + if (self.peek() == '>') { + self.index += 1; + return self.makeToken(.arrow, start, self.index); + } + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.minus_equal, start, self.index); + } + return self.makeToken(.minus, start, self.index); + }, + '*' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.star_equal, start, self.index); + } + return self.makeToken(.star, start, self.index); + }, + '/' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.slash_equal, start, self.index); + } + return self.makeToken(.slash, start, self.index); + }, + '%' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.percent_equal, start, self.index); + } + return self.makeToken(.percent, start, self.index); + }, + '!' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.bang_equal, start, self.index); + } + return self.makeToken(.bang, start, self.index); + }, + '<' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.less_equal, start, self.index); + } + return self.makeToken(.less, start, self.index); + }, + '>' => { + if (self.peek() == '=') { + self.index += 1; + return self.makeToken(.greater_equal, start, self.index); + } + return self.makeToken(.greater, start, self.index); + }, + else => return self.makeToken(.invalid, start, self.index), + } + } + + fn lexNumber(self: *Lexer, start: u32) Token { + // Advance past the initial digit that was already matched + self.index += 1; + + // Check for hex (0x/0X) or binary (0b/0B) prefix + if (self.source[start] == '0' and self.index < self.source.len) { + const prefix = self.source[self.index]; + if (prefix == 'x' or prefix == 'X') { + self.index += 1; // skip 'x'/'X' + while (self.index < self.source.len and isHexDigit(self.source[self.index])) { + self.index += 1; + } + return self.makeToken(.int_literal, start, self.index); + } + if (prefix == 'b' or prefix == 'B') { + self.index += 1; // skip 'b'/'B' + while (self.index < self.source.len and (self.source[self.index] == '0' or self.source[self.index] == '1')) { + self.index += 1; + } + return self.makeToken(.int_literal, start, self.index); + } + } + + while (self.index < self.source.len and isDigit(self.source[self.index])) { + self.index += 1; + } + // Check for float + if (self.index < self.source.len and self.source[self.index] == '.') { + // Look ahead: must be followed by a digit (not `.identifier`) + if (self.index + 1 < self.source.len and isDigit(self.source[self.index + 1])) { + self.index += 1; // skip '.' + while (self.index < self.source.len and isDigit(self.source[self.index])) { + self.index += 1; + } + return self.makeToken(.float_literal, start, self.index); + } + } + return self.makeToken(.int_literal, start, self.index); + } + + fn lexIdentifier(self: *Lexer, start: u32) Token { + while (self.index < self.source.len and isIdentContinue(self.source[self.index])) { + self.index += 1; + } + const text = self.source[start..self.index]; + if (getKeyword(text)) |kw| { + return self.makeToken(kw, start, self.index); + } + return self.makeToken(.identifier, start, self.index); + } + + fn lexString(self: *Lexer, start: u32) Token { + self.index += 1; // skip opening " + while (self.index < self.source.len) { + const ch = self.source[self.index]; + if (ch == '"') { + self.index += 1; + return self.makeToken(.string_literal, start, self.index); + } + if (ch == '\\') { + self.index += 1; // skip escape + } + self.index += 1; + } + // Unterminated string + return self.makeToken(.invalid, start, self.index); + } + + fn peek(self: *const Lexer) u8 { + if (self.index < self.source.len) { + return self.source[self.index]; + } + return 0; + } + + fn makeToken(_: *const Lexer, tag: Tag, start: u32, end: u32) Token { + return .{ .tag = tag, .loc = .{ .start = start, .end = end } }; + } + + fn isDigit(c: u8) bool { + return c >= '0' and c <= '9'; + } + + fn isIdentStart(c: u8) bool { + return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or c == '_'; + } + + fn isHexDigit(c: u8) bool { + return isDigit(c) or (c >= 'a' and c <= 'f') or (c >= 'A' and c <= 'F'); + } + + fn isIdentContinue(c: u8) bool { + return isIdentStart(c) or isDigit(c); + } +}; + +test "lex minimal main" { + var lex = Lexer.init("main :: () { 42; }"); + const expected = [_]Tag{ .identifier, .colon_colon, .l_paren, .r_paren, .l_brace, .int_literal, .semicolon, .r_brace, .eof }; + for (expected) |exp| { + const tok = lex.next(); + try std.testing.expectEqual(exp, tok.tag); + } +} + +test "lex with comments" { + var lex = Lexer.init("// comment\nmain :: () { 0; }"); + try std.testing.expectEqual(Tag.identifier, lex.next().tag); + try std.testing.expectEqual(Tag.colon_colon, lex.next().tag); +} + +test "lex operators" { + var lex = Lexer.init(":= : :: += -= *= /= -> => == != <= >="); + const expected = [_]Tag{ + .colon_equal, .colon, .colon_colon, .plus_equal, .minus_equal, + .star_equal, .slash_equal, .arrow, .fat_arrow, .equal_equal, + .bang_equal, .less_equal, .greater_equal, + }; + for (expected) |exp| { + try std.testing.expectEqual(exp, lex.next().tag); + } +} + +test "lex float" { + var lex = Lexer.init("0.3 42 0.9"); + try std.testing.expectEqual(Tag.float_literal, lex.next().tag); + try std.testing.expectEqual(Tag.int_literal, lex.next().tag); + try std.testing.expectEqual(Tag.float_literal, lex.next().tag); +} + +test "lex keywords" { + var lex = Lexer.init("if else then true false enum case break return f32 f64 struct"); + const expected = [_]Tag{ + .kw_if, .kw_else, .kw_then, .kw_true, .kw_false, + .kw_enum, .kw_case, .kw_break, .kw_return, .kw_f32, .kw_f64, .kw_struct, + }; + for (expected) |exp| { + try std.testing.expectEqual(exp, lex.next().tag); + } +} + +test "lex type-like identifiers" { + // s32, u8, bool, string are identifiers, not keywords + var lex = Lexer.init("s32 u8 bool string"); + for (0..4) |_| { + try std.testing.expectEqual(Tag.identifier, lex.next().tag); + } +} + +test "lex hash_run" { + var lex = Lexer.init("#run"); + try std.testing.expectEqual(Tag.hash_run, lex.next().tag); + try std.testing.expectEqual(Tag.eof, lex.next().tag); + + // #run followed by identifier + var lex2 = Lexer.init("#run compute(5)"); + try std.testing.expectEqual(Tag.hash_run, lex2.next().tag); + try std.testing.expectEqual(Tag.identifier, lex2.next().tag); + + // #running should not match (identContinue after "run") + var lex3 = Lexer.init("#running"); + try std.testing.expectEqual(Tag.invalid, lex3.next().tag); +} + +test "lex hash_import" { + var lex = Lexer.init("#import \"foo.sx\""); + try std.testing.expectEqual(Tag.hash_import, lex.next().tag); + try std.testing.expectEqual(Tag.string_literal, lex.next().tag); + try std.testing.expectEqual(Tag.eof, lex.next().tag); + + // #importing should not match + var lex2 = Lexer.init("#importing"); + try std.testing.expectEqual(Tag.invalid, lex2.next().tag); +} + +test "lex hash_insert" { + var lex = Lexer.init("#insert #run generate()"); + try std.testing.expectEqual(Tag.hash_insert, lex.next().tag); + try std.testing.expectEqual(Tag.hash_run, lex.next().tag); + try std.testing.expectEqual(Tag.identifier, lex.next().tag); + + // #inserting should not match + var lex2 = Lexer.init("#inserting"); + try std.testing.expectEqual(Tag.invalid, lex2.next().tag); +} + +test "lex string" { + var lex = Lexer.init("\"Hello\""); + const tok = lex.next(); + try std.testing.expectEqual(Tag.string_literal, tok.tag); + try std.testing.expectEqualStrings("\"Hello\"", tok.slice("\"Hello\"")); +} + +test "lex hex literal" { + var lex = Lexer.init("0xFF 0X1A"); + const tok1 = lex.next(); + try std.testing.expectEqual(Tag.int_literal, tok1.tag); + try std.testing.expectEqualStrings("0xFF", tok1.slice("0xFF 0X1A")); + const tok2 = lex.next(); + try std.testing.expectEqual(Tag.int_literal, tok2.tag); + try std.testing.expectEqualStrings("0X1A", tok2.slice("0xFF 0X1A")); +} + +test "lex binary literal" { + var lex = Lexer.init("0b1010 0B110"); + const tok1 = lex.next(); + try std.testing.expectEqual(Tag.int_literal, tok1.tag); + try std.testing.expectEqualStrings("0b1010", tok1.slice("0b1010 0B110")); + const tok2 = lex.next(); + try std.testing.expectEqual(Tag.int_literal, tok2.tag); + try std.testing.expectEqualStrings("0B110", tok2.slice("0b1010 0B110")); +} diff --git a/src/llvm_api.zig b/src/llvm_api.zig new file mode 100644 index 0000000..6a2c701 --- /dev/null +++ b/src/llvm_api.zig @@ -0,0 +1,54 @@ +pub const c = @cImport({ + @cInclude("llvm-c/Core.h"); + @cInclude("llvm-c/Analysis.h"); + @cInclude("llvm-c/BitWriter.h"); + @cInclude("llvm-c/Target.h"); + @cInclude("llvm-c/TargetMachine.h"); + @cInclude("llvm-c/LLJIT.h"); + @cInclude("llvm-c/Orc.h"); + @cInclude("llvm-c/Error.h"); +}); + +extern fn sx_llvm_init_all_targets() void; +extern fn sx_llvm_init_native_target() void; + +pub fn initAllTargets() void { + sx_llvm_init_all_targets(); +} + +pub fn initNativeTarget() void { + sx_llvm_init_native_target(); +} + +// Type aliases for ergonomics +pub const Context = c.LLVMContextRef; +pub const Module = c.LLVMModuleRef; +pub const Builder = c.LLVMBuilderRef; +pub const Value = c.LLVMValueRef; +pub const Type = c.LLVMTypeRef; +pub const BasicBlock = c.LLVMBasicBlockRef; +pub const TargetMachine = c.LLVMTargetMachineRef; + +pub fn createContext() Context { + return c.LLVMContextCreate(); +} + +pub fn disposeContext(ctx: Context) void { + c.LLVMContextDispose(ctx); +} + +pub fn moduleCreateWithName(name: [*:0]const u8) Module { + return c.LLVMModuleCreateWithNameInContext(name, c.LLVMGetGlobalContext()); +} + +pub fn disposeModule(module: Module) void { + c.LLVMDisposeModule(module); +} + +pub fn createBuilderInContext(ctx: Context) Builder { + return c.LLVMCreateBuilderInContext(ctx); +} + +pub fn disposeBuilder(builder: Builder) void { + c.LLVMDisposeBuilder(builder); +} diff --git a/src/lsp/document.zig b/src/lsp/document.zig new file mode 100644 index 0000000..c250043 --- /dev/null +++ b/src/lsp/document.zig @@ -0,0 +1,48 @@ +const std = @import("std"); + +pub const DocumentStore = struct { + documents: std.StringHashMap(Document), + allocator: std.mem.Allocator, + + pub const Document = struct { + uri: []const u8, + text: []const u8, + version: i64, + }; + + pub fn init(allocator: std.mem.Allocator) DocumentStore { + return .{ + .documents = std.StringHashMap(Document).init(allocator), + .allocator = allocator, + }; + } + + pub fn open(self: *DocumentStore, uri: []const u8, text: []const u8, version: i64) !void { + const uri_copy = try self.allocator.dupe(u8, uri); + const text_copy = try self.allocator.dupe(u8, text); + try self.documents.put(uri_copy, .{ + .uri = uri_copy, + .text = text_copy, + .version = version, + }); + } + + pub fn update(self: *DocumentStore, uri: []const u8, text: []const u8, version: i64) !void { + if (self.documents.getPtr(uri)) |doc| { + self.allocator.free(doc.text); + doc.text = try self.allocator.dupe(u8, text); + doc.version = version; + } + } + + pub fn close(self: *DocumentStore, uri: []const u8) void { + if (self.documents.fetchRemove(uri)) |kv| { + self.allocator.free(kv.value.text); + self.allocator.free(kv.key); + } + } + + pub fn get(self: *const DocumentStore, uri: []const u8) ?*const Document { + return self.documents.getPtr(uri); + } +}; diff --git a/src/lsp/server.zig b/src/lsp/server.zig new file mode 100644 index 0000000..5ac347a --- /dev/null +++ b/src/lsp/server.zig @@ -0,0 +1,1776 @@ +const std = @import("std"); +const sx = struct { + pub const ast = @import("../ast.zig"); + pub const parser = @import("../parser.zig"); + pub const lexer = @import("../lexer.zig"); + pub const token = @import("../token.zig"); + pub const types = @import("../types.zig"); + pub const sema = @import("../sema.zig"); + pub const errors = @import("../errors.zig"); + pub const imports = @import("../imports.zig"); + pub const core = @import("../core.zig"); +}; +const lsp = @import("types.zig"); +const DocumentStore = @import("document.zig").DocumentStore; +const Transport = @import("transport.zig").Transport; +const Analyzer = sx.sema.Analyzer; +const SemaResult = sx.sema.SemaResult; + +pub const Server = struct { + allocator: std.mem.Allocator, + documents: DocumentStore, + transport: *Transport, + io: std.Io, + shutdown_requested: bool = false, + /// Cached sema results per document URI. + sema_cache: std.StringHashMap(DocumentAnalysis), + + const DocumentAnalysis = struct { + source: [:0]const u8, + root: *sx.ast.Node, + sema: SemaResult, + /// Namespace name → resolved file path (for namespace member lookups). + import_map: std.StringHashMap([]const u8), + /// Resolved import file path → source content (populated by resolveImports). + import_sources: std.StringHashMap([:0]const u8), + }; + + pub fn init(allocator: std.mem.Allocator, transport: *Transport, io: std.Io) Server { + return .{ + .allocator = allocator, + .documents = DocumentStore.init(allocator), + .transport = transport, + .io = io, + .sema_cache = std.StringHashMap(DocumentAnalysis).init(allocator), + }; + } + + pub fn handleMessage(self: *Server, raw: []const u8) bool { + const parsed = std.json.parseFromSlice(std.json.Value, self.allocator, raw, .{}) catch { + return true; + }; + const root = parsed.value; + + const method = jsonStr(jsonGet(root, "method") orelse return true) orelse return true; + + const id = jsonGet(root, "id"); + const params = jsonGet(root, "params"); + + if (std.mem.eql(u8, method, "initialize")) { + self.handleInitialize(id) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "initialized")) { + // Nothing to do + } else if (std.mem.eql(u8, method, "shutdown")) { + self.shutdown_requested = true; + if (id) |req_id| { + const id_json = lsp.valueToJson(self.allocator, req_id) catch return true; + const resp = lsp.jsonRpcResponse(self.allocator, id_json, "null") catch return true; + self.transport.writeMessage(resp) catch {}; + } + } else if (std.mem.eql(u8, method, "exit")) { + return false; + } else if (std.mem.eql(u8, method, "textDocument/didOpen")) { + if (params) |p| self.handleDidOpen(p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/didChange")) { + if (params) |p| self.handleDidChange(p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/didClose")) { + if (params) |p| self.handleDidClose(p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/definition")) { + if (params) |p| self.handleDefinition(id, p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/hover")) { + if (params) |p| self.handleHover(id, p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/documentSymbol")) { + if (params) |p| self.handleDocumentSymbol(id, p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/completion")) { + if (params) |p| self.handleCompletion(id, p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/signatureHelp")) { + if (params) |p| self.handleSignatureHelp(id, p) catch |e| self.logError(method, e); + } else if (std.mem.eql(u8, method, "textDocument/semanticTokens/full")) { + if (params) |p| self.handleSemanticTokens(id, p) catch |e| self.logError(method, e); + } + + return true; + } + + fn logError(self: *Server, method: []const u8, err: anyerror) void { + const stderr = std.Io.File.stderr(); + var buf: [256]u8 = undefined; + const msg = std.fmt.bufPrint(&buf, "lsp: {s} failed: {s}\n", .{ method, @errorName(err) }) catch return; + stderr.writeStreamingAll(self.io, msg) catch {}; + } + + fn handleInitialize(self: *Server, id: ?std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + const result_json = try lsp.initializeResultJson(self.allocator); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, result_json); + try self.transport.writeMessage(resp); + } + + fn handleDidOpen(self: *Server, params: std.json.Value) !void { + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + const text = jsonStr(jsonGet(td, "text") orelse return) orelse return; + const version = jsonInt(jsonGet(td, "version") orelse return) orelse return; + + try self.documents.open(uri, text, version); + try self.analyzeAndPublish(uri, text); + } + + fn handleDidChange(self: *Server, params: std.json.Value) !void { + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + const version = jsonInt(jsonGet(td, "version") orelse return) orelse return; + + const changes_arr = jsonArr(jsonGet(params, "contentChanges") orelse return) orelse return; + if (changes_arr.len == 0) return; + + const last = changes_arr[changes_arr.len - 1]; + const text = jsonStr(jsonGet(last, "text") orelse return) orelse return; + + try self.documents.update(uri, text, version); + try self.analyzeAndPublish(uri, text); + } + + fn handleDidClose(self: *Server, params: std.json.Value) !void { + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + + self.documents.close(uri); + _ = self.sema_cache.remove(uri); + try self.sendDiagnostics(uri, &.{}); + } + + fn handleDefinition(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + const position = jsonGet(params, "position") orelse return; + const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; + const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + + const analysis = self.sema_cache.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + const offset = positionToOffset(analysis.source, line, character) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + // Check if cursor is on a reference → jump to definition + if (sx.sema.findReferenceAtOffset(analysis.sema.references, offset)) |ref_idx| { + const ref = analysis.sema.references[ref_idx]; + if (ref.symbol_index < analysis.sema.symbols.len) { + const sym = analysis.sema.symbols[ref.symbol_index]; + if (try self.sendSymbolLocation(id_json, uri, analysis, sym)) return; + } + } + + // Check if cursor is on a symbol definition name + if (findSymbolNameAtOffset(analysis.sema.symbols, analysis.source, offset)) |sym_idx| { + const sym = analysis.sema.symbols[sym_idx]; + if (try self.sendSymbolLocation(id_json, uri, analysis, sym)) return; + } + + // Check if cursor is on a qualified name (e.g. "std.print") + if (extractQualifiedName(analysis.source, offset)) |qn| { + if (try self.resolveImportedLocation(id_json, analysis, qn.ns, qn.member)) |_| return; + } + + // Check if cursor is on an #import "path" string → open the file + if (findImportPathAtOffset(analysis.source, offset)) |import_path| { + const file_path = uriToFilePath(uri) orelse ""; + const base_dir = sx.imports.dirName(file_path); + const resolved = if (std.mem.eql(u8, base_dir, ".")) + import_path + else + try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ base_dir, import_path }); + const target_uri = try std.fmt.allocPrint(self.allocator, "file://{s}", .{resolved}); + const range = lsp.Range{ + .start = .{ .line = 0, .character = 0 }, + .end = .{ .line = 0, .character = 0 }, + }; + const loc_json = try lsp.locationJson(self.allocator, target_uri, range); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, loc_json); + try self.transport.writeMessage(resp); + return; + } + + // Fallback: identifier inside string interpolation (not in sema) + if (extractIdentAtOffset(analysis.source, offset)) |name| { + const name_start = @as(u32, @intCast(@intFromPtr(name.ptr) - @intFromPtr(analysis.source.ptr))); + const is_qualified = name_start > 0 and analysis.source[name_start - 1] == '.'; + if (!is_qualified) { + if (findSymbolByName(analysis.sema.symbols, name)) |si| { + const sym = analysis.sema.symbols[si]; + if (try self.sendSymbolLocation(id_json, uri, analysis, sym)) return; + } + } + } + + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + } + + fn handleHover(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + const position = jsonGet(params, "position") orelse return; + const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; + const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + + const analysis = self.sema_cache.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + const offset = positionToOffset(analysis.source, line, character) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + // Check if cursor is on a qualified name (e.g. std.print) — source-based, no AST + if (extractQualifiedName(analysis.source, offset)) |qn| { + // Namespace member hover + if (analysis.import_map.get(qn.ns)) |import_path| { + if (try self.formatNamespaceMemberHover(analysis, qn.ns, qn.member, import_path)) |hover_text| { + const hover_json = try lsp.hoverJson(self.allocator, hover_text); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); + try self.transport.writeMessage(resp); + return; + } + } + // Struct field hover (e.g. point.x) + if (try self.formatStructFieldHover(analysis, qn.ns, qn.member)) |hover_text| { + const hover_json = try lsp.hoverJson(self.allocator, hover_text); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); + try self.transport.writeMessage(resp); + return; + } + } + + // Check if cursor is on an enum variant or other AST node + if (sx.sema.findNodeAtOffset(analysis.root, offset)) |node| { + // Enum variant hover (e.g. .red) + if (node.data == .enum_literal) { + if (try self.formatEnumVariantHover(analysis, node.data.enum_literal.name)) |hover_text| { + const hover_json = try lsp.hoverJson(self.allocator, hover_text); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); + try self.transport.writeMessage(resp); + return; + } + } + } + + // Find symbol via reference or definition name + var sym_idx: ?usize = null; + if (sx.sema.findReferenceAtOffset(analysis.sema.references, offset)) |ref_idx| { + const si = analysis.sema.references[ref_idx].symbol_index; + if (si < analysis.sema.symbols.len) sym_idx = si; + } else { + sym_idx = findSymbolNameAtOffset(analysis.sema.symbols, analysis.source, offset); + } + + // Fallback: identifier inside string interpolation (not in sema) + if (sym_idx == null) { + if (extractIdentAtOffset(analysis.source, offset)) |name| { + // If preceded by '.', this is a qualified access — don't match bare name + const name_start = @as(u32, @intCast(@intFromPtr(name.ptr) - @intFromPtr(analysis.source.ptr))); + const is_qualified = name_start > 0 and analysis.source[name_start - 1] == '.'; + if (!is_qualified) { + sym_idx = findSymbolByName(analysis.sema.symbols, name); + } + } + } + + if (sym_idx) |si| { + const sym = analysis.sema.symbols[si]; + const resolved = resolveSource(analysis, sym); + const source_for_hover = if (resolved) |r| r.source else analysis.source; + const hover_text = try formatSymbolHover(self.allocator, sym, analysis.root, source_for_hover); + const hover_json = try lsp.hoverJson(self.allocator, hover_text); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); + try self.transport.writeMessage(resp); + return; + } + + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + } + + fn handleDocumentSymbol(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + + const analysis = self.sema_cache.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "[]"); + try self.transport.writeMessage(resp); + return; + }; + + var doc_symbols = std.ArrayList(lsp.DocumentSymbol).empty; + for (analysis.sema.symbols) |sym| { + // Only top-level symbols (scope_depth == 0) + if (sym.scope_depth != 0) continue; + // Skip symbols from imported files (their spans don't map to this source) + if (!isSymbolInMainSource(analysis.source, sym)) continue; + + const kind: u32 = switch (sym.kind) { + .function => @intFromEnum(lsp.SymbolKindLsp.Function), + .variable => @intFromEnum(lsp.SymbolKindLsp.Variable), + .constant => @intFromEnum(lsp.SymbolKindLsp.Constant), + .enum_type => @intFromEnum(lsp.SymbolKindLsp.Enum), + .struct_type => @intFromEnum(lsp.SymbolKindLsp.Struct), + .type_alias => @intFromEnum(lsp.SymbolKindLsp.Class), + .param => @intFromEnum(lsp.SymbolKindLsp.Variable), + .namespace => @intFromEnum(lsp.SymbolKindLsp.Namespace), + }; + + const range = spanToRange(analysis.source, sym.def_span); + try doc_symbols.append(self.allocator, .{ + .name = sym.name, + .kind = kind, + .range = range, + .selection_range = range, + }); + } + + const symbols_json = try lsp.documentSymbolsJson(self.allocator, doc_symbols.items); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, symbols_json); + try self.transport.writeMessage(resp); + } + + fn handleCompletion(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + const position = jsonGet(params, "position") orelse return; + const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; + const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + + // Check if cursor is right after a dot — if so, do dot-completion + if (self.documents.get(uri)) |doc| { + if (positionToOffset(doc.text, line, character)) |off| { + if (off > 0 and doc.text[off - 1] == '.') { + try self.handleDotCompletion(id_json, uri, doc.text, off); + return; + } + } + } + + // Regular completion: all in-scope symbols + keywords + const analysis = self.sema_cache.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "[]"); + try self.transport.writeMessage(resp); + return; + }; + + var items = std.ArrayList(lsp.CompletionItem).empty; + + for (analysis.sema.symbols) |sym| { + const kind: u32 = switch (sym.kind) { + .function => @intFromEnum(lsp.CompletionItemKind.Function), + .variable => @intFromEnum(lsp.CompletionItemKind.Variable), + .constant => @intFromEnum(lsp.CompletionItemKind.Constant), + .enum_type => @intFromEnum(lsp.CompletionItemKind.Enum), + .struct_type => @intFromEnum(lsp.CompletionItemKind.Struct), + .type_alias => @intFromEnum(lsp.CompletionItemKind.Class), + .param => @intFromEnum(lsp.CompletionItemKind.Variable), + .namespace => @intFromEnum(lsp.CompletionItemKind.Module), + }; + + const detail = if (sym.ty) |ty| try ty.displayName(self.allocator) else null; + + try items.append(self.allocator, .{ + .label = sym.name, + .kind = kind, + .detail = detail, + }); + } + + const keywords = [_][]const u8{ + "if", "else", "then", "return", "defer", + "case", "break", "enum", "struct", "true", + "false", "xx", "while", "continue", + "and", "or", "union", + }; + + const builtins = [_]struct { label: []const u8, detail: []const u8 }{ + .{ .label = "type_of", .detail = "(val: $T) -> Type" }, + .{ .label = "type_name", .detail = "($T: Type) -> string" }, + .{ .label = "field_count", .detail = "($T: Type) -> s32" }, + .{ .label = "field_name", .detail = "($T: Type, idx: s32) -> string" }, + .{ .label = "field_value", .detail = "(s: $T, idx: s32) -> Any" }, + .{ .label = "size_of", .detail = "($T: Type) -> s32" }, + .{ .label = "cast", .detail = "(Type) expr — prefix type cast" }, + .{ .label = "alloc", .detail = "(size: s32) -> string" }, + .{ .label = "sqrt", .detail = "(x: $T) -> T" }, + }; + for (&keywords) |kw| { + try items.append(self.allocator, .{ + .label = kw, + .kind = @intFromEnum(lsp.CompletionItemKind.Keyword), + }); + } + for (&builtins) |b| { + try items.append(self.allocator, .{ + .label = b.label, + .kind = @intFromEnum(lsp.CompletionItemKind.Function), + .detail = b.detail, + }); + } + + const items_json = try lsp.completionItemsJson(self.allocator, items.items); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, items_json); + try self.transport.writeMessage(resp); + } + + fn handleDotCompletion(self: *Server, id_json: []const u8, uri: []const u8, text: []const u8, cursor_offset: u32) !void { + var items = std.ArrayList(lsp.CompletionItem).empty; + + if (extractDotPrefix(text, cursor_offset)) |prefix| { + if (self.sema_cache.get(uri)) |analysis| { + // Check if prefix is a namespace — offer its inner declarations + if (!try self.collectNamespaceCompletions(&items, analysis, prefix)) { + // Otherwise look up prefix as a struct/enum type name in sema symbols + try self.collectMemberCompletions(&items, analysis, prefix); + } + } + } + + const items_json = try lsp.completionItemsJson(self.allocator, items.items); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, items_json); + try self.transport.writeMessage(resp); + } + + fn collectMemberCompletions(self: *Server, items: *std.ArrayList(lsp.CompletionItem), analysis: DocumentAnalysis, name: []const u8) !void { + for (analysis.sema.symbols) |sym| { + if (!std.mem.eql(u8, sym.name, name)) continue; + + if (sym.kind == .struct_type) { + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .struct_decl) { + const sd = node.data.struct_decl; + for (sd.field_names, 0..) |field_name, fi| { + const detail: ?[]const u8 = if (fi < sd.field_types.len) blk: { + const ft = sd.field_types[fi]; + break :blk if (ft.data == .type_expr) ft.data.type_expr.name else null; + } else null; + + try items.append(self.allocator, .{ + .label = field_name, + .kind = @intFromEnum(lsp.CompletionItemKind.Field), + .detail = detail, + }); + } + } + } + } else if (sym.kind == .enum_type) { + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .enum_decl) { + const ed = node.data.enum_decl; + for (ed.variants) |variant| { + try items.append(self.allocator, .{ + .label = variant, + .kind = @intFromEnum(lsp.CompletionItemKind.EnumMember), + }); + } + } + } + } + break; // Found the symbol + } + } + + fn collectNamespaceCompletions(self: *Server, items: *std.ArrayList(lsp.CompletionItem), analysis: DocumentAnalysis, name: []const u8) !bool { + // Find a namespace symbol matching the prefix + for (analysis.sema.symbols) |sym| { + if (sym.kind != .namespace or !std.mem.eql(u8, sym.name, name)) continue; + + // Find the namespace_decl AST node + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .namespace_decl) { + for (node.data.namespace_decl.decls) |decl| { + switch (decl.data) { + .fn_decl => |fd| { + var detail_buf = std.ArrayList(u8).empty; + try detail_buf.append(self.allocator, '('); + for (fd.params, 0..) |param, pi| { + if (pi > 0) try detail_buf.appendSlice(self.allocator, ", "); + try detail_buf.appendSlice(self.allocator, param.name); + try detail_buf.appendSlice(self.allocator, ": "); + if (param.type_expr.data == .type_expr) { + try detail_buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name); + } else { + try detail_buf.appendSlice(self.allocator, "?"); + } + } + try detail_buf.append(self.allocator, ')'); + if (fd.return_type) |rt| { + try detail_buf.appendSlice(self.allocator, " -> "); + if (rt.data == .type_expr) { + try detail_buf.appendSlice(self.allocator, rt.data.type_expr.name); + } + } + try items.append(self.allocator, .{ + .label = fd.name, + .kind = @intFromEnum(lsp.CompletionItemKind.Function), + .detail = detail_buf.items, + }); + }, + .const_decl => |cd| { + const kind: u32 = if (cd.value.data == .lambda) + @intFromEnum(lsp.CompletionItemKind.Function) + else + @intFromEnum(lsp.CompletionItemKind.Constant); + try items.append(self.allocator, .{ + .label = cd.name, + .kind = kind, + }); + }, + .enum_decl => |ed| { + try items.append(self.allocator, .{ + .label = ed.name, + .kind = @intFromEnum(lsp.CompletionItemKind.Enum), + }); + }, + .struct_decl => |sd| { + try items.append(self.allocator, .{ + .label = sd.name, + .kind = @intFromEnum(lsp.CompletionItemKind.Struct), + }); + }, + .union_decl => |ud| { + try items.append(self.allocator, .{ + .label = ud.name, + .kind = @intFromEnum(lsp.CompletionItemKind.Enum), + }); + }, + else => {}, + } + } + return true; + } + } + break; + } + return false; + } + + fn handleSignatureHelp(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + const position = jsonGet(params, "position") orelse return; + const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; + const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + + const doc = self.documents.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + const offset = positionToOffset(doc.text, line, character) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + const ctx = findCallContext(doc.text, offset) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + // Built-in function signatures + const builtin_sigs = [_]struct { name: []const u8, label: []const u8, params: []const []const u8 }{ + .{ .name = "type_of", .label = "type_of(val: $T) -> Type", .params = &.{"val: $T"} }, + .{ .name = "type_name", .label = "type_name($T: Type) -> string", .params = &.{"$T: Type"} }, + .{ .name = "field_count", .label = "field_count($T: Type) -> s32", .params = &.{"$T: Type"} }, + .{ .name = "field_name", .label = "field_name($T: Type, idx: s32) -> string", .params = &.{ "$T: Type", "idx: s32" } }, + .{ .name = "field_value", .label = "field_value(s: $T, idx: s32) -> Any", .params = &.{ "s: $T", "idx: s32" } }, + .{ .name = "size_of", .label = "size_of($T: Type) -> s32", .params = &.{"$T: Type"} }, + .{ .name = "cast", .label = "cast(Type) expr", .params = &.{"Type"} }, + .{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} }, + .{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} }, + .{ .name = "print", .label = "print(fmt: string, args: ..Any)", .params = &.{ "fmt: string", "args: ..Any" } }, + .{ .name = "write", .label = "write(str: string) -> void", .params = &.{"str: string"} }, + }; + for (&builtin_sigs) |b| { + const matches = std.mem.eql(u8, ctx.name, b.name) or + (std.mem.startsWith(u8, ctx.name, "std.") and std.mem.eql(u8, ctx.name[4..], b.name)); + if (matches) { + const sig_json = try lsp.signatureHelpJson(self.allocator, b.label, b.params, ctx.active_param); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, sig_json); + try self.transport.writeMessage(resp); + return; + } + } + + // Look up function in sema cache + const analysis = self.sema_cache.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + return; + }; + + // Try to find the function — either at top level or inside a namespace + const fn_node = findFnDeclByName(analysis, ctx.name); + + if (fn_node) |fd| { + var label_buf = std.ArrayList(u8).empty; + try label_buf.appendSlice(self.allocator, fd.name); + try label_buf.append(self.allocator, '('); + + var param_labels = std.ArrayList([]const u8).empty; + for (fd.params, 0..) |param, pi| { + if (pi > 0) try label_buf.appendSlice(self.allocator, ", "); + const param_start = label_buf.items.len; + try label_buf.appendSlice(self.allocator, param.name); + try label_buf.appendSlice(self.allocator, ": "); + if (param.type_expr.data == .type_expr) { + try label_buf.appendSlice(self.allocator, param.type_expr.data.type_expr.name); + } else { + try label_buf.appendSlice(self.allocator, "?"); + } + const param_label = try self.allocator.dupe(u8, label_buf.items[param_start..]); + try param_labels.append(self.allocator, param_label); + } + try label_buf.append(self.allocator, ')'); + + if (fd.return_type) |rt| { + try label_buf.appendSlice(self.allocator, " -> "); + if (rt.data == .type_expr) { + try label_buf.appendSlice(self.allocator, rt.data.type_expr.name); + } + } + + const sig_json = try lsp.signatureHelpJson(self.allocator, label_buf.items, param_labels.items, ctx.active_param); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, sig_json); + try self.transport.writeMessage(resp); + return; + } + + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); + try self.transport.writeMessage(resp); + } + + fn handleSemanticTokens(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { + const req_id = id orelse return; + const id_json = try lsp.valueToJson(self.allocator, req_id); + + const td = jsonGet(params, "textDocument") orelse return; + const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; + + const analysis = self.sema_cache.get(uri) orelse { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "{\"data\":[]}"); + try self.transport.writeMessage(resp); + return; + }; + + var data = std.ArrayList(u32).empty; + var prev_line: u32 = 0; + var prev_char: u32 = 0; + + // Re-lex the cached source to get all tokens + var lexer = sx.lexer.Lexer.init(analysis.source); + while (true) { + const tok = lexer.next(); + if (tok.tag == .eof) break; + + if (tok.tag == .string_literal) { + try emitStringParts(&data, self.allocator, analysis.source, tok.loc.start, tok.loc.end, &prev_line, &prev_char); + continue; + } + + const token_type = classifyToken(tok, analysis) orelse continue; + try emitToken(&data, self.allocator, analysis.source, tok.loc.start, tok.loc.end, token_type, &prev_line, &prev_char); + } + + const result_json = try lsp.semanticTokensJson(self.allocator, data.items); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, result_json); + try self.transport.writeMessage(resp); + } + + fn classifyToken(tok: sx.token.Token, analysis: DocumentAnalysis) ?u32 { + const ST = lsp.SemanticTokenType; + return switch (tok.tag) { + // Keywords + .kw_if, + .kw_else, + .kw_then, + .kw_true, + .kw_false, + .kw_enum, + .kw_case, + .kw_break, + .kw_continue, + .kw_while, + .kw_for, + .kw_return, + .kw_defer, + .kw_struct, + .kw_union, + .kw_xx, + .kw_and, + .kw_or, + .hash_run, + .hash_import, + .hash_insert, + .hash_builtin, + => ST.keyword, + + // Type keywords + .kw_f32, .kw_f64, .kw_Type => ST.type_, + + // Literals + .int_literal, .float_literal => ST.number, + .string_literal => null, // let TextMate grammar handle strings (interpolation) + + // Operators + .plus, + .minus, + .star, + .slash, + .equal, + .equal_equal, + .bang, + .bang_equal, + .less, + .less_equal, + .greater, + .greater_equal, + .plus_equal, + .minus_equal, + .star_equal, + .slash_equal, + .percent, + .percent_equal, + .arrow, + .fat_arrow, + .colon_colon, + .colon_equal, + .triple_minus, + => ST.operator_, + + // Identifiers — need sema lookup + .identifier => classifyIdentifier(tok, analysis), + + // Punctuation — no semantic coloring + .colon, + .semicolon, + .comma, + .dot, + .dot_dot, + .dollar, + .l_paren, + .r_paren, + .l_brace, + .r_brace, + .l_bracket, + .r_bracket, + .eof, + .invalid, + => null, + }; + } + + fn classifyIdentifier(tok: sx.token.Token, analysis: DocumentAnalysis) ?u32 { + const ST = lsp.SemanticTokenType; + const offset = tok.loc.start; + if (tok.loc.start >= analysis.source.len or tok.loc.end > analysis.source.len or tok.loc.start >= tok.loc.end) return null; + const name = analysis.source[tok.loc.start..tok.loc.end]; + + // Check if it's a reference to a known symbol + if (sx.sema.findReferenceAtOffset(analysis.sema.references, offset)) |ref_idx| { + const si = analysis.sema.references[ref_idx].symbol_index; + if (si >= analysis.sema.symbols.len) return null; + const sym = analysis.sema.symbols[si]; + return symbolKindToTokenType(sym.kind); + } + + // Check if it's a symbol definition by matching exact start position and name + for (analysis.sema.symbols) |sym| { + if (sym.def_span.start == offset and std.mem.eql(u8, sym.name, name)) { + return symbolKindToTokenType(sym.kind); + } + } + + // Check if it's a built-in type name (s32, u8, bool, string, etc.) + if (sx.types.Type.fromName(name) != null) { + return ST.type_; + } + + return null; + } + + fn symbolKindToTokenType(kind: sx.sema.SymbolKind) u32 { + const ST = lsp.SemanticTokenType; + return switch (kind) { + .function => ST.function, + .variable => ST.variable, + .constant => ST.variable, + .param => ST.parameter, + .enum_type => ST.enum_, + .struct_type => ST.struct_, + .type_alias => ST.type_, + .namespace => ST.namespace, + }; + } + + fn emitToken( + data: *std.ArrayList(u32), + allocator: std.mem.Allocator, + source: [:0]const u8, + start: u32, + end: u32, + token_type: u32, + prev_line: *u32, + prev_char: *u32, + ) !void { + if (start >= source.len or end > source.len or start >= end) return; + const loc = sx.errors.SourceLoc.compute(source, start); + if (loc.line == 0 or loc.col == 0) return; + const line = loc.line - 1; + const col = loc.col - 1; + const length = end - start; + + if (line < prev_line.*) return; // out-of-order token, skip + const delta_line = line - prev_line.*; + const delta_char = if (delta_line == 0) (if (col >= prev_char.*) col - prev_char.* else return) else col; + + try data.append(allocator, delta_line); + try data.append(allocator, delta_char); + try data.append(allocator, length); + try data.append(allocator, token_type); + try data.append(allocator, 0); + + prev_line.* = line; + prev_char.* = col; + } + + fn emitStringParts( + data: *std.ArrayList(u32), + allocator: std.mem.Allocator, + source: [:0]const u8, + tok_start: u32, + tok_end: u32, + prev_line: *u32, + prev_char: *u32, + ) !void { + const ST = lsp.SemanticTokenType; + var pos = tok_start; + var seg_start = tok_start; + var in_interp = false; + + while (pos < tok_end) : (pos += 1) { + if (in_interp) { + if (source[pos] == '}') { + in_interp = false; + seg_start = pos + 1; + } + } else { + if (source[pos] == '\\' and pos + 1 < tok_end) { + pos += 1; // skip escaped char + } else if (source[pos] == '{') { + if (pos > seg_start) { + try emitToken(data, allocator, source, seg_start, pos, ST.string_, prev_line, prev_char); + } + in_interp = true; + } + } + } + + // Emit remaining segment (after last interpolation, or the whole string if none) + if (!in_interp and seg_start < tok_end) { + try emitToken(data, allocator, source, seg_start, tok_end, ST.string_, prev_line, prev_char); + } + } + + fn analyzeAndPublish(self: *Server, uri: []const u8, text: []const u8) !void { + const source = try self.allocator.dupeZ(u8, text); + const file_path = uriToFilePath(uri) orelse ""; + + var comp = sx.core.Compilation.init(self.allocator, self.io, file_path, source); + defer comp.deinit(); + + comp.parse() catch { + try self.sendDiagnostics(uri, diagListToLsp(self.allocator, source, &comp.diagnostics)); + return; + }; + comp.resolveImports() catch {}; + comp.analyze() catch {}; + + // Only run codegen when earlier stages produced no errors. + // Codegen has unreachable/force-unwrap paths that panic on broken ASTs. + const has_errors = for (comp.diagnostics.items.items) |d| { + if (d.level == .err) break true; + } else false; + if (!has_errors) { + comp.generateCode() catch {}; + } + + // Build import_map (namespace name → resolved file path) for go-to-definition + var import_map = std.StringHashMap([]const u8).init(self.allocator); + const resolved_root = comp.resolved_root orelse comp.root orelse { + try self.sendDiagnostics(uri, diagListToLsp(self.allocator, source, &comp.diagnostics)); + return; + }; + if (resolved_root.data == .root) { + for (resolved_root.data.root.decls) |decl| { + if (decl.data == .namespace_decl) { + const ns = decl.data.namespace_decl; + var it = comp.import_sources.keyIterator(); + while (it.next()) |path| { + if (ns.decls.len > 0) { + const first_decl_name = ns.decls[0].data.declName() orelse continue; + const imp_src = comp.import_sources.get(path.*) orelse continue; + const start = ns.decls[0].span.start; + const end = start + @as(u32, @intCast(first_decl_name.len)); + if (end <= imp_src.len and std.mem.eql(u8, imp_src[start..end], first_decl_name)) { + import_map.put(ns.name, path.*) catch {}; + break; + } + } + } + } + } + } + + // Cache the analysis result + const uri_owned = try self.allocator.dupe(u8, uri); + try self.sema_cache.put(uri_owned, .{ + .source = source, + .root = resolved_root, + .sema = comp.sema_result orelse SemaResult{ + .symbols = &.{}, + .references = &.{}, + .diagnostics = &.{}, + .fn_signatures = std.StringHashMap(sx.sema.FnSignature).init(self.allocator), + .struct_types = std.StringHashMap(sx.sema.StructTypeInfo).init(self.allocator), + .enum_types = std.StringHashMap([]const []const u8).init(self.allocator), + .type_aliases = std.StringHashMap([]const u8).init(self.allocator), + .type_map = sx.sema.TypeMap.init(self.allocator), + }, + .import_map = import_map, + .import_sources = comp.import_sources, + }); + + // Publish all diagnostics (parse + import + sema + codegen) + try self.sendDiagnostics(uri, diagListToLsp(self.allocator, source, &comp.diagnostics)); + } + + fn sendDiagnostics(self: *Server, uri: []const u8, diagnostics: []const lsp.Diagnostic) !void { + const params_json = try lsp.publishDiagnosticsJson(self.allocator, uri, diagnostics); + const body = try lsp.jsonRpcNotification(self.allocator, "textDocument/publishDiagnostics", params_json); + try self.transport.writeMessage(body); + } + + fn diagListToLsp(allocator: std.mem.Allocator, source: [:0]const u8, diag_list: *const sx.errors.DiagnosticList) []const lsp.Diagnostic { + var result = std.ArrayList(lsp.Diagnostic).empty; + for (diag_list.items.items) |d| { + const range = if (d.span) |span| spanToRange(source, span) else lsp.Range{ + .start = .{ .line = 0, .character = 0 }, + .end = .{ .line = 0, .character = 1 }, + }; + const severity: u32 = switch (d.level) { + .err => 1, + .warn => 2, + .note => 3, + }; + result.append(allocator, .{ + .range = range, + .severity = severity, + .message = d.message, + }) catch continue; + } + return result.items; + } + + // ---- Safe JSON accessors (avoid panicking on wrong union tag) ---- + + fn jsonGet(val: std.json.Value, key: []const u8) ?std.json.Value { + return switch (val) { + .object => |obj| obj.get(key), + else => null, + }; + } + + fn jsonStr(val: std.json.Value) ?[]const u8 { + return switch (val) { + .string => |s| s, + else => null, + }; + } + + fn jsonInt(val: std.json.Value) ?i64 { + return switch (val) { + .integer => |i| i, + else => null, + }; + } + + fn jsonArr(val: std.json.Value) ?[]std.json.Value { + return switch (val) { + .array => |a| a.items, + else => null, + }; + } + + // ---- Helpers ---- + + /// Find a symbol whose name text starts at `offset` in the source. + /// Unlike findSymbolAtOffset (span containment), this only matches + /// when the cursor is actually on the symbol's name characters. + fn findSymbolNameAtOffset(symbols: []const sx.sema.Symbol, source: [:0]const u8, offset: u32) ?usize { + for (symbols, 0..) |sym, i| { + const name_start = sym.def_span.start; + const name_end = name_start + @as(u32, @intCast(sym.name.len)); + if (offset >= name_start and offset < name_end and name_end <= source.len) { + if (std.mem.eql(u8, source[name_start..name_end], sym.name)) { + return i; + } + } + } + return null; + } + + fn positionToOffset(source: []const u8, line: u32, character: u32) ?u32 { + var cur_line: u32 = 0; + var cur_col: u32 = 0; + for (source, 0..) |ch, i| { + if (cur_line == line and cur_col == character) { + return @intCast(i); + } + if (ch == '\n') { + if (cur_line == line) return @intCast(i); // past end of line + cur_line += 1; + cur_col = 0; + } else { + cur_col += 1; + } + } + // Handle position at end of source + if (cur_line == line and cur_col == character) { + return @intCast(source.len); + } + return null; + } + + /// Extract the dotted prefix before a dot at cursor_offset. + /// E.g. for "io.stdout." with cursor at 10, returns "io.stdout". + /// For "Color." with cursor at 6, returns "Color". + fn extractDotPrefix(source: []const u8, cursor_offset: u32) ?[]const u8 { + if (cursor_offset < 2) return null; + const dot_pos = cursor_offset - 1; + if (source[dot_pos] != '.') return null; + + var start: u32 = dot_pos; + while (start > 0) { + const ch = source[start - 1]; + if (std.ascii.isAlphanumeric(ch) or ch == '_' or ch == '.') { + start -= 1; + } else { + break; + } + } + + if (start == dot_pos) return null; + const prefix = source[start..dot_pos]; + + // Strip leading dots (e.g. ".Color" → "Color") + var trimmed_start: usize = 0; + while (trimmed_start < prefix.len and prefix[trimmed_start] == '.') { + trimmed_start += 1; + } + if (trimmed_start == prefix.len) return null; + return prefix[trimmed_start..]; + } + + /// Scan backwards from cursor to find the enclosing function call context. + /// Returns the function name (including dotted path) and active parameter index. + fn findCallContext(source: []const u8, cursor_offset: u32) ?struct { name: []const u8, active_param: u32 } { + if (cursor_offset == 0) return null; + + var depth: i32 = 0; + var comma_count: u32 = 0; + var pos: u32 = cursor_offset; + + while (pos > 0) { + pos -= 1; + const ch = source[pos]; + + if (ch == ')') { + depth += 1; + } else if (ch == '(') { + if (depth == 0) { + // Found the unmatched opening paren — extract function name before it + if (pos == 0) return null; + const name_end: u32 = pos; + var name_start: u32 = pos; + while (name_start > 0) { + const nc = source[name_start - 1]; + if (std.ascii.isAlphanumeric(nc) or nc == '_' or nc == '.') { + name_start -= 1; + } else { + break; + } + } + if (name_start == name_end) return null; + // Trim leading dots + var trimmed = name_start; + while (trimmed < name_end and source[trimmed] == '.') { + trimmed += 1; + } + if (trimmed == name_end) return null; + return .{ + .name = source[trimmed..name_end], + .active_param = comma_count, + }; + } + depth -= 1; + } else if (ch == ',' and depth == 0) { + comma_count += 1; + } + } + + return null; + } + + /// Extract the last segment of a dotted name. "io.foo" → "foo", "bar" → "bar". + fn extractLastSegment(name: []const u8) []const u8 { + var i = name.len; + while (i > 0) { + i -= 1; + if (name[i] == '.') { + return name[i + 1 ..]; + } + } + return name; + } + + /// Extract the identifier word surrounding the given offset. + fn extractIdentAtOffset(source: []const u8, offset: u32) ?[]const u8 { + if (offset >= source.len) return null; + var start: u32 = offset; + while (start > 0 and isIdentChar(source[start - 1])) { + start -= 1; + } + var end: u32 = offset; + while (end < source.len and isIdentChar(source[end])) { + end += 1; + } + if (start == end) return null; + return source[start..end]; + } + + fn isIdentChar(c: u8) bool { + return (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or c == '_'; + } + + /// Check if cursor is inside an `#import "..."` string and return the path. + fn findImportPathAtOffset(source: []const u8, offset: u32) ?[]const u8 { + if (offset >= source.len) return null; + + // Find the enclosing "..." by scanning for quotes + var qstart: u32 = offset; + while (qstart > 0 and source[qstart] != '"') : (qstart -= 1) {} + if (source[qstart] != '"') return null; + + const qend: u32 = if (offset < source.len and source[offset] == '"') + offset // cursor is on closing quote + else blk: { + var e = offset; + while (e < source.len and source[e] != '"' and source[e] != '\n') : (e += 1) {} + if (e >= source.len or source[e] != '"') return null; + break :blk e; + }; + + if (qstart == qend) return null; + + // Check that #import precedes the opening quote (with optional whitespace) + var scan = qstart; + while (scan > 0 and (source[scan - 1] == ' ' or source[scan - 1] == '\t')) : (scan -= 1) {} + const kw = "#import"; + if (scan < kw.len) return null; + if (!std.mem.eql(u8, source[scan - kw.len .. scan], kw)) return null; + + return source[qstart + 1 .. qend]; + } + + /// Extract a qualified name (namespace.member) at a given byte offset. + /// Works directly from source text — no AST traversal needed. + fn extractQualifiedName(source: []const u8, offset: u32) ?struct { ns: []const u8, member: []const u8 } { + if (offset >= source.len) return null; + + // Find the word at offset + var end: u32 = offset; + while (end < source.len and isIdentChar(source[end])) end += 1; + var start: u32 = offset; + while (start > 0 and isIdentChar(source[start - 1])) start -= 1; + + if (start == end) return null; + + // Check if preceded by a dot and another identifier (ns.member) + if (start >= 2 and source[start - 1] == '.') { + var ns_start: u32 = start - 1; + while (ns_start > 0 and isIdentChar(source[ns_start - 1])) ns_start -= 1; + if (ns_start < start - 1) { + return .{ + .ns = source[ns_start .. start - 1], + .member = source[start..end], + }; + } + } + + // Check if followed by a dot and another identifier (cursor on ns part) + if (end < source.len and source[end] == '.') { + var member_end: u32 = end + 1; + while (member_end < source.len and isIdentChar(source[member_end])) member_end += 1; + if (member_end > end + 1) { + return .{ + .ns = source[start..end], + .member = source[end + 1 .. member_end], + }; + } + } + + return null; + } + + /// Find a symbol by name (searches backwards for innermost match). + fn findSymbolByName(symbols: []const sx.sema.Symbol, name: []const u8) ?usize { + var i = symbols.len; + while (i > 0) { + i -= 1; + if (std.mem.eql(u8, symbols[i].name, name)) { + return i; + } + } + return null; + } + + /// Check if a symbol's def_span corresponds to valid text in the given source. + fn isSymbolInMainSource(source: [:0]const u8, sym: sx.sema.Symbol) bool { + const start = sym.def_span.start; + const end = start + @as(u32, @intCast(sym.name.len)); + if (end > source.len) return false; + return std.mem.eql(u8, source[start..end], sym.name); + } + + /// Resolve a symbol's location (main source or import) and send a definition response. + fn sendSymbolLocation(self: *Server, id_json: []const u8, uri: []const u8, analysis: DocumentAnalysis, sym: sx.sema.Symbol) !bool { + const resolved = resolveSource(analysis, sym) orelse return false; + const range = spanToRange(resolved.source, sym.def_span); + const target_uri = if (resolved.import_path) |ip| + try std.fmt.allocPrint(self.allocator, "file://{s}", .{ip}) + else + uri; + const loc_json = try lsp.locationJson(self.allocator, target_uri, range); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, loc_json); + try self.transport.writeMessage(resp); + return true; + } + + const ResolvedSource = struct { + source: [:0]const u8, + import_path: ?[]const u8, + }; + + /// Resolve the source buffer that contains a symbol's definition. + /// Returns the main source if the symbol is local, or the imported file's source if not. + fn resolveSource(analysis: DocumentAnalysis, sym: sx.sema.Symbol) ?ResolvedSource { + if (isSymbolInMainSource(analysis.source, sym)) { + return .{ .source = analysis.source, .import_path = null }; + } + var it = analysis.import_sources.iterator(); + while (it.next()) |entry| { + const imp_source = entry.value_ptr.*; + const start = sym.def_span.start; + const end = start + @as(u32, @intCast(sym.name.len)); + if (end > imp_source.len) continue; + if (std.mem.eql(u8, imp_source[start..end], sym.name)) { + return .{ .source = imp_source, .import_path = entry.key_ptr.* }; + } + } + return null; + } + + /// Resolve a namespace member reference to a Location in the imported file. + /// Returns true if a response was sent, false if no match found. + fn resolveImportedLocation( + self: *Server, + id_json: []const u8, + analysis: DocumentAnalysis, + ns_name: []const u8, + member_name: []const u8, + ) !?void { + const import_path = analysis.import_map.get(ns_name) orelse return null; + const imp_source = analysis.import_sources.get(import_path) orelse return null; + + const ns_decls = findNamespaceDeclsByName(analysis.root, ns_name) orelse return null; + const target = findDeclInNamespace(ns_decls, member_name) orelse return null; + + const range = spanToRange(imp_source, target.span); + const target_uri = try std.fmt.allocPrint(self.allocator, "file://{s}", .{import_path}); + const loc_json = try lsp.locationJson(self.allocator, target_uri, range); + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, loc_json); + try self.transport.writeMessage(resp); + return; + } + + /// Find a fn_decl by name. Supports dotted names like "ns.func" by looking + /// inside namespace_decl nodes. Falls back to top-level sema symbol lookup. + fn findFnDeclByName(analysis: DocumentAnalysis, name: []const u8) ?sx.ast.FnDecl { + // Check if name has a dot (e.g. "std.print") + if (std.mem.indexOfScalar(u8, name, '.')) |dot_idx| { + const ns_name = name[0..dot_idx]; + const fn_name = name[dot_idx + 1 ..]; + + // Find the namespace symbol + for (analysis.sema.symbols) |sym| { + if (sym.kind != .namespace or !std.mem.eql(u8, sym.name, ns_name)) continue; + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .namespace_decl) { + for (node.data.namespace_decl.decls) |decl| { + if (decl.data == .fn_decl and std.mem.eql(u8, decl.data.fn_decl.name, fn_name)) { + return decl.data.fn_decl; + } + } + } + } + break; + } + } + + // Top-level lookup + const func_name = extractLastSegment(name); + for (analysis.sema.symbols) |sym| { + if (sym.kind != .function or !std.mem.eql(u8, sym.name, func_name)) continue; + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .fn_decl) return node.data.fn_decl; + } + break; + } + + return null; + } + + /// Convert a file:// URI to a local file path. Returns null for non-file URIs. + fn uriToFilePath(uri: []const u8) ?[]const u8 { + if (std.mem.startsWith(u8, uri, "file://")) { + return uri[7..]; + } + return null; + } + + fn spanToRange(source: [:0]const u8, span: sx.ast.Span) lsp.Range { + const clamped_start = @min(span.start, @as(u32, @intCast(source.len))); + const clamped_end = @min(span.end, @as(u32, @intCast(source.len))); + const start = sx.errors.SourceLoc.compute(source, clamped_start); + const end = sx.errors.SourceLoc.compute(source, clamped_end); + return .{ + .start = .{ .line = start.line - 1, .character = start.col - 1 }, + .end = .{ .line = end.line - 1, .character = end.col - 1 }, + }; + } + + /// Extract consecutive // comment lines immediately above a declaration. + fn extractDocComment(source: []const u8, def_start: u32) ?[]const u8 { + if (def_start == 0 or def_start > source.len) return null; + + // Find start of the declaration line + var pos: u32 = def_start; + while (pos > 0 and source[pos - 1] != '\n') : (pos -= 1) {} + if (pos == 0) return null; + + // Scan backward for consecutive comment lines + const block_end = pos; + var block_start = pos; + + while (block_start > 0) { + var scan = block_start - 1; // position of \n + while (scan > 0 and source[scan - 1] != '\n') : (scan -= 1) {} + // scan..block_start is the previous line (incl trailing \n) + const line = std.mem.trimEnd(u8, source[scan..block_start], "\r\n"); + const trimmed = std.mem.trimStart(u8, line, " \t"); + if (trimmed.len >= 2 and trimmed[0] == '/' and trimmed[1] == '/') { + block_start = scan; + } else { + break; + } + } + + if (block_start >= block_end) return null; + var end = block_end; + while (end > block_start and (source[end - 1] == '\n' or source[end - 1] == '\r')) : (end -= 1) {} + if (end <= block_start) return null; + return source[block_start..end]; + } + + /// Format hover text for an AST declaration node with its source (for doc comments). + fn formatDeclHover(allocator: std.mem.Allocator, decl: *sx.ast.Node, source: []const u8) ![]const u8 { + var buf = std.ArrayList(u8).empty; + + if (extractDocComment(source, decl.span.start)) |comment| { + try buf.appendSlice(allocator, comment); + try buf.appendSlice(allocator, "\n\n"); + } + + try buf.appendSlice(allocator, "```sx\n"); + + switch (decl.data) { + .fn_decl => |fd| { + try buf.appendSlice(allocator, fd.name); + try buf.appendSlice(allocator, " :: ("); + for (fd.params, 0..) |param, pi| { + if (pi > 0) try buf.appendSlice(allocator, ", "); + try buf.appendSlice(allocator, param.name); + try buf.appendSlice(allocator, ": "); + if (param.type_expr.data == .type_expr) { + try buf.appendSlice(allocator, param.type_expr.data.type_expr.name); + } else { + try buf.appendSlice(allocator, "?"); + } + } + try buf.append(allocator, ')'); + if (fd.return_type) |rt| { + try buf.appendSlice(allocator, " -> "); + if (rt.data == .type_expr) { + try buf.appendSlice(allocator, rt.data.type_expr.name); + } + } + }, + .enum_decl => |ed| { + try buf.appendSlice(allocator, ed.name); + try buf.appendSlice(allocator, " :: enum { "); + for (ed.variants, 0..) |v, i| { + if (i > 0) try buf.appendSlice(allocator, ", "); + try buf.append(allocator, '.'); + try buf.appendSlice(allocator, v); + } + try buf.appendSlice(allocator, " }"); + }, + .struct_decl => |sd| { + try buf.appendSlice(allocator, sd.name); + try buf.appendSlice(allocator, " :: struct { "); + for (sd.field_names, 0..) |fn_, fi| { + if (fi > 0) try buf.appendSlice(allocator, ", "); + try buf.appendSlice(allocator, fn_); + if (fi < sd.field_types.len) { + if (sd.field_types[fi].data == .type_expr) { + try buf.appendSlice(allocator, ": "); + try buf.appendSlice(allocator, sd.field_types[fi].data.type_expr.name); + } + } + } + try buf.appendSlice(allocator, " }"); + }, + .union_decl => |ud| { + try buf.appendSlice(allocator, ud.name); + try buf.appendSlice(allocator, " :: union { "); + for (ud.variant_names, 0..) |vn, i| { + if (i > 0) try buf.appendSlice(allocator, ", "); + try buf.appendSlice(allocator, vn); + } + try buf.appendSlice(allocator, " }"); + }, + .const_decl => |cd| { + try buf.appendSlice(allocator, cd.name); + try buf.appendSlice(allocator, " :: "); + if (cd.type_annotation) |ta| { + if (ta.data == .type_expr) { + try buf.appendSlice(allocator, ta.data.type_expr.name); + } + } + }, + .var_decl => |vd| { + try buf.appendSlice(allocator, vd.name); + if (vd.type_annotation) |ta| { + if (ta.data == .type_expr) { + try buf.appendSlice(allocator, " : "); + try buf.appendSlice(allocator, ta.data.type_expr.name); + } + } + }, + else => { + try buf.appendSlice(allocator, "(declaration)"); + }, + } + + try buf.appendSlice(allocator, "\n```"); + return buf.items; + } + + /// Find namespace_decl's inner decls by namespace name. + fn findNamespaceDeclsByName(root: *sx.ast.Node, name: []const u8) ?[]const *sx.ast.Node { + if (root.data != .root) return null; + for (root.data.root.decls) |decl| { + if (decl.data == .namespace_decl) { + if (std.mem.eql(u8, decl.data.namespace_decl.name, name)) { + return decl.data.namespace_decl.decls; + } + } + } + return null; + } + + /// Find a specific declaration node inside a namespace by member name. + fn findDeclInNamespace(ns_decls: []const *sx.ast.Node, member_name: []const u8) ?*sx.ast.Node { + for (ns_decls) |decl| { + if (decl.data.declName()) |n| { + if (std.mem.eql(u8, n, member_name)) return decl; + } + } + return null; + } + + /// Build hover text for a namespace member (e.g. std.print). Reads imported file for doc comments. + fn formatNamespaceMemberHover(self: *Server, analysis: DocumentAnalysis, ns_name: []const u8, member_name: []const u8, import_path: []const u8) !?[]const u8 { + const ns_decls = findNamespaceDeclsByName(analysis.root, ns_name) orelse return null; + const target = findDeclInNamespace(ns_decls, member_name) orelse return null; + + const imp_source = analysis.import_sources.get(import_path) orelse return null; + return try formatDeclHover(self.allocator, target, imp_source); + } + + /// Build hover for a struct field: StructName.field : type, with doc comment. + fn formatStructFieldHover(self: *Server, analysis: DocumentAnalysis, obj_name: []const u8, field_name: []const u8) !?[]const u8 { + const struct_name = resolveStructTypeName(analysis, obj_name) orelse return null; + + for (analysis.sema.symbols) |sym| { + if (sym.kind != .struct_type or !std.mem.eql(u8, sym.name, struct_name)) continue; + if (sym.def_span.start >= analysis.source.len) break; + + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .struct_decl) { + const sd = node.data.struct_decl; + for (sd.field_names, 0..) |fn_, fi| { + if (!std.mem.eql(u8, fn_, field_name)) continue; + + var buf = std.ArrayList(u8).empty; + + // Doc comment above field (use pointer offset into source) + const fn_addr = @intFromPtr(fn_.ptr); + const src_addr = @intFromPtr(analysis.source.ptr); + const src_end = src_addr + analysis.source.len; + if (fn_addr >= src_addr and fn_addr < src_end) { + const field_offset = @as(u32, @intCast(fn_addr - src_addr)); + if (extractDocComment(analysis.source, field_offset)) |comment| { + try buf.appendSlice(self.allocator, comment); + try buf.appendSlice(self.allocator, "\n\n"); + } + } + + try buf.appendSlice(self.allocator, "```sx\n"); + try buf.appendSlice(self.allocator, struct_name); + try buf.append(self.allocator, '.'); + try buf.appendSlice(self.allocator, field_name); + if (fi < sd.field_types.len) { + if (sd.field_types[fi].data == .type_expr) { + try buf.appendSlice(self.allocator, " : "); + try buf.appendSlice(self.allocator, sd.field_types[fi].data.type_expr.name); + } + } + try buf.appendSlice(self.allocator, "\n```"); + return buf.items; + } + } + } + break; + } + return null; + } + + /// Try to resolve a variable/param name to its struct type name. + fn resolveStructTypeName(analysis: DocumentAnalysis, var_name: []const u8) ?[]const u8 { + var i = analysis.sema.symbols.len; + while (i > 0) { + i -= 1; + const sym = analysis.sema.symbols[i]; + if (!std.mem.eql(u8, sym.name, var_name)) continue; + + const ty = sym.ty orelse return null; + if (ty != .struct_type) return null; + + // For params, def_span is the type_expr span — read the type name from source + if (sym.kind == .param) { + if (sym.def_span.start < analysis.source.len and sym.def_span.end <= analysis.source.len) { + return analysis.source[sym.def_span.start..sym.def_span.end]; + } + } + + // For variables, find the var_decl node and check type_annotation + if (sym.kind == .variable) { + if (sym.def_span.start < analysis.source.len) { + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .var_decl) { + if (node.data.var_decl.type_annotation) |ta| { + if (ta.data == .type_expr) return ta.data.type_expr.name; + } + } + } + } + } + + return null; + } + return null; + } + + /// Build hover for an enum variant like .red — show EnumName.variant with doc comment. + fn formatEnumVariantHover(self: *Server, analysis: DocumentAnalysis, variant_name: []const u8) !?[]const u8 { + for (analysis.sema.symbols) |sym| { + if (sym.kind != .enum_type) continue; + if (sym.def_span.start >= analysis.source.len) continue; + + if (sx.sema.findNodeAtOffset(analysis.root, sym.def_span.start)) |node| { + if (node.data == .enum_decl) { + const ed = node.data.enum_decl; + for (ed.variants) |v| { + if (!std.mem.eql(u8, v, variant_name)) continue; + + var buf = std.ArrayList(u8).empty; + + // Doc comment above variant + const v_addr = @intFromPtr(v.ptr); + const src_addr2 = @intFromPtr(analysis.source.ptr); + const src_end2 = src_addr2 + analysis.source.len; + if (v_addr >= src_addr2 and v_addr < src_end2) { + const variant_offset = @as(u32, @intCast(v_addr - src_addr2)); + if (extractDocComment(analysis.source, variant_offset)) |comment| { + try buf.appendSlice(self.allocator, comment); + try buf.appendSlice(self.allocator, "\n\n"); + } + } + + try buf.appendSlice(self.allocator, "```sx\n"); + try buf.appendSlice(self.allocator, sym.name); + try buf.append(self.allocator, '.'); + try buf.appendSlice(self.allocator, variant_name); + try buf.appendSlice(self.allocator, "\n```"); + return buf.items; + } + } + } + } + return null; + } + + /// Find a top-level declaration node by name. + fn findDeclByName(root: *sx.ast.Node, name: []const u8) ?*sx.ast.Node { + if (root.data != .root) return null; + for (root.data.root.decls) |decl| { + if (decl.data.declName()) |dn| { + if (std.mem.eql(u8, dn, name)) return decl; + } + } + return null; + } + + fn formatSymbolHover(allocator: std.mem.Allocator, sym: sx.sema.Symbol, root: *sx.ast.Node, source: [:0]const u8) ![]const u8 { + // Try offset-based AST lookup (works when symbol is in the same source buffer) + if (sym.def_span.start < source.len) { + if (sx.sema.findNodeAtOffset(root, sym.def_span.start)) |node| { + if (node.data.declName()) |dn| { + if (std.mem.eql(u8, dn, sym.name)) { + return try formatDeclHover(allocator, node, source); + } + } + } + } + + // Fallback: name-based lookup (works for imported symbols whose offsets collide) + if (findDeclByName(root, sym.name)) |decl| { + return try formatDeclHover(allocator, decl, source); + } + + // Last resort: simple format (for params, namespace symbols, etc.) + var buf = std.ArrayList(u8).empty; + + if (sym.def_span.start < source.len) { + if (extractDocComment(source, sym.def_span.start)) |comment| { + try buf.appendSlice(allocator, comment); + try buf.appendSlice(allocator, "\n\n"); + } + } + + try buf.appendSlice(allocator, "```sx\n"); + + switch (sym.kind) { + .function => { + try buf.appendSlice(allocator, sym.name); + try buf.appendSlice(allocator, " :: (...)"); + if (sym.ty) |ty| { + try buf.appendSlice(allocator, " -> "); + const type_name = try ty.displayName(allocator); + try buf.appendSlice(allocator, type_name); + } + }, + .variable => { + try buf.appendSlice(allocator, sym.name); + if (sym.ty) |ty| { + try buf.appendSlice(allocator, " : "); + const type_name = try ty.displayName(allocator); + try buf.appendSlice(allocator, type_name); + } + }, + .constant => { + try buf.appendSlice(allocator, sym.name); + try buf.appendSlice(allocator, " :: "); + if (sym.ty) |ty| { + const type_name = try ty.displayName(allocator); + try buf.appendSlice(allocator, type_name); + } else { + try buf.appendSlice(allocator, "(constant)"); + } + }, + .param => { + try buf.appendSlice(allocator, sym.name); + if (sym.ty) |ty| { + try buf.appendSlice(allocator, " : "); + const type_name = try ty.displayName(allocator); + try buf.appendSlice(allocator, type_name); + } + }, + .enum_type => { + try buf.appendSlice(allocator, sym.name); + try buf.appendSlice(allocator, " :: enum { ... }"); + }, + .struct_type => { + try buf.appendSlice(allocator, sym.name); + try buf.appendSlice(allocator, " :: struct { ... }"); + }, + .type_alias => { + try buf.appendSlice(allocator, sym.name); + try buf.appendSlice(allocator, " :: (type)"); + }, + .namespace => { + try buf.appendSlice(allocator, sym.name); + try buf.appendSlice(allocator, " :: (namespace)"); + }, + } + + try buf.appendSlice(allocator, "\n```"); + return buf.items; + } +}; diff --git a/src/lsp/transport.zig b/src/lsp/transport.zig new file mode 100644 index 0000000..32cd509 --- /dev/null +++ b/src/lsp/transport.zig @@ -0,0 +1,75 @@ +const std = @import("std"); + +pub const Transport = struct { + in: *std.Io.Reader, + out_file: std.Io.File, + io: std.Io, + allocator: std.mem.Allocator, + + pub fn init(allocator: std.mem.Allocator, io: std.Io, in: *std.Io.Reader, out_file: std.Io.File) Transport { + return .{ + .in = in, + .out_file = out_file, + .io = io, + .allocator = allocator, + }; + } + + /// Read one LSP message: parse Content-Length header, read body. + pub fn readMessage(self: *Transport) ![]const u8 { + var content_length: ?usize = null; + + // Parse headers (terminated by \r\n\r\n) + while (true) { + const line = try self.readLine(); + if (line.len == 0) break; // empty line = end of headers + + if (std.mem.startsWith(u8, line, "Content-Length: ")) { + content_length = std.fmt.parseInt(usize, line["Content-Length: ".len..], 10) catch + return error.InvalidContentLength; + } + } + + const len = content_length orelse return error.MissingContentLength; + + const body = try self.allocator.alloc(u8, len); + try self.in.readSliceAll(body); + + return body; + } + + /// Write one LSP message: Content-Length header + body. + pub fn writeMessage(self: *Transport, body: []const u8) !void { + var buf: [32]u8 = undefined; + const len_str = std.fmt.bufPrint(&buf, "{d}", .{body.len}) catch unreachable; + + self.out_file.writeStreamingAll(self.io, "Content-Length: ") catch return error.WriteFailed; + self.out_file.writeStreamingAll(self.io, len_str) catch return error.WriteFailed; + self.out_file.writeStreamingAll(self.io, "\r\n\r\n") catch return error.WriteFailed; + self.out_file.writeStreamingAll(self.io, body) catch return error.WriteFailed; + } + + /// Read a single line terminated by \r\n. Returns content without \r\n. + fn readLine(self: *Transport) ![]const u8 { + var buf = std.ArrayList(u8).empty; + while (true) { + const byte = self.in.takeByte() catch |err| switch (err) { + error.EndOfStream => { + if (buf.items.len == 0) return error.EndOfStream; + return buf.items; + }, + else => return error.ReadFailed, + }; + + if (byte == '\n') { + const line = buf.items; + if (line.len > 0 and line[line.len - 1] == '\r') { + return line[0 .. line.len - 1]; + } + return line; + } + + try buf.append(self.allocator, byte); + } + } +}; diff --git a/src/lsp/types.zig b/src/lsp/types.zig new file mode 100644 index 0000000..74e2f9a --- /dev/null +++ b/src/lsp/types.zig @@ -0,0 +1,331 @@ +const std = @import("std"); + +pub const Position = struct { + line: u32, + character: u32, +}; + +pub const Range = struct { + start: Position, + end: Position, +}; + +pub const Location = struct { + uri: []const u8, + range: Range, +}; + +pub const Diagnostic = struct { + range: Range, + severity: u32, + message: []const u8, + source: []const u8 = "sx", +}; + +/// Build a JSON-RPC response with a pre-built result JSON string. +pub fn jsonRpcResponse(allocator: std.mem.Allocator, id_json: []const u8, result_json: []const u8) ![]const u8 { + return std.fmt.allocPrint(allocator, "{{\"jsonrpc\":\"2.0\",\"id\":{s},\"result\":{s}}}", .{ id_json, result_json }); +} + +/// Build a JSON-RPC notification. +pub fn jsonRpcNotification(allocator: std.mem.Allocator, method: []const u8, params_json: []const u8) ![]const u8 { + return std.fmt.allocPrint(allocator, "{{\"jsonrpc\":\"2.0\",\"method\":\"{s}\",\"params\":{s}}}", .{ method, params_json }); +} + +/// Serialize a JSON Value to string. +pub fn valueToJson(allocator: std.mem.Allocator, value: std.json.Value) ![]const u8 { + var buf = std.ArrayList(u8).empty; + try writeJsonValue(&buf, allocator, value); + return buf.items; +} + +/// Escape a string for JSON. +pub fn jsonString(allocator: std.mem.Allocator, s: []const u8) ![]const u8 { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, '"'); + for (s) |ch| { + switch (ch) { + '"' => try buf.appendSlice(allocator, "\\\""), + '\\' => try buf.appendSlice(allocator, "\\\\"), + '\n' => try buf.appendSlice(allocator, "\\n"), + '\r' => try buf.appendSlice(allocator, "\\r"), + '\t' => try buf.appendSlice(allocator, "\\t"), + else => try buf.append(allocator, ch), + } + } + try buf.append(allocator, '"'); + return buf.items; +} + +fn writeJsonValue(buf: *std.ArrayList(u8), allocator: std.mem.Allocator, value: std.json.Value) !void { + switch (value) { + .null => try buf.appendSlice(allocator, "null"), + .bool => |b| try buf.appendSlice(allocator, if (b) "true" else "false"), + .integer => |i| { + const s = try std.fmt.allocPrint(allocator, "{d}", .{i}); + try buf.appendSlice(allocator, s); + }, + .float => |f| { + const s = try std.fmt.allocPrint(allocator, "{d}", .{f}); + try buf.appendSlice(allocator, s); + }, + .string => |s| { + const escaped = try jsonString(allocator, s); + try buf.appendSlice(allocator, escaped); + }, + .array => |arr| { + try buf.append(allocator, '['); + for (arr.items, 0..) |item, idx| { + if (idx > 0) try buf.append(allocator, ','); + try writeJsonValue(buf, allocator, item); + } + try buf.append(allocator, ']'); + }, + .object => |obj| { + try buf.append(allocator, '{'); + var first = true; + var it = obj.iterator(); + while (it.next()) |entry| { + if (!first) try buf.append(allocator, ','); + first = false; + const key = try jsonString(allocator, entry.key_ptr.*); + try buf.appendSlice(allocator, key); + try buf.append(allocator, ':'); + try writeJsonValue(buf, allocator, entry.value_ptr.*); + } + try buf.append(allocator, '}'); + }, + .number_string => |s| try buf.appendSlice(allocator, s), + } +} + +/// Build the initialize result JSON. +pub fn initializeResultJson(allocator: std.mem.Allocator) ![]const u8 { + return std.fmt.allocPrint(allocator, + "{{\"capabilities\":{{\"textDocumentSync\":1,\"definitionProvider\":true,\"hoverProvider\":true,\"documentSymbolProvider\":true," ++ + "\"completionProvider\":{{\"triggerCharacters\":[\".\"]}}," ++ + "\"signatureHelpProvider\":{{\"triggerCharacters\":[\"(\",\",\"]}}," ++ + "\"semanticTokensProvider\":{{\"legend\":{{" ++ + "\"tokenTypes\":[\"namespace\",\"type\",\"enum\",\"struct\",\"parameter\",\"variable\",\"enumMember\",\"function\",\"keyword\",\"number\",\"string\",\"operator\"]," ++ + "\"tokenModifiers\":[\"declaration\",\"readonly\"]" ++ + "}},\"full\":true}}}}}}", + .{}, + ); +} + +/// LSP SymbolKind enum values. +pub const SymbolKindLsp = enum(u32) { + File = 1, + Module = 2, + Namespace = 3, + Package = 4, + Class = 5, + Method = 6, + Property = 7, + Field = 8, + Constructor = 9, + Enum = 10, + Interface = 11, + Function = 12, + Variable = 13, + Constant = 14, + String = 15, + Number = 16, + Boolean = 17, + Array = 18, + Object = 19, + Key = 20, + Null = 21, + EnumMember = 22, + Struct = 23, + Event = 24, + Operator = 25, + TypeParameter = 26, +}; + +/// LSP CompletionItemKind enum values. +pub const CompletionItemKind = enum(u32) { + Text = 1, + Method = 2, + Function = 3, + Constructor = 4, + Field = 5, + Variable = 6, + Class = 7, + Interface = 8, + Module = 9, + Property = 10, + Unit = 11, + Value = 12, + Enum = 13, + Keyword = 14, + Snippet = 15, + Color = 16, + File = 17, + Reference = 18, + Folder = 19, + EnumMember = 20, + Constant = 21, + Struct = 22, + Event = 23, + Operator = 24, + TypeParameter = 25, +}; + +/// Build document symbols JSON array. +pub fn documentSymbolsJson(allocator: std.mem.Allocator, symbols: []const DocumentSymbol) ![]const u8 { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, '['); + for (symbols, 0..) |sym, idx| { + if (idx > 0) try buf.append(allocator, ','); + const name_escaped = try jsonString(allocator, sym.name); + const item = try std.fmt.allocPrint(allocator, + "{{\"name\":{s},\"kind\":{d},\"range\":{{\"start\":{{\"line\":{d},\"character\":{d}}},\"end\":{{\"line\":{d},\"character\":{d}}}}},\"selectionRange\":{{\"start\":{{\"line\":{d},\"character\":{d}}},\"end\":{{\"line\":{d},\"character\":{d}}}}}}}", + .{ + name_escaped, sym.kind, + sym.range.start.line, sym.range.start.character, + sym.range.end.line, sym.range.end.character, + sym.selection_range.start.line, sym.selection_range.start.character, + sym.selection_range.end.line, sym.selection_range.end.character, + }, + ); + try buf.appendSlice(allocator, item); + } + try buf.append(allocator, ']'); + return buf.items; +} + +pub const DocumentSymbol = struct { + name: []const u8, + kind: u32, + range: Range, + selection_range: Range, +}; + +/// Build completion items JSON array. +pub fn completionItemsJson(allocator: std.mem.Allocator, items: []const CompletionItem) ![]const u8 { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, '['); + for (items, 0..) |item, idx| { + if (idx > 0) try buf.append(allocator, ','); + const label_escaped = try jsonString(allocator, item.label); + const detail_escaped = if (item.detail) |d| try jsonString(allocator, d) else null; + if (detail_escaped) |de| { + const json = try std.fmt.allocPrint(allocator, + "{{\"label\":{s},\"kind\":{d},\"detail\":{s}}}", + .{ label_escaped, item.kind, de }, + ); + try buf.appendSlice(allocator, json); + } else { + const json = try std.fmt.allocPrint(allocator, + "{{\"label\":{s},\"kind\":{d}}}", + .{ label_escaped, item.kind }, + ); + try buf.appendSlice(allocator, json); + } + } + try buf.append(allocator, ']'); + return buf.items; +} + +pub const CompletionItem = struct { + label: []const u8, + kind: u32, + detail: ?[]const u8 = null, +}; + +/// Build a Location JSON response (for go-to-definition). +pub fn locationJson(allocator: std.mem.Allocator, uri: []const u8, range: Range) ![]const u8 { + const uri_escaped = try jsonString(allocator, uri); + return std.fmt.allocPrint(allocator, + "{{\"uri\":{s},\"range\":{{\"start\":{{\"line\":{d},\"character\":{d}}},\"end\":{{\"line\":{d},\"character\":{d}}}}}}}", + .{ uri_escaped, range.start.line, range.start.character, range.end.line, range.end.character }, + ); +} + +/// Build a Hover JSON response. +pub fn hoverJson(allocator: std.mem.Allocator, contents: []const u8) ![]const u8 { + const escaped = try jsonString(allocator, contents); + return std.fmt.allocPrint(allocator, + "{{\"contents\":{{\"kind\":\"markdown\",\"value\":{s}}}}}", + .{escaped}, + ); +} + +/// Build a SignatureHelp JSON response. +pub fn signatureHelpJson(allocator: std.mem.Allocator, label: []const u8, param_labels: []const []const u8, active_param: u32) ![]const u8 { + var buf = std.ArrayList(u8).empty; + const label_escaped = try jsonString(allocator, label); + + try buf.appendSlice(allocator, "{\"signatures\":[{\"label\":"); + try buf.appendSlice(allocator, label_escaped); + try buf.appendSlice(allocator, ",\"parameters\":["); + + for (param_labels, 0..) |pl, idx| { + if (idx > 0) try buf.append(allocator, ','); + const pl_escaped = try jsonString(allocator, pl); + try buf.appendSlice(allocator, "{\"label\":"); + try buf.appendSlice(allocator, pl_escaped); + try buf.append(allocator, '}'); + } + + const ap_str = try std.fmt.allocPrint(allocator, "{d}", .{active_param}); + try buf.appendSlice(allocator, "]}],\"activeSignature\":0,\"activeParameter\":"); + try buf.appendSlice(allocator, ap_str); + try buf.append(allocator, '}'); + + return buf.items; +} + +/// Semantic token type indices (must match legend in initializeResultJson). +pub const SemanticTokenType = struct { + pub const namespace: u32 = 0; + pub const type_: u32 = 1; + pub const enum_: u32 = 2; + pub const struct_: u32 = 3; + pub const parameter: u32 = 4; + pub const variable: u32 = 5; + pub const enum_member: u32 = 6; + pub const function: u32 = 7; + pub const keyword: u32 = 8; + pub const number: u32 = 9; + pub const string_: u32 = 10; + pub const operator_: u32 = 11; +}; + +/// Build a SemanticTokens JSON response. +pub fn semanticTokensJson(allocator: std.mem.Allocator, data: []const u32) ![]const u8 { + var buf = std.ArrayList(u8).empty; + try buf.appendSlice(allocator, "{\"data\":["); + for (data, 0..) |val, idx| { + if (idx > 0) try buf.append(allocator, ','); + const s = try std.fmt.allocPrint(allocator, "{d}", .{val}); + try buf.appendSlice(allocator, s); + } + try buf.appendSlice(allocator, "]}"); + return buf.items; +} + +/// Build publishDiagnostics params JSON. +pub fn publishDiagnosticsJson(allocator: std.mem.Allocator, uri: []const u8, diagnostics: []const Diagnostic) ![]const u8 { + var buf = std.ArrayList(u8).empty; + const uri_escaped = try jsonString(allocator, uri); + + try buf.appendSlice(allocator, "{\"uri\":"); + try buf.appendSlice(allocator, uri_escaped); + try buf.appendSlice(allocator, ",\"diagnostics\":["); + + for (diagnostics, 0..) |d, idx| { + if (idx > 0) try buf.append(allocator, ','); + const msg_escaped = try jsonString(allocator, d.message); + const src_escaped = try jsonString(allocator, d.source); + const diag_json = try std.fmt.allocPrint(allocator, + "{{\"range\":{{\"start\":{{\"line\":{d},\"character\":{d}}},\"end\":{{\"line\":{d},\"character\":{d}}}}},\"severity\":{d},\"message\":{s},\"source\":{s}}}", + .{ d.range.start.line, d.range.start.character, d.range.end.line, d.range.end.character, d.severity, msg_escaped, src_escaped }, + ); + try buf.appendSlice(allocator, diag_json); + } + + try buf.appendSlice(allocator, "]}"); + return buf.items; +} diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000..caea656 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,158 @@ +const std = @import("std"); +const sx = @import("sx"); + +pub fn main(init: std.process.Init) !void { + const allocator = init.arena.allocator(); + const io = init.io; + const args = try init.minimal.args.toSlice(allocator); + + if (args.len < 2) { + printUsage(); + return; + } + + const command = args[1]; + + // LSP subcommand doesn't need a file argument + if (std.mem.eql(u8, command, "lsp")) { + runLsp(allocator, io); + return; + } + + if (args.len < 3) { + printUsage(); + return; + } + + const input_path = args[2]; + + if (std.mem.eql(u8, command, "build")) { + const output_name = deriveOutputName(input_path); + compile(allocator, io, input_path, output_name) catch return; + std.debug.print("compiled: {s}\n", .{output_name}); + } else if (std.mem.eql(u8, command, "ir")) { + emitIR(allocator, io, input_path) catch return; + } else if (std.mem.eql(u8, command, "run")) { + const tmp_bin = "/tmp/sx_run_tmp"; + compile(allocator, io, input_path, tmp_bin) catch return; + defer { + std.Io.Dir.deleteFile(.cwd(), io, tmp_bin) catch {}; + } + var child = std.process.spawn(io, .{ + .argv = &.{tmp_bin}, + }) catch { + std.debug.print("error: failed to run program\n", .{}); + return; + }; + _ = child.wait(io) catch { + std.debug.print("error: program execution failed\n", .{}); + return; + }; + } else { + printUsage(); + } +} + +fn printUsage() void { + std.debug.print( + \\Usage: sx [file.sx] + \\ + \\Commands: + \\ run Build and run immediately + \\ build Build binary in current directory + \\ ir Print LLVM IR to stdout + \\ lsp Start language server (LSP) + \\ + , .{}); +} + +fn runLsp(allocator: std.mem.Allocator, io: std.Io) void { + const Transport = sx.lsp.transport.Transport; + const Server = sx.lsp.server.Server; + + const stdin_file = std.Io.File.stdin(); + const stdout_file = std.Io.File.stdout(); + + var read_buf: [4096]u8 = undefined; + var stdin_reader = stdin_file.readerStreaming(io, &read_buf); + + var transport = Transport.init(allocator, io, &stdin_reader.interface, stdout_file); + var server = Server.init(allocator, &transport, io); + + while (true) { + const msg = transport.readMessage() catch |err| { + if (err == error.EndOfStream) break; + std.debug.print("lsp: read error: {}\n", .{err}); + break; + }; + + const keep_going = server.handleMessage(msg); + + if (!keep_going) break; + } +} + +fn deriveOutputName(input_path: []const u8) []const u8 { + // Get basename (strip directory) + var start: usize = 0; + for (input_path, 0..) |ch, i| { + if (ch == '/') start = i + 1; + } + const basename = input_path[start..]; + // Strip .sx extension + if (std.mem.endsWith(u8, basename, ".sx")) { + return basename[0 .. basename.len - 3]; + } + return basename; +} + + +fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) ![:0]const u8 { + const source_bytes = std.Io.Dir.readFileAlloc(.cwd(), io, input_path, allocator, .limited(10 * 1024 * 1024)) catch |err| { + std.debug.print("error: cannot read '{s}': {}\n", .{ input_path, err }); + return error.CompileError; + }; + return try allocator.dupeZ(u8, source_bytes); +} + +fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !void { + const source = try readSource(allocator, io, input_path); + + var comp = sx.core.Compilation.init(allocator, io, input_path, source); + defer comp.deinit(); + + comp.parse() catch { comp.renderErrors(); return error.CompileError; }; + comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; + comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; + + var cg = &comp.cg.?; + cg.verify() catch { comp.renderErrors(); return error.CompileError; }; + cg.printIR(); +} + +fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8) !void { + const source = try readSource(allocator, io, input_path); + + var comp = sx.core.Compilation.init(allocator, io, input_path, source); + defer comp.deinit(); + + comp.parse() catch { comp.renderErrors(); return error.CompileError; }; + comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; + comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; + + var cg = &comp.cg.?; + cg.verify() catch { comp.renderErrors(); return error.CompileError; }; + + // Emit object file + const obj_path = try std.fmt.allocPrintSentinel(allocator, "{s}.o", .{output_path}, 0); + cg.emitObject(obj_path.ptr) catch { comp.renderErrors(); return error.CompileError; }; + + // Link + sx.codegen.CodeGen.link(io, obj_path, output_path) catch { + std.debug.print("error: linking failed\n", .{}); + return error.CompileError; + }; + + // Clean up object file + std.Io.Dir.deleteFile(.cwd(), io, obj_path) catch {}; +} diff --git a/src/parser.zig b/src/parser.zig new file mode 100644 index 0000000..d1c227f --- /dev/null +++ b/src/parser.zig @@ -0,0 +1,1573 @@ +const std = @import("std"); +const Token = @import("token.zig").Token; +const Tag = @import("token.zig").Tag; +const Lexer = @import("lexer.zig").Lexer; +const ast = @import("ast.zig"); +const Node = ast.Node; +const Type = @import("types.zig").Type; +const errors = @import("errors.zig"); + +pub const Parser = struct { + lexer: Lexer, + current: Token, + source: [:0]const u8, + allocator: std.mem.Allocator, + err_msg: ?[]const u8, + err_offset: ?u32 = null, + prev_end: u32 = 0, + diagnostics: ?*errors.DiagnosticList = null, + + pub fn init(allocator: std.mem.Allocator, source: [:0]const u8) Parser { + var lexer = Lexer.init(source); + const first = lexer.next(); + return .{ + .lexer = lexer, + .current = first, + .source = source, + .allocator = allocator, + .err_msg = null, + .err_offset = null, + }; + } + + fn createNode(self: *Parser, start: u32, data: Node.Data) !*Node { + const node = try self.allocator.create(Node); + node.* = .{ .span = .{ .start = start, .end = self.prev_end }, .data = data }; + return node; + } + + pub fn parse(self: *Parser) anyerror!*Node { + var decls = std.ArrayList(*Node).empty; + while (self.current.tag != .eof) { + const decl = try self.parseTopLevel(); + try decls.append(self.allocator, decl); + } + const node = try self.createNode(0, .{ .root = .{ .decls = try decls.toOwnedSlice(self.allocator) } }); + return node; + } + + fn parseTopLevel(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + + // Top-level flat import: #import "path"; + if (self.current.tag == .hash_import) { + self.advance(); + if (self.current.tag != .string_literal) { + return self.fail("expected string path after '#import'"); + } + const raw = self.tokenSlice(self.current); + const path = raw[1 .. raw.len - 1]; + self.advance(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .import_decl = .{ .path = path, .name = null } }); + } + + // Top-level #run directive + if (self.current.tag == .hash_run) { + self.advance(); + const expr = try self.parseExpr(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .comptime_expr = .{ .expr = expr } }); + } + + // All top-level declarations start with an identifier + if (self.current.tag != .identifier) { + return self.fail("expected identifier at top level"); + } + const name = self.tokenSlice(self.current); + self.advance(); + + // IDENT :: ... + if (self.current.tag == .colon_colon) { + self.advance(); + return self.parseConstBinding(name, start); + } + + // IDENT : type : value; (typed constant) + // IDENT : type = value; (typed variable) + if (self.current.tag == .colon) { + self.advance(); + return self.parseTypedBinding(name, start); + } + + // IDENT := value; (variable) + if (self.current.tag == .colon_equal) { + self.advance(); + const value = try self.parseExpr(); + try self.expectSemicolonAfter(value); + return try self.createNode(start, .{ .var_decl = .{ .name = name, .type_annotation = null, .value = value } }); + } + + return self.fail("expected '::', ':=', or ':' after identifier"); + } + + fn parseConstBinding(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + // After `::` + // Could be: #run expr, enum { ... }, (params) -> type { body }, or expr; + + // Namespaced import: name :: #import "path"; + if (self.current.tag == .hash_import) { + self.advance(); + if (self.current.tag != .string_literal) { + return self.fail("expected string path after '#import'"); + } + const raw = self.tokenSlice(self.current); + const path = raw[1 .. raw.len - 1]; + self.advance(); + try self.expect(.semicolon); + return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } }); + } + + // Compile-time evaluation: name :: #run expr; + if (self.current.tag == .hash_run) { + const run_start = self.current.loc.start; + self.advance(); + const inner = try self.parseExpr(); + try self.expect(.semicolon); + const ct = try self.createNode(run_start, .{ .comptime_expr = .{ .expr = inner } }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = ct } }); + } + + // Built-in declaration: name :: #builtin; + if (self.current.tag == .hash_builtin) { + const bi_start = self.current.loc.start; + self.advance(); + try self.expect(.semicolon); + const bi = try self.createNode(bi_start, .{ .builtin_expr = {} }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = bi } }); + } + + // Enum declaration + if (self.current.tag == .kw_enum) { + return self.parseEnumDecl(name, start_pos); + } + + // Struct declaration + if (self.current.tag == .kw_struct) { + return self.parseStructDecl(name, start_pos); + } + + // Union declaration + if (self.current.tag == .kw_union) { + return self.parseUnionDecl(name, start_pos); + } + + // Function declaration: (params) -> type { body } or () { body } + if (self.current.tag == .l_paren) { + // Look ahead: is this a function or an expression starting with `(`? + // Heuristic: if after matching parens we see `{` or `->`, it's a function. + if (self.isFunctionDef()) { + return self.parseFnDecl(name, start_pos); + } + } + + // Bare block shorthand: name :: { body } is equivalent to name :: () { body } + if (self.current.tag == .l_brace) { + const body = try self.parseBlock(); + return try self.createNode(start_pos, .{ .fn_decl = .{ .name = name, .params = &.{}, .return_type = null, .body = body } }); + } + + // Otherwise it's a constant expression + const value = try self.parseExpr(); + try self.expect(.semicolon); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value } }); + } + + fn parseTypedBinding(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + // After `name :` + // Parse type + const type_node = try self.parseTypeExpr(); + + if (self.current.tag == .colon) { + // name : type : value; (typed constant) + self.advance(); + const value = try self.parseExpr(); + try self.expectSemicolonAfter(value); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = type_node, .value = value } }); + } + + if (self.current.tag == .equal) { + // name : type = value; (typed variable) + self.advance(); + const value = try self.parseExpr(); + try self.expectSemicolonAfter(value); + return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = value } }); + } + + if (self.current.tag == .semicolon) { + // name : type; (default-initialized variable) + self.advance(); + return try self.createNode(start_pos, .{ .var_decl = .{ .name = name, .type_annotation = type_node, .value = null } }); + } + + return self.fail("expected ':', '=' or ';' after type annotation"); + } + + fn parseTypeExpr(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + + // Array type: [N]T + if (self.current.tag == .l_bracket) { + self.advance(); // skip '[' + const len_node = try self.parseExpr(); + try self.expect(.r_bracket); + const elem_type = try self.parseTypeExpr(); + return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } }); + } + + // Generic type parameter introduction: $T + if (self.current.tag == .dollar) { + self.advance(); + if (self.current.tag != .identifier) { + return self.fail("expected type parameter name after '$'"); + } + const name = self.tokenSlice(self.current); + self.advance(); + return try self.createNode(start, .{ .type_expr = .{ .name = name, .is_generic = true } }); + } + if (self.current.tag.isTypeKeyword() or self.current.tag == .identifier) { + var name = self.tokenSlice(self.current); + self.advance(); + + // Qualified name: ns.Type or ns.Type(args) + while (self.current.tag == .dot) { + self.advance(); + if (self.current.tag == .identifier or self.current.tag.isTypeKeyword()) { + name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ name, self.tokenSlice(self.current) }); + self.advance(); + } else break; + } + + // Parameterized type: Vector(N, T) or later generic struct instantiation + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + var args = std.ArrayList(*Node).empty; + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (args.items.len > 0) { + try self.expect(.comma); + } + // Args can be int literals (for lengths) or type expressions + if (self.current.tag == .int_literal) { + const arg_start = self.current.loc.start; + const text = self.tokenSlice(self.current); + const value = std.fmt.parseInt(i64, text, 10) catch 0; + self.advance(); + try args.append(self.allocator, try self.createNode(arg_start, .{ .int_literal = .{ .value = value } })); + } else { + try args.append(self.allocator, try self.parseTypeExpr()); + } + } + try self.expect(.r_paren); + return try self.createNode(start, .{ .parameterized_type_expr = .{ + .name = name, + .args = try args.toOwnedSlice(self.allocator), + } }); + } + + return try self.createNode(start, .{ .type_expr = .{ .name = name } }); + } + return self.fail("expected type name"); + } + + fn parseEnumDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + self.advance(); // skip 'enum' + try self.expect(.l_brace); + var variants = std.ArrayList([]const u8).empty; + while (self.current.tag != .r_brace and self.current.tag != .eof) { + if (self.current.tag != .identifier) { + return self.fail("expected variant name"); + } + try variants.append(self.allocator, self.tokenSlice(self.current)); + self.advance(); + if (self.current.tag == .semicolon) { + self.advance(); + } + } + try self.expect(.r_brace); + return try self.createNode(start_pos, .{ .enum_decl = .{ .name = name, .variants = try variants.toOwnedSlice(self.allocator) } }); + } + + fn parseUnionDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + self.advance(); // skip 'union' + try self.expect(.l_brace); + var variant_names = std.ArrayList([]const u8).empty; + var variant_types = std.ArrayList(?*Node).empty; + while (self.current.tag != .r_brace and self.current.tag != .eof) { + if (self.current.tag != .identifier) { + return self.fail("expected variant name in union"); + } + try variant_names.append(self.allocator, self.tokenSlice(self.current)); + self.advance(); + if (self.current.tag == .colon) { + // Typed variant: name: type; + self.advance(); + const vtype = try self.parseTypeExpr(); + try variant_types.append(self.allocator, vtype); + } else { + // Void variant: name; + try variant_types.append(self.allocator, null); + } + if (self.current.tag == .semicolon) { + self.advance(); + } + } + try self.expect(.r_brace); + return try self.createNode(start_pos, .{ .union_decl = .{ + .name = name, + .variant_names = try variant_names.toOwnedSlice(self.allocator), + .variant_types = try variant_types.toOwnedSlice(self.allocator), + } }); + } + + fn parseStructDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + self.advance(); // skip 'struct' + + // Optional type params: struct($N: u32, $T: Type) { ... } + var type_params = std.ArrayList(ast.StructTypeParam).empty; + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (type_params.items.len > 0) { + try self.expect(.comma); + } + // Expect $name : constraint + try self.expect(.dollar); + if (self.current.tag != .identifier) { + return self.fail("expected type parameter name after '$'"); + } + const param_name = self.tokenSlice(self.current); + self.advance(); + try self.expect(.colon); + const constraint = try self.parseTypeExpr(); + try type_params.append(self.allocator, .{ .name = param_name, .constraint = constraint }); + } + try self.expect(.r_paren); + } + + try self.expect(.l_brace); + + var field_names = std.ArrayList([]const u8).empty; + var field_types = std.ArrayList(*Node).empty; + var field_defaults = std.ArrayList(?*Node).empty; + + while (self.current.tag != .r_brace and self.current.tag != .eof) { + // Parse field group: name1, name2, ...: type (= default)?; + var group_names = std.ArrayList([]const u8).empty; + + if (self.current.tag != .identifier) { + return self.fail("expected field name in struct"); + } + try group_names.append(self.allocator, self.tokenSlice(self.current)); + self.advance(); + + while (self.current.tag == .comma) { + self.advance(); // skip ',' + if (self.current.tag != .identifier) { + return self.fail("expected field name after ','"); + } + try group_names.append(self.allocator, self.tokenSlice(self.current)); + self.advance(); + } + + try self.expect(.colon); + const field_type = try self.parseTypeExpr(); + + // Check for default value: = expr + var default_val: ?*Node = null; + if (self.current.tag == .equal) { + self.advance(); + default_val = try self.parseExpr(); + } + + // All names in the group share the same type and default + for (group_names.items) |fname| { + try field_names.append(self.allocator, fname); + try field_types.append(self.allocator, field_type); + try field_defaults.append(self.allocator, default_val); + } + + if (self.current.tag == .semicolon) { + self.advance(); + } + } + try self.expect(.r_brace); + + return try self.createNode(start_pos, .{ .struct_decl = .{ + .name = name, + .field_names = try field_names.toOwnedSlice(self.allocator), + .field_types = try field_types.toOwnedSlice(self.allocator), + .field_defaults = try field_defaults.toOwnedSlice(self.allocator), + .type_params = try type_params.toOwnedSlice(self.allocator), + } }); + } + + fn parseStructLiteral(self: *Parser, struct_name: ?[]const u8, type_expr: ?*Node, start_pos: u32) anyerror!*Node { + try self.expect(.l_brace); + + var field_inits = std.ArrayList(ast.StructFieldInit).empty; + + while (self.current.tag != .r_brace and self.current.tag != .eof) { + if (field_inits.items.len > 0) { + try self.expect(.comma); + } + + // Check if this is a named field: identifier followed by '=' + if (self.current.tag == .identifier) { + const saved_lexer = self.lexer; + const saved_current = self.current; + const saved_prev_end = self.prev_end; + const fname = self.tokenSlice(self.current); + const ident_start = self.current.loc.start; + self.advance(); + + if (self.current.tag == .equal) { + // Named field: name = expr + self.advance(); // skip '=' + const value = try self.parseExpr(); + try field_inits.append(self.allocator, .{ .name = fname, .value = value }); + continue; + } else if (self.current.tag == .comma or self.current.tag == .r_brace) { + // Shorthand: just an identifier (name = identifier with same name) + const ident_node = try self.createNode(ident_start, .{ .identifier = .{ .name = fname } }); + try field_inits.append(self.allocator, .{ .name = fname, .value = ident_node }); + continue; + } + + // Not named — backtrack and parse as positional expression + self.lexer = saved_lexer; + self.current = saved_current; + self.prev_end = saved_prev_end; + } + + // Positional field: just an expression + const value = try self.parseExpr(); + try field_inits.append(self.allocator, .{ .name = null, .value = value }); + } + try self.expect(.r_brace); + + return try self.createNode(start_pos, .{ .struct_literal = .{ + .struct_name = struct_name, + .type_expr = type_expr, + .field_inits = try field_inits.toOwnedSlice(self.allocator), + } }); + } + + fn reconstructQualifiedName(self: *Parser, node: *Node) ![]const u8 { + if (node.data == .identifier) return node.data.identifier.name; + if (node.data == .field_access) { + const obj_name = try self.reconstructQualifiedName(node.data.field_access.object); + return std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ obj_name, node.data.field_access.field }); + } + return error.ParseError; + } + + /// Parse a parenthesized parameter list: `(name: type, $T: Type, args: ..Any)` + /// Handles `$` generic params, `..` variadic marker, and comptime detection. + /// Expects opening `(` already NOT consumed — this function consumes `(` through `)`. + fn parseParams(self: *Parser) anyerror![]const ast.Param { + try self.expect(.l_paren); + var params = std.ArrayList(ast.Param).empty; + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (params.items.len > 0) { + try self.expect(.comma); + } + var is_ct_param = false; + if (self.current.tag == .dollar) { + is_ct_param = true; + self.advance(); + } + if (self.current.tag != .identifier) { + return self.fail("expected parameter name"); + } + const param_name = self.tokenSlice(self.current); + const param_name_span = ast.Span{ .start = self.current.loc.start, .end = self.current.loc.end }; + self.advance(); + try self.expect(.colon); + const is_variadic = self.current.tag == .dot_dot; + if (is_variadic) self.advance(); + const param_type = try self.parseTypeExpr(); + var is_comptime_param = false; + if (is_ct_param and param_type.data == .type_expr) { + const constraint_name = param_type.data.type_expr.name; + if (std.mem.eql(u8, constraint_name, "Type")) { + param_type.data = .{ .type_expr = .{ .name = param_name, .is_generic = true } }; + } else { + is_comptime_param = true; + } + } + try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = param_type, .is_variadic = is_variadic, .is_comptime = is_comptime_param }); + } + for (params.items, 0..) |param, i| { + if (param.is_variadic and i != params.items.len - 1) { + return self.fail("variadic parameter must be the last parameter"); + } + } + try self.expect(.r_paren); + return try params.toOwnedSlice(self.allocator); + } + + /// Collect generic type params and comptime value params from parameter annotations. + fn collectTypeParams(self: *Parser, params: []const ast.Param) ![]const ast.StructTypeParam { + var type_params = std.ArrayList(ast.StructTypeParam).empty; + for (params) |param| { + if (param.is_comptime) { + var found = false; + for (type_params.items) |existing| { + if (std.mem.eql(u8, existing.name, param.name)) { + found = true; + break; + } + } + if (!found) { + try type_params.append(self.allocator, .{ .name = param.name, .constraint = param.type_expr }); + } + } else if (param.type_expr.data == .type_expr and param.type_expr.data.type_expr.is_generic) { + var found = false; + for (type_params.items) |existing| { + if (std.mem.eql(u8, existing.name, param.type_expr.data.type_expr.name)) { + found = true; + break; + } + } + if (!found) { + const type_constraint = try self.createNode(param.type_expr.span.start, .{ .type_expr = .{ .name = "Type" } }); + try type_params.append(self.allocator, .{ .name = param.type_expr.data.type_expr.name, .constraint = type_constraint }); + } + } + } + return try type_params.toOwnedSlice(self.allocator); + } + + fn parseFnDecl(self: *Parser, name: []const u8, start_pos: u32) anyerror!*Node { + const params = try self.parseParams(); + + // Optional return type + var return_type: ?*Node = null; + if (self.current.tag == .arrow) { + self.advance(); + return_type = try self.parseTypeExpr(); + } + + // Body: block `{ ... }`, arrow `=> expr;`, or #builtin marker + const body = if (self.current.tag == .hash_builtin) blk: { + const bi_start = self.current.loc.start; + self.advance(); + try self.expect(.semicolon); + break :blk try self.createNode(bi_start, .{ .builtin_expr = {} }); + } else if (self.current.tag == .fat_arrow) blk: { + self.advance(); + const expr = try self.parseExpr(); + try self.expect(.semicolon); + const stmts = try self.allocator.alloc(*Node, 1); + stmts[0] = expr; + const block_start = expr.span.start; + const block = try self.createNode(block_start, .{ .block = .{ .stmts = stmts } }); + break :blk block; + } else try self.parseBlock(); + + const type_params = try self.collectTypeParams(params); + + return try self.createNode(start_pos, .{ .fn_decl = .{ + .name = name, + .params = params, + .return_type = return_type, + .body = body, + .type_params = type_params, + } }); + } + + fn parseBlock(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + try self.expect(.l_brace); + var stmts = std.ArrayList(*Node).empty; + while (self.current.tag != .r_brace and self.current.tag != .eof) { + const stmt = try self.parseStmt(); + try stmts.append(self.allocator, stmt); + } + try self.expect(.r_brace); + return try self.createNode(start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator) } }); + } + + /// Block-form if/match/while/bare blocks don't require trailing semicolon. + fn expectSemicolonAfter(self: *Parser, expr: *Node) anyerror!void { + const needs_semi = switch (expr.data) { + .if_expr => |ie| ie.is_inline, + .match_expr => false, + .while_expr => false, + .for_expr => false, + .block => false, + else => true, + }; + if (needs_semi) { + try self.expect(.semicolon); + } else if (self.current.tag == .semicolon) { + self.advance(); // consume optional ; + } + } + + pub fn parseStmt(self: *Parser) anyerror!*Node { + // Check if this is a declaration (IDENT followed by ::, :=, or : type) + if (self.current.tag == .identifier) { + const saved_lexer = self.lexer; + const saved_current = self.current; + const saved_prev_end = self.prev_end; + const start = self.current.loc.start; + const name = self.tokenSlice(self.current); + self.advance(); + + if (self.current.tag == .colon_colon) { + self.advance(); + return self.parseConstBinding(name, start); + } + if (self.current.tag == .colon_equal) { + self.advance(); + const value = try self.parseExpr(); + try self.expectSemicolonAfter(value); + return try self.createNode(start, .{ .var_decl = .{ .name = name, .type_annotation = null, .value = value } }); + } + if (self.current.tag == .colon) { + self.advance(); + return self.parseTypedBinding(name, start); + } + + // Check for assignment operators + if (self.isAssignOp()) { + const op = self.assignOp(); + self.advance(); + const value = try self.parseExpr(); + try self.expect(.semicolon); + const target = try self.createNode(start, .{ .identifier = .{ .name = name } }); + return try self.createNode(start, .{ .assignment = .{ .target = target, .op = op, .value = value } }); + } + + // Not a declaration or assignment — backtrack and parse as expression + self.lexer = saved_lexer; + self.current = saved_current; + self.prev_end = saved_prev_end; + } + + // Return statement: return expr; or return; + if (self.current.tag == .kw_return) { + const start = self.current.loc.start; + self.advance(); + if (self.current.tag == .semicolon) { + self.advance(); + return try self.createNode(start, .{ .return_stmt = .{ .value = null } }); + } + const value = try self.parseExpr(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .return_stmt = .{ .value = value } }); + } + + // Defer statement: defer ; + if (self.current.tag == .kw_defer) { + const start = self.current.loc.start; + self.advance(); + const deferred = try self.parseExpr(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .defer_stmt = .{ .expr = deferred } }); + } + + // Break statement: break; + if (self.current.tag == .kw_break) { + const start = self.current.loc.start; + self.advance(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .break_expr = {} }); + } + + // Continue statement: continue; + if (self.current.tag == .kw_continue) { + const start = self.current.loc.start; + self.advance(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .continue_expr = {} }); + } + + // Insert directive: #insert ; + if (self.current.tag == .hash_insert) { + const start = self.current.loc.start; + self.advance(); + const inner = try self.parseExpr(); + try self.expect(.semicolon); + return try self.createNode(start, .{ .insert_expr = .{ .expr = inner } }); + } + + // Expression statement + const expr = try self.parseExpr(); + + // Check for field assignment: expr = value; (e.g. a.b = 1;) + if (self.isAssignOp()) { + const op = self.assignOp(); + self.advance(); + const value = try self.parseExpr(); + try self.expect(.semicolon); + return try self.createNode(expr.span.start, .{ .assignment = .{ .target = expr, .op = op, .value = value } }); + } + + // Block-form if/match/while/bare blocks don't require trailing semicolon + try self.expectSemicolonAfter(expr); + return expr; + } + + // ---- Expression parsing (Pratt / precedence climbing) ---- + + pub fn parseExpr(self: *Parser) anyerror!*Node { + return self.parseBinary(0); + } + + fn parseBinary(self: *Parser, min_prec: u8) anyerror!*Node { + const lhs = try self.parseUnary(); + return self.parseBinaryRhs(lhs, min_prec); + } + + fn parseBinaryRhs(self: *Parser, initial_lhs: *Node, min_prec: u8) anyerror!*Node { + var lhs = initial_lhs; + + while (true) { + const prec = self.binaryPrec(); + if (prec == 0 or prec < min_prec) break; + + const op = self.binaryOp() orelse break; + self.advance(); + + const rhs = try self.parseBinary(prec + 1); + + // Chained comparison detection: if op is a comparison and the next + // token is also a comparison at the same precedence, accumulate + // into a ChainedComparison node. + if (isComparisonOp(op) and self.binaryPrec() == prec and self.isComparisonToken()) { + var operands = std.ArrayList(*Node).empty; + var ops = std.ArrayList(ast.BinaryOp.Op).empty; + try operands.append(self.allocator, lhs); + try operands.append(self.allocator, rhs); + try ops.append(self.allocator, op); + + while (self.binaryPrec() == prec and self.isComparisonToken()) { + const chain_op = self.binaryOp() orelse break; + self.advance(); + const chain_rhs = try self.parseBinary(prec + 1); + try operands.append(self.allocator, chain_rhs); + try ops.append(self.allocator, chain_op); + } + + lhs = try self.createNode(lhs.span.start, .{ .chained_comparison = .{ + .operands = try operands.toOwnedSlice(self.allocator), + .ops = try ops.toOwnedSlice(self.allocator), + } }); + } else { + lhs = try self.createNode(lhs.span.start, .{ .binary_op = .{ .op = op, .lhs = lhs, .rhs = rhs } }); + } + } + + return lhs; + } + + fn parseUnary(self: *Parser) anyerror!*Node { + if (self.current.tag == .minus) { + const start = self.current.loc.start; + self.advance(); + const operand = try self.parseUnary(); + return try self.createNode(start, .{ .unary_op = .{ .op = .negate, .operand = operand } }); + } + if (self.current.tag == .bang) { + const start = self.current.loc.start; + self.advance(); + const operand = try self.parseUnary(); + return try self.createNode(start, .{ .unary_op = .{ .op = .not, .operand = operand } }); + } + if (self.current.tag == .kw_xx) { + const start = self.current.loc.start; + self.advance(); + const operand = try self.parseUnary(); + return try self.createNode(start, .{ .unary_op = .{ .op = .xx, .operand = operand } }); + } + // cast(Type) expr — prefix operator with type parameter + if (self.current.tag == .identifier and std.mem.eql(u8, self.tokenSlice(self.current), "cast")) { + const saved_lexer = self.lexer; + const next_tok = self.lexer.next(); + self.lexer = saved_lexer; + if (next_tok.tag == .l_paren) { + const start = self.current.loc.start; + self.advance(); // consume 'cast' + self.advance(); // consume '(' + const type_arg = try self.parseExpr(); + try self.expect(.r_paren); + const operand = try self.parseUnary(); + const callee = try self.createNode(start, .{ .identifier = .{ .name = "cast" } }); + const args = try self.allocator.alloc(*Node, 2); + args[0] = type_arg; + args[1] = operand; + return try self.createNode(start, .{ .call = .{ .callee = callee, .args = args } }); + } + } + return self.parsePostfix(); + } + + fn parsePostfix(self: *Parser) anyerror!*Node { + var expr = try self.parsePrimary(); + + while (true) { + if (self.current.tag == .l_paren) { + // Call + self.advance(); + var args = std.ArrayList(*Node).empty; + while (self.current.tag != .r_paren and self.current.tag != .eof) { + if (args.items.len > 0) { + try self.expect(.comma); + } + // Spread operator: ..expr + if (self.current.tag == .dot_dot) { + const spread_start = self.current.loc.start; + self.advance(); + const operand = try self.parseExpr(); + try args.append(self.allocator, try self.createNode(spread_start, .{ .spread_expr = .{ .operand = operand } })); + } else { + try args.append(self.allocator, try self.parseExpr()); + } + } + try self.expect(.r_paren); + expr = try self.createNode(expr.span.start, .{ .call = .{ .callee = expr, .args = try args.toOwnedSlice(self.allocator) } }); + } else if (self.current.tag == .dot) { + self.advance(); + if (self.current.tag == .l_brace) { + // Struct literal: Type.{ ... } + if (expr.data == .identifier) { + // Simple name: Vec4.{ ... } + expr = try self.parseStructLiteral(expr.data.identifier.name, null, expr.span.start); + } else if (expr.data == .field_access) { + // Qualified name: std.Vec4.{ ... } + const qname = try self.reconstructQualifiedName(expr); + expr = try self.parseStructLiteral(qname, null, expr.span.start); + } else { + // Expression type: Vec(3, f32).{ ... } + expr = try self.parseStructLiteral(null, expr, expr.span.start); + } + } else { + // Field access + if (self.current.tag != .identifier) { + return self.fail("expected field name after '.'"); + } + const field = self.tokenSlice(self.current); + self.advance(); + expr = try self.createNode(expr.span.start, .{ .field_access = .{ .object = expr, .field = field } }); + } + } else if (self.current.tag == .l_bracket) { + // Index access: expr[expr] + self.advance(); + const index = try self.parseExpr(); + try self.expect(.r_bracket); + expr = try self.createNode(expr.span.start, .{ .index_expr = .{ .object = expr, .index = index } }); + } else { + break; + } + } + + return expr; + } + + fn parsePrimary(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + switch (self.current.tag) { + .int_literal => { + const text = self.tokenSlice(self.current); + const base: u8 = if (text.len > 2 and text[0] == '0' and (text[1] == 'x' or text[1] == 'X')) + 16 + else if (text.len > 2 and text[0] == '0' and (text[1] == 'b' or text[1] == 'B')) + 2 + else + 10; + const digits = if (base != 10) text[2..] else text; + const value = std.fmt.parseInt(i64, digits, base) catch { + return self.fail("integer literal overflow"); + }; + self.advance(); + return try self.createNode(start, .{ .int_literal = .{ .value = value } }); + }, + .float_literal => { + const text = self.tokenSlice(self.current); + const value = std.fmt.parseFloat(f64, text) catch { + return self.fail("float literal overflow"); + }; + self.advance(); + return try self.createNode(start, .{ .float_literal = .{ .value = value } }); + }, + .string_literal => { + // raw includes quotes + const raw = self.tokenSlice(self.current); + self.advance(); + return try self.createNode(start, .{ .string_literal = .{ .raw = raw[1 .. raw.len - 1] } }); + }, + .kw_true => { + self.advance(); + return try self.createNode(start, .{ .bool_literal = .{ .value = true } }); + }, + .kw_false => { + self.advance(); + return try self.createNode(start, .{ .bool_literal = .{ .value = false } }); + }, + .identifier => { + const name = self.tokenSlice(self.current); + // Check if this identifier is a type name (e.g. s32, u8, s128) + if (Type.fromName(name) != null) { + self.advance(); + return try self.createNode(start, .{ .type_expr = .{ .name = name } }); + } + self.advance(); + return try self.createNode(start, .{ .identifier = .{ .name = name } }); + }, + .dot => { + self.advance(); + // Anonymous struct literal: .{ ... } + if (self.current.tag == .l_brace) { + return self.parseStructLiteral(null, null, start); + } + // Array literal: .[expr, expr, ...] + if (self.current.tag == .l_bracket) { + self.advance(); // skip '[' + var elements = std.ArrayList(*Node).empty; + while (self.current.tag != .r_bracket and self.current.tag != .eof) { + if (elements.items.len > 0) { + try self.expect(.comma); + } + const elem = try self.parseExpr(); + try elements.append(self.allocator, elem); + } + try self.expect(.r_bracket); + return try self.createNode(start, .{ .array_literal = .{ .elements = try elements.toOwnedSlice(self.allocator) } }); + } + // Enum literal: .variant_name + if (self.current.tag != .identifier) { + return self.fail("expected variant name, '{', or '[' after '.'"); + } + const name = self.tokenSlice(self.current); + self.advance(); + // Union literal: .variant(payload) + if (self.current.tag == .l_paren) { + self.advance(); // skip '(' + const payload = try self.parseExpr(); + try self.expect(.r_paren); + return try self.createNode(start, .{ .union_literal = .{ + .union_name = null, + .variant_name = name, + .payload = payload, + } }); + } + return try self.createNode(start, .{ .enum_literal = .{ .name = name } }); + }, + .l_paren => { + // Lambda: (params) => expr + if (self.isLambda()) { + return self.parseLambda(); + } + // Grouped expression + self.advance(); + const expr = try self.parseExpr(); + try self.expect(.r_paren); + return expr; + }, + .kw_f32, .kw_f64, .kw_Type => { + // Type keyword used as expression (for type aliases: SOME_TYPE :: f64;) + const name = self.tokenSlice(self.current); + self.advance(); + return try self.createNode(start, .{ .type_expr = .{ .name = name } }); + }, + .kw_struct => { + // Anonymous struct expression: struct { value: T; count: u32; } + return try self.parseStructDecl("__anon", start); + }, + .kw_union => { + // Anonymous union expression: union { variant: T; other: u32; } + return try self.parseUnionDecl("__anon", start); + }, + .kw_if => { + return self.parseIfExpr(); + }, + .kw_while => { + return self.parseWhileExpr(); + }, + .kw_for => { + return self.parseForExpr(); + }, + .kw_break => { + self.advance(); + return try self.createNode(start, .{ .break_expr = {} }); + }, + .kw_continue => { + self.advance(); + return try self.createNode(start, .{ .continue_expr = {} }); + }, + .kw_return => { + self.advance(); + // return with optional value + const value = if (self.current.tag != .semicolon and self.current.tag != .eof) + try self.parseExpr() + else + null; + return try self.createNode(start, .{ .return_stmt = .{ .value = value } }); + }, + .l_brace => { + return self.parseBlock(); + }, + .triple_minus => { + self.advance(); + return try self.createNode(start, .{ .undef_literal = {} }); + }, + .hash_run => { + self.advance(); // skip '#run' + const inner = try self.parseExpr(); + return try self.createNode(start, .{ .comptime_expr = .{ .expr = inner } }); + }, + else => { + return self.fail("unexpected token in expression"); + }, + } + } + + fn parseIfExpr(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + self.advance(); // skip 'if' + + // Parse condition at prec 5 (arithmetic+), leaving all comparisons + // unconsumed for manual handling with match disambiguation. + var condition = try self.parseBinary(5); + + // Handle comparisons with chain detection and match disambiguation. + // All comparisons (< <= > >= == !=) are at the same precedence. + if (self.isComparisonToken()) { + var operands = std.ArrayList(*Node).empty; + var ops = std.ArrayList(ast.BinaryOp.Op).empty; + try operands.append(self.allocator, condition); + + while (self.isComparisonToken()) { + // Match disambiguation: == followed by { is a match expression + if (self.current.tag == .equal_equal) { + self.advance(); + if (self.current.tag == .l_brace) { + // Match expression: if expr == { case ... } + // Only valid as the first comparison (no chain before it) + if (ops.items.len == 0) { + return self.parseMatchBody(condition, start); + } + // Chain followed by == { is an error — fall through to + // regular comparison (will likely fail at parse time) + } + const rhs = try self.parseBinary(5); + try operands.append(self.allocator, rhs); + try ops.append(self.allocator, .eq); + } else { + const cmp_op = self.binaryOp() orelse break; + self.advance(); + const rhs = try self.parseBinary(5); + try operands.append(self.allocator, rhs); + try ops.append(self.allocator, cmp_op); + } + } + + if (ops.items.len == 1) { + // Single comparison — regular binary_op + condition = try self.createNode(condition.span.start, .{ .binary_op = .{ + .op = ops.items[0], + .lhs = operands.items[0], + .rhs = operands.items[1], + } }); + } else { + // Chained comparison + condition = try self.createNode(condition.span.start, .{ .chained_comparison = .{ + .operands = try operands.toOwnedSlice(self.allocator), + .ops = try ops.toOwnedSlice(self.allocator), + } }); + } + } + + // Handle and/or with proper Pratt precedence + condition = try self.parseBinaryRhs(condition, 1); + + // Inline form: if cond then expr [else expr] + if (self.current.tag == .kw_then) { + self.advance(); + const then_branch = try self.parseExpr(); + var else_branch: ?*Node = null; + if (self.current.tag == .kw_else) { + self.advance(); + else_branch = try self.parseExpr(); + } + return try self.createNode(start, .{ .if_expr = .{ + .condition = condition, + .then_branch = then_branch, + .else_branch = else_branch, + .is_inline = true, + } }); + } + + // Block form: if cond { ... } else { ... } + const then_branch = try self.parseBlock(); + var else_branch: ?*Node = null; + if (self.current.tag == .kw_else) { + self.advance(); + if (self.current.tag == .kw_if) { + else_branch = try self.parseIfExpr(); + } else { + else_branch = try self.parseBlock(); + } + } + return try self.createNode(start, .{ .if_expr = .{ + .condition = condition, + .then_branch = then_branch, + .else_branch = else_branch, + .is_inline = false, + } }); + } + + fn parseWhileExpr(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + self.advance(); // skip 'while' + + const condition = try self.parseExpr(); + const body = try self.parseBlock(); + + return try self.createNode(start, .{ .while_expr = .{ + .condition = condition, + .body = body, + } }); + } + + fn parseForExpr(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + self.advance(); // skip 'for' + + const iterable = try self.parseExpr(); + const body = try self.parseBlock(); + + return try self.createNode(start, .{ .for_expr = .{ + .iterable = iterable, + .body = body, + } }); + } + + fn parseMatchBody(self: *Parser, subject: *Node, start_pos: u32) anyerror!*Node { + try self.expect(.l_brace); + var arms = std.ArrayList(ast.MatchArm).empty; + while (self.current.tag == .kw_case) { + const arm_start = self.current.loc.start; + self.advance(); // skip 'case' + // Allow keyword tokens (struct, enum, union) as type category names in match arms + const pattern: *Node = if (self.current.tag == .kw_struct or self.current.tag == .kw_enum or self.current.tag == .kw_union) blk: { + const name = self.tokenSlice(self.current); + self.advance(); + break :blk try self.createNode(arm_start, .{ .identifier = .{ .name = name } }); + } else try self.parsePrimary(); // .variant + try self.expect(.colon); + + if (self.current.tag == .kw_break) { + self.advance(); + try self.expect(.semicolon); + const body = try self.createNode(arm_start, .{ .block = .{ .stmts = &.{} } }); + try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = true }); + } else { + const stmts_start = self.current.loc.start; + var stmts = std.ArrayList(*Node).empty; + while (self.current.tag != .kw_case and self.current.tag != .kw_else and self.current.tag != .r_brace and self.current.tag != .eof) { + try stmts.append(self.allocator, try self.parseStmt()); + } + const body = try self.createNode(stmts_start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator) } }); + try arms.append(self.allocator, .{ .pattern = pattern, .body = body, .is_break = false }); + } + } + // Optional else arm (default) + if (self.current.tag == .kw_else) { + const else_start = self.current.loc.start; + self.advance(); // skip 'else' + try self.expect(.colon); + var stmts = std.ArrayList(*Node).empty; + while (self.current.tag != .r_brace and self.current.tag != .eof) { + try stmts.append(self.allocator, try self.parseStmt()); + } + const body = try self.createNode(else_start, .{ .block = .{ .stmts = try stmts.toOwnedSlice(self.allocator) } }); + try arms.append(self.allocator, .{ .pattern = null, .body = body, .is_break = false }); + } + try self.expect(.r_brace); + return try self.createNode(start_pos, .{ .match_expr = .{ .subject = subject, .arms = try arms.toOwnedSlice(self.allocator) } }); + } + + fn isLambda(self: *Parser) bool { + // Peek ahead: save state, scan to matching ), check if => or -> ... => follows + const saved_lexer = self.lexer; + const saved_current = self.current; + const saved_prev_end = self.prev_end; + defer { + self.lexer = saved_lexer; + self.current = saved_current; + self.prev_end = saved_prev_end; + } + + self.advance(); // skip '(' + var depth: u32 = 1; + while (depth > 0 and self.current.tag != .eof) { + if (self.current.tag == .l_paren) depth += 1; + if (self.current.tag == .r_paren) depth -= 1; + if (depth > 0) self.advance(); + } + if (self.current.tag == .r_paren) { + self.advance(); // skip ')' + if (self.current.tag == .fat_arrow) return true; + // (params) -> ReturnType => expr + if (self.current.tag == .arrow) { + self.advance(); // skip '->' + // Skip past the return type tokens until we see '=>' or something unexpected + while (self.current.tag != .eof) { + if (self.current.tag == .fat_arrow) return true; + // Return type tokens: identifiers, dots, parens, type keywords, dollar, brackets + if (self.current.tag == .identifier or self.current.tag.isTypeKeyword() or + self.current.tag == .dot or self.current.tag == .dollar or + self.current.tag == .l_bracket or self.current.tag == .r_bracket or + self.current.tag == .l_paren or self.current.tag == .r_paren or + self.current.tag == .comma or self.current.tag == .int_literal) + { + self.advance(); + } else break; + } + } + } + return false; + } + + fn parseLambda(self: *Parser) anyerror!*Node { + const start = self.current.loc.start; + const params = try self.parseParams(); + + // Optional return type: (params) -> Type => expr + var return_type: ?*Node = null; + if (self.current.tag == .arrow) { + self.advance(); + return_type = try self.parseTypeExpr(); + } + + try self.expect(.fat_arrow); + const body = try self.parseExpr(); + const type_params = try self.collectTypeParams(params); + return try self.createNode(start, .{ .lambda = .{ + .params = params, + .return_type = return_type, + .body = body, + .type_params = type_params, + } }); + } + + // ---- Helpers ---- + + fn isFunctionDef(self: *Parser) bool { + // Peek ahead: save state, scan to matching ), check what follows + const saved_lexer = self.lexer; + const saved_current = self.current; + const saved_prev_end = self.prev_end; + defer { + self.lexer = saved_lexer; + self.current = saved_current; + self.prev_end = saved_prev_end; + } + + self.advance(); // skip '(' + var depth: u32 = 1; + while (depth > 0 and self.current.tag != .eof) { + if (self.current.tag == .l_paren) depth += 1; + if (self.current.tag == .r_paren) depth -= 1; + if (depth > 0) self.advance(); + } + if (self.current.tag == .r_paren) { + self.advance(); // skip ')' + // Function if followed by '{', '->', '#builtin', or '=>' + return self.current.tag == .l_brace or self.current.tag == .arrow or self.current.tag == .hash_builtin or self.current.tag == .fat_arrow; + } + return false; + } + + fn isAssignOp(self: *const Parser) bool { + return switch (self.current.tag) { + .equal, .plus_equal, .minus_equal, .star_equal, .slash_equal, .percent_equal => true, + else => false, + }; + } + + fn assignOp(self: *const Parser) ast.Assignment.Op { + return switch (self.current.tag) { + .equal => .assign, + .plus_equal => .add_assign, + .minus_equal => .sub_assign, + .star_equal => .mul_assign, + .slash_equal => .div_assign, + .percent_equal => .mod_assign, + else => unreachable, + }; + } + + fn binaryPrec(self: *const Parser) u8 { + return switch (self.current.tag) { + .kw_or => 1, + .kw_and => 2, + .equal_equal, .bang_equal, .less, .less_equal, .greater, .greater_equal => 4, + .plus, .minus => 5, + .star, .slash, .percent => 6, + else => 0, + }; + } + + fn binaryOp(self: *const Parser) ?ast.BinaryOp.Op { + return switch (self.current.tag) { + .kw_and => .and_op, + .kw_or => .or_op, + .plus => .add, + .minus => .sub, + .star => .mul, + .slash => .div, + .percent => .mod, + .equal_equal => .eq, + .bang_equal => .neq, + .less => .lt, + .less_equal => .lte, + .greater => .gt, + .greater_equal => .gte, + else => null, + }; + } + + fn isComparisonOp(op: ast.BinaryOp.Op) bool { + return switch (op) { + .lt, .lte, .gt, .gte, .eq, .neq => true, + else => false, + }; + } + + fn isComparisonToken(self: *const Parser) bool { + return switch (self.current.tag) { + .less, .less_equal, .greater, .greater_equal, .equal_equal, .bang_equal => true, + else => false, + }; + } + + fn advance(self: *Parser) void { + self.prev_end = self.current.loc.end; + self.current = self.lexer.next(); + } + + fn expect(self: *Parser, tag: Tag) !void { + if (self.current.tag != tag) { + const expected = tag.lexeme() orelse @tagName(tag); + return self.failFmt("expected '{s}'", .{expected}); + } + self.advance(); + } + + fn failFmt(self: *Parser, comptime fmt: []const u8, args: anytype) error{ParseError} { + const msg = std.fmt.allocPrint(self.allocator, fmt, args) catch return error.ParseError; + return self.fail(msg); + } + + fn tokenSlice(self: *const Parser, token: Token) []const u8 { + return self.source[token.loc.start..token.loc.end]; + } + + fn fail(self: *Parser, msg: []const u8) error{ParseError} { + self.err_msg = msg; + self.err_offset = self.current.loc.start; + if (self.diagnostics) |diags| { + diags.add(.err, msg, .{ .start = self.current.loc.start, .end = self.current.loc.end }); + } + return error.ParseError; + } +}; + +test "parse minimal main" { + const source = "main :: () { 42; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expect(root.data == .root); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .fn_decl); + try std.testing.expectEqualStrings("main", decl.data.fn_decl.name); + const body = decl.data.fn_decl.body; + try std.testing.expect(body.data == .block); + try std.testing.expectEqual(@as(usize, 1), body.data.block.stmts.len); + try std.testing.expect(body.data.block.stmts[0].data == .int_literal); + try std.testing.expectEqual(@as(i64, 42), body.data.block.stmts[0].data.int_literal.value); +} + +test "parse #run const binding" { + const source = "x :: #run compute(5);"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .const_decl); + try std.testing.expectEqualStrings("x", decl.data.const_decl.name); + try std.testing.expect(decl.data.const_decl.value.data == .comptime_expr); + // inner expr is a call + try std.testing.expect(decl.data.const_decl.value.data.comptime_expr.expr.data == .call); +} + +test "parse top-level #run" { + const source = "#run main();"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .comptime_expr); + // inner expr is a call + try std.testing.expect(decl.data.comptime_expr.expr.data == .call); +} + +test "parse flat import" { + const source = "#import \"modules/std/math.sx\";"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .import_decl); + try std.testing.expectEqualStrings("modules/std/math.sx", decl.data.import_decl.path); + try std.testing.expect(decl.data.import_decl.name == null); +} + +test "parse namespaced import" { + const source = "std :: #import \"modules/std/std.sx\";"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .import_decl); + try std.testing.expectEqualStrings("modules/std/std.sx", decl.data.import_decl.path); + try std.testing.expectEqualStrings("std", decl.data.import_decl.name.?); +} + +test "parse void function with builtin body" { + const source = "foo :: () #builtin;"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .fn_decl); + try std.testing.expectEqualStrings("foo", decl.data.fn_decl.name); + try std.testing.expect(decl.data.fn_decl.body.data == .builtin_expr); +} + +test "parse void function with arrow body" { + const source = "foo :: () => 42;"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + try std.testing.expectEqual(@as(usize, 1), root.data.root.decls.len); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .fn_decl); + try std.testing.expectEqualStrings("foo", decl.data.fn_decl.name); + try std.testing.expect(decl.data.fn_decl.body.data == .int_literal); + try std.testing.expectEqual(@as(i64, 42), decl.data.fn_decl.body.data.int_literal.value); +} + +test "parse hex and binary literals" { + const source = "main :: () { 0xFF; 0b1010; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + const body = root.data.root.decls[0].data.fn_decl.body; + try std.testing.expectEqual(@as(usize, 2), body.data.block.stmts.len); + try std.testing.expectEqual(@as(i64, 255), body.data.block.stmts[0].data.int_literal.value); + try std.testing.expectEqual(@as(i64, 10), body.data.block.stmts[1].data.int_literal.value); +} + +test "parse array type with identifier length" { + const source = "foo :: (arr: [N]f32) => arr;"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .fn_decl); + const param_type = decl.data.fn_decl.params[0].type_expr; + try std.testing.expect(param_type.data == .array_type_expr); + // length is an identifier "N", not an int literal + try std.testing.expect(param_type.data.array_type_expr.length.data == .identifier); + try std.testing.expectEqualStrings("N", param_type.data.array_type_expr.length.data.identifier.name); + try std.testing.expect(param_type.data.array_type_expr.element_type.data == .type_expr); +} + +test "parse lambda with generic params" { + const source = "f :: (x: $T) => x;"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .const_decl); + const lambda = decl.data.const_decl.value; + try std.testing.expect(lambda.data == .lambda); + try std.testing.expectEqual(@as(usize, 1), lambda.data.lambda.params.len); + try std.testing.expectEqualStrings("x", lambda.data.lambda.params[0].name); + // has generic type param + try std.testing.expectEqual(@as(usize, 1), lambda.data.lambda.type_params.len); + try std.testing.expectEqualStrings("T", lambda.data.lambda.type_params[0].name); +} + +test "parse lambda with return type" { + const source = "f :: (x: s32) -> s32 => x;"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + const decl = root.data.root.decls[0]; + try std.testing.expect(decl.data == .const_decl); + const lambda = decl.data.const_decl.value; + try std.testing.expect(lambda.data == .lambda); + try std.testing.expect(lambda.data.lambda.return_type != null); + try std.testing.expect(lambda.data.lambda.return_type.?.data == .type_expr); + try std.testing.expectEqualStrings("s32", lambda.data.lambda.return_type.?.data.type_expr.name); +} + +test "parse match with else arm" { + const source = + \\main :: () { + \\ x := 5; + \\ if x == { + \\ case 1: 10; + \\ else: 99; + \\ }; + \\} + ; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const root = try parser.parse(); + const body = root.data.root.decls[0].data.fn_decl.body; + // second stmt is the match expr (after var decl) + const match_node = body.data.block.stmts[1]; + try std.testing.expect(match_node.data == .match_expr); + const arms = match_node.data.match_expr.arms; + try std.testing.expectEqual(@as(usize, 2), arms.len); + // first arm has a pattern + try std.testing.expect(arms[0].pattern != null); + // second arm is the else arm (null pattern) + try std.testing.expect(arms[1].pattern == null); +} + +test "integer literal overflow error" { + const source = "main :: () { 99999999999999999999; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + const result = parser.parse(); + try std.testing.expectError(error.ParseError, result); + try std.testing.expectEqualStrings("integer literal overflow", parser.err_msg.?); +} diff --git a/src/root.zig b/src/root.zig new file mode 100644 index 0000000..96d64ea --- /dev/null +++ b/src/root.zig @@ -0,0 +1,19 @@ +pub const llvm_api = @import("llvm_api.zig"); +pub const token = @import("token.zig"); +pub const lexer = @import("lexer.zig"); +pub const ast = @import("ast.zig"); +pub const parser = @import("parser.zig"); +pub const types = @import("types.zig"); +pub const codegen = @import("codegen.zig"); +pub const builtins = @import("builtins.zig"); +pub const errors = @import("errors.zig"); +pub const sema = @import("sema.zig"); +pub const imports = @import("imports.zig"); +pub const core = @import("core.zig"); + +pub const lsp = struct { + pub const server = @import("lsp/server.zig"); + pub const transport = @import("lsp/transport.zig"); + pub const types = @import("lsp/types.zig"); + pub const document = @import("lsp/document.zig"); +}; diff --git a/src/sema.zig b/src/sema.zig new file mode 100644 index 0000000..72ee22d --- /dev/null +++ b/src/sema.zig @@ -0,0 +1,1006 @@ +const std = @import("std"); +const ast = @import("ast.zig"); +const Node = ast.Node; +const Span = ast.Span; +const Type = @import("types.zig").Type; +const errors = @import("errors.zig"); +const Diagnostic = errors.Diagnostic; + +pub const SymbolKind = enum { + variable, + constant, + function, + enum_type, + struct_type, + type_alias, + param, + namespace, +}; + +pub const Symbol = struct { + name: []const u8, + kind: SymbolKind, + ty: ?Type, + def_span: Span, + scope_depth: u32, +}; + +pub const Reference = struct { + span: Span, + symbol_index: u32, +}; + +pub const FnSignature = struct { + param_types: []const Type, + return_type: Type, + is_variadic: bool = false, +}; + +pub const StructTypeInfo = struct { + field_names: []const []const u8, + field_types: []const Type, +}; + +pub const TypeMap = std.AutoHashMap(*const Node, Type); + +pub const SemaResult = struct { + symbols: []const Symbol, + references: []const Reference, + diagnostics: []const Diagnostic, + fn_signatures: std.StringHashMap(FnSignature), + struct_types: std.StringHashMap(StructTypeInfo), + enum_types: std.StringHashMap([]const []const u8), + type_aliases: std.StringHashMap([]const u8), + type_map: TypeMap, +}; + +pub const Analyzer = struct { + allocator: std.mem.Allocator, + symbols: std.ArrayList(Symbol), + references: std.ArrayList(Reference), + diagnostics: std.ArrayList(Diagnostic), + scope_depth: u32, + /// Stack of symbol counts at each scope entry, for popScope cleanup. + scope_starts: std.ArrayList(u32), + // Type registries + fn_signatures: std.StringHashMap(FnSignature), + struct_types: std.StringHashMap(StructTypeInfo), + enum_types: std.StringHashMap([]const []const u8), + type_aliases: std.StringHashMap([]const u8), + type_map: TypeMap, + + pub fn init(allocator: std.mem.Allocator) Analyzer { + return .{ + .allocator = allocator, + .symbols = std.ArrayList(Symbol).empty, + .references = std.ArrayList(Reference).empty, + .diagnostics = std.ArrayList(Diagnostic).empty, + .scope_depth = 0, + .scope_starts = std.ArrayList(u32).empty, + .fn_signatures = std.StringHashMap(FnSignature).init(allocator), + .struct_types = std.StringHashMap(StructTypeInfo).init(allocator), + .enum_types = std.StringHashMap([]const []const u8).init(allocator), + .type_aliases = std.StringHashMap([]const u8).init(allocator), + .type_map = TypeMap.init(allocator), + }; + } + + pub fn analyze(self: *Analyzer, root: *Node) !SemaResult { + if (root.data != .root) return error.InvalidRoot; + + // Pass 1: Register all top-level declarations so forward references work. + for (root.data.root.decls) |decl| { + try self.registerTopLevelDecl(decl); + } + + // Pass 2: Analyze bodies (all top-level names are now in scope). + for (root.data.root.decls) |decl| { + try self.analyzeTopLevelDecl(decl); + } + + return .{ + .symbols = try self.symbols.toOwnedSlice(self.allocator), + .references = try self.references.toOwnedSlice(self.allocator), + .diagnostics = try self.diagnostics.toOwnedSlice(self.allocator), + .fn_signatures = self.fn_signatures, + .struct_types = self.struct_types, + .enum_types = self.enum_types, + .type_aliases = self.type_aliases, + .type_map = self.type_map, + }; + } + + /// Pass 1: register the name/kind/type of a top-level declaration without + /// analysing its body or value expression. + fn registerTopLevelDecl(self: *Analyzer, node: *Node) !void { + try self.registerTopLevelDeclPrefixed(node, null); + } + + fn registerTopLevelDeclPrefixed(self: *Analyzer, node: *Node, ns_prefix: ?[]const u8) !void { + switch (node.data) { + .fn_decl => |fd| { + const ret_ty = resolveReturnType(fd); + try self.addSymbol(fd.name, .function, ret_ty, node.span); + // Populate fn_signatures registry + var param_types = std.ArrayList(Type).empty; + var has_variadic = false; + for (fd.params) |param| { + const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(32); + if (param.is_variadic) { + has_variadic = true; + // Variadic param becomes a slice type + const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; + try param_types.append(self.allocator, .{ .slice_type = .{ .element_name = elem_name } }); + } else { + try param_types.append(self.allocator, pt); + } + } + const key = if (ns_prefix) |pfx| + try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ pfx, fd.name }) + else + fd.name; + try self.fn_signatures.put(key, .{ + .param_types = try param_types.toOwnedSlice(self.allocator), + .return_type = ret_ty orelse .void_type, + .is_variadic = has_variadic, + }); + }, + .const_decl => |cd| { + const ty = resolveTypeAnnotation(cd.type_annotation) orelse inferValueType(cd.value); + const kind = classifyConstDecl(cd); + try self.addSymbol(cd.name, kind, ty, node.span); + // Populate type_aliases registry + if (cd.value.data == .type_expr) { + try self.type_aliases.put(cd.name, cd.value.data.type_expr.name); + } + // Lambda as function + if (cd.value.data == .lambda) { + const lam = cd.value.data.lambda; + var param_types = std.ArrayList(Type).empty; + for (lam.params) |param| { + const pt = Type.fromTypeExpr(param.type_expr) orelse Type.s(32); + try param_types.append(self.allocator, pt); + } + const ret = if (lam.return_type) |rt| Type.fromTypeExpr(rt) orelse .void_type else .void_type; + const key = if (ns_prefix) |pfx| + try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ pfx, cd.name }) + else + cd.name; + try self.fn_signatures.put(key, .{ + .param_types = try param_types.toOwnedSlice(self.allocator), + .return_type = ret, + }); + } + }, + .var_decl => |vd| { + const ty = resolveTypeAnnotation(vd.type_annotation); + try self.addSymbol(vd.name, .variable, ty, node.span); + }, + .enum_decl => |ed| { + try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span); + try self.enum_types.put(ed.name, ed.variants); + }, + .struct_decl => |sd| { + try self.addSymbol(sd.name, .struct_type, .{ .struct_type = sd.name }, node.span); + // Populate struct_types registry + var field_types = std.ArrayList(Type).empty; + for (sd.field_types) |ft| { + const resolved = Type.fromTypeExpr(ft) orelse Type.s(32); + try field_types.append(self.allocator, resolved); + } + try self.struct_types.put(sd.name, .{ + .field_names = sd.field_names, + .field_types = try field_types.toOwnedSlice(self.allocator), + }); + }, + .union_decl => |ud| { + try self.addSymbol(ud.name, .enum_type, .{ .union_type = ud.name }, node.span); + }, + .namespace_decl => |ns| { + try self.addSymbol(ns.name, .namespace, null, node.span); + // Recurse into namespace decls with qualified prefix (in own scope + // so inner names don't collide with flat imports of the same names) + try self.pushScope(); + for (ns.decls) |d| { + try self.registerTopLevelDeclPrefixed(d, ns.name); + } + self.popScope(); + }, + else => {}, + } + } + + /// Resolve a type annotation node to a Type. + /// Handles primitives, type_expr, array_type_expr, parameterized_type_expr, + /// type aliases, enum types, and struct types. + pub fn resolveTypeNode(self: *Analyzer, type_node: ?*Node) Type { + if (type_node) |tn| { + if (Type.fromTypeExpr(tn)) |t| return t; + // Array type: [N]T + if (tn.data == .array_type_expr) { + const ate = tn.data.array_type_expr; + const length: u32 = @intCast(ate.length.data.int_literal.value); + const elem_type = self.resolveTypeNode(ate.element_type); + const elem_name = elem_type.displayName(self.allocator) catch return .void_type; + return .{ .array_type = .{ .element_name = elem_name, .length = length } }; + } + // Parameterized type: Vector(N, T) or generic struct + if (tn.data == .parameterized_type_expr) { + // For now, skip generic instantiation — just return void_type + // (will be extended when generic support is added to sema) + return .void_type; + } + // type_expr or identifier — check aliases, enums, structs + if (tn.data == .type_expr or tn.data == .identifier) { + const name = if (tn.data == .type_expr) tn.data.type_expr.name else tn.data.identifier.name; + if (Type.fromName(name)) |t| return t; + if (self.type_aliases.get(name)) |target| { + if (Type.fromName(target)) |t| return t; + if (self.struct_types.contains(target)) return .{ .struct_type = target }; + } + if (self.enum_types.contains(name)) return .{ .enum_type = name }; + if (self.struct_types.contains(name)) return .{ .struct_type = name }; + } + return .void_type; + } + return .void_type; + } + + /// Infer the type of an expression node without LLVM. + /// Uses fn_signatures for call return types, struct_types for field access, + /// symbols for identifier types, and Type.widen for arithmetic promotion. + pub fn inferExprType(self: *Analyzer, node: *const Node) Type { + return switch (node.data) { + .int_literal => Type.s(32), + .float_literal => .f32, + .bool_literal => .boolean, + .string_literal => .string_type, + .insert_expr => .void_type, + .comptime_expr => |ct| self.inferExprType(ct.expr), + .binary_op => |binop| { + switch (binop.op) { + .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op => return .boolean, + else => { + const lhs_ty = self.inferExprType(binop.lhs); + const rhs_ty = self.inferExprType(binop.rhs); + return Type.widen(lhs_ty, rhs_ty); + }, + } + }, + .chained_comparison => .boolean, + .identifier => |ident| { + // Search symbols backwards for matching name at or above current scope + var i = self.symbols.items.len; + while (i > 0) { + i -= 1; + const sym = self.symbols.items[i]; + if (sym.scope_depth <= self.scope_depth and std.mem.eql(u8, sym.name, ident.name)) { + return sym.ty orelse Type.s(32); + } + } + return Type.s(32); + }, + .if_expr => |ie| { + return self.inferExprType(ie.then_branch); + }, + .block => |blk| { + if (blk.stmts.len > 0) { + return self.inferExprType(blk.stmts[blk.stmts.len - 1]); + } + return .void_type; + }, + .match_expr => |me| { + for (me.arms) |arm| { + if (!arm.is_break) return self.inferExprType(arm.body); + } + return .void_type; + }, + .call => |call_node| { + const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(32); + // Check fn_signatures registry + if (self.fn_signatures.get(callee_name)) |sig| { + return sig.return_type; + } + // Built-in: sqrt returns same type as argument + const base = if (std.mem.lastIndexOfScalar(u8, callee_name, '.')) |idx| callee_name[idx + 1 ..] else callee_name; + if (std.mem.eql(u8, base, "sqrt")) { + if (call_node.args.len > 0) return self.inferExprType(call_node.args[0]); + return .f32; + } + return Type.s(32); + }, + .unary_op => |unop| { + return self.inferExprType(unop.operand); + }, + .field_access => |fa| { + const obj_ty = self.inferExprType(fa.object); + if (obj_ty == .string_type) { + if (std.mem.eql(u8, fa.field, "len")) return Type.s(32); + if (std.mem.eql(u8, fa.field, "ptr")) return .string_type; + } + if (obj_ty.isStruct()) { + if (self.struct_types.get(obj_ty.struct_type)) |info| { + for (info.field_names, 0..) |fname, idx| { + if (std.mem.eql(u8, fname, fa.field)) { + return info.field_types[idx]; + } + } + } + } + if (obj_ty.isArray()) { + return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(32); + } + return Type.s(32); + }, + .index_expr => |ie| { + const obj_ty = self.inferExprType(ie.object); + if (obj_ty == .string_type) return Type.s(32); + if (obj_ty.isArray()) { + return Type.fromName(obj_ty.array_type.element_name) orelse Type.s(32); + } + return Type.s(32); + }, + .while_expr => .void_type, + .for_expr => .void_type, + .spread_expr => .void_type, + .break_expr => .void_type, + .continue_expr => .void_type, + .enum_literal => .{ .enum_type = "" }, + .union_literal => |ul| { + if (ul.union_name) |name| return .{ .union_type = name }; + return .void_type; + }, + .struct_literal => |sl| { + if (sl.struct_name) |name| { + if (self.struct_types.contains(name)) return .{ .struct_type = name }; + if (self.type_aliases.get(name)) |target| { + if (self.struct_types.contains(target)) return .{ .struct_type = target }; + } + } else if (sl.type_expr) |te| { + return self.inferExprType(te); + } + return .void_type; + }, + .array_literal => .void_type, + .type_expr => |te| .{ .meta_type = .{ .name = te.name } }, + else => .void_type, + }; + } + + /// Resolve the callee name from a call node (handles identifiers and field_access). + fn resolveCalleeName(self: *Analyzer, call_node: ast.Call) ?[]const u8 { + _ = self; + if (call_node.callee.data == .identifier) { + return call_node.callee.data.identifier.name; + } + if (call_node.callee.data == .field_access) { + const fa = call_node.callee.data.field_access; + if (fa.object.data == .identifier) { + // Return qualified name — caller will look up in fn_signatures + // We can't allocate here easily, so just return the field name + // and let the caller try both qualified and unqualified + return fa.field; + } + } + return null; + } + + /// Pass 2: analyse the body/value of a top-level declaration. + /// The symbol itself was already registered in Pass 1. + fn analyzeTopLevelDecl(self: *Analyzer, node: *Node) !void { + switch (node.data) { + .fn_decl => |fd| { + try self.pushScope(); + for (fd.params) |param| { + const param_type = Type.fromTypeExpr(param.type_expr); + try self.addSymbol(param.name, .param, param_type, param.name_span); + } + try self.analyzeNode(fd.body); + self.popScope(); + }, + .const_decl => |cd| { + try self.analyzeNode(cd.value); + }, + .var_decl => |vd| { + if (vd.value) |val| { + try self.analyzeNode(val); + } + }, + .enum_decl, .struct_decl, .union_decl, .array_type_expr, .array_literal, .parameterized_type_expr, .index_expr, .insert_expr => {}, + .namespace_decl => |ns| { + try self.pushScope(); + for (ns.decls) |d| { + try self.registerTopLevelDecl(d); + } + for (ns.decls) |d| { + try self.analyzeTopLevelDecl(d); + } + self.popScope(); + }, + else => { + try self.analyzeNode(node); + }, + } + } + + fn pushScope(self: *Analyzer) !void { + try self.scope_starts.append(self.allocator, @intCast(self.symbols.items.len)); + self.scope_depth += 1; + } + + fn popScope(self: *Analyzer) void { + if (self.scope_starts.items.len > 0) { + _ = self.scope_starts.pop(); + self.scope_depth -= 1; + } + } + + fn addSymbol(self: *Analyzer, name: []const u8, kind: SymbolKind, ty: ?Type, span: Span) !void { + // Check for duplicate only within the current scope window. + const scope_start: usize = if (self.scope_starts.items.len > 0) + self.scope_starts.items[self.scope_starts.items.len - 1] + else + 0; + for (self.symbols.items[scope_start..]) |sym| { + if (sym.scope_depth == self.scope_depth and std.mem.eql(u8, sym.name, name)) { + try self.diagnostics.append(self.allocator, .{ + .level = .warn, + .span = span, + .message = "duplicate declaration", + }); + break; + } + } + + try self.symbols.append(self.allocator, .{ + .name = name, + .kind = kind, + .ty = ty, + .def_span = span, + .scope_depth = self.scope_depth, + }); + } + + fn resolveIdentifier(self: *Analyzer, name: []const u8, span: Span) !void { + // Search backwards to find the most recent declaration with this name + // that is at or above the current scope depth. + var i = self.symbols.items.len; + while (i > 0) { + i -= 1; + const sym = self.symbols.items[i]; + if (sym.scope_depth <= self.scope_depth and std.mem.eql(u8, sym.name, name)) { + try self.references.append(self.allocator, .{ + .span = span, + .symbol_index = @intCast(i), + }); + return; + } + } + + // Built-in names that aren't declared in source + if (std.mem.eql(u8, name, "io")) return; + if (std.mem.eql(u8, name, "true") or std.mem.eql(u8, name, "false")) return; + if (std.mem.eql(u8, name, "cast")) return; + + try self.diagnostics.append(self.allocator, .{ + .level = .warn, + .span = span, + .message = "undefined variable", + }); + } + + fn analyzeNode(self: *Analyzer, node: *Node) !void { + switch (node.data) { + .fn_decl => |fd| { + try self.addSymbol(fd.name, .function, resolveReturnType(fd), node.span); + try self.pushScope(); + // Add params as symbols + for (fd.params) |param| { + const param_type = Type.fromTypeExpr(param.type_expr); + try self.addSymbol(param.name, .param, param_type, param.name_span); + } + try self.analyzeNode(fd.body); + self.popScope(); + }, + .block => |blk| { + try self.pushScope(); + for (blk.stmts) |stmt| { + try self.analyzeNode(stmt); + } + self.popScope(); + }, + .const_decl => |cd| { + // Analyze value first (so it can't reference itself) + try self.analyzeNode(cd.value); + const ty = resolveTypeAnnotation(cd.type_annotation) orelse inferValueType(cd.value); + const kind = classifyConstDecl(cd); + try self.addSymbol(cd.name, kind, ty, node.span); + }, + .var_decl => |vd| { + if (vd.value) |val| { + try self.analyzeNode(val); + } + const ty = resolveTypeAnnotation(vd.type_annotation); + try self.addSymbol(vd.name, .variable, ty, node.span); + }, + .enum_decl => |ed| { + try self.addSymbol(ed.name, .enum_type, .{ .enum_type = ed.name }, node.span); + }, + .struct_decl => |sd| { + try self.addSymbol(sd.name, .struct_type, .{ .struct_type = sd.name }, node.span); + }, + .identifier => |id| { + try self.resolveIdentifier(id.name, node.span); + }, + .binary_op => |bop| { + try self.analyzeNode(bop.lhs); + try self.analyzeNode(bop.rhs); + }, + .chained_comparison => |cc| { + for (cc.operands) |operand| { + try self.analyzeNode(operand); + } + }, + .unary_op => |uop| { + try self.analyzeNode(uop.operand); + }, + .call => |call| { + try self.analyzeNode(call.callee); + for (call.args) |arg| { + try self.analyzeNode(arg); + } + }, + .field_access => |fa| { + try self.analyzeNode(fa.object); + }, + .if_expr => |ie| { + try self.analyzeNode(ie.condition); + try self.analyzeNode(ie.then_branch); + if (ie.else_branch) |eb| { + try self.analyzeNode(eb); + } + }, + .match_expr => |me| { + try self.analyzeNode(me.subject); + for (me.arms) |arm| { + try self.analyzeNode(arm.body); + } + }, + .while_expr => |we| { + try self.analyzeNode(we.condition); + try self.analyzeNode(we.body); + }, + .for_expr => |fe| { + try self.analyzeNode(fe.iterable); + try self.analyzeNode(fe.body); + }, + .spread_expr => |se| try self.analyzeNode(se.operand), + .break_expr, .continue_expr => {}, + .assignment => |asgn| { + try self.analyzeNode(asgn.target); + try self.analyzeNode(asgn.value); + }, + .return_stmt => |ret| { + if (ret.value) |val| { + try self.analyzeNode(val); + } + }, + .defer_stmt => |ds| { + try self.analyzeNode(ds.expr); + }, + .comptime_expr => |ct| { + try self.analyzeNode(ct.expr); + }, + .insert_expr => |ins| { + try self.analyzeNode(ins.expr); + }, + .lambda => |lam| { + try self.pushScope(); + for (lam.params) |param| { + const param_type = Type.fromTypeExpr(param.type_expr); + try self.addSymbol(param.name, .param, param_type, param.name_span); + } + try self.analyzeNode(lam.body); + self.popScope(); + }, + .struct_literal => |sl| { + if (sl.type_expr) |te| try self.analyzeNode(te); + for (sl.field_inits) |fi| { + try self.analyzeNode(fi.value); + } + }, + .union_decl => |ud| { + try self.addSymbol(ud.name, .enum_type, .{ .union_type = ud.name }, node.span); + }, + .union_literal => |ul| { + if (ul.payload) |p| { + try self.analyzeNode(p); + } + }, + // Leaf nodes — nothing to recurse into + .int_literal, + .float_literal, + .bool_literal, + .string_literal, + .enum_literal, + .type_expr, + .param, + .match_arm, + .undef_literal, + .builtin_expr, + .import_decl, + .array_type_expr, + .array_literal, + .parameterized_type_expr, + .index_expr, + => {}, + .namespace_decl => |ns| { + for (ns.decls) |d| { + try self.analyzeNode(d); + } + }, + .root => { + // Should not appear nested + }, + } + + // Populate TypeMap for expression nodes + switch (node.data) { + .int_literal, + .float_literal, + .bool_literal, + .string_literal, + .identifier, + .binary_op, + .chained_comparison, + .unary_op, + .call, + .field_access, + .if_expr, + .match_expr, + .block, + .comptime_expr, + .enum_literal, + .struct_literal, + .union_literal, + .array_literal, + .index_expr, + .type_expr, + .insert_expr, + .while_expr, + .for_expr, + .spread_expr, + .break_expr, + .continue_expr, + => { + const ty = self.inferExprType(node); + self.type_map.put(node, ty) catch {}; + }, + else => {}, + } + } + + fn resolveReturnType(fd: ast.FnDecl) ?Type { + if (fd.return_type) |rt| { + return Type.fromTypeExpr(rt); + } + return null; + } + + fn resolveTypeAnnotation(type_node: ?*Node) ?Type { + if (type_node) |tn| { + return Type.fromTypeExpr(tn); + } + return null; + } + + fn inferValueType(value: *Node) ?Type { + return switch (value.data) { + .int_literal => Type.s(32), + .float_literal => .f64, + .bool_literal => .boolean, + .string_literal => .string_type, + .type_expr => null, // type alias — no value type + .lambda => null, + .comptime_expr => null, + .insert_expr => null, + else => null, + }; + } + + fn classifyConstDecl(cd: ast.ConstDecl) SymbolKind { + return switch (cd.value.data) { + .type_expr => .type_alias, + .lambda => .function, + else => .constant, + }; + } +}; + +/// Convenience: parse and analyze in one call. +pub fn analyzeSource(allocator: std.mem.Allocator, root: *Node) !SemaResult { + var analyzer = Analyzer.init(allocator); + return analyzer.analyze(root); +} + +/// Find the symbol whose definition span contains the given byte offset. +pub fn findSymbolAtOffset(symbols: []const Symbol, offset: u32) ?usize { + for (symbols, 0..) |sym, i| { + if (offset >= sym.def_span.start and offset < sym.def_span.end) { + return i; + } + } + return null; +} + +/// Find the reference at the given byte offset. +pub fn findReferenceAtOffset(references: []const Reference, offset: u32) ?usize { + for (references, 0..) |ref_, i| { + if (offset >= ref_.span.start and offset < ref_.span.end) { + return i; + } + } + return null; +} + +/// Walk the AST to find the innermost node whose span contains the offset. +pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node { + if (offset < node.span.start or offset >= node.span.end) return null; + + // Try to find a more specific child node + switch (node.data) { + .root => |r| { + for (r.decls) |decl| { + if (findNodeAtOffset(decl, offset)) |found| return found; + } + }, + .fn_decl => |fd| { + if (fd.return_type) |rt| { + if (findNodeAtOffset(rt, offset)) |found| return found; + } + if (findNodeAtOffset(fd.body, offset)) |found| return found; + }, + .block => |blk| { + for (blk.stmts) |stmt| { + if (findNodeAtOffset(stmt, offset)) |found| return found; + } + }, + .const_decl => |cd| { + if (cd.type_annotation) |ta| { + if (findNodeAtOffset(ta, offset)) |found| return found; + } + if (findNodeAtOffset(cd.value, offset)) |found| return found; + }, + .var_decl => |vd| { + if (vd.type_annotation) |ta| { + if (findNodeAtOffset(ta, offset)) |found| return found; + } + if (vd.value) |val| { + if (findNodeAtOffset(val, offset)) |found| return found; + } + }, + .binary_op => |bop| { + if (findNodeAtOffset(bop.lhs, offset)) |found| return found; + if (findNodeAtOffset(bop.rhs, offset)) |found| return found; + }, + .chained_comparison => |cc| { + for (cc.operands) |operand| { + if (findNodeAtOffset(operand, offset)) |found| return found; + } + }, + .unary_op => |uop| { + if (findNodeAtOffset(uop.operand, offset)) |found| return found; + }, + .call => |call| { + if (findNodeAtOffset(call.callee, offset)) |found| return found; + for (call.args) |arg| { + if (findNodeAtOffset(arg, offset)) |found| return found; + } + }, + .field_access => |fa| { + if (findNodeAtOffset(fa.object, offset)) |found| return found; + }, + .if_expr => |ie| { + if (findNodeAtOffset(ie.condition, offset)) |found| return found; + if (findNodeAtOffset(ie.then_branch, offset)) |found| return found; + if (ie.else_branch) |eb| { + if (findNodeAtOffset(eb, offset)) |found| return found; + } + }, + .match_expr => |me| { + if (findNodeAtOffset(me.subject, offset)) |found| return found; + for (me.arms) |arm| { + if (findNodeAtOffset(arm.body, offset)) |found| return found; + if (arm.pattern) |pat| { + if (findNodeAtOffset(pat, offset)) |found| return found; + } + } + }, + .while_expr => |we| { + if (findNodeAtOffset(we.condition, offset)) |found| return found; + if (findNodeAtOffset(we.body, offset)) |found| return found; + }, + .for_expr => |fe| { + if (findNodeAtOffset(fe.iterable, offset)) |found| return found; + if (findNodeAtOffset(fe.body, offset)) |found| return found; + }, + .spread_expr => |se| { + if (findNodeAtOffset(se.operand, offset)) |found| return found; + }, + .break_expr, .continue_expr => {}, + .assignment => |asgn| { + if (findNodeAtOffset(asgn.target, offset)) |found| return found; + if (findNodeAtOffset(asgn.value, offset)) |found| return found; + }, + .return_stmt => |ret| { + if (ret.value) |val| { + if (findNodeAtOffset(val, offset)) |found| return found; + } + }, + .defer_stmt => |ds| { + if (findNodeAtOffset(ds.expr, offset)) |found| return found; + }, + .comptime_expr => |ct| { + if (findNodeAtOffset(ct.expr, offset)) |found| return found; + }, + .insert_expr => |ins| { + if (findNodeAtOffset(ins.expr, offset)) |found| return found; + }, + .lambda => |lam| { + if (findNodeAtOffset(lam.body, offset)) |found| return found; + }, + .struct_literal => |sl| { + for (sl.field_inits) |fi| { + if (findNodeAtOffset(fi.value, offset)) |found| return found; + } + }, + .union_literal => |ul| { + if (ul.payload) |p| { + if (findNodeAtOffset(p, offset)) |found| return found; + } + }, + // Leaf nodes + .identifier, + .int_literal, + .float_literal, + .bool_literal, + .string_literal, + .enum_literal, + .type_expr, + .param, + .match_arm, + .undef_literal, + .builtin_expr, + .enum_decl, + .struct_decl, + .union_decl, + .import_decl, + .array_type_expr, + .array_literal, + .parameterized_type_expr, + .index_expr, + => {}, + .namespace_decl => |ns| { + for (ns.decls) |d| { + if (findNodeAtOffset(d, offset)) |found| return found; + } + }, + } + + return node; +} + +test "sema: collect top-level declarations" { + const parser_mod = @import("parser.zig"); + + const source = "main :: () { 42; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + + var analyzer = Analyzer.init(alloc); + const result = try analyzer.analyze(root); + + // Should have one symbol: main (function) + try std.testing.expectEqual(@as(usize, 1), result.symbols.len); + try std.testing.expectEqualStrings("main", result.symbols[0].name); + try std.testing.expectEqual(SymbolKind.function, result.symbols[0].kind); +} + +test "sema: function params as symbols" { + const parser_mod = @import("parser.zig"); + + const source = "add :: (a: s32, b: s32) -> s32 { a + b; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + + var analyzer = Analyzer.init(alloc); + const result = try analyzer.analyze(root); + + // Symbols: add (function), a (param), b (param) + try std.testing.expectEqual(@as(usize, 3), result.symbols.len); + try std.testing.expectEqualStrings("add", result.symbols[0].name); + try std.testing.expectEqual(SymbolKind.function, result.symbols[0].kind); + try std.testing.expectEqualStrings("a", result.symbols[1].name); + try std.testing.expectEqual(SymbolKind.param, result.symbols[1].kind); + try std.testing.expectEqualStrings("b", result.symbols[2].name); + try std.testing.expectEqual(SymbolKind.param, result.symbols[2].kind); + + // References: a and b used in body should be resolved + try std.testing.expect(result.references.len >= 2); +} + +test "sema: variable declaration and reference" { + const parser_mod = @import("parser.zig"); + + const source = "main :: () { x := 42; x; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + + var analyzer = Analyzer.init(alloc); + const result = try analyzer.analyze(root); + + // Symbols: main (function), x (variable) + try std.testing.expectEqual(@as(usize, 2), result.symbols.len); + try std.testing.expectEqualStrings("main", result.symbols[0].name); + try std.testing.expectEqualStrings("x", result.symbols[1].name); + try std.testing.expectEqual(SymbolKind.variable, result.symbols[1].kind); + + // x should have a reference + try std.testing.expect(result.references.len >= 1); + // The reference should point to symbol index 1 (x) + try std.testing.expectEqual(@as(u32, 1), result.references[0].symbol_index); +} + +test "sema: undefined variable diagnostic" { + const parser_mod = @import("parser.zig"); + + const source = "main :: () { y; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + + var analyzer = Analyzer.init(alloc); + const result = try analyzer.analyze(root); + + // Should have a diagnostic for undefined 'y' + try std.testing.expect(result.diagnostics.len >= 1); + try std.testing.expectEqualStrings("undefined variable", result.diagnostics[0].message); +} + +test "sema: enum and struct declarations" { + const parser_mod = @import("parser.zig"); + + const source = "Color :: enum { red; green; blue; } Vec2 :: struct { x, y: f32; } main :: () { 0; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var parser = parser_mod.Parser.init(alloc, source); + const root = try parser.parse(); + + var analyzer = Analyzer.init(alloc); + const result = try analyzer.analyze(root); + + // Symbols: Color (enum), Vec2 (struct), main (function) + try std.testing.expectEqual(@as(usize, 3), result.symbols.len); + try std.testing.expectEqualStrings("Color", result.symbols[0].name); + try std.testing.expectEqual(SymbolKind.enum_type, result.symbols[0].kind); + try std.testing.expectEqualStrings("Vec2", result.symbols[1].name); + try std.testing.expectEqual(SymbolKind.struct_type, result.symbols[1].kind); + try std.testing.expectEqualStrings("main", result.symbols[2].name); +} diff --git a/src/token.zig b/src/token.zig new file mode 100644 index 0000000..e946907 --- /dev/null +++ b/src/token.zig @@ -0,0 +1,175 @@ +pub const Tag = enum { + // Literals + int_literal, + float_literal, + string_literal, + + // Identifiers and keywords + identifier, + kw_if, + kw_else, + kw_then, + kw_true, + kw_false, + kw_enum, + kw_case, + kw_break, + kw_continue, + kw_while, + kw_for, + kw_return, + kw_defer, + kw_f32, + kw_f64, + kw_struct, + kw_union, + kw_xx, + kw_and, + kw_or, + kw_Type, // Type (metatype keyword) + + // Symbols + colon, // : + colon_colon, // :: + colon_equal, // := + semicolon, // ; + comma, // , + dot, // . + dot_dot, // .. + dollar, // $ + + // Operators + plus, // + + minus, // - + star, // * + slash, // / + equal, // = + equal_equal, // == + bang, // ! + bang_equal, // != + less, // < + less_equal, // <= + greater, // > + greater_equal, // >= + plus_equal, // += + minus_equal, // -= + star_equal, // *= + slash_equal, // /= + percent, // % + percent_equal, // %= + + // Delimiters + l_paren, // ( + r_paren, // ) + l_brace, // { + r_brace, // } + l_bracket, // [ + r_bracket, // ] + + // Arrows + arrow, // -> + fat_arrow, // => + + // Directives + hash_run, // #run + hash_import, // #import + hash_insert, // #insert + hash_builtin, // #builtin + triple_minus, // --- + + // Special + eof, + invalid, + + pub fn lexeme(tag: Tag) ?[]const u8 { + return switch (tag) { + .colon => ":", + .colon_colon => "::", + .colon_equal => ":=", + .semicolon => ";", + .comma => ",", + .dot => ".", + .dot_dot => "..", + .dollar => "$", + .plus => "+", + .minus => "-", + .star => "*", + .slash => "/", + .equal => "=", + .equal_equal => "==", + .bang => "!", + .bang_equal => "!=", + .less => "<", + .less_equal => "<=", + .greater => ">", + .greater_equal => ">=", + .plus_equal => "+=", + .minus_equal => "-=", + .star_equal => "*=", + .slash_equal => "/=", + .percent => "%", + .percent_equal => "%=", + .l_paren => "(", + .r_paren => ")", + .l_brace => "{", + .r_brace => "}", + .l_bracket => "[", + .r_bracket => "]", + .arrow => "->", + .fat_arrow => "=>", + .triple_minus => "---", + else => null, + }; + } + + pub fn isTypeKeyword(tag: Tag) bool { + return switch (tag) { + .kw_f32, .kw_f64, .kw_Type => true, + else => false, + }; + } +}; + +pub const Token = struct { + tag: Tag, + loc: Loc, + + pub const Loc = struct { + start: u32, + end: u32, + }; + + pub fn slice(self: Token, source: []const u8) []const u8 { + return source[self.loc.start..self.loc.end]; + } +}; + +pub const keywords = std.StaticStringMap(Tag).initComptime(.{ + .{ "if", .kw_if }, + .{ "else", .kw_else }, + .{ "then", .kw_then }, + .{ "true", .kw_true }, + .{ "false", .kw_false }, + .{ "enum", .kw_enum }, + .{ "case", .kw_case }, + .{ "break", .kw_break }, + .{ "continue", .kw_continue }, + .{ "while", .kw_while }, + .{ "for", .kw_for }, + .{ "return", .kw_return }, + .{ "defer", .kw_defer }, + .{ "f32", .kw_f32 }, + .{ "f64", .kw_f64 }, + .{ "struct", .kw_struct }, + .{ "union", .kw_union }, + .{ "xx", .kw_xx }, + .{ "and", .kw_and }, + .{ "or", .kw_or }, + .{ "Type", .kw_Type }, +}); + +pub fn getKeyword(bytes: []const u8) ?Tag { + return keywords.get(bytes); +} + +const std = @import("std"); diff --git a/src/types.zig b/src/types.zig new file mode 100644 index 0000000..e06aeda --- /dev/null +++ b/src/types.zig @@ -0,0 +1,323 @@ +const std = @import("std"); +const ast = @import("ast.zig"); +const Node = ast.Node; + +pub const Type = union(enum) { + // Variable-width integers (1–64 bits) + signed: u8, + unsigned: u8, + // Fixed-width floats + f32, + f64, + // Other + void_type, + boolean, + string_type, + enum_type: []const u8, + struct_type: []const u8, + union_type: []const u8, + array_type: ArrayTypeInfo, + slice_type: SliceTypeInfo, + vector_type: VectorTypeInfo, + any_type, + meta_type: MetaTypeInfo, + + pub const SliceTypeInfo = struct { + element_name: []const u8, + }; + + pub const ArrayTypeInfo = struct { + element_name: []const u8, + length: u32, + }; + + pub const VectorTypeInfo = struct { + element_name: []const u8, + length: u32, + }; + + pub const MetaTypeInfo = struct { + name: []const u8, + }; + + // Convenience constructors + pub fn s(width: u8) Type { + return .{ .signed = width }; + } + + pub fn u(width: u8) Type { + return .{ .unsigned = width }; + } + + pub fn fromName(name: []const u8) ?Type { + // Named types (check before variable-width integers since "string" starts with 's') + if (std.mem.eql(u8, name, "string")) return .string_type; + if (std.mem.eql(u8, name, "bool")) return .boolean; + if (std.mem.eql(u8, name, "f32")) return .f32; + if (std.mem.eql(u8, name, "f64")) return .f64; + if (std.mem.eql(u8, name, "Any")) return .any_type; + // Variable-width integers: s1..s64, u1..u64 + if (name.len >= 2 and (name[0] == 's' or name[0] == 'u')) { + const width = std.fmt.parseInt(u8, name[1..], 10) catch return null; + if (width < 1 or width > 64) return null; + return if (name[0] == 's') Type.s(width) else Type.u(width); + } + return null; + } + + pub fn fromTypeExpr(node: *Node) ?Type { + if (node.data != .type_expr) return null; + return fromName(node.data.type_expr.name); + } + + pub fn isEnum(self: Type) bool { + return switch (self) { + .enum_type => true, + else => false, + }; + } + + pub fn isStruct(self: Type) bool { + return switch (self) { + .struct_type => true, + else => false, + }; + } + + pub fn isUnion(self: Type) bool { + return switch (self) { + .union_type => true, + else => false, + }; + } + + pub fn isAny(self: Type) bool { + return switch (self) { + .any_type => true, + else => false, + }; + } + + pub fn isSlice(self: Type) bool { + return switch (self) { + .slice_type => true, + else => false, + }; + } + + pub fn sliceElementType(self: Type) ?Type { + return switch (self) { + .slice_type => |info| fromName(info.element_name), + else => null, + }; + } + + pub fn isArray(self: Type) bool { + return switch (self) { + .array_type => true, + else => false, + }; + } + + pub fn isVector(self: Type) bool { + return switch (self) { + .vector_type => true, + else => false, + }; + } + + pub fn vectorElementType(self: Type) ?Type { + return switch (self) { + .vector_type => |info| fromName(info.element_name), + else => null, + }; + } + + pub fn isFloat(self: Type) bool { + return switch (self) { + .f32, .f64 => true, + else => false, + }; + } + + pub fn isInt(self: Type) bool { + return self.isSigned() or self.isUnsigned(); + } + + pub fn isSigned(self: Type) bool { + return switch (self) { + .signed => true, + else => false, + }; + } + + pub fn isUnsigned(self: Type) bool { + return switch (self) { + .unsigned => true, + else => false, + }; + } + + pub fn bitWidth(self: Type) u32 { + return switch (self) { + .signed => |w| w, + .unsigned => |w| w, + .f32 => 32, + .f64 => 64, + .boolean => 1, + else => 0, + }; + } + + /// Check if this type can be implicitly converted to `target` without `xx`. + /// Safe (implicit) conversions: + /// - Same type + /// - Both unsigned int, target width >= source width + /// - Both signed int, target width >= source width + /// - Unsigned to signed, target width strictly > source width + /// - Any int to any float + /// - Float to wider float (f32 → f64) + /// Everything else requires `xx`. + pub fn isImplicitlyConvertibleTo(self: Type, target: Type) bool { + if (std.meta.eql(self, target)) return true; + + const src_float = self.isFloat(); + const dst_float = target.isFloat(); + const src_int = self.isInt(); + + // Float → wider float + if (src_float and dst_float) { + return target.bitWidth() >= self.bitWidth(); + } + + // Int → float (always safe) + if (src_int and dst_float) return true; + + // Both unsigned → target width >= source width + if (self.isUnsigned() and target.isUnsigned()) { + return target.bitWidth() >= self.bitWidth(); + } + + // Both signed → target width >= source width + if (self.isSigned() and target.isSigned()) { + return target.bitWidth() >= self.bitWidth(); + } + + // Unsigned → signed: target must be strictly wider + if (self.isUnsigned() and target.isSigned()) { + return target.bitWidth() > self.bitWidth(); + } + + // Everything else requires xx + return false; + } + + /// Format type name for mangling and display (e.g. "s32", "u8", "f64") + pub fn displayName(self: Type, allocator: std.mem.Allocator) ![]const u8 { + return switch (self) { + .signed => |w| { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, 's'); + var tmp: [4]u8 = undefined; + const width_str = std.fmt.bufPrint(&tmp, "{d}", .{w}) catch unreachable; + try buf.appendSlice(allocator, width_str); + return try buf.toOwnedSlice(allocator); + }, + .unsigned => |w| { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, 'u'); + var tmp: [4]u8 = undefined; + const width_str = std.fmt.bufPrint(&tmp, "{d}", .{w}) catch unreachable; + try buf.appendSlice(allocator, width_str); + return try buf.toOwnedSlice(allocator); + }, + .f32 => "f32", + .f64 => "f64", + .boolean => "bool", + .string_type => "string", + .void_type => "void", + .any_type => "Any", + .enum_type => |name| name, + .struct_type => |name| name, + .union_type => |name| name, + .slice_type => |info| { + var buf = std.ArrayList(u8).empty; + try buf.appendSlice(allocator, "[]"); + try buf.appendSlice(allocator, info.element_name); + return try buf.toOwnedSlice(allocator); + }, + .array_type => |info| { + var buf = std.ArrayList(u8).empty; + try buf.append(allocator, '['); + var tmp: [10]u8 = undefined; + const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable; + try buf.appendSlice(allocator, len_str); + try buf.append(allocator, ']'); + try buf.appendSlice(allocator, info.element_name); + return try buf.toOwnedSlice(allocator); + }, + .vector_type => |info| { + var buf = std.ArrayList(u8).empty; + try buf.appendSlice(allocator, "Vector("); + var tmp: [10]u8 = undefined; + const len_str = std.fmt.bufPrint(&tmp, "{d}", .{info.length}) catch unreachable; + try buf.appendSlice(allocator, len_str); + try buf.appendSlice(allocator, ","); + try buf.appendSlice(allocator, info.element_name); + try buf.append(allocator, ')'); + return try buf.toOwnedSlice(allocator); + }, + .meta_type => |info| info.name, + }; + } + + /// Widen two types to a common type for binary operations. + /// Used for arithmetic type promotion (e.g., s16 + s32 → s32, int + float → float). + pub fn widen(a: Type, b: Type) Type { + // Same type → return it + if (std.meta.eql(a, b)) return a; + + // Vector + vector of same dimensions → return a + if (a.isVector() and b.isVector()) return a; + // Vector + scalar → return vector (scalar will be broadcast) + if (a.isVector() and !b.isVector()) return a; + if (b.isVector() and !a.isVector()) return b; + + const a_float = a.isFloat(); + const b_float = b.isFloat(); + const a_int = a.isInt(); + const b_int = b.isInt(); + + // Both float → wider float + if (a_float and b_float) { + return if (a.bitWidth() >= b.bitWidth()) a else b; + } + + // int + float → float + if (a_int and b_float) return b; + if (b_int and a_float) return a; + + // Both signed → wider signed + if (a.isSigned() and b.isSigned()) { + return Type.s(@intCast(@max(a.bitWidth(), b.bitWidth()))); + } + + // Both unsigned → wider unsigned + if (a.isUnsigned() and b.isUnsigned()) { + return Type.u(@intCast(@max(a.bitWidth(), b.bitWidth()))); + } + + // signed + unsigned (mixed) + if (a_int and b_int) { + const aw = a.bitWidth(); + const bw = b.bitWidth(); + const max_w = @max(aw, bw); + // If same width, need one extra bit for sign; otherwise max is enough + const need: u32 = if (aw == bw) max_w + 1 else max_w; + const capped: u8 = @intCast(@min(need, 128)); + return Type.s(capped); + } + + return a; + } +};