http server
This commit is contained in:
@@ -10,6 +10,9 @@
|
|||||||
"categories": [
|
"categories": [
|
||||||
"Programming Languages"
|
"Programming Languages"
|
||||||
],
|
],
|
||||||
|
"activationEvents": [
|
||||||
|
"onLanguage:sx"
|
||||||
|
],
|
||||||
"main": "./out/extension.js",
|
"main": "./out/extension.js",
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"languages": [
|
"languages": [
|
||||||
|
|||||||
Binary file not shown.
@@ -10,26 +10,26 @@ sum :: (args: ..s32) -> s32 {
|
|||||||
|
|
||||||
print_all :: (args: ..s32) {
|
print_all :: (args: ..s32) {
|
||||||
for args: (it) {
|
for args: (it) {
|
||||||
write(int_to_string(it));
|
out(int_to_string(it));
|
||||||
write(" ");
|
out(" ");
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
main :: () -> s32 {
|
main :: () -> s32 {
|
||||||
write(int_to_string(sum(10, 20, 30)));
|
out(int_to_string(sum(10, 20, 30)));
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
print_all(1, 2, 3, 4, 5);
|
print_all(1, 2, 3, 4, 5);
|
||||||
|
|
||||||
arr : [3]s32 = .[10, 20, 30];
|
arr : [3]s32 = .[10, 20, 30];
|
||||||
write(int_to_string(sum(..arr)));
|
out(int_to_string(sum(..arr)));
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
for arr: (it) {
|
for arr: (it) {
|
||||||
write(int_to_string(it));
|
out(int_to_string(it));
|
||||||
write(" ");
|
out(" ");
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
0;
|
0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,22 +10,22 @@ print_any :: (args: ..Any) {
|
|||||||
for args: (it) {
|
for args: (it) {
|
||||||
type := type_of(it);
|
type := type_of(it);
|
||||||
if type == {
|
if type == {
|
||||||
case int: write(int_to_string(cast(s32) it));
|
case int: out(int_to_string(cast(s32) it));
|
||||||
case string: write(cast(string) it);
|
case string: out(cast(string) it);
|
||||||
case bool: write(bool_to_string(cast(bool) it));
|
case bool: out(bool_to_string(cast(bool) it));
|
||||||
case float: write(float_to_string(cast(f64) it));
|
case float: out(float_to_string(cast(f64) it));
|
||||||
case Point: {
|
case Point: {
|
||||||
p := cast(Point) it;
|
p := cast(Point) it;
|
||||||
write("(");
|
out("(");
|
||||||
write(int_to_string(p.x));
|
out(int_to_string(p.x));
|
||||||
write(",");
|
out(",");
|
||||||
write(int_to_string(p.y));
|
out(int_to_string(p.y));
|
||||||
write(")");
|
out(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write(" ");
|
out(" ");
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
count :: (args: ..Any) -> s32 {
|
count :: (args: ..Any) -> s32 {
|
||||||
@@ -40,8 +40,8 @@ main :: () -> s32 {
|
|||||||
print_any("point:", p, 99);
|
print_any("point:", p, 99);
|
||||||
|
|
||||||
// Test count
|
// Test count
|
||||||
write(int_to_string(count(1, 2, 3)));
|
out(int_to_string(count(1, 2, 3)));
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
0;
|
0;
|
||||||
}
|
}
|
||||||
|
|||||||
63
examples/32-http-server.sx
Normal file
63
examples/32-http-server.sx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// HTTP server example (macOS only)
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
#import "modules/socket.sx";
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
PORT :: 8080;
|
||||||
|
|
||||||
|
fd := socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if fd < 0 {
|
||||||
|
print("error: socket()\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
opt : s32 = 1;
|
||||||
|
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, @opt, 4);
|
||||||
|
|
||||||
|
addr := SockAddr.{
|
||||||
|
sin_len = 16,
|
||||||
|
sin_family = 2,
|
||||||
|
sin_port = htons(PORT),
|
||||||
|
sin_addr = 0,
|
||||||
|
sin_zero = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if bind(fd, @addr, 16) < 0 {
|
||||||
|
print("error: bind()\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if listen(fd, 10) < 0 {
|
||||||
|
print("error: listen()\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("listening on http://localhost:{}\n", PORT);
|
||||||
|
|
||||||
|
while true {
|
||||||
|
client := accept(fd, null, null);
|
||||||
|
if client < 0 { continue; }
|
||||||
|
|
||||||
|
// Read request
|
||||||
|
buf := alloc(4096);
|
||||||
|
read(client, buf, 4096);
|
||||||
|
|
||||||
|
// Send response
|
||||||
|
body := "<html><body><h1>Hello from sx!</h1></body></html>";
|
||||||
|
response := format("HTTP/1.1 200 OK\r
|
||||||
|
Content-Type: text/html\r
|
||||||
|
Connection: close\r
|
||||||
|
Content-Length: {}\r
|
||||||
|
\r
|
||||||
|
{}", body.len, body);
|
||||||
|
|
||||||
|
write(client, response, response.len);
|
||||||
|
|
||||||
|
close(client);
|
||||||
|
print(" served request\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
0;
|
||||||
|
}
|
||||||
@@ -91,6 +91,10 @@ gen_val :: () -> string {
|
|||||||
return "print(\"insert-gen: {}\\n\", 42);";
|
return "print(\"insert-gen: {}\\n\", 42);";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Foreign function binding ---
|
||||||
|
libc :: #library "c";
|
||||||
|
c_abs :: (n: s32) -> s32 #foreign libc "abs";
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
main :: {
|
main :: {
|
||||||
|
|
||||||
@@ -634,76 +638,76 @@ END;
|
|||||||
|
|
||||||
// For loop basic
|
// For loop basic
|
||||||
farr : [4]s32 = .[10, 20, 30, 40];
|
farr : [4]s32 = .[10, 20, 30, 40];
|
||||||
write("for:");
|
out("for:");
|
||||||
for farr: (it) {
|
for farr: (it) {
|
||||||
write(" ");
|
out(" ");
|
||||||
write(int_to_string(it));
|
out(int_to_string(it));
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For with print
|
// For with print
|
||||||
write("for-print:");
|
out("for-print:");
|
||||||
for farr: (it) {
|
for farr: (it) {
|
||||||
print(" {}", it);
|
print(" {}", it);
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For with index
|
// For with index
|
||||||
write("for-idx:");
|
out("for-idx:");
|
||||||
for farr: (_, ix) {
|
for farr: (_, ix) {
|
||||||
write(" ");
|
out(" ");
|
||||||
write(int_to_string(ix));
|
out(int_to_string(ix));
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For with print two args
|
// For with print two args
|
||||||
write("for-2arg:");
|
out("for-2arg:");
|
||||||
for farr: (it, ix) {
|
for farr: (it, ix) {
|
||||||
print(" {}@{}", it, ix);
|
print(" {}@{}", it, ix);
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For with break
|
// For with break
|
||||||
write("for-break:");
|
out("for-break:");
|
||||||
for farr: (it) {
|
for farr: (it) {
|
||||||
if it == 30 { break; }
|
if it == 30 { break; }
|
||||||
print(" {}", it);
|
print(" {}", it);
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For with continue
|
// For with continue
|
||||||
write("for-continue:");
|
out("for-continue:");
|
||||||
for farr: (it) {
|
for farr: (it) {
|
||||||
if it == 20 { continue; }
|
if it == 20 { continue; }
|
||||||
print(" {}", it);
|
print(" {}", it);
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For on slice
|
// For on slice
|
||||||
fsl : []s32 = .[10, 20, 30];
|
fsl : []s32 = .[10, 20, 30];
|
||||||
write("for-slice:");
|
out("for-slice:");
|
||||||
for fsl: (it) {
|
for fsl: (it) {
|
||||||
print(" {}", it);
|
print(" {}", it);
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For on slice with index
|
// For on slice with index
|
||||||
write("for-slice-idx:");
|
out("for-slice-idx:");
|
||||||
for fsl: (it, ix) {
|
for fsl: (it, ix) {
|
||||||
print(" {}:{}", ix, it);
|
print(" {}:{}", ix, it);
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// Nested for
|
// Nested for
|
||||||
nf_a : [2]s32 = .[0, 1];
|
nf_a : [2]s32 = .[0, 1];
|
||||||
nf_b : [2]s32 = .[0, 1];
|
nf_b : [2]s32 = .[0, 1];
|
||||||
write("for-nested:");
|
out("for-nested:");
|
||||||
for nf_a: (oa) {
|
for nf_a: (oa) {
|
||||||
for nf_b: (ob) {
|
for nf_b: (ob) {
|
||||||
print(" ({},{})", oa, ob);
|
print(" ({},{})", oa, ob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// For with break preserving index
|
// For with break preserving index
|
||||||
fbi : [5]s32 = .[10, 20, 30, 40, 50];
|
fbi : [5]s32 = .[10, 20, 30, 40, 50];
|
||||||
@@ -857,8 +861,8 @@ END;
|
|||||||
// ========================================================
|
// ========================================================
|
||||||
print("=== 7. Builtins ===\n");
|
print("=== 7. Builtins ===\n");
|
||||||
|
|
||||||
// write
|
// out
|
||||||
write("write-ok\n");
|
out("out-ok\n");
|
||||||
|
|
||||||
// sqrt
|
// sqrt
|
||||||
print("sqrt: {}\n", sqrt(9.0));
|
print("sqrt: {}\n", sqrt(9.0));
|
||||||
@@ -932,12 +936,12 @@ END;
|
|||||||
|
|
||||||
// field_value (use any_to_string to avoid sext-on-Any bug)
|
// field_value (use any_to_string to avoid sext-on-Any bug)
|
||||||
fv_pt := Point.{ 11, 22 };
|
fv_pt := Point.{ 11, 22 };
|
||||||
write("fieldval0: ");
|
out("fieldval0: ");
|
||||||
write(any_to_string(field_value(fv_pt, 0)));
|
out(any_to_string(field_value(fv_pt, 0)));
|
||||||
write("\n");
|
out("\n");
|
||||||
write("fieldval1: ");
|
out("fieldval1: ");
|
||||||
write(any_to_string(field_value(fv_pt, 1)));
|
out(any_to_string(field_value(fv_pt, 1)));
|
||||||
write("\n");
|
out("\n");
|
||||||
|
|
||||||
// field_index on plain enum
|
// field_index on plain enum
|
||||||
fi_c : Color = .green;
|
fi_c : Color = .green;
|
||||||
@@ -1039,5 +1043,13 @@ END;
|
|||||||
print("3-way: {} {} {}\n", ra, rb, rc);
|
print("3-way: {} {} {}\n", ra, rb, rc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// 15. FOREIGN FUNCTION BINDING
|
||||||
|
// ========================================================
|
||||||
|
print("=== 15. Foreign ===\n");
|
||||||
|
|
||||||
|
// Symbol rename: c_abs maps to C's abs()
|
||||||
|
print("foreign-rename: {}\n", c_abs(xx -42));
|
||||||
|
|
||||||
print("=== DONE ===\n");
|
print("=== DONE ===\n");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#library "raylib";
|
raylib :: #library "raylib";
|
||||||
|
|
||||||
Color :: struct {
|
Color :: struct {
|
||||||
r, g, b, a: u8;
|
r, g, b, a: u8;
|
||||||
@@ -8,10 +8,10 @@ Vector2 :: struct {
|
|||||||
x, y: f32;
|
x, y: f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign;
|
InitWindow :: (width: s32, height: s32, title: [:0]u8) -> void #foreign raylib;
|
||||||
CloseWindow :: () -> void #foreign;
|
CloseWindow :: () -> void #foreign raylib;
|
||||||
WindowShouldClose :: () -> bool #foreign;
|
WindowShouldClose :: () -> bool #foreign raylib;
|
||||||
BeginDrawing :: () -> void #foreign;
|
BeginDrawing :: () -> void #foreign raylib;
|
||||||
EndDrawing :: () -> void #foreign;
|
EndDrawing :: () -> void #foreign raylib;
|
||||||
ClearBackground :: (color: Color) -> void #foreign;
|
ClearBackground :: (color: Color) -> void #foreign raylib;
|
||||||
DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign;
|
DrawTriangle :: (v1: Vector2, v2: Vector2, v3: Vector2, color: Color) -> void #foreign raylib;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#library "SDL3";
|
sdl3 :: #library "SDL3";
|
||||||
|
|
||||||
// SDL_InitFlags
|
// SDL_InitFlags
|
||||||
SDL_INIT_VIDEO :u32: 0x20;
|
SDL_INIT_VIDEO :u32: 0x20;
|
||||||
@@ -317,17 +317,17 @@ SDL_Event :: enum struct { tag: u32; _: u32; payload: [30]u32; } {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
SDL_Init :: (flags: u32) -> bool #foreign;
|
SDL_Init :: (flags: u32) -> bool #foreign sdl3;
|
||||||
SDL_Quit :: () -> void #foreign;
|
SDL_Quit :: () -> void #foreign sdl3;
|
||||||
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign;
|
SDL_CreateWindow :: (title: [:0]u8, w: s32, h: s32, flags: u64) -> *void #foreign sdl3;
|
||||||
SDL_DestroyWindow :: (window: *void) -> void #foreign;
|
SDL_DestroyWindow :: (window: *void) -> void #foreign sdl3;
|
||||||
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign;
|
SDL_GL_SetAttribute :: (attr: s32, value: s32) -> bool #foreign sdl3;
|
||||||
SDL_GL_CreateContext :: (window: *void) -> *void #foreign;
|
SDL_GL_CreateContext :: (window: *void) -> *void #foreign sdl3;
|
||||||
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign;
|
SDL_GL_DestroyContext :: (context: *void) -> bool #foreign sdl3;
|
||||||
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign;
|
SDL_GL_MakeCurrent :: (window: *void, context: *void) -> bool #foreign sdl3;
|
||||||
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign;
|
SDL_GL_SwapWindow :: (window: *void) -> bool #foreign sdl3;
|
||||||
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign;
|
SDL_GL_SetSwapInterval :: (interval: s32) -> bool #foreign sdl3;
|
||||||
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign;
|
SDL_GL_GetProcAddress :: (proc: [:0]u8) -> *void #foreign sdl3;
|
||||||
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign;
|
SDL_PollEvent :: (event: *SDL_Event) -> bool #foreign sdl3;
|
||||||
SDL_GetTicks :: () -> u64 #foreign;
|
SDL_GetTicks :: () -> u64 #foreign sdl3;
|
||||||
SDL_Delay :: (ms: u32) -> void #foreign;
|
SDL_Delay :: (ms: u32) -> void #foreign sdl3;
|
||||||
|
|||||||
33
examples/modules/socket.sx
Normal file
33
examples/modules/socket.sx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// POSIX socket module (macOS only)
|
||||||
|
// sockaddr_in layout and constants are platform-specific.
|
||||||
|
|
||||||
|
libc :: #library "c";
|
||||||
|
|
||||||
|
// POSIX socket API
|
||||||
|
socket :: (domain: s32, kind: s32, protocol: s32) -> s32 #foreign libc;
|
||||||
|
setsockopt :: (fd: s32, level: s32, optname: s32, optval: *s32, optlen: u32) -> s32 #foreign libc;
|
||||||
|
bind :: (fd: s32, addr: *SockAddr, addrlen: u32) -> s32 #foreign libc;
|
||||||
|
listen :: (fd: s32, backlog: s32) -> s32 #foreign libc;
|
||||||
|
accept :: (fd: s32, addr: *SockAddr, addrlen: *u32) -> s32 #foreign libc;
|
||||||
|
read :: (fd: s32, buf: [:0]u8, count: s64) -> s64 #foreign libc;
|
||||||
|
write :: (fd: s32, buf: [:0]u8, count: s64) -> s64 #foreign libc;
|
||||||
|
close :: (fd: s32) -> s32 #foreign libc;
|
||||||
|
|
||||||
|
// Constants (macOS)
|
||||||
|
AF_INET :s32: 2;
|
||||||
|
SOCK_STREAM :s32: 1;
|
||||||
|
SOL_SOCKET :s32: 0xFFFF;
|
||||||
|
SO_REUSEADDR :s32: 0x4;
|
||||||
|
|
||||||
|
// macOS sockaddr_in (16 bytes, has sin_len field)
|
||||||
|
SockAddr :: struct {
|
||||||
|
sin_len: u8;
|
||||||
|
sin_family: u8;
|
||||||
|
sin_port: u16;
|
||||||
|
sin_addr: u32;
|
||||||
|
sin_zero: u64;
|
||||||
|
}
|
||||||
|
|
||||||
|
htons :: (port: s64) -> u16 {
|
||||||
|
cast(u16) ((port / 256) | ((port % 256) * 256));
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
Vector :: ($N: int, $T: Type) -> Type #builtin;
|
Vector :: ($N: int, $T: Type) -> Type #builtin;
|
||||||
write :: (str: string) -> void #builtin;
|
out :: (str: string) -> void #builtin;
|
||||||
sqrt :: (x: $T) -> T #builtin;
|
sqrt :: (x: $T) -> T #builtin;
|
||||||
sin :: (x: $T) -> T #builtin;
|
sin :: (x: $T) -> T #builtin;
|
||||||
cos :: (x: $T) -> T #builtin;
|
cos :: (x: $T) -> T #builtin;
|
||||||
@@ -315,10 +315,81 @@ build_print :: (fmt: string) -> string {
|
|||||||
code = concat(code, int_to_string(fmt.len - seg_start));
|
code = concat(code, int_to_string(fmt.len - seg_start));
|
||||||
code = concat(code, ")); ");
|
code = concat(code, ")); ");
|
||||||
}
|
}
|
||||||
code = concat(code, "write(result);");
|
code = concat(code, "out(result);");
|
||||||
code;
|
code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
build_format :: (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, "result;");
|
||||||
|
code;
|
||||||
|
}
|
||||||
|
|
||||||
|
format :: ($fmt: string, args: ..Any) -> string {
|
||||||
|
#insert build_format(fmt);
|
||||||
|
}
|
||||||
|
|
||||||
print :: ($fmt: string, args: ..Any) {
|
print :: ($fmt: string, args: ..Any) {
|
||||||
#insert build_print(fmt);
|
#insert build_print(fmt);
|
||||||
}
|
}
|
||||||
|
|||||||
23
specs.md
23
specs.md
@@ -343,6 +343,27 @@ val := mp[2]; // 30
|
|||||||
|
|
||||||
**Fat pointer layout**: `[:0]u8`, `string`, and `[]T` are `{ptr, i64}` structs. The raw pointer is always the first field at offset 0. This means `*[:0]u8` works as C's `char**` — a C function dereferences through the outer pointer and reads the raw `char*` from offset 0.
|
**Fat pointer layout**: `[:0]u8`, `string`, and `[]T` are `{ptr, i64}` structs. The raw pointer is always the first field at offset 0. This means `*[:0]u8` works as C's `char**` — a C function dereferences through the outer pointer and reads the raw `char*` from offset 0.
|
||||||
|
|
||||||
|
### Foreign Function Interface (C Interop)
|
||||||
|
|
||||||
|
To call C functions, declare a library constant with `#library` and bind functions with `#foreign`:
|
||||||
|
|
||||||
|
```sx
|
||||||
|
// Declare a named library constant
|
||||||
|
libc :: #library "c";
|
||||||
|
sdl :: #library "SDL3";
|
||||||
|
|
||||||
|
// Bind foreign functions — library ref is required
|
||||||
|
socket :: (domain: s32, type: s32, protocol: s32) -> s32 #foreign libc;
|
||||||
|
SDL_Init :: (flags: u32) -> bool #foreign sdl;
|
||||||
|
|
||||||
|
// Symbol renaming — optional second argument gives the C symbol name
|
||||||
|
write_fd :: (fd: s32, buf: [*]u8, count: u64) -> s64 #foreign libc "write";
|
||||||
|
```
|
||||||
|
|
||||||
|
- `#library "name"` must be assigned to a named constant. The library is passed to the linker (`-lname` on Unix, `name.lib` on Windows).
|
||||||
|
- `#foreign lib_ref` declares a function as external C. The library reference is mandatory.
|
||||||
|
- `#foreign lib_ref "c_symbol"` renames the binding: the sx function name differs from the C symbol. This avoids name collisions (e.g. POSIX `write` vs an sx builtin).
|
||||||
|
|
||||||
### C Interop Type Mapping
|
### C Interop Type Mapping
|
||||||
|
|
||||||
| C type | sx type | Notes |
|
| C type | sx type | Notes |
|
||||||
@@ -906,7 +927,7 @@ A variable declaration (`name :=`) inside an inner scope shadows any variable wi
|
|||||||
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.
|
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
|
### I/O
|
||||||
- `write(str: string) -> void` — write a string to standard output
|
- `out(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).
|
- `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
|
### Math
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ pub const Node = struct {
|
|||||||
continue_expr: void,
|
continue_expr: void,
|
||||||
undef_literal: void,
|
undef_literal: void,
|
||||||
builtin_expr: void,
|
builtin_expr: void,
|
||||||
foreign_expr: void,
|
foreign_expr: ForeignExpr,
|
||||||
library_decl: LibraryDecl,
|
library_decl: LibraryDecl,
|
||||||
function_type_expr: FunctionTypeExpr,
|
function_type_expr: FunctionTypeExpr,
|
||||||
|
|
||||||
@@ -368,8 +368,14 @@ pub const NamespaceDecl = struct {
|
|||||||
decls: []const *Node,
|
decls: []const *Node,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const ForeignExpr = struct {
|
||||||
|
library_ref: ?[]const u8 = null, // identifier name of library constant
|
||||||
|
c_name: ?[]const u8 = null, // C symbol name override
|
||||||
|
};
|
||||||
|
|
||||||
pub const LibraryDecl = struct {
|
pub const LibraryDecl = struct {
|
||||||
lib_name: []const u8,
|
lib_name: []const u8,
|
||||||
|
name: []const u8, // sx-side constant name
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FunctionTypeExpr = struct {
|
pub const FunctionTypeExpr = struct {
|
||||||
|
|||||||
101
src/codegen.zig
101
src/codegen.zig
@@ -186,6 +186,10 @@ pub const CodeGen = struct {
|
|||||||
foreign_libraries: std.ArrayList([]const u8),
|
foreign_libraries: std.ArrayList([]const u8),
|
||||||
// Set of foreign function names (for ABI lowering at call sites)
|
// Set of foreign function names (for ABI lowering at call sites)
|
||||||
foreign_fns: std.StringHashMap(void),
|
foreign_fns: std.StringHashMap(void),
|
||||||
|
// Named library constants: sx name → lib filename (e.g. "libc" → "c")
|
||||||
|
library_constants: std.StringHashMap([]const u8),
|
||||||
|
// Foreign function rename map: sx name → C symbol name (e.g. "write_fd" → "write")
|
||||||
|
foreign_name_map: std.StringHashMap([]const u8),
|
||||||
// Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime)
|
// Global mutable variables (from top-level var_decl, e.g. function pointers loaded at runtime)
|
||||||
global_mutable_vars: std.StringHashMap(NamedValue),
|
global_mutable_vars: std.StringHashMap(NamedValue),
|
||||||
// Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip)
|
// Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip)
|
||||||
@@ -394,6 +398,8 @@ pub const CodeGen = struct {
|
|||||||
.deferred_fn_bodies = std.ArrayList(DeferredFn).empty,
|
.deferred_fn_bodies = std.ArrayList(DeferredFn).empty,
|
||||||
.foreign_libraries = std.ArrayList([]const u8).empty,
|
.foreign_libraries = std.ArrayList([]const u8).empty,
|
||||||
.foreign_fns = std.StringHashMap(void).init(allocator),
|
.foreign_fns = std.StringHashMap(void).init(allocator),
|
||||||
|
.library_constants = std.StringHashMap([]const u8).init(allocator),
|
||||||
|
.foreign_name_map = std.StringHashMap([]const u8).init(allocator),
|
||||||
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
|
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
|
||||||
.function_return_types = std.StringHashMap(Type).init(allocator),
|
.function_return_types = std.StringHashMap(Type).init(allocator),
|
||||||
.target_config = target_config,
|
.target_config = target_config,
|
||||||
@@ -426,6 +432,8 @@ pub const CodeGen = struct {
|
|||||||
self.deferred_fn_bodies.deinit(self.allocator);
|
self.deferred_fn_bodies.deinit(self.allocator);
|
||||||
self.foreign_libraries.deinit(self.allocator);
|
self.foreign_libraries.deinit(self.allocator);
|
||||||
self.foreign_fns.deinit();
|
self.foreign_fns.deinit();
|
||||||
|
self.library_constants.deinit();
|
||||||
|
self.foreign_name_map.deinit();
|
||||||
c.LLVMDisposeBuilder(self.builder);
|
c.LLVMDisposeBuilder(self.builder);
|
||||||
if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm);
|
if (self.target_machine) |tm| c.LLVMDisposeTargetMachine(tm);
|
||||||
if (self.module_owned) {
|
if (self.module_owned) {
|
||||||
@@ -1038,6 +1046,27 @@ pub const CodeGen = struct {
|
|||||||
// Initialize built-in function declarations (printf, etc.)
|
// Initialize built-in function declarations (printf, etc.)
|
||||||
self.builtins = Builtins.init(self.module, self.context);
|
self.builtins = Builtins.init(self.module, self.context);
|
||||||
|
|
||||||
|
// Pre-scan: collect named library constants (handles forward references)
|
||||||
|
for (root.data.root.decls) |decl| {
|
||||||
|
switch (decl.data) {
|
||||||
|
.library_decl => |ld| {
|
||||||
|
try self.library_constants.put(ld.name, ld.lib_name);
|
||||||
|
},
|
||||||
|
.namespace_decl => |ns| {
|
||||||
|
for (ns.decls) |nd| {
|
||||||
|
switch (nd.data) {
|
||||||
|
.library_decl => |nld| {
|
||||||
|
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, nld.name });
|
||||||
|
try self.library_constants.put(qualified, nld.lib_name);
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Pass 1: Register all declarations (signatures only, no bodies)
|
// Pass 1: Register all declarations (signatures only, no bodies)
|
||||||
for (root.data.root.decls) |decl| {
|
for (root.data.root.decls) |decl| {
|
||||||
switch (decl.data) {
|
switch (decl.data) {
|
||||||
@@ -1904,11 +1933,34 @@ pub const CodeGen = struct {
|
|||||||
|
|
||||||
fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void {
|
fn registerFnDecl(self: *CodeGen, fd: ast.FnDecl, llvm_name: []const u8) !void {
|
||||||
const is_foreign = fd.body.data == .foreign_expr;
|
const is_foreign = fd.body.data == .foreign_expr;
|
||||||
|
// For foreign functions: resolve C symbol name (rename) and validate library ref
|
||||||
|
const actual_llvm_name = if (is_foreign) blk: {
|
||||||
|
const fe = fd.body.data.foreign_expr;
|
||||||
|
// Validate library reference
|
||||||
|
if (fe.library_ref) |lib_ref| {
|
||||||
|
if (!self.library_constants.contains(lib_ref)) {
|
||||||
|
return self.emitErrorFmt("unknown library '{s}' in #foreign", .{lib_ref});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Use C symbol name if provided, otherwise use the sx name
|
||||||
|
const c_name = fe.c_name orelse llvm_name;
|
||||||
|
// Track rename mapping for call resolution
|
||||||
|
if (fe.c_name != null and !std.mem.eql(u8, c_name, llvm_name)) {
|
||||||
|
try self.foreign_name_map.put(llvm_name, c_name);
|
||||||
|
}
|
||||||
|
break :blk c_name;
|
||||||
|
} else llvm_name;
|
||||||
const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name, is_foreign);
|
const fn_type = try self.buildFnType(fd.params, fd.return_type, fd.name, is_foreign);
|
||||||
const name_z = try self.allocator.dupeZ(u8, llvm_name);
|
const name_z = try self.allocator.dupeZ(u8, actual_llvm_name);
|
||||||
_ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type);
|
_ = c.LLVMAddFunction(self.module, name_z.ptr, fn_type);
|
||||||
// Track foreign functions for ABI lowering at call sites
|
// Track foreign functions for ABI lowering at call sites (use sx name for call-site lookup)
|
||||||
if (is_foreign) try self.foreign_fns.put(llvm_name, {});
|
if (is_foreign) {
|
||||||
|
try self.foreign_fns.put(llvm_name, {});
|
||||||
|
// Also track under the C name for direct lookups
|
||||||
|
if (!std.mem.eql(u8, actual_llvm_name, llvm_name)) {
|
||||||
|
try self.foreign_fns.put(actual_llvm_name, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
// Track resolved parameter types for accurate call-site conversion
|
// Track resolved parameter types for accurate call-site conversion
|
||||||
var param_types = std.ArrayList(Type).empty;
|
var param_types = std.ArrayList(Type).empty;
|
||||||
for (fd.params) |param| {
|
for (fd.params) |param| {
|
||||||
@@ -1949,10 +2001,17 @@ pub const CodeGen = struct {
|
|||||||
}
|
}
|
||||||
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
|
const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name });
|
||||||
if (fd.body.data == .foreign_expr) {
|
if (fd.body.data == .foreign_expr) {
|
||||||
// External C function in namespace — register LLVM declaration with C name only
|
// External C function in namespace — register LLVM declaration
|
||||||
try self.registerFnDecl(fd, fd.name);
|
try self.registerFnDecl(fd, fd.name);
|
||||||
// Also track qualified name as foreign for ABI lowering at call sites
|
// Also track qualified name as foreign for ABI lowering at call sites
|
||||||
try self.foreign_fns.put(qualified, {});
|
try self.foreign_fns.put(qualified, {});
|
||||||
|
// Track qualified rename mapping if C name differs
|
||||||
|
const fe = fd.body.data.foreign_expr;
|
||||||
|
if (fe.c_name) |c_name| {
|
||||||
|
if (!std.mem.eql(u8, c_name, fd.name)) {
|
||||||
|
try self.foreign_name_map.put(qualified, c_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Store param types under qualified name so call-site type resolution works
|
// Store param types under qualified name so call-site type resolution works
|
||||||
var param_types = std.ArrayList(Type).empty;
|
var param_types = std.ArrayList(Type).empty;
|
||||||
for (fd.params) |param| {
|
for (fd.params) |param| {
|
||||||
@@ -2054,8 +2113,16 @@ pub const CodeGen = struct {
|
|||||||
if (expr.data == .call) {
|
if (expr.data == .call) {
|
||||||
if (self.resolveCalleeName(expr.data.call)) |callee_name| {
|
if (self.resolveCalleeName(expr.data.call)) |callee_name| {
|
||||||
var cnbuf: [256]u8 = undefined;
|
var cnbuf: [256]u8 = undefined;
|
||||||
const callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf)) orelse return Type.s(64);
|
var callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(callee_name, &cnbuf));
|
||||||
const fn_type = c.LLVMGlobalGetValueType(callee_fn);
|
// Foreign rename fallback
|
||||||
|
if (callee_fn == null) {
|
||||||
|
if (self.foreign_name_map.get(callee_name)) |c_name| {
|
||||||
|
var rbuf: [256]u8 = undefined;
|
||||||
|
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resolved_fn = callee_fn orelse return Type.s(64);
|
||||||
|
const fn_type = c.LLVMGlobalGetValueType(resolved_fn);
|
||||||
const ret_llvm = c.LLVMGetReturnType(fn_type);
|
const ret_llvm = c.LLVMGetReturnType(fn_type);
|
||||||
return self.llvmTypeToSxType(ret_llvm);
|
return self.llvmTypeToSxType(ret_llvm);
|
||||||
}
|
}
|
||||||
@@ -3160,6 +3227,13 @@ pub const CodeGen = struct {
|
|||||||
fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified, &qbuf));
|
fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified, &qbuf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Foreign rename fallback: sx name → C symbol name
|
||||||
|
if (fn_val == null) {
|
||||||
|
if (self.foreign_name_map.get(ident.name)) |c_name| {
|
||||||
|
var rbuf: [256]u8 = undefined;
|
||||||
|
fn_val = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf));
|
||||||
|
}
|
||||||
|
}
|
||||||
if (fn_val != null) return fn_val.?;
|
if (fn_val != null) return fn_val.?;
|
||||||
}
|
}
|
||||||
return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name});
|
return self.emitErrorFmt("undefined identifier '{s}'", .{ident.name});
|
||||||
@@ -5386,6 +5460,13 @@ pub const CodeGen = struct {
|
|||||||
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified2, &qbuf));
|
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(qualified2, &qbuf));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Foreign rename fallback: sx name → C symbol name (e.g. "write_fd" → "write")
|
||||||
|
if (callee_fn == null) {
|
||||||
|
if (self.foreign_name_map.get(callee_name)) |c_name| {
|
||||||
|
var rbuf: [256]u8 = undefined;
|
||||||
|
callee_fn = c.LLVMGetNamedFunction(self.module, self.nameToCStr(c_name, &rbuf));
|
||||||
|
}
|
||||||
|
}
|
||||||
// Function pointer indirect call: callee is a variable with function_type
|
// Function pointer indirect call: callee is a variable with function_type
|
||||||
if (callee_fn == null) {
|
if (callee_fn == null) {
|
||||||
if (self.lookupValue(callee_name)) |v| {
|
if (self.lookupValue(callee_name)) |v| {
|
||||||
@@ -6795,7 +6876,7 @@ pub const CodeGen = struct {
|
|||||||
fn dispatchBuiltin(self: *CodeGen, name: []const u8, call_node: ast.Call) !c.LLVMValueRef {
|
fn dispatchBuiltin(self: *CodeGen, name: []const u8, call_node: ast.Call) !c.LLVMValueRef {
|
||||||
// Extract base name (strip namespace prefix)
|
// Extract base name (strip namespace prefix)
|
||||||
const base = baseName(name);
|
const base = baseName(name);
|
||||||
if (std.mem.eql(u8, base, "write")) return self.genWriteCall(call_node.args);
|
if (std.mem.eql(u8, base, "out")) return self.genOutCall(call_node.args);
|
||||||
if (std.mem.eql(u8, base, "sqrt")) return self.genMathIntrinsic(call_node, "sqrt");
|
if (std.mem.eql(u8, base, "sqrt")) return self.genMathIntrinsic(call_node, "sqrt");
|
||||||
if (std.mem.eql(u8, base, "sin")) return self.genMathIntrinsic(call_node, "sin");
|
if (std.mem.eql(u8, base, "sin")) return self.genMathIntrinsic(call_node, "sin");
|
||||||
if (std.mem.eql(u8, base, "cos")) return self.genMathIntrinsic(call_node, "cos");
|
if (std.mem.eql(u8, base, "cos")) return self.genMathIntrinsic(call_node, "cos");
|
||||||
@@ -6816,8 +6897,8 @@ pub const CodeGen = struct {
|
|||||||
return self.emitErrorFmt("unknown builtin function '{s}'", .{name});
|
return self.emitErrorFmt("unknown builtin function '{s}'", .{name});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genWriteCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
|
fn genOutCall(self: *CodeGen, args: []const *Node) !c.LLVMValueRef {
|
||||||
if (args.len != 1) return self.emitError("write expects exactly 1 argument");
|
if (args.len != 1) return self.emitError("out expects exactly 1 argument");
|
||||||
const builtins = try self.requireBuiltins();
|
const builtins = try self.requireBuiltins();
|
||||||
const val = try self.genExpr(args[0]);
|
const val = try self.genExpr(args[0]);
|
||||||
// Extract ptr and len from string slice
|
// Extract ptr and len from string slice
|
||||||
@@ -7311,7 +7392,7 @@ pub const CodeGen = struct {
|
|||||||
}
|
}
|
||||||
defer _ = c.LLVMOrcDisposeLLJIT(jit);
|
defer _ = c.LLVMOrcDisposeLLJIT(jit);
|
||||||
|
|
||||||
// Add process symbols so JIT can find libc (printf, write, etc.)
|
// Add process symbols so JIT can find libc (printf, etc.)
|
||||||
const jd = c.LLVMOrcLLJITGetMainJITDylib(jit);
|
const jd = c.LLVMOrcLLJITGetMainJITDylib(jit);
|
||||||
const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit);
|
const prefix = c.LLVMOrcLLJITGetGlobalPrefix(jit);
|
||||||
var gen: c.LLVMOrcDefinitionGeneratorRef = null;
|
var gen: c.LLVMOrcDefinitionGeneratorRef = null;
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ pub const Instruction = union(enum) {
|
|||||||
|
|
||||||
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
|
pub const ValueKind = enum { int, float, f32_k, bool_k, string };
|
||||||
|
|
||||||
pub const BuiltinId = enum { print, write, sqrt, size_of, cast, alloc };
|
pub const BuiltinId = enum { print, out, sqrt, size_of, cast, alloc };
|
||||||
|
|
||||||
/// A compiled function or expression — a flat sequence of instructions.
|
/// A compiled function or expression — a flat sequence of instructions.
|
||||||
pub const Chunk = struct {
|
pub const Chunk = struct {
|
||||||
@@ -1354,8 +1354,8 @@ pub const VM = struct {
|
|||||||
|
|
||||||
fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void {
|
fn callBuiltin(self: *VM, id: BuiltinId, arg_count: u8) !void {
|
||||||
switch (id) {
|
switch (id) {
|
||||||
.write => {
|
.out => {
|
||||||
// write(str) — raw string output
|
// out(str) — raw string output
|
||||||
if (arg_count >= 1) {
|
if (arg_count >= 1) {
|
||||||
const val = try self.pop();
|
const val = try self.pop();
|
||||||
const str = try val.format(self.allocator);
|
const str = try val.format(self.allocator);
|
||||||
|
|||||||
@@ -879,7 +879,7 @@ pub const Server = struct {
|
|||||||
.{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} },
|
.{ .name = "alloc", .label = "alloc(size: s32) -> string", .params = &.{"size: s32"} },
|
||||||
.{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} },
|
.{ .name = "sqrt", .label = "sqrt(x: $T) -> T", .params = &.{"x: $T"} },
|
||||||
.{ .name = "print", .label = "print(fmt: string, args: ..Any)", .params = &.{ "fmt: string", "args: ..Any" } },
|
.{ .name = "print", .label = "print(fmt: string, args: ..Any)", .params = &.{ "fmt: string", "args: ..Any" } },
|
||||||
.{ .name = "write", .label = "write(str: string) -> void", .params = &.{"str: string"} },
|
.{ .name = "out", .label = "out(str: string) -> void", .params = &.{"str: string"} },
|
||||||
};
|
};
|
||||||
for (&builtin_sigs) |b| {
|
for (&builtin_sigs) |b| {
|
||||||
const matches = std.mem.eql(u8, call_ctx.name, b.name) or
|
const matches = std.mem.eql(u8, call_ctx.name, b.name) or
|
||||||
|
|||||||
@@ -62,19 +62,6 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start, .{ .import_decl = .{ .path = path, .name = null } });
|
return try self.createNode(start, .{ .import_decl = .{ .path = path, .name = null } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top-level #library directive: #library "libname";
|
|
||||||
if (self.current.tag == .hash_library) {
|
|
||||||
self.advance();
|
|
||||||
if (self.current.tag != .string_literal) {
|
|
||||||
return self.fail("expected string after '#library'");
|
|
||||||
}
|
|
||||||
const raw = self.tokenSlice(self.current);
|
|
||||||
const lib_name = raw[1 .. raw.len - 1];
|
|
||||||
self.advance();
|
|
||||||
try self.expect(.semicolon);
|
|
||||||
return try self.createNode(start, .{ .library_decl = .{ .lib_name = lib_name } });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top-level #run directive
|
// Top-level #run directive
|
||||||
if (self.current.tag == .hash_run) {
|
if (self.current.tag == .hash_run) {
|
||||||
self.advance();
|
self.advance();
|
||||||
@@ -131,6 +118,19 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } });
|
return try self.createNode(start_pos, .{ .import_decl = .{ .path = path, .name = name } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Named library: name :: #library "libname";
|
||||||
|
if (self.current.tag == .hash_library) {
|
||||||
|
self.advance();
|
||||||
|
if (self.current.tag != .string_literal) {
|
||||||
|
return self.fail("expected string after '#library'");
|
||||||
|
}
|
||||||
|
const raw = self.tokenSlice(self.current);
|
||||||
|
const lib_name = raw[1 .. raw.len - 1];
|
||||||
|
self.advance();
|
||||||
|
try self.expect(.semicolon);
|
||||||
|
return try self.createNode(start_pos, .{ .library_decl = .{ .lib_name = lib_name, .name = name } });
|
||||||
|
}
|
||||||
|
|
||||||
// Compile-time evaluation: name :: #run expr;
|
// Compile-time evaluation: name :: #run expr;
|
||||||
if (self.current.tag == .hash_run) {
|
if (self.current.tag == .hash_run) {
|
||||||
const run_start = self.current.loc.start;
|
const run_start = self.current.loc.start;
|
||||||
@@ -192,12 +192,27 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } });
|
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// name :: type_expr #foreign; — foreign with type annotation
|
// name :: type_expr #foreign lib ["c_name"]; — foreign with type annotation
|
||||||
if (self.current.tag == .hash_foreign) {
|
if (self.current.tag == .hash_foreign) {
|
||||||
const fi_start = self.current.loc.start;
|
const fi_start = self.current.loc.start;
|
||||||
self.advance();
|
self.advance();
|
||||||
|
// Required: library reference (identifier)
|
||||||
|
if (self.current.tag != .identifier)
|
||||||
|
return self.fail("expected library name after '#foreign'");
|
||||||
|
const lib_ref = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
// Optional: C symbol name (string literal)
|
||||||
|
var c_name: ?[]const u8 = null;
|
||||||
|
if (self.current.tag == .string_literal) {
|
||||||
|
const raw = self.tokenSlice(self.current);
|
||||||
|
c_name = raw[1 .. raw.len - 1];
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
try self.expect(.semicolon);
|
try self.expect(.semicolon);
|
||||||
const fi = try self.createNode(fi_start, .{ .foreign_expr = {} });
|
const fi = try self.createNode(fi_start, .{ .foreign_expr = .{
|
||||||
|
.library_ref = lib_ref,
|
||||||
|
.c_name = c_name,
|
||||||
|
} });
|
||||||
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi } });
|
return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = fi } });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -754,8 +769,23 @@ pub const Parser = struct {
|
|||||||
} else if (self.current.tag == .hash_foreign) blk: {
|
} else if (self.current.tag == .hash_foreign) blk: {
|
||||||
const fi_start = self.current.loc.start;
|
const fi_start = self.current.loc.start;
|
||||||
self.advance();
|
self.advance();
|
||||||
|
// Required: library reference (identifier)
|
||||||
|
if (self.current.tag != .identifier)
|
||||||
|
return self.fail("expected library name after '#foreign'");
|
||||||
|
const lib_ref = self.tokenSlice(self.current);
|
||||||
|
self.advance();
|
||||||
|
// Optional: C symbol name (string literal)
|
||||||
|
var c_name: ?[]const u8 = null;
|
||||||
|
if (self.current.tag == .string_literal) {
|
||||||
|
const raw = self.tokenSlice(self.current);
|
||||||
|
c_name = raw[1 .. raw.len - 1];
|
||||||
|
self.advance();
|
||||||
|
}
|
||||||
try self.expect(.semicolon);
|
try self.expect(.semicolon);
|
||||||
break :blk try self.createNode(fi_start, .{ .foreign_expr = {} });
|
break :blk try self.createNode(fi_start, .{ .foreign_expr = .{
|
||||||
|
.library_ref = lib_ref,
|
||||||
|
.c_name = c_name,
|
||||||
|
} });
|
||||||
} else if (self.current.tag == .fat_arrow) blk: {
|
} else if (self.current.tag == .fat_arrow) blk: {
|
||||||
is_arrow = true;
|
is_arrow = true;
|
||||||
self.advance();
|
self.advance();
|
||||||
@@ -1826,7 +1856,7 @@ test "parse namespaced import" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "parse library declaration" {
|
test "parse library declaration" {
|
||||||
const source = "#library \"raylib\";";
|
const source = "rl :: #library \"raylib\";";
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), source);
|
var parser = Parser.init(arena.allocator(), source);
|
||||||
@@ -1835,6 +1865,7 @@ test "parse library declaration" {
|
|||||||
const decl = root.data.root.decls[0];
|
const decl = root.data.root.decls[0];
|
||||||
try std.testing.expect(decl.data == .library_decl);
|
try std.testing.expect(decl.data == .library_decl);
|
||||||
try std.testing.expectEqualStrings("raylib", decl.data.library_decl.lib_name);
|
try std.testing.expectEqualStrings("raylib", decl.data.library_decl.lib_name);
|
||||||
|
try std.testing.expectEqualStrings("rl", decl.data.library_decl.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse void function with builtin body" {
|
test "parse void function with builtin body" {
|
||||||
@@ -1851,7 +1882,7 @@ test "parse void function with builtin body" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "parse void function with foreign body" {
|
test "parse void function with foreign body" {
|
||||||
const source = "InitWindow :: (width: s32, height: s32, title: *u8) -> void #foreign;";
|
const source = "InitWindow :: (width: s32, height: s32, title: *u8) -> void #foreign rl;";
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), source);
|
var parser = Parser.init(arena.allocator(), source);
|
||||||
@@ -1861,6 +1892,7 @@ test "parse void function with foreign body" {
|
|||||||
try std.testing.expect(decl.data == .fn_decl);
|
try std.testing.expect(decl.data == .fn_decl);
|
||||||
try std.testing.expectEqualStrings("InitWindow", decl.data.fn_decl.name);
|
try std.testing.expectEqualStrings("InitWindow", decl.data.fn_decl.name);
|
||||||
try std.testing.expect(decl.data.fn_decl.body.data == .foreign_expr);
|
try std.testing.expect(decl.data.fn_decl.body.data == .foreign_expr);
|
||||||
|
try std.testing.expectEqualStrings("rl", decl.data.fn_decl.body.data.foreign_expr.library_ref.?);
|
||||||
try std.testing.expectEqual(@as(usize, 3), decl.data.fn_decl.params.len);
|
try std.testing.expectEqual(@as(usize, 3), decl.data.fn_decl.params.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ outer-defer
|
|||||||
defer-in-if: body
|
defer-in-if: body
|
||||||
defer-in-if: deferred
|
defer-in-if: deferred
|
||||||
=== 7. Builtins ===
|
=== 7. Builtins ===
|
||||||
write-ok
|
out-ok
|
||||||
sqrt: 3.000000
|
sqrt: 3.000000
|
||||||
sqrt-f64: 4.000000
|
sqrt-f64: 4.000000
|
||||||
sizeof-s32: 4
|
sizeof-s32: 4
|
||||||
@@ -240,4 +240,6 @@ flags-explicit-raw: 68
|
|||||||
var swap: 20 10
|
var swap: 20 10
|
||||||
arr swap: 3 1
|
arr swap: 3 1
|
||||||
3-way: 3 1 2
|
3-way: 3 1 2
|
||||||
|
=== 15. Foreign ===
|
||||||
|
foreign-rename: 42
|
||||||
=== DONE ===
|
=== DONE ===
|
||||||
|
|||||||
Reference in New Issue
Block a user