so... jai :D
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.zig-cache
|
||||||
|
zig-out
|
||||||
|
.DS_Store
|
||||||
|
.vscode/
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||||
90
build.zig
Normal file
90
build.zig
Normal file
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
81
build.zig.zon
Normal file
81
build.zig.zon
Normal file
@@ -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 <url>`, 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 <url>` 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",
|
||||||
|
},
|
||||||
|
}
|
||||||
2
editors/vscode/.gitignore
vendored
Normal file
2
editors/vscode/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
out/
|
||||||
3
editors/vscode/.vscodeignore
Normal file
3
editors/vscode/.vscodeignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
src/
|
||||||
|
tsconfig.json
|
||||||
|
.gitignore
|
||||||
22
editors/vscode/language-configuration.json
Normal file
22
editors/vscode/language-configuration.json
Normal file
@@ -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*\\}"
|
||||||
|
}
|
||||||
|
}
|
||||||
121
editors/vscode/package-lock.json
generated
Normal file
121
editors/vscode/package-lock.json
generated
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
68
editors/vscode/package.json
Normal file
68
editors/vscode/package.json
Normal file
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
editors/vscode/src/extension.ts
Normal file
41
editors/vscode/src/extension.ts
Normal file
@@ -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<string>("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<void> | undefined {
|
||||||
|
if (!client) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return client.stop();
|
||||||
|
}
|
||||||
BIN
editors/vscode/sx-lang-0.0.1.vsix
Normal file
BIN
editors/vscode/sx-lang-0.0.1.vsix
Normal file
Binary file not shown.
208
editors/vscode/syntaxes/sx.tmLanguage.json
Normal file
208
editors/vscode/syntaxes/sx.tmLanguage.json
Normal file
@@ -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": "(?<![:]):\\s*(?![=:])([A-Z][a-zA-Z0-9_]*)\\b",
|
||||||
|
"captures": {
|
||||||
|
"1": { "name": "entity.name.type.sx" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"constants": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.language.sx",
|
||||||
|
"match": "\\b(true|false)\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.language.undefined.sx",
|
||||||
|
"match": "---"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"numbers": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.float.sx",
|
||||||
|
"match": "\\b[0-9]+\\.[0-9]+\\b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "constant.numeric.integer.sx",
|
||||||
|
"match": "\\b[0-9]+\\b"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"operators": {
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.declaration.sx",
|
||||||
|
"match": "::"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.walrus.sx",
|
||||||
|
"match": ":="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keyword.operator.arrow.sx",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
editors/vscode/tsconfig.json
Normal file
15
editors/vscode/tsconfig.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
5
examples/01-basic.sx
Normal file
5
examples/01-basic.sx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
if false then 40 else 42;
|
||||||
|
}
|
||||||
4
examples/02-stdout.sx
Normal file
4
examples/02-stdout.sx
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
main :: () {
|
||||||
|
print("Hello\n");
|
||||||
|
}
|
||||||
22
examples/03-structs.sx
Normal file
22
examples/03-structs.sx
Normal file
@@ -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}
|
||||||
20
examples/04-shadow.sx
Normal file
20
examples/04-shadow.sx
Normal file
@@ -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
|
||||||
|
//
|
||||||
20
examples/05-run.sx
Normal file
20
examples/05-run.sx
Normal file
@@ -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
|
||||||
17
examples/06-generic.sx
Normal file
17
examples/06-generic.sx
Normal file
@@ -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
|
||||||
|
//
|
||||||
11
examples/07-defer.sx
Normal file
11
examples/07-defer.sx
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
main :: () -> s32 {
|
||||||
|
defer print("still here\n");
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ** exit code **
|
||||||
|
// 42
|
||||||
|
// ** stdout **
|
||||||
|
// still here
|
||||||
|
//
|
||||||
43
examples/08-types.sx
Normal file
43
examples/08-types.sx
Normal file
@@ -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;
|
||||||
|
}
|
||||||
16
examples/09-import.sx
Normal file
16
examples/09-import.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
91
examples/10-generic-struct.sx
Normal file
91
examples/10-generic-struct.sx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
Vec :: struct($N: u32, $T:Type) {
|
||||||
|
// <N x T> (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);
|
||||||
|
}
|
||||||
28
examples/11-vector-math.sx
Normal file
28
examples/11-vector-math.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
12
examples/12-meta.sx
Normal file
12
examples/12-meta.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
9
examples/13-code.sx
Normal file
9
examples/13-code.sx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
generate::() -> string {
|
||||||
|
return "print(\"hello from the other side\n\");";
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
#insert #run generate();
|
||||||
|
}
|
||||||
15
examples/14-demo.sx
Normal file
15
examples/14-demo.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
50
examples/15-while.sx
Normal file
50
examples/15-while.sx
Normal file
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
48
examples/16-union.sx
Normal file
48
examples/16-union.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
9
examples/17-lambda.sx
Normal file
9
examples/17-lambda.sx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
main :: () {
|
||||||
|
fx :: (s:s3) -> s3 {
|
||||||
|
s;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("{}\n", fx(133));
|
||||||
|
}
|
||||||
23
examples/18-conditions.sx
Normal file
23
examples/18-conditions.sx
Normal file
@@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
35
examples/19-varargs.sx
Normal file
35
examples/19-varargs.sx
Normal file
@@ -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;
|
||||||
|
}
|
||||||
47
examples/20-any-varargs.sx
Normal file
47
examples/20-any-varargs.sx
Normal file
@@ -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;
|
||||||
|
}
|
||||||
19
examples/21-categories.sx
Normal file
19
examples/21-categories.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
11
examples/22-anytype.sx
Normal file
11
examples/22-anytype.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
17
examples/modules/math.sx
Normal file
17
examples/modules/math.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
220
examples/modules/std.sx
Normal file
220
examples/modules/std.sx
Normal file
@@ -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);
|
||||||
|
}
|
||||||
98
examples/vision.sx
Normal file
98
examples/vision.sx
Normal file
@@ -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.* = .{};
|
||||||
|
}
|
||||||
|
|
||||||
17
llvm_shim.c
Normal file
17
llvm_shim.c
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include <llvm-c/Core.h>
|
||||||
|
#include <llvm-c/Target.h>
|
||||||
|
#include <llvm-c/TargetMachine.h>
|
||||||
|
#include <llvm-c/Analysis.h>
|
||||||
|
|
||||||
|
void sx_llvm_init_all_targets(void) {
|
||||||
|
LLVMInitializeAllTargetInfos();
|
||||||
|
LLVMInitializeAllTargets();
|
||||||
|
LLVMInitializeAllTargetMCs();
|
||||||
|
LLVMInitializeAllAsmPrinters();
|
||||||
|
LLVMInitializeAllAsmParsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sx_llvm_init_native_target(void) {
|
||||||
|
LLVMInitializeNativeTarget();
|
||||||
|
LLVMInitializeNativeAsmPrinter();
|
||||||
|
}
|
||||||
40
readme.md
Normal file
40
readme.md
Normal file
@@ -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
|
||||||
853
specs.md
Normal file
853
specs.md
Normal file
@@ -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 `<TypeName tag=N>`:
|
||||||
|
```sx
|
||||||
|
print("{s}"); // <Shape tag=0>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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?
|
||||||
326
src/ast.zig
Normal file
326
src/ast.zig
Normal file
@@ -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,
|
||||||
|
};
|
||||||
25
src/builtins.zig
Normal file
25
src/builtins.zig
Normal file
@@ -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 };
|
||||||
|
}
|
||||||
|
};
|
||||||
4999
src/codegen.zig
Normal file
4999
src/codegen.zig
Normal file
File diff suppressed because it is too large
Load Diff
1753
src/comptime.zig
Normal file
1753
src/comptime.zig
Normal file
File diff suppressed because it is too large
Load Diff
110
src/core.zig
Normal file
110
src/core.zig
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
96
src/errors.zig
Normal file
96
src/errors.zig
Normal file
@@ -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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
150
src/imports.zig
Normal file
150
src/imports.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
403
src/lexer.zig
Normal file
403
src/lexer.zig
Normal file
@@ -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"));
|
||||||
|
}
|
||||||
54
src/llvm_api.zig
Normal file
54
src/llvm_api.zig
Normal file
@@ -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);
|
||||||
|
}
|
||||||
48
src/lsp/document.zig
Normal file
48
src/lsp/document.zig
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
};
|
||||||
1776
src/lsp/server.zig
Normal file
1776
src/lsp/server.zig
Normal file
File diff suppressed because it is too large
Load Diff
75
src/lsp/transport.zig
Normal file
75
src/lsp/transport.zig
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
331
src/lsp/types.zig
Normal file
331
src/lsp/types.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
158
src/main.zig
Normal file
158
src/main.zig
Normal file
@@ -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 <command> [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 {};
|
||||||
|
}
|
||||||
1573
src/parser.zig
Normal file
1573
src/parser.zig
Normal file
File diff suppressed because it is too large
Load Diff
19
src/root.zig
Normal file
19
src/root.zig
Normal file
@@ -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");
|
||||||
|
};
|
||||||
1006
src/sema.zig
Normal file
1006
src/sema.zig
Normal file
File diff suppressed because it is too large
Load Diff
175
src/token.zig
Normal file
175
src/token.zig
Normal file
@@ -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");
|
||||||
323
src/types.zig
Normal file
323
src/types.zig
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user