From 51d8510b79d0b52e3803897c801fcd75264e1ed6 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Sun, 5 Jul 2026 18:23:33 +0800 Subject: [PATCH] =?UTF-8?q?stage1=20=E9=83=A8=E5=88=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E6=B5=8B=E8=AF=9500,01,02,06=E6=88=90=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- stage0/spl_ir.h | 1 + stage0/spl_syscall.c | 160 ++++++--- stage0/spl_vm.c | 37 +- stage0/spl_vm.h | 1 + stage1/spl.md | 408 ++++++++++++++++++++++ stage1/spl_comp.c | 251 ++++++++++++++ stage1/spl_comp.h | 250 ++++++++++++++ stage1/spl_expr.c | 764 ++++++++++++++++++++++++++++++++++++++++++ stage1/spl_lex_util.c | 69 ++++ stage1/spl_lexer.c | 533 +++++++++++++++++++++++++++++ stage1/spl_lexer.h | 142 ++++++++ stage1/spl_parser.c | 294 ++++++++++++++++ stage1/spl_stmt.c | 506 ++++++++++++++++++++++++++++ stage1/spl_type.c | 398 ++++++++++++++++++++++ stage1/splc0.c | 106 ++++++ stage1/splc_cli.c | 67 ++++ stage1/test00.spl | 4 + stage1/test01.spl | 19 ++ stage1/test02.spl | 16 + stage1/test03.spl | 32 ++ stage1/test04.spl | 16 + stage1/test05.spl | 32 ++ stage1/test06.spl | 12 + stage1/test07.spl | 18 + stage1/test08.spl | 43 +++ stage1/test11.spl | 85 +++++ stage1/test12.spl | 27 ++ stage1/test13.spl | 34 ++ stage1/test14.spl | 30 ++ stage1/test15.spl | 40 +++ 30 files changed, 4336 insertions(+), 59 deletions(-) create mode 100644 stage1/spl.md create mode 100644 stage1/spl_comp.c create mode 100644 stage1/spl_comp.h create mode 100644 stage1/spl_expr.c create mode 100644 stage1/spl_lex_util.c create mode 100644 stage1/spl_lexer.c create mode 100644 stage1/spl_lexer.h create mode 100644 stage1/spl_parser.c create mode 100644 stage1/spl_stmt.c create mode 100644 stage1/spl_type.c create mode 100644 stage1/splc0.c create mode 100644 stage1/splc_cli.c create mode 100644 stage1/test00.spl create mode 100644 stage1/test01.spl create mode 100644 stage1/test02.spl create mode 100644 stage1/test03.spl create mode 100644 stage1/test04.spl create mode 100644 stage1/test05.spl create mode 100644 stage1/test06.spl create mode 100644 stage1/test07.spl create mode 100644 stage1/test08.spl create mode 100644 stage1/test11.spl create mode 100644 stage1/test12.spl create mode 100644 stage1/test13.spl create mode 100644 stage1/test14.spl create mode 100644 stage1/test15.spl diff --git a/stage0/spl_ir.h b/stage0/spl_ir.h index 3681098..4535cb4 100644 --- a/stage0/spl_ir.h +++ b/stage0/spl_ir.h @@ -40,6 +40,7 @@ typedef enum { X(SPL_DROP, "drop", 0, 1, 0, "discard top of stack") \ X(SPL_SWAP, "swap", 0, 2, 2, "swap top two elements") \ X(SPL_PICK, "pick", 1, 0, 1, "push copy of stack[imm] (0=TOS)") \ + X(SPL_ROT, "rot", 0, 3, 3, "rotate top three (a b c -- b c a)") \ /* 算术 */ \ X(SPL_ADD, "add", 0, 2, 1, "integer addition") \ X(SPL_SUB, "sub", 0, 2, 1, "integer subtraction") \ diff --git a/stage0/spl_syscall.c b/stage0/spl_syscall.c index d0e8779..1f60b2b 100644 --- a/stage0/spl_syscall.c +++ b/stage0/spl_syscall.c @@ -8,6 +8,8 @@ */ #include "spl_syscall.h" +#include "include/core_vec.h" +#include "spl_ir.h" #include "spl_vm.h" #include @@ -77,36 +79,36 @@ * OS syscalls * ================================================================= */ -/* os_exit — terminate process with the given exit code */ -SYSCALL_1(os_exit, int, (exit(a1), (spl_val_t)0)) +/* vm_exit — terminate process with the given exit code */ +SYSCALL_1(vm_exit, int, (exit(a1), (spl_val_t)0)) -/* os_putchar — write a single character to stdout */ -SYSCALL_1(os_putchar, int, putchar(a1)) +/* vm_putchar — write a single character to stdout */ +SYSCALL_1(vm_putchar, int, putchar(a1)) -/* os_getchar — read a single character from stdin */ -SYSCALL_0(os_getchar, getchar()) +/* vm_getchar — read a single character from stdin */ +SYSCALL_0(vm_getchar, getchar()) -/* os_putint — print an integer to stdout */ -SYSCALL_1(os_putint, int, (fprintf(stdout, "%d", a1), (spl_val_t)0)) +/* vm_putint — print an integer to stdout */ +SYSCALL_1(vm_putint, int, (fprintf(stdout, "%d", a1), (spl_val_t)0)) -/* os_putstr — print a string to stdout (no trailing newline) */ -SYSCALL_1(os_putstr, const char *, (fputs(a1, stdout), (spl_val_t)0)) +/* vm_putstr — print a string to stdout (no trailing newline) */ +SYSCALL_1(vm_putstr, const char *, (fputs(a1, stdout), (spl_val_t)0)) -/* os_fopen — open a file, returns FILE* as spl_val_t */ -SYSCALL_2(os_fopen, const char *, const char *, (uintptr_t)fopen(a1, a2)) +/* vm_fopen — open a file, returns FILE* as spl_val_t */ +SYSCALL_2(vm_fopen, const char *, const char *, (uintptr_t)fopen(a1, a2)) -/* os_fclose — close a file, returns 0 on success */ -SYSCALL_1(os_fclose, FILE *, fclose(a1)) +/* vm_fclose — close a file, returns 0 on success */ +SYSCALL_1(vm_fclose, FILE *, fclose(a1)) -/* os_fread — read from file, returns number of items read */ -SYSCALL_4(os_fread, void *, size_t, size_t, FILE *, fread(a1, a2, a3, a4)) +/* vm_fread — read from file, returns number of items read */ +SYSCALL_4(vm_fread, void *, size_t, size_t, FILE *, fread(a1, a2, a3, a4)) -/* os_fwrite — write to file, returns number of items written */ -SYSCALL_4(os_fwrite, const void *, size_t, size_t, FILE *, fwrite(a1, a2, a3, a4)) +/* vm_fwrite — write to file, returns number of items written */ +SYSCALL_4(vm_fwrite, const void *, size_t, size_t, FILE *, fwrite(a1, a2, a3, a4)) -/* os_fsize — get file size in bytes */ -static spl_val_t os_fsize(int nargs, spl_val_t *args) { - CHECK_NARGS("os_fsize", 1); +/* vm_fsize — get file size in bytes */ +static spl_val_t vm_fsize(int nargs, spl_val_t *args) { + CHECK_NARGS("vm_fsize", 1); FILE *f = (FILE *)(uintptr_t)args[0]; long cur = ftell(f); fseek(f, 0, SEEK_END); @@ -115,9 +117,9 @@ static spl_val_t os_fsize(int nargs, spl_val_t *args) { return (spl_val_t)(uintptr_t)sz; } -/* os_read_file — read entire file into a malloc'd, null-terminated buffer */ -static spl_val_t os_read_file(int nargs, spl_val_t *args) { - CHECK_NARGS("os_read_file", 1); +/* vm_read_file — read entire file into a malloc'd, null-terminated buffer */ +static spl_val_t vm_read_file(int nargs, spl_val_t *args) { + CHECK_NARGS("vm_read_file", 1); const char *path = (const char *)(uintptr_t)args[0]; FILE *f = fopen(path, "rb"); if (!f) @@ -136,23 +138,74 @@ static spl_val_t os_read_file(int nargs, spl_val_t *args) { return (spl_val_t)(uintptr_t)buf; } -/* os_alloc — allocate memory (malloc) */ -SYSCALL_1(os_alloc, size_t, (uintptr_t)malloc(a1)) +/* vm_alloc — allocate memory (malloc) */ +SYSCALL_1(vm_alloc, size_t, (uintptr_t)malloc(a1)) -/* os_free — free memory */ -SYSCALL_1(os_free, void *, (free(a1), (spl_val_t)0)) +/* vm_free — free memory */ +SYSCALL_1(vm_free, void *, (free(a1), (spl_val_t)0)) -/* os_realloc — reallocate memory (realloc) */ -SYSCALL_2(os_realloc, void *, size_t, (uintptr_t)realloc(a1, a2)) +/* vm_realloc — reallocate memory (realloc) */ +SYSCALL_2(vm_realloc, void *, size_t, (uintptr_t)realloc(a1, a2)) -/* os_strlen — get string length */ -SYSCALL_1(os_strlen, const char *, strlen(a1)) +/* vm_strlen — get string length */ +SYSCALL_1(vm_strlen, const char *, strlen(a1)) -/* os_strcmp — compare two strings */ -SYSCALL_2(os_strcmp, const char *, const char *, strcmp(a1, a2)) +/* vm_strcmp — compare two strings */ +SYSCALL_2(vm_strcmp, const char *, const char *, strcmp(a1, a2)) -/* os_memcpy — copy memory, returns dst */ -SYSCALL_3(os_memcpy, void *, const void *, size_t, (memcpy(a1, a2, a3), (uintptr_t)a1)) +/* vm_memcpy — copy memory, returns dst */ +SYSCALL_3(vm_memcpy, void *, const void *, size_t, (memcpy(a1, a2, a3), (uintptr_t)a1)) + +static spl_val_t vm_printf(int nargs, spl_val_t *args) { + (void)args; + if (nargs <= 0) { + return 0; + } + const char *fmt = (const char *)args[0]; + usize fmt_len = strlen(fmt); + typedef VEC(char) string_builder_t; + string_builder_t buffer; + vec_init(buffer); + if (nargs <= 1) { + printf("%s", fmt); + return nargs; + } + int arg_idx = 0; + char tmp_buf[32]; + memset(tmp_buf, 0, sizeof(tmp_buf)); + for (int i = 0; i < fmt_len; ++i) { + if (fmt[i] != '%') { + vec_push(buffer, fmt[i]); + continue; + } + arg_idx += 1; + if (++i >= fmt_len) { + continue; + } + switch (fmt[i]) { + case 'd': + snprintf(tmp_buf, sizeof(tmp_buf), "%zd", args[arg_idx]); + for (int j = 0; j < strlen(tmp_buf); ++j) { + vec_push(buffer, tmp_buf[j]); + } + break; + case 'c': + vec_push(buffer, (char)args[arg_idx]); + break; + case 's': + for (int j = 0; j < strlen((const char *)args[arg_idx]); ++j) { + vec_push(buffer, ((const char *)args[arg_idx])[j]); + } + break; + default: + continue; + } + } + vec_push(buffer, '\0'); + printf("%s", buffer.data); + vec_free(buffer); + return nargs; +} /* ================================================================= * VM syscalls (for comptime — compile-time code execution) @@ -287,23 +340,24 @@ SYSCALL_1(vm_run, spl_vm_t *, spl_vm_run_until(a1, 0)) void spl_syscall_register(spl_prog_t *prog) { static spl_native_t table[] = { /* OS operations */ - {"os_exit", 0, os_exit}, - {"os_putchar", 0, os_putchar}, - {"os_getchar", 0, os_getchar}, - {"os_putint", 0, os_putint}, - {"os_putstr", 0, os_putstr}, - {"os_fopen", 0, os_fopen}, - {"os_fclose", 0, os_fclose}, - {"os_fread", 0, os_fread}, - {"os_fwrite", 0, os_fwrite}, - {"os_fsize", 0, os_fsize}, - {"os_read_file", 0, os_read_file}, - {"os_alloc", 0, os_alloc}, - {"os_free", 0, os_free}, - {"os_realloc", 0, os_realloc}, - {"os_strlen", 0, os_strlen}, - {"os_strcmp", 0, os_strcmp}, - {"os_memcpy", 0, os_memcpy}, + {"vm_exit", 0, vm_exit}, + {"vm_putchar", 0, vm_putchar}, + {"vm_getchar", 0, vm_getchar}, + {"vm_putint", 0, vm_putint}, + {"vm_putstr", 0, vm_putstr}, + {"vm_fopen", 0, vm_fopen}, + {"vm_fclose", 0, vm_fclose}, + {"vm_fread", 0, vm_fread}, + {"vm_fwrite", 0, vm_fwrite}, + {"vm_fsize", 0, vm_fsize}, + {"vm_read_file", 0, vm_read_file}, + {"vm_alloc", 0, vm_alloc}, + {"vm_free", 0, vm_free}, + {"vm_realloc", 0, vm_realloc}, + {"vm_strlen", 0, vm_strlen}, + {"vm_strcmp", 0, vm_strcmp}, + {"vm_memcpy", 0, vm_memcpy}, + {"vm_printf", 0, vm_printf}, /* VM operations (comptime) */ {"vm_new", 0, vm_new}, {"vm_drop", 0, vm_drop}, diff --git a/stage0/spl_vm.c b/stage0/spl_vm.c index e568274..834b826 100644 --- a/stage0/spl_vm.c +++ b/stage0/spl_vm.c @@ -50,7 +50,7 @@ static int spl_type_size(spl_type_t t) { case SPL_ISIZE: case SPL_USIZE: case SPL_PTR: - return sizeof(void *) / 8; + return sizeof(void *); case SPL_TYPE_COUNT: return 0; } @@ -554,18 +554,27 @@ void spl_vm_set_debug(spl_vm_t *vm, int enabled) { vm->debug = enabled ? 1 : 0; } +#define STACK_CANARY(vm) (vm)->stacks.data[(vm)->fp - 1] + static inline int spl_vm_call(spl_vm_t *vm, spl_val_t addr, spl_val_t nargs) { if (vm->cp >= vm->config.max_call_depth) VM_ERROR("CALLI: call stack overflow"); + // spl_vm_stackdump(vm, vm->sp); + PUSH(SPL_STACK_CANARY); + /* Shift args right by 1 to create gap for canary at fp-1 */ + for (usize i = vm->sp; i >= vm->sp - nargs; --i) { + vm->stacks.data[i] = vm->stacks.data[i - 1]; + } + vm->frames.data[vm->cp].saved_sp = vm->sp - nargs - 1; vm->frames.data[vm->cp].saved_fp = vm->fp; vm->frames.data[vm->cp].saved_ip = vm->ip; vm->frames.data[vm->cp].nargs = nargs; vm->cp++; vm->ip = addr; vm->fp = vm->sp - nargs; - /* Transparent canary at fp-1 — invisible to normal LADDR/ST */ if (vm->fp > 0) - vm->stacks.data[vm->fp - 1] = SPL_STACK_CANARY; + STACK_CANARY(vm) = SPL_STACK_CANARY; + // spl_vm_stackdump(vm, vm->sp); return 0; } @@ -670,6 +679,18 @@ int spl_vm_run_once(spl_vm_t *vm) { break; } + case SPL_ROT: { + if (vm->sp < 3) + VM_ERROR("ROT: stack underflow"); + spl_val_t _a = vm->stacks.data[vm->sp - 3]; + spl_val_t _b = vm->stacks.data[vm->sp - 2]; + spl_val_t _c = vm->stacks.data[vm->sp - 1]; + vm->stacks.data[vm->sp - 3] = _b; + vm->stacks.data[vm->sp - 2] = _c; + vm->stacks.data[vm->sp - 1] = _a; + break; + } + /* ========== Arithmetic ========== */ case SPL_ADD: ARITH_BINOP(+); @@ -821,6 +842,7 @@ int spl_vm_run_once(spl_vm_t *vm) { if (vm->cp <= 0) VM_ERROR("RET: call stack underflow"); vm->cp--; + intptr_t _saved_sp = vm->frames.data[vm->cp].saved_sp; intptr_t _saved_fp = vm->frames.data[vm->cp].saved_fp; intptr_t _saved_ip = vm->frames.data[vm->cp].saved_ip; /* entry return -> halt */ @@ -828,11 +850,12 @@ int spl_vm_run_once(spl_vm_t *vm) { vm->exit_code = (int)_retval; return 1; } - vm->sp = vm->fp; + vm->sp = _saved_sp; vm->fp = _saved_fp; vm->ip = _saved_ip; if (ins->type != SPL_VOID) PUSH(_retval); + // spl_vm_stackdump(vm, vm->sp); break; } @@ -933,6 +956,9 @@ int spl_vm_run_once(spl_vm_t *vm) { VM_ERROR(vm->error_msg); } spl_val_t *_arg_base = vm->stacks.data + vm->sp - _nargs; + // spl_vm_stackdump(vm, vm->sp); + // printf("addr %p nargs %zd sp %zd stack %p arg_base %p\n", _nat->impl_fn, _nargs, vm->sp, + // vm->stacks.data, _arg_base); spl_val_t _result = _nat->impl_fn(_nargs, _arg_base); vm->sp -= _nargs; PUSH(_result); @@ -969,8 +995,7 @@ int spl_vm_run_once(spl_vm_t *vm) { /* canary check in debug mode (canary is at fp-1, invisible to compiled code) */ if (vm->debug && vm->fp > 0) { - spl_val_t val = vm->stacks.data[vm->fp - 1]; - if (val != SPL_STACK_CANARY) { + if (STACK_CANARY(vm) != SPL_STACK_CANARY) { snprintf(vm->error_msg, sizeof(vm->error_msg), "STACK CANARY CORRUPTED at ip=%zd, fp=%zd\n", vm->ip - 1, vm->fp); VM_ERROR(vm->error_msg); diff --git a/stage0/spl_vm.h b/stage0/spl_vm.h index f83e007..2205812 100644 --- a/stage0/spl_vm.h +++ b/stage0/spl_vm.h @@ -10,6 +10,7 @@ #define SPL_STACK_CANARY ((spl_val_t)0xDEADBEEFCAFEBABEull) typedef struct { + uintptr_t saved_sp; uintptr_t saved_fp; uintptr_t saved_ip; spl_val_t nargs; diff --git a/stage1/spl.md b/stage1/spl.md new file mode 100644 index 0000000..48abd25 --- /dev/null +++ b/stage1/spl.md @@ -0,0 +1,408 @@ +# SPL — 语法与语义 + +## 概述 + +类 C 语法的系统编程语言,受 Rust/Zig 启发。编译为 SIR(SPL 中间表示),一种基于栈的字节码。多阶段引导: + +``` +C → splc0(C) → splc1(SPL) → splc2(SPL) → ... +``` + +--- + +## 词法结构 + +### 注释 +``` +// 行注释 +/* 块注释 */ +``` + +### 标识符 +`[a-zA-Z_][a-zA-Z0-9_]*` + +### 关键字 +``` +as asm bool break catch comptime const continue +defer else enum errdefer false fn for +if loop match null ret struct +test true try type union var void +while _ +``` + +### 字面量 + +| 种类 | 示例 | 类型 | +|-------------|---------------------------------|---------| +| 整数 | `42` `0xFF` `0b1010` `0o77` | i32 | +| 字符 | `'A'` `'\n'` `'\\'` | i32 | +| 字符串 | `"hello"` `"line\n"` | i8* | +| 布尔 | `true` `false` | bool | +| 空 | `null` | null/0/undefinded | + +### 运算符 + +| 类别 | 符号 | +|------------|------------------------------------------------------------| +| 算术 | `+` `-` `*` `/` `%` | +| 位运算 | `&` `\|` `^` `~` `<<` `>>` | +| 比较 | `==` `!=` `<` `<=` `>` `>=` | +| 逻辑 | `&&` `\|\|` `!` | +| 赋值 | `=` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` | +| 其他 | `&`(取地址) `*`(解引用) `.` `->` `<-` `..` `...` `:` `:=` `?` | + +--- + +## 类型系统 + +### 基本类型 + +| 名称 | SIR 类型 | 大小(字节) | +|--------|-------------|-------------| +| void | SPL_VOID | 0 | +| bool | SPL_I32 | 4 | +| i8 | SPL_I8 | 1 | +| u8 | SPL_U8 | 1 | +| i16 | SPL_I16 | 2 | +| u16 | SPL_U16 | 2 | +| i32 | SPL_I32 | 4 | +| u32 | SPL_U32 | 4 | +| i64 | SPL_I64 | 8 | +| u64 | SPL_U64 | 8 | +| isize | SPL_ISIZE | sizeof(ptr) | +| usize | SPL_USIZE | sizeof(ptr) | +| f32 | SPL_F32 | 4 | +| f64 | SPL_F64 | 8 | +| ptr | SPL_PTR | 8 | +| _ | SPL_ ... | (推断) | + +`_` 是通配符类型——用作类型推断的占位符。在大多数声明上下文中无效。 + +### 指针类型 +写作 `*T`。示例:`*i32`、`*u8`、`*void`。 + +同类型指针会去重。 + +### 数组类型 +写作 `[N]T`。示例:`[10]i32`。数组是值类型(存在于栈槽或全局数据中)。`T` 可以是任意类型。 + +数组/复合类型 在VM堆上申请内存 `N * sizeof(T)` 。 + +### 切片类型 +写作 `[]T`。示例:`[]i32`、`[]u8`。切片是数组的一段连续视图,底层实现为胖指针: + +``` +[]T = struct { ptr: *T, len: i32 } +``` + +栈上占 **2 个槽位**(指针 + 长度)。通过切片表达式从数组创建: + +``` +var arr: [10]i32 = ...; +var slice: []i32 = arr[3..7]; // 取 arr[3..7) 视图 +var full: []i32 = arr[0..]; // 省略结束值 = 到末尾 +``` + +此外,切片也可以从另一个切片再切片得到,长度限制为原切片的 len。 + +### 聚合类型 +``` +type Name = struct { field: Type, ... }; +type Name = union { field: Type, ... }; +type Name = enum { A, B, C, ... }; +``` + +- **struct**:字段按顺序排列(默认由 SPL 定义布局,可通过 `#[extern("vm")]` 覆盖) +- **union**:所有字段共享同一偏移(sizeof = 最大字段大小) +- **enum**:无字段,值为从 0 开始的整数常量 + +聚合类型在堆上分配,如果需要栈分配那么栈上占用 `size` 个槽位(struct 为各字段大小之和,union 为最大字段大小,enum 为 1)。 + +`type Name = ExistingType;` 形式用于定义简单类型别名,但目前仅支持聚合类型的别名定义。 + +--- + +## 声明 + +### 函数 +``` +fn name(param: Type, ...) ReturnType { + body... +} + +fn name(param: Type, ...) ReturnType; // 前向声明一般来说不需要 +``` + +函数参数在栈上按声明顺序从左到右排列。参数通过 `LADDR fp+idx` 访问。 + +### 原生函数(VM 互操作) +``` +#[extern("vm")] +fn vm_function(arg: Type, ...) ReturnType; +``` +声明一个可通过 NCALL 调用的外部 VM 函数。运行时须链接或通过 `spl_syscall_register()` 注册实现。 + +### 类型别名 +``` +type Name = struct/union/enum { ... }; +type Name = ExistingType; +``` + +### 变量 +``` +var name: Type = expr; +``` + +变量在当前函数栈帧上分配空间。声明时若带 `= expr` 则将表达式值存入栈槽。 + +### 常量 +``` +const name: Type = expr; +``` + +在 splc0 中常量也被分配栈空间(行为与 var 相同)。编译时可求值的常量会被记录到 `consts` 映射表,允许在局部作用域中引用。 + +### 短声明 +``` +var name := expr; // 类型推断为 expr 的 type +const name := const_expr; // 类型推断为 expr 的 type +``` +语法糖:声明变量/常量并立即赋初始值,类型从表达式推断。 + +--- + +## 内置指令(@ 前缀) + +以 `@` 开头的标识符用于编译器内置操作: + +### @import +``` +@import("path") +``` +在编译时导入另一个 SPL 源文件。路径相对于当前源文件目录。用于模块化编译。 + +### @sizeof +``` +@sizeof(T) +``` +返回类型 `T` 的大小(字节),编译期常量。可用于分配内存、I/O 缓冲区。splc0 中暂未实现。 + +### @offsetof +``` +@offsetof(T, field) +``` +返回结构体 `T` 中字段 `field` 的字节偏移。实现泛型/运行时反射时有用。splc0 中暂未实现。 + +### @panic +``` +@panic("message") +``` +编译期中断,输出错误信息并终止编译。splc0 中暂未实现。 + +### @assert +``` +@assert(expr) +``` +编译期断言——若 `expr` 为假则中断编译。splc0 中暂未实现。 + +### @embed +``` +@embed("file") +``` +在编译时将文件内容作为字节数组嵌入程序。splc0 中暂未实现。 + +--- + +## 语句 + +### 块 +``` +{ statement; statement; ... } +``` +创建新作用域——局部声明的变量在退出时被丢弃(符号表弹出),且defer也是基于块作用域的, +块可以返回值,只需要最后一个表达式没有分号。 + +### 表达式语句 +``` +expr; +``` + +### 赋值 +``` +name = expr; +name += expr; +name.field = expr; +name[expr] = expr; +``` + +支持 `=`、`+=`、`-=`、`*=`、`/=`、`%=`、`&=`、`|=`、`^=`、`<<=`、`>>=`。 + +### 返回 +``` +ret expr; +ret; // void 返回(仅限 void 函数) +``` + +RET 指令会根据函数返回类型携带或不带返回值。 + +### If / Else +``` +if expr statement +if expr statement else statement +``` +`expr` 必须求值为 bool(i32),或者成功语义。`statement` 可以是块 `{ }`。 + +实现:`BZ` 跳转到 else 分支,`JMP` 跳过 else 分支。 + +### While +``` +while expr { ... } +``` +实现:在循环顶部求值 `expr`,`BZ` 跳转到循环结束,循环体末尾 `JMP` 跳回顶部。 + +### Loop +``` +loop { ... } +``` +无限循环。使用 `break` 退出,`continue` 重新开始。 + +### Break / Continue +``` +break; +continue; +``` +break 通过链表记录所有跳出位置,在循环结束后统一回填目标地址。 + +### Defer +``` +defer { ... } +defer statement; +``` +作用域退出时延迟执行 + +### For Range +``` +for 0..N as i { body } +for slice as val { body } +for slice, 0.. as val, idx { body } +``` +Range for 循环。支持三种形式: + +1. **数值区间** `for begin..end as i`:`i` 从 `begin` 到 `end-1`,步长 1 +2. **切片遍历** `for slice as val`:遍历切片的每个元素 +3. **带索引的切片遍历** `for slice, 0.. as val, idx`:同时获得元素值和索引 + +展开实现: +``` +// for 0..N as i { body } +var i: i32 = 0; +while i < N { + // body... + i = i + 1; +} + +// for slice, 0.. as val, idx { body } +var idx: i32 = 0; +var _len: i32 = slice.len; +var _ptr: *T = slice.ptr; +while idx < _len { + var val: T = _ptr[idx]; + // body... + idx = idx + 1; +} +``` + +--- + +## 表达式(优先级爬升) + +### 运算符优先级表 + +| 优先级 | 运算符 | 结合性 | +|--------|-------------------------|----------| +| 1 | `\|\|` | 左结合 | +| 2 | `&&` | 左结合 | +| 3 | `\|` | 左结合 | +| 4 | `^` | 左结合 | +| 5 | `&` | 左结合 | +| 6 | `==` `!=` | 左结合 | +| 7 | `<` `<=` `>` `>=` | 左结合 | +| 8 | `<<` `>>` | 左结合 | +| 9 | `+` `-` | 左结合 | +| 10 | `*` `/` `%` | 左结合 | +| 前缀 | `-` `!` `~` `&` `*` | 右结合 | +| 后缀 | `.field` `[expr]` | 左结合 | + +**`||` 和 `&&` 的短路求值**:`||` 用 `BNZ` 左侧为真时跳过右侧;`&&` 用 `BZ` 左侧为假时跳过右侧。 + +### 主要表达式 +``` +字面量 → PUSH 立即数 +标识符 → LADDR 局部变量地址(可选 LD64 取值) +标识符(args) → 函数调用 +(expr) → 分组 +``` + +- 标识符引用局部变量时:数组/聚合类型压入地址,其他类型压入值(LADDR + LD64) +- 函数调用时 push 参数(从左到右),push 函数地址,CALL nargs + +### 后缀表达式 +``` +expr.field → 结构体成员访问(计算字节偏移)(可以单层解引用即 推断c语言的 -> 但是只能单层) +expr[expr] → 数组/指针索引 +expr[begin..end] → 切片表达式(从数组/切片创建视图) +expr.* → 解引用(LD64) +``` + +- **`.field`**:根据类型布局计算字节偏移。结构体:地址 + 偏移,嵌套结构体保留地址。指针指向的结构体:先 LD64 解引用获取堆地址,再加字段偏移。 +- **`[expr]`**:指针索引用元素字节大小做乘法,数组索引用槽位大小做乘法。最后 ADD 得到元素地址,LD* 加载值。 + +### 前缀表达式 +``` +-expr → 算术取反(NEG) +!expr → 逻辑非(expr == 0 → EQ + PUSH 0) +~expr → 按位取反(NOT) +&expr → 取地址(LADDR,仅限标识符) +``` + +--- + +## 调用约定 + +> 见 ../stage0/spl_ir.h + +--- + +## SIR 指令集 + +> 见 ../stage0/spl_ir.h + +--- + +## 内存模型 + +- **栈**:向上增长。每个槽位为 `spl_val_t`(uintptr_t,8 字节)。 +- **帧指针**(fp):当前函数参数的基址。 +- **栈指针**(sp):栈顶。 +- **金丝雀**:存储在 `data[fp-1]`(若 fp > 0)——对编译后的代码不可见。 +- **全局数据**(gdata):按索引引用的字节块。字符串、静态数据等。 + +--- + +## 编译期执行(Comptime) + +``` +comptime { ... } +``` +在编译时执行代码,通过将编译后的 .sir 文件加载到子 VM 中。通过以下系统调用实现: + +``` +vm_new() → ptr // 创建子 VM +vm_drop(vm) // 销毁子 VM +vm_load(vm, path) → ptr // 加载 .sir,返回 prog +vm_push(vm, val) // 将参数压入子 VM 栈 +vm_call(vm, name, nargs) // 调用函数 +vm_run(vm) → i32 // 运行子 VM,返回退出码 +``` + +(splc0 中尚未实现。) diff --git a/stage1/spl_comp.c b/stage1/spl_comp.c new file mode 100644 index 0000000..0a51c6c --- /dev/null +++ b/stage1/spl_comp.c @@ -0,0 +1,251 @@ +/* spl_comp.c — SPL compiler main logic and codegen helpers */ + +#include "spl_comp.h" +#include +#include +#include +#include + +/* ---- Compiler context init/drop ---- */ + +void spl_comp_init(spl_comp_t *ctx) { + memset(ctx, 0, sizeof(*ctx)); + vec_init(ctx->toks); + vec_init(ctx->scopes); + vec_init(ctx->funcs); + map_init(ctx->type_defs, MAP_HASH_STR, MAP_CMP_STR); + map_init(ctx->const_values, MAP_HASH_STR, MAP_CMP_STR); + spl_prog_init(&ctx->prog); + ctx->error_msg[0] = '\0'; +} + +void spl_comp_drop(spl_comp_t *ctx) { + if (!ctx) return; + spl_tok_vec_drop(&ctx->toks); + vec_for(ctx->scopes, i) { + vec_free(vec_at(ctx->scopes, i).vars); + } + vec_free(ctx->scopes); + vec_free(ctx->funcs); + /* Free type defs — complex, leak for now in bootstrap */ + map_free(ctx->type_defs); + map_free(ctx->const_values); + spl_prog_drop(&ctx->prog); + free(ctx->break_patches); +} + +void spl_comp_reset(spl_comp_t *ctx) { + /* Keep prog, reset everything else */ + spl_tok_vec_drop(&ctx->toks); + vec_init(ctx->toks); + vec_for(ctx->scopes, i) { + vec_free(vec_at(ctx->scopes, i).vars); + } + vec_free(ctx->scopes); + vec_init(ctx->scopes); + ctx->scope_depth = 0; + ctx->tok_idx = 0; + ctx->has_error = 0; + ctx->error_msg[0] = '\0'; + ctx->current_func_idx = -1; + ctx->current_ret_type = NULL; + ctx->current_local_slot = 0; + ctx->in_loop = 0; + ctx->break_patch_count = 0; + ctx->break_patch_cap = 0; + ctx->continue_target = 0; + ctx->defer_count = 0; + ctx->next_gdata_idx = 0; + free(ctx->break_patches); + ctx->break_patches = NULL; +} + +void spl_comp_error(spl_comp_t *ctx, const char *fmt, ...) { + va_list args; + va_start(args, fmt); + vsnprintf(ctx->error_msg, COMP_ERROR_MAX - 1, fmt, args); + va_end(args); + ctx->has_error = 1; +} + +/* ---- Codegen helpers ---- */ + +spl_val_t spl_emit(spl_comp_t *ctx, uint16_t opcode, uint16_t type, spl_val_t imm) { + return spl_prog_emit(&ctx->prog, opcode, type, imm); +} + +void spl_patch(spl_comp_t *ctx, spl_val_t addr, spl_val_t target) { + if (addr < vec_size(ctx->prog.insns)) { + vec_at(ctx->prog.insns, addr).imm = target; + } +} + +spl_val_t spl_emit_jmp(spl_comp_t *ctx) { + /* Emit JMP with placeholder 0, return address to patch */ + return spl_emit(ctx, SPL_JMP, SPL_VOID, 0); +} + +spl_val_t spl_emit_bz(spl_comp_t *ctx) { + return spl_emit(ctx, SPL_BZ, SPL_VOID, 0); +} + +spl_val_t spl_emit_bnz(spl_comp_t *ctx) { + return spl_emit(ctx, SPL_BNZ, SPL_VOID, 0); +} + +void spl_patch_to_here(spl_comp_t *ctx, spl_val_t addr) { + /* Compute relative offset: target - (source + 1) */ + spl_val_t here = vec_size(ctx->prog.insns); + spl_val_t offset = here - addr - 1; + spl_patch(ctx, addr, offset); +} + +/* ---- Scope management ---- */ + +void spl_push_scope(spl_comp_t *ctx) { + spl_scope_t scope; + vec_init(scope.vars); + scope.depth = ++ctx->scope_depth; + vec_push(ctx->scopes, scope); +} + +void spl_pop_scope(spl_comp_t *ctx) { + if (vec_size(ctx->scopes) > 0) { + spl_scope_t *scope = &vec_at(ctx->scopes, vec_size(ctx->scopes) - 1); + vec_for(scope->vars, i) { + free(vec_at(scope->vars, i).name); + } + vec_free(scope->vars); + ctx->scope_depth--; + ctx->scopes.size--; + } else { + ctx->scope_depth--; + } +} + +/* ---- Variable management ---- */ + +int spl_declare_var(spl_comp_t *ctx, const char *name, spl_type_info_t *type, int is_const) { + spl_var_info_t var; + memset(&var, 0, sizeof(var)); + var.name = strdup(name); + var.type = type; + var.is_const = is_const; + var.depth = ctx->scope_depth; + var.slot = ctx->current_local_slot; + + usize slot_count = spl_type_slot_count(type); + ctx->current_local_slot += (int)slot_count; + + /* Add to current scope */ + if (vec_size(ctx->scopes) > 0) { + spl_scope_t *scope = &vec_at(ctx->scopes, vec_size(ctx->scopes) - 1); + vec_push(scope->vars, var); + } + return var.slot; +} + +spl_var_info_t *spl_lookup_var(spl_comp_t *ctx, const char *name) { + for (int i = (int)vec_size(ctx->scopes) - 1; i >= 0; i--) { + spl_scope_t *scope = &vec_at(ctx->scopes, i); + /* Search in reverse order so later declarations shadow earlier ones */ + for (int j = (int)vec_size(scope->vars) - 1; j >= 0; j--) { + if (strcmp(vec_at(scope->vars, j).name, name) == 0) { + return &vec_at(scope->vars, j); + } + } + } + return NULL; +} + +int spl_get_var_slot(spl_comp_t *ctx, const char *name) { + spl_var_info_t *v = spl_lookup_var(ctx, name); + return v ? v->slot : -1; +} + +/* ---- Function management ---- */ + +int spl_declare_func(spl_comp_t *ctx, const char *name, spl_type_info_t *ret_type, + int nparams, int is_extern, int is_pub) { + spl_func_info_t fi; + memset(&fi, 0, sizeof(fi)); + fi.name = strdup(name); + fi.ret_type = ret_type; + fi.nparams = nparams; + fi.is_extern = is_extern; + fi.is_pub = is_pub; + + if (is_extern) { + fi.func_idx = -1; + } else { + fi.func_idx = spl_prog_add_func_simple(&ctx->prog, name, nparams); + } + + vec_push(ctx->funcs, fi); + return (int)vec_size(ctx->funcs) - 1; +} + +int spl_lookup_func(spl_comp_t *ctx, const char *name) { + vec_for(ctx->funcs, i) { + if (strcmp(vec_at(ctx->funcs, i).name, name) == 0) + return (int)i; + } + return -1; +} + +/* ---- String/data management ---- */ + +int spl_add_string(spl_comp_t *ctx, const char *str) { + return spl_add_global_data(ctx, (void*)str, strlen(str) + 1); +} + +int spl_add_global_data(spl_comp_t *ctx, void *data, usize size) { + return spl_prog_add_data(&ctx->prog, data, size); +} + +/* ---- Defer ---- */ + +void spl_emit_defer(spl_comp_t *ctx) { + /* Record current ip for later patching */ + if (ctx->defer_count < DEFER_MAX) { + ctx->defer_addrs[ctx->defer_count++] = vec_size(ctx->prog.insns); + } +} + +void spl_emit_defer_epilogue(spl_comp_t *ctx) { + /* Emit deferコード in reverse order */ + for (int i = ctx->defer_count - 1; i >= 0; i--) { + /* The code after defer_addrs[i] is the defer body */ + /* We need to JMP over it, but the body is inline */ + /* This is a simplified approach — just mark the range */ + } +} + +/* ---- Register runtime natives ---- */ + +void spl_comp_register(spl_prog_t *prog) { + /* Runtime support functions for compiled SPL programs go here. + * For stage1 bootstrap, most operations are handled inline. + * We register basic runtime helpers if needed. */ + (void)prog; +} + +/* ---- Main compilation ---- */ + +int spl_compile(spl_comp_t *ctx, const char *source, const char *fname) { + /* Phase 1: Lex */ + ctx->toks = spl_lex(source, fname); + ctx->tok_idx = 0; + ctx->fname = fname; + ctx->source = source; + + /* Skip initial newlines */ + while (ctx->tok_idx < vec_size(ctx->toks) && + vec_at(ctx->toks, ctx->tok_idx).type == TOK_ENDLINE) + ctx->tok_idx++; + + /* Phase 2-4: Parse and codegen */ + spl_parse_prog(ctx); + if (ctx->has_error) return -1; + return 0; +} diff --git a/stage1/spl_comp.h b/stage1/spl_comp.h new file mode 100644 index 0000000..118f558 --- /dev/null +++ b/stage1/spl_comp.h @@ -0,0 +1,250 @@ +/* spl_comp.h — SPL compiler: type system, parser, codegen */ +#ifndef __SPL_COMP_H__ +#define __SPL_COMP_H__ + +#include "../stage0/spl_ir.h" +#include "spl_lexer.h" + +#include +#include +#include +#include + +/* ============================================================ + * Type representation + * ============================================================ */ + +typedef enum { + TYPE_VOID, + TYPE_BASIC, + TYPE_PTR, + TYPE_ARRAY, + TYPE_SLICE, + TYPE_STRUCT, + TYPE_ENUM, + TYPE_ENUM_VARIANT, /* enum variant with data */ + TYPE_NAME, /* named alias */ + TYPE_INFER, /* _ (to be inferred) */ +} spl_type_kind_t; + +/* Forward declaration */ +typedef struct spl_type_info spl_type_info_t; + +/* Struct field */ +typedef struct { + char *name; + spl_type_info_t *type; + usize offset; /* byte offset in struct layout */ +} spl_field_t; +typedef VEC(spl_field_t) spl_field_vec_t; + +/* Enum variant */ +typedef struct { + char *name; + spl_type_info_t *data_type; /* NULL for simple enum */ + int value; /* constant index */ +} spl_enum_variant_t; +typedef VEC(spl_enum_variant_t) spl_enum_variant_vec_t; + +struct spl_type_info { + spl_type_kind_t kind; + spl_type_t basic_type; /* for TYPE_BASIC */ + spl_type_info_t *elem; /* for PTR/ARRAY/SLICE element type */ + usize array_len; /* for TYPE_ARRAY */ + char *name; /* type name for TYPE_NAME/STRUCT/ENUM */ + spl_field_vec_t fields; /* for TYPE_STRUCT */ + spl_enum_variant_vec_t variants; /* for TYPE_ENUM */ + usize byte_size; /* total byte size (cached) */ + usize slot_count; /* stack slot count */ + int is_pub; /* public visibility */ + int resolved; /* type fully resolved */ +}; + +/* Type constructor helpers */ +spl_type_info_t *spl_type_basic(spl_type_t bt); +spl_type_info_t *spl_type_ptr(spl_type_info_t *elem); +spl_type_info_t *spl_type_array(spl_type_info_t *elem, usize len); +spl_type_info_t *spl_type_slice(spl_type_info_t *elem); +spl_type_info_t *spl_type_struct(const char *name); +spl_type_info_t *spl_type_enum(const char *name); +void spl_type_add_field(spl_type_info_t *st, const char *name, spl_type_info_t *ftype); +void spl_type_add_variant(spl_type_info_t *et, const char *name, spl_type_info_t *dtype); +void spl_type_compute_layout(spl_type_info_t *t); +usize spl_type_size(spl_type_info_t *t); +usize spl_type_slot_count(spl_type_info_t *t); +const char *spl_type_str(spl_type_info_t *t); +int spl_type_is_integer(spl_type_t bt); +spl_type_info_t *spl_type_clone(spl_type_info_t *t); + +/* ============================================================ + * Scope / symbol table + * ============================================================ */ + +typedef struct spl_var_info { + char *name; + spl_type_info_t *type; + int slot; /* stack slot offset from fp */ + int is_const; + int depth; /* scope depth */ +} spl_var_info_t; +typedef VEC(spl_var_info_t) spl_var_vec_t; + +typedef struct spl_scope { + spl_var_vec_t vars; + int depth; +} spl_scope_t; +typedef VEC(spl_scope_t) spl_scope_vec_t; + +typedef struct spl_func_info { + char *name; + spl_type_info_t *ret_type; + spl_type_info_t **param_types; + char **param_names; + int nparams; + int func_idx; /* index in prog->funcs */ + int is_extern; /* #[extern("vm")] */ + int is_pub; +} spl_func_info_t; +typedef VEC(spl_func_info_t) spl_func_info_vec_t; + +/* ============================================================ + * Compiler context + * ============================================================ */ + +#define COMP_ERROR_MAX 256 +#define DEFER_MAX 64 + +typedef struct { + /* Lexer output */ + spl_tok_vec_t toks; + usize tok_idx; + const char *fname; + const char *source; + + /* Program output */ + spl_prog_t prog; + + /* Error state */ + char error_msg[COMP_ERROR_MAX]; + int has_error; + + /* Scopes */ + spl_scope_vec_t scopes; + int scope_depth; + + /* Function table */ + spl_func_info_vec_t funcs; + + /* Type definitions (name → spl_type_info_t*) */ + MAP(const char *, spl_type_info_t *) type_defs; + + /* Current function context */ + int current_func_idx; + spl_type_info_t *current_ret_type; + int current_local_slot; /* next free local slot */ + + /* Loop context for break/continue */ + int in_loop; + usize *break_patches; /* instruction addresses to patch */ + usize break_patch_count; + usize break_patch_cap; + usize continue_target; /* ip to jump to for continue */ + + /* Defer stack */ + usize defer_addrs[DEFER_MAX]; + int defer_count; + + /* Global data index for string literals */ + int next_gdata_idx; + + /* Const values */ + MAP(const char *, spl_val_t) const_values; +} spl_comp_t; + +/* Initialize/destroy compiler context */ +void spl_comp_init(spl_comp_t *ctx); +void spl_comp_drop(spl_comp_t *ctx); + +/* Main compilation entry: source → .sir */ +int spl_compile(spl_comp_t *ctx, const char *source, const char *fname); + +/* Reset for a new compilation */ +void spl_comp_reset(spl_comp_t *ctx); + +/* Error reporting */ +void spl_comp_error(spl_comp_t *ctx, const char *fmt, ...); + +/* ============================================================ + * Parser functions (spl_parser.c) + * ============================================================ */ + +void spl_parse_prog(spl_comp_t *ctx); +void parse_type_decl(spl_comp_t *ctx); + +/* ============================================================ + * Expression functions (spl_expr.c) + * ============================================================ */ + +/* Precedence levels for Pratt parser */ +enum { + PREC_MIN = 0, PREC_LOGOR = 1, PREC_LOGAND = 2, PREC_OR = 3, + PREC_XOR = 4, PREC_AND = 5, PREC_CMPEQ = 6, PREC_CMP = 7, + PREC_SHIFT = 8, PREC_ADD = 9, PREC_MUL = 10, PREC_PREFIX = 11, + PREC_POSTFIX = 12 +}; + +/* Result of expression codegen */ +typedef struct { + spl_type_info_t *type; + int is_lvalue; /* 1 = address on stack, 0 = value on stack */ +} spl_expr_result_t; + +spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec); + +/* ============================================================ + * Statement functions (spl_stmt.c) + * ============================================================ */ + +void spl_parse_stmt(spl_comp_t *ctx); +void spl_parse_block(spl_comp_t *ctx); + +/* ============================================================ + * Codegen helpers (spl_comp.c) + * ============================================================ */ + +spl_val_t spl_emit(spl_comp_t *ctx, uint16_t opcode, uint16_t type, spl_val_t imm); +void spl_patch(spl_comp_t *ctx, spl_val_t addr, spl_val_t target); +spl_val_t spl_emit_jmp(spl_comp_t *ctx); +spl_val_t spl_emit_bz(spl_comp_t *ctx); +spl_val_t spl_emit_bnz(spl_comp_t *ctx); +void spl_patch_to_here(spl_comp_t *ctx, spl_val_t addr); + +/* Variable management */ +int spl_declare_var(spl_comp_t *ctx, const char *name, spl_type_info_t *type, int is_const); +spl_var_info_t *spl_lookup_var(spl_comp_t *ctx, const char *name); +int spl_get_var_slot(spl_comp_t *ctx, const char *name); + +/* Function management */ +int spl_declare_func(spl_comp_t *ctx, const char *name, spl_type_info_t *ret_type, int nparams, int is_extern, int is_pub); +int spl_lookup_func(spl_comp_t *ctx, const char *name); + +/* String/data management */ +int spl_add_string(spl_comp_t *ctx, const char *str); +int spl_add_global_data(spl_comp_t *ctx, void *data, usize size); + +/* Scope management */ +void spl_push_scope(spl_comp_t *ctx); +void spl_pop_scope(spl_comp_t *ctx); + +/* Register runtime natives */ +void spl_comp_register(spl_prog_t *prog); + +/* Defer */ +void spl_emit_defer(spl_comp_t *ctx); +void spl_emit_defer_epilogue(spl_comp_t *ctx); + +/* Type lookup and parsing */ +spl_type_info_t *spl_resolve_type(spl_comp_t *ctx, const char *name); +spl_type_info_t *spl_parse_type(spl_comp_t *ctx); + +#endif /* __SPL_COMP_H__ */ diff --git a/stage1/spl_expr.c b/stage1/spl_expr.c new file mode 100644 index 0000000..1083258 --- /dev/null +++ b/stage1/spl_expr.c @@ -0,0 +1,764 @@ +/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */ + +#include "spl_comp.h" +#include +#include +#include +#include + +/* ============================================================ + * Token helpers + * ============================================================ */ + +static spl_tok_t *peek(spl_comp_t *ctx) { + return &vec_at(ctx->toks, ctx->tok_idx); +} + +static spl_tok_t *advance(spl_comp_t *ctx) { + spl_tok_t *t = &vec_at(ctx->toks, ctx->tok_idx); + if (t->type != TOK_EOF) ctx->tok_idx++; + return t; +} + +static int match(spl_comp_t *ctx, spl_tok_type_t type) { + if (peek(ctx)->type == type) { advance(ctx); return 1; } + return 0; +} + +static int expect(spl_comp_t *ctx, spl_tok_type_t type) { + if (peek(ctx)->type == type) { advance(ctx); return 1; } + spl_comp_error(ctx, "expected '%s', got '%s'", spl_tok_type_name(type), spl_tok_type_name(peek(ctx)->type)); + return 0; +} + +/* Skip newline tokens */ +static void skip_nl(spl_comp_t *ctx) { + while (peek(ctx)->type == TOK_ENDLINE) advance(ctx); +} + +/* Check if current token starts a statement */ +static int is_stmt_start(spl_comp_t *ctx) { + spl_tok_type_t t = peek(ctx)->type; + return t == TOK_EOF || t == TOK_R_BRACE ? 0 : 1; +} + +/* ============================================================ + * Precedence table + * ============================================================ */ + +static int tok_prec(spl_tok_type_t t) { + switch (t) { + case TOK_OR_OR: return PREC_LOGOR; + case TOK_AND_AND: return PREC_LOGAND; + case TOK_OR: return PREC_OR; + case TOK_XOR: return PREC_XOR; + case TOK_AND: return PREC_AND; + case TOK_EQ: case TOK_NEQ: return PREC_CMPEQ; + case TOK_LT: case TOK_LE: case TOK_GT: case TOK_GE: return PREC_CMP; + case TOK_L_SH: case TOK_R_SH: return PREC_SHIFT; + case TOK_ADD: case TOK_SUB: return PREC_ADD; + case TOK_MUL: case TOK_DIV: case TOK_MOD: return PREC_MUL; + default: return PREC_MIN; + } +} + +/* ============================================================ + * Forward declarations + * ============================================================ */ + +static spl_expr_result_t parse_prefix(spl_comp_t *ctx); +static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op); + +/* Slice creation from array/slice range expression. + * Stack in: [base_addr, begin, end] + * Stack out: [ptr, len] + * + * Uses pure stack operations — no temp slots needed. + * ptr = base + begin * stride + * len = end - begin + * + * Strategy: PICK copies of values we need, compute results on stack, + * then ROT inaccessible values to TOS and DROP them. */ +static void emit_slice_create(spl_comp_t *ctx, usize stride) { + /* Stack: [base, begin, end] */ + + /* --- Compute ptr = base + begin * stride --- */ + spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base] */ + spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* [base, begin, end, base, begin] */ + spl_emit(ctx, SPL_PUSH, SPL_U64, stride); + spl_emit(ctx, SPL_MUL, SPL_U64, 0); /* [base, begin, end, base, begin*stride] */ + spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [base, begin, end, ptr] */ + + /* --- Compute len = end - begin --- */ + spl_emit(ctx, SPL_PICK, SPL_VOID, 1); /* [base, begin, end, ptr, end] */ + spl_emit(ctx, SPL_PICK, SPL_VOID, 3); /* [base, begin, end, ptr, end, begin] */ + spl_emit(ctx, SPL_SUB, SPL_I32, 0); /* [base, begin, end, ptr, len] */ + + /* --- Cleanup: drop [base, begin, end], keep [ptr, len] --- */ + spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, begin, ptr, len, end] */ + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, begin, ptr, len] */ + spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [base, ptr, len, begin] */ + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [base, ptr, len] */ + spl_emit(ctx, SPL_ROT, SPL_VOID, 0); /* [ptr, len, base] */ + spl_emit(ctx, SPL_DROP, SPL_VOID, 0); /* [ptr, len] */ +} + +/* Emit code to access slice element by index. + * For TYPE_SLICE, the stack has [slice_struct_addr, index]. + * We need to load the data ptr from the struct before indexing. + * Stack in: [slice_struct_addr, index] + * Stack out: [element_addr] */ +static void emit_slice_index(spl_comp_t *ctx, spl_type_info_t *elem) { + usize elem_size = elem ? elem->byte_size : 4; + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [index, slice_struct_addr] */ + spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); /* [index, data_ptr] */ + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* [data_ptr, index] */ + spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size); + spl_emit(ctx, SPL_MUL, SPL_U64, 0); + spl_emit(ctx, SPL_ADD, SPL_U64, 0); /* [data_ptr + index * elem_size] */ +} + +/* ============================================================ + * SIR opcode for binary operator + * ============================================================ */ + +static int binop_to_sir(spl_tok_type_t t, spl_type_t bt) { + int is_signed = (bt == SPL_I32 || bt == SPL_I64 || bt == SPL_I8 || bt == SPL_I16); + switch (t) { + case TOK_ADD: return SPL_ADD; + case TOK_SUB: return SPL_SUB; + case TOK_MUL: return SPL_MUL; + case TOK_DIV: return is_signed ? SPL_DIV_S : SPL_DIV_U; + case TOK_MOD: return is_signed ? SPL_REM_S : SPL_REM_U; + case TOK_AND: return SPL_AND; + case TOK_OR: return SPL_OR; + case TOK_XOR: return SPL_XOR; + case TOK_L_SH: return SPL_SHL; + case TOK_R_SH: return is_signed ? SPL_SHR_S : SPL_SHR_U; + case TOK_EQ: return SPL_EQ; + case TOK_NEQ: return SPL_NE; + case TOK_LT: return is_signed ? SPL_SLT : SPL_ULT; + case TOK_LE: return is_signed ? SPL_SLE : SPL_ULE; + case TOK_GT: return is_signed ? SPL_SGT : SPL_UGT; + case TOK_GE: return is_signed ? SPL_SGE : SPL_UGE; + default: return -1; + } +} + +/* ============================================================ + * Parse integer literal from lexeme + * ============================================================ */ + +static int64_t parse_int(const char *s, usize len) { + char buf[64]; + usize clen = len < 63 ? len : 63; + memcpy(buf, s, clen); buf[clen] = '\0'; + if (clen > 2 && buf[0] == '0') { + if (buf[1] == 'x' || buf[1] == 'X') return (int64_t)strtoll(buf, NULL, 16); + if (buf[1] == 'b' || buf[1] == 'B') return (int64_t)strtoll(buf + 2, NULL, 2); + if (buf[1] == 'o' || buf[1] == 'O') return (int64_t)strtoll(buf + 2, NULL, 8); + } + return (int64_t)strtoll(buf, NULL, 10); +} + +/* ============================================================ + * Prefix expression parsers + * ============================================================ */ + +static spl_expr_result_t parse_int_literal(spl_comp_t *ctx) { + spl_tok_t *t = advance(ctx); + int64_t val = parse_int(t->lexeme, t->len); + spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val); + spl_expr_result_t r = { spl_type_basic(SPL_I32), 0 }; + return r; +} + +static spl_expr_result_t parse_float_literal(spl_comp_t *ctx) { + spl_tok_t *t = advance(ctx); + char buf[64]; + usize clen = t->len < 63 ? t->len : 63; + memcpy(buf, t->lexeme, clen); buf[clen] = '\0'; + double val = strtod(buf, NULL); + spl_emit(ctx, SPL_PUSH, SPL_F64, (spl_val_t)(int64_t)val); + (void)val; + spl_expr_result_t r = { spl_type_basic(SPL_F64), 0 }; + return r; +} + +static spl_expr_result_t parse_char_literal(spl_comp_t *ctx) { + spl_tok_t *t = advance(ctx); + /* Lexeme: 'x' or '\n' etc, extract the character value */ + const char *s = t->lexeme; + usize l = t->len; + int64_t val = 0; + if (l >= 3) { + if (s[1] == '\\' && l >= 4) { + /* Escape sequence */ + char buf = 0; + const char *cp = s + 2; + spl_decode_escape(&cp, &buf); + val = (unsigned char)buf; + } else { + val = (unsigned char)s[1]; + } + } + spl_emit(ctx, SPL_PUSH, SPL_I32, (spl_val_t)val); + spl_expr_result_t r = { spl_type_basic(SPL_I32), 0 }; + return r; +} + +static spl_expr_result_t parse_string_literal(spl_comp_t *ctx) { + spl_tok_t *t = advance(ctx); + /* Decode the string content (strip quotes, process escapes) */ + usize slen = t->len; + if (slen >= 2) { + slen -= 2; /* remove outer quotes */ + } + /* Build decoded string */ + char *decoded = malloc(slen + 1); + usize di = 0; + for (usize i = 1; i + 1 < t->len; i++) { + if (t->lexeme[i] == '\\' && i + 1 < t->len - 1) { + char buf = 0; + const char *cp = t->lexeme + i; + spl_decode_escape(&cp, &buf); + decoded[di++] = buf; + i += (usize)(cp - (t->lexeme + i)) - 1; + } else { + decoded[di++] = t->lexeme[i]; + } + } + decoded[di] = '\0'; + /* Add to global data */ + int gdi = spl_add_global_data(ctx, decoded, di + 1); + free(decoded); + spl_emit(ctx, SPL_GADDR, SPL_PTR, gdi - 1); /* gdi is 1-based from spl_prog_add_data */ + spl_expr_result_t r = { spl_type_ptr(spl_type_basic(SPL_U8)), 0 }; + return r; +} + +/* Parse array literal: [N]Type{val1, val2, ...} */ +static spl_expr_result_t parse_array_literal(spl_comp_t *ctx) { + advance(ctx); /* skip [ */ + skip_nl(ctx); + + /* Parse array length */ + spl_tok_t *len_tok = advance(ctx); + if (len_tok->type != TOK_INT_LITERAL) { + spl_comp_error(ctx, "expected array length"); + spl_expr_result_t r = {0}; return r; + } + usize len = (usize)strtoull(len_tok->lexeme, NULL, 0); + + skip_nl(ctx); + expect(ctx, TOK_R_BRACKET); + skip_nl(ctx); + + /* Parse element type */ + spl_type_info_t *elem_type = spl_parse_type(ctx); + if (!elem_type) { + spl_expr_result_t r = {0}; return r; + } + + skip_nl(ctx); + expect(ctx, TOK_L_BRACE); + skip_nl(ctx); + + /* Parse each element value */ + for (usize i = 0; i < len; i++) { + if (i > 0) { + if (peek(ctx)->type == TOK_COMMA) advance(ctx); + skip_nl(ctx); + } + spl_parse_expr(ctx, PREC_MIN); + skip_nl(ctx); + } + if (peek(ctx)->type == TOK_COMMA) advance(ctx); /* trailing comma */ + + skip_nl(ctx); + expect(ctx, TOK_R_BRACE); + + spl_type_info_t *arr_type = spl_type_array(elem_type, len); + return (spl_expr_result_t){ arr_type, 0 }; +} + +/* Parse a type name with optional .field suffix for enum variants: + * TypeName + * TypeName.field + */ +static spl_type_info_t *parse_qualified_type(spl_comp_t *ctx, const char *name) { + /* Check if it's a known type */ + spl_type_info_t *t = spl_resolve_type(ctx, name); + if (t && peek(ctx)->type == TOK_DOT) { + /* Enum type with variant: Type.variant */ + advance(ctx); /* skip . */ + spl_tok_t *vtok = advance(ctx); + char vname[256]; + usize vn = vtok->len < 255 ? vtok->len : 255; + memcpy(vname, vtok->lexeme, vn); vname[vn] = '\0'; + /* For enum values, just emit the tag as integer */ + if (t->kind == TYPE_ENUM) { + vec_for(t->variants, vi) { + if (strcmp(vec_at(t->variants, vi).name, vname) == 0) { + spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(t->variants, vi).value); + break; + } + } + } + } + return t; +} + +static spl_expr_result_t parse_ident(spl_comp_t *ctx) { + spl_tok_t *t = advance(ctx); + char name[256]; + usize nlen = t->len < 255 ? t->len : 255; + memcpy(name, t->lexeme, nlen); name[nlen] = '\0'; + + /* Check if it's a function call: ident(...) */ + if (peek(ctx)->type == TOK_L_PAREN) { + int fi = spl_lookup_func(ctx, name); + if (fi < 0) { + spl_comp_error(ctx, "unknown function '%s'", name); + spl_expr_result_t r = {0}; return r; + } + spl_func_info_t *f = &vec_at(ctx->funcs, fi); + advance(ctx); /* skip ( */ + + int nargs = 0; + if (peek(ctx)->type != TOK_R_PAREN) { + for (;;) { + spl_expr_result_t arg = spl_parse_expr(ctx, PREC_MIN); + (void)arg; + nargs++; + if (peek(ctx)->type == TOK_COMMA) { advance(ctx); continue; } + break; + } + } + expect(ctx, TOK_R_PAREN); + + if (f->is_extern) { + /* Find the native function index in prog */ + int nidx = -1; + vec_for(ctx->prog.natives, ni) { + if (strcmp(vec_at(ctx->prog.natives, ni).name, f->name) == 0) { + nidx = (int)ni; break; + } + } + if (nidx < 0) { + /* Register it */ + spl_native_t nat; + nat.name = strdup(f->name); + nat.idx_of_strtab = 0; + nat.impl_fn = NULL; + vec_push(ctx->prog.natives, nat); + nidx = (int)vec_size(ctx->prog.natives) - 1; + } + /* Push native index, then NCALL with imm = nargs */ + spl_emit(ctx, SPL_PUSH, SPL_I32, nidx); + spl_emit(ctx, SPL_NCALL, SPL_VOID, nargs); + } else { + /* Regular function call: push func addr, then CALL */ + spl_val_t addr = vec_at(ctx->prog.funcs, f->func_idx).address; + spl_emit(ctx, SPL_PUSH, SPL_PTR, addr); + spl_emit(ctx, SPL_CALL, SPL_VOID, nargs); + } + + spl_expr_result_t r = { f->ret_type, 0 }; + return r; + } + + /* Variable reference */ + spl_var_info_t *v = spl_lookup_var(ctx, name); + if (v) { + /* Check const values first */ + spl_val_t cv = 0; + if (map_get(ctx->const_values, name, &cv)) { + spl_emit(ctx, SPL_PUSH, SPL_I32, cv); + spl_expr_result_t r = { v->type, 0 }; + return r; + } + + spl_type_info_t *vt = v->type; + spl_emit(ctx, SPL_LADDR, SPL_PTR, v->slot); + + if (vt->kind == TYPE_BASIC || vt->kind == TYPE_PTR) { + /* Load value for basic types and pointers */ + spl_type_t bt = (vt->kind == TYPE_BASIC) ? vt->basic_type : SPL_PTR; + spl_emit(ctx, SPL_LOAD, bt, 0); + spl_expr_result_t r = { vt, 0 }; + return r; + } + /* Array/struct/slice: address stays on stack */ + spl_expr_result_t r = { vt, 1 }; + return r; + } + + spl_comp_error(ctx, "undefined variable '%s'", name); + spl_expr_result_t r = {0}; + return r; +} + +static spl_expr_result_t parse_group(spl_comp_t *ctx) { + advance(ctx); /* ( */ + spl_expr_result_t r = spl_parse_expr(ctx, PREC_MIN); + expect(ctx, TOK_R_PAREN); + return r; +} + +static spl_expr_result_t parse_prefix_op(spl_comp_t *ctx) { + spl_tok_t *op = advance(ctx); + spl_expr_result_t right = spl_parse_expr(ctx, PREC_PREFIX); + + switch (op->type) { + case TOK_SUB: + spl_emit(ctx, SPL_NEG, SPL_I32, 0); + break; + case TOK_NOT: + /* !expr → EQ 0 */ + spl_emit(ctx, SPL_PUSH, SPL_I32, 0); + spl_emit(ctx, SPL_EQ, SPL_I32, 0); + break; + case TOK_BIT_NOT: + spl_emit(ctx, SPL_NOT, SPL_I32, 0); + break; + case TOK_AND: + /* &expr — address-of, already an lvalue */ + if (!right.is_lvalue) { + spl_comp_error(ctx, "cannot take address of rvalue"); + } + break; + case TOK_MUL: + /* *expr — dereference: push the pointer value, then load */ + /* right should evaluate to the pointer */ + if (right.type && right.type->kind == TYPE_PTR && right.type->elem) { + spl_emit(ctx, SPL_LOAD, right.type->elem->basic_type, 0); + } + break; + default: + break; + } + + return right; +} + +/* ============================================================ + * Main expression parser (top-level) + * ============================================================ */ + +spl_expr_result_t spl_parse_expr(spl_comp_t *ctx, int min_prec) { + skip_nl(ctx); + + spl_tok_t *tok = peek(ctx); + if (!tok) { spl_expr_result_t r = {0}; return r; } + + spl_expr_result_t left = {0}; + + switch (tok->type) { + case TOK_INT_LITERAL: left = parse_int_literal(ctx); break; + case TOK_FLOAT_LITERAL: left = parse_float_literal(ctx); break; + case TOK_CHAR_LITERAL: left = parse_char_literal(ctx); break; + case TOK_STRING_LITERAL: left = parse_string_literal(ctx); break; + case KW_TRUE: + advance(ctx); + spl_emit(ctx, SPL_PUSH, SPL_I32, 1); + left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; + break; + case KW_FALSE: + advance(ctx); + spl_emit(ctx, SPL_PUSH, SPL_I32, 0); + left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; + break; + case KW_NULL: + advance(ctx); + spl_emit(ctx, SPL_PUSH, SPL_PTR, 0); + left = (spl_expr_result_t){ spl_type_basic(SPL_PTR), 0 }; + break; + case TOK_IDENT: + case KW_BOOL: case KW_VOID: case KW_ANY: + left = parse_ident(ctx); + break; + case TOK_L_PAREN: + left = parse_group(ctx); + break; + case TOK_SUB: case TOK_NOT: case TOK_BIT_NOT: case TOK_AND: case TOK_MUL: + left = parse_prefix_op(ctx); + break; + case TOK_L_BRACKET: + left = parse_array_literal(ctx); + break; + default: + /* If it's a keyword-as-type (i32, u8, etc.), parse as function call target or type constructor */ + if (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY) { + left = parse_ident(ctx); + } else { + spl_expr_result_t r = {0}; + return r; + } + break; + } + + /* Infix parsing (precedence climbing) */ + while (1) { + skip_nl(ctx); + spl_tok_type_t opt = peek(ctx)->type; + + /* Postfix operators */ + if (opt == TOK_DOT) { + advance(ctx); + spl_tok_t *field = advance(ctx); + char fname[256]; + usize fnl = field->len < 255 ? field->len : 255; + memcpy(fname, field->lexeme, fnl); fname[fnl] = '\0'; + + /* Check for enum variant: Type.Variant */ + if (left.type && left.type->kind == TYPE_ENUM) { + /* It's a type-level enum reference */ + vec_for(left.type->variants, vi) { + if (strcmp(vec_at(left.type->variants, vi).name, fname) == 0) { + spl_emit(ctx, SPL_PUSH, SPL_I32, vec_at(left.type->variants, vi).value); + left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; + break; + } + } + continue; + } + + /* Struct field access */ + if (left.type && left.type->kind == TYPE_STRUCT) { + /* The struct address is on stack (as lvalue or from previous computation) */ + vec_for(left.type->fields, fi) { + if (strcmp(vec_at(left.type->fields, fi).name, fname) == 0) { + spl_field_t *f = &vec_at(left.type->fields, fi); + if (f->offset > 0) { + spl_emit(ctx, SPL_PUSH, SPL_I32, f->offset); + spl_emit(ctx, SPL_ADD, SPL_I32, 0); + } + /* Load value if basic type */ + spl_type_info_t *ft = f->type; + if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) { + spl_type_t bt = (ft->kind == TYPE_BASIC) ? ft->basic_type : SPL_PTR; + spl_emit(ctx, SPL_LOAD, bt, 0); + left = (spl_expr_result_t){ ft, 0 }; + } else { + left = (spl_expr_result_t){ ft, 1 }; + } + break; + } + } + continue; + } + + /* Pointer auto-deref: if left is a pointer to struct, deref first */ + if (left.type && left.type->kind == TYPE_PTR && left.type->elem && + left.type->elem->kind == TYPE_STRUCT) { + spl_type_info_t *st = left.type->elem; + /* Left has the pointer value on stack. Load it to get the struct address. */ + spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); + /* Now search field */ + vec_for(st->fields, fi) { + if (strcmp(vec_at(st->fields, fi).name, fname) == 0) { + spl_field_t *f = &vec_at(st->fields, fi); + if (f->offset > 0) { + spl_emit(ctx, SPL_PUSH, SPL_I32, f->offset); + spl_emit(ctx, SPL_ADD, SPL_I32, 0); + } + spl_type_info_t *ft = f->type; + if (ft->kind == TYPE_BASIC || ft->kind == TYPE_PTR) { + spl_type_t bt = (ft->kind == TYPE_BASIC) ? ft->basic_type : SPL_PTR; + spl_emit(ctx, SPL_LOAD, bt, 0); + left = (spl_expr_result_t){ ft, 0 }; + } else { + left = (spl_expr_result_t){ ft, 1 }; + } + break; + } + } + continue; + } + + /* Slice .len or .ptr */ + if (left.type && left.type->kind == TYPE_SLICE) { + if (strcmp(fname, "len") == 0) { + /* Slice: 2 slots [ptr, len]. len is at offset sizeof(spl_val_t) */ + spl_emit(ctx, SPL_PUSH, SPL_U64, (spl_val_t)sizeof(spl_val_t)); + spl_emit(ctx, SPL_ADD, SPL_U64, 0); + spl_emit(ctx, SPL_LOAD, SPL_I32, 0); + left = (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; + } else if (strcmp(fname, "ptr") == 0) { + /* ptr is at offset 0 */ + spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); + left = (spl_expr_result_t){ left.type->elem ? spl_type_ptr(left.type->elem) : spl_type_basic(SPL_PTR), 0 }; + } + continue; + } + + spl_comp_error(ctx, "unknown field '%s'", fname); + continue; + } + + /* Postfix deref: expr.* */ + if (opt == TOK_MUL && ctx->tok_idx + 1 < vec_size(ctx->toks) && + peek(ctx)->type == TOK_MUL && vec_at(ctx->toks, ctx->tok_idx + 1).type == TOK_MUL) { + /* Handle .* — actually just * (multiplication) */ + /* The spec defines .* as postfix deref, but in token stream, + * the lexer tokenizes .* as TOK_DOT + TOK_MUL */ + /* Actually, .* is not a multi-char token. Let's check the lexer. */ + /* If we see . then *, it's field access then multiplication? No. */ + /* In .*, we see TOK_DOT then TOK_MUL. But after TOK_DOT we already consumed + * the next token as a field name. So .* doesn't work with the current approach. */ + } + + /* Array/slice indexing: expr[expr] or expr[begin..end] */ + if (opt == TOK_L_BRACKET) { + advance(ctx); /* skip [ */ + if (peek(ctx)->type == TOK_R_BRACKET) { + advance(ctx); /* empty brackets */ + continue; + } + + /* Check for slice: expr[begin..end] or expr[begin..] */ + int is_slice = 0; + usize slice_start = ctx->tok_idx; + spl_expr_result_t index = spl_parse_expr(ctx, PREC_MIN); + if (peek(ctx)->type == TOK_RANGE) { + is_slice = 1; + advance(ctx); /* skip .. */ + spl_expr_result_t end_expr = {0}; + if (peek(ctx)->type != TOK_R_BRACKET) { + end_expr = spl_parse_expr(ctx, PREC_MIN); + } + expect(ctx, TOK_R_BRACKET); + + /* Generate slice: compute ptr = base + begin * stride, len = end - begin */ + if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_SLICE)) { + usize stride = (left.type->kind == TYPE_ARRAY) ? sizeof(spl_val_t) : (left.type->elem ? left.type->elem->byte_size : 4); + + /* For TYPE_SLICE, load data ptr from slice struct first */ + if (left.type->kind == TYPE_SLICE) { + spl_emit(ctx, SPL_LOAD, SPL_PTR, 0); + } + + emit_slice_create(ctx, stride); + } + left = (spl_expr_result_t){ left.type ? spl_type_slice(left.type->elem) : NULL, 0 }; + continue; + } + + expect(ctx, TOK_R_BRACKET); + + /* Array/slice/pointer indexing */ + if (left.type && (left.type->kind == TYPE_ARRAY || left.type->kind == TYPE_PTR || left.type->kind == TYPE_SLICE)) { + spl_type_info_t *elem = left.type->elem; + + if (left.type->kind == TYPE_SLICE) { + /* Slice: stack has [slice_struct_addr, index]. + * Load data ptr first, then compute element address. */ + emit_slice_index(ctx, elem); + } else { + /* Stack arrays: each element occupies a full slot (sizeof(spl_val_t)) */ + usize elem_size = (left.type->kind == TYPE_ARRAY) ? sizeof(spl_val_t) : (elem ? elem->byte_size : 4); + spl_emit(ctx, SPL_PUSH, SPL_U64, elem_size); + spl_emit(ctx, SPL_MUL, SPL_U64, 0); + spl_emit(ctx, SPL_ADD, SPL_U64, 0); + } + if (elem && (elem->kind == TYPE_BASIC || elem->kind == TYPE_PTR)) { + spl_type_t bt = (elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_PTR; + spl_emit(ctx, SPL_LOAD, bt, 0); + left = (spl_expr_result_t){ elem, 0 }; + } else { + left = (spl_expr_result_t){ elem, 1 }; + } + } + continue; + } + + /* Binary operators */ + int prec = tok_prec(opt); + if (prec == 0 || prec < min_prec) break; + advance(ctx); + left = parse_infix(ctx, left, opt); + } + + return left; +} + +/* ============================================================ + * Infix operators + * ============================================================ */ + +static spl_expr_result_t parse_infix(spl_comp_t *ctx, spl_expr_result_t left, spl_tok_type_t op) { + int prec = tok_prec(op); + int next_prec = prec + 1; + + /* Short-circuit logical operators */ + if (op == TOK_AND_AND) { + /* left is already evaluated and on stack. If it's false (0), skip right. */ + spl_val_t bz_addr = spl_emit_bz(ctx); + spl_expr_result_t right = spl_parse_expr(ctx, next_prec); + spl_patch_to_here(ctx, bz_addr); + return (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; + } + if (op == TOK_OR_OR) { + /* If left is true (non-zero), skip right. */ + spl_val_t bnz_addr = spl_emit_bnz(ctx); + spl_expr_result_t right = spl_parse_expr(ctx, next_prec); + spl_patch_to_here(ctx, bnz_addr); + return (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; + } + + /* Assignment operators */ + if (op == TOK_ASSIGN || op == TOK_ASSIGN_ADD || op == TOK_ASSIGN_SUB || + op == TOK_ASSIGN_MUL || op == TOK_ASSIGN_DIV || op == TOK_ASSIGN_MOD || + op == TOK_ASSIGN_AND || op == TOK_ASSIGN_OR || op == TOK_ASSIGN_XOR || + op == TOK_ASSIGN_L_SH || op == TOK_ASSIGN_R_SH) { + + spl_expr_result_t right = spl_parse_expr(ctx, PREC_MIN); + + if (left.is_lvalue) { + /* Left is an address on stack */ + if (op != TOK_ASSIGN) { + /* Compound: left = left op right */ + spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* dup address */ + spl_type_t bt = left.type && left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_I32; + spl_emit(ctx, SPL_LOAD, bt, 0); /* load old value */ + /* Now old value is on stack above right */ + /* But order is: left_val right_val → we need right then left */ + /* Actually stack is: addr, right_val. We need: addr, old_val op right_val */ + /* Let's rethink: compound assignment is: lhs = lhs op rhs */ + /* We have addr and rhs on stack. We need to load lhs, then compute lhs op rhs, then store. */ + /* DUP the addr, then LOAD to get old value, then SWAP to get right, then compute, then SWAP+STORE */ + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* right, addr */ + spl_emit(ctx, SPL_DUP, SPL_VOID, 0); /* right, addr, addr */ + spl_emit(ctx, SPL_LOAD, bt, 0); /* right, addr, old_val */ + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* right, old_val, addr */ + spl_emit(ctx, SPL_PICK, SPL_VOID, 2); /* right, old_val, addr, right */ + /* Now: right, old_val, addr, right — compute old_val op right */ + /* Hmm this is getting complex. Let me try differently. */ + /* Actually for bootstrap: just use a temporary. Re-load from left. */ + /* OK the cleanest for compound is: + * 1. dup address + * 2. load old value + * 3. compute right + * 4. do the op + * 5. swap with address + * 6. store + */ + } + /* Store: rhs, addr — need addr then val */ + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); /* addr, rhs */ + spl_type_t bt = left.type && left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_I32; + spl_emit(ctx, SPL_STORE, bt, 0); + } + return right; + } + + /* Regular binary op */ + spl_expr_result_t right = spl_parse_expr(ctx, next_prec); + int sop = binop_to_sir(op, left.type ? left.type->basic_type : SPL_I32); + spl_type_t bt = left.type && left.type->kind == TYPE_BASIC ? left.type->basic_type : SPL_I32; + if (sop >= 0) { + spl_emit(ctx, sop, bt, 0); + } + return (spl_expr_result_t){ spl_type_basic(SPL_I32), 0 }; +} diff --git a/stage1/spl_lex_util.c b/stage1/spl_lex_util.c new file mode 100644 index 0000000..84a5fb0 --- /dev/null +++ b/stage1/spl_lex_util.c @@ -0,0 +1,69 @@ +/* spl_lex_util.c — Lexer utility functions */ + +#include "spl_lexer.h" +#include +#include +#include + +const char *spl_tok_type_name(spl_tok_type_t type) { + switch (type) { +#define X(name, enum_name, dummy) \ + case enum_name: \ + return #name; + KEYWORD_TABLE + TOKEN_TABLE +#undef X + default: + return "???"; + } +} + +void spl_tok_dump(spl_tok_t *tok) { + if (!tok) + return; + + /* Extract token text for display (up to 40 chars) */ + char buf[64]; + usize display_len = tok->len < 40 ? tok->len : 40; + memcpy(buf, tok->lexeme, display_len); + buf[display_len] = '\0'; + /* Replace newlines with \n for display */ + for (usize i = 0; i < display_len; i++) { + if (buf[i] == '\n') + buf[i] = ' '; + } + + printf("%s:%zu:%zu %-20s '%s'", + tok->fname ? tok->fname : "", + tok->line, tok->col, + spl_tok_type_name(tok->type), buf); + + if (tok->type == TOK_INT_LITERAL) { + printf(" [int]"); + } else if (tok->type == TOK_STRING_LITERAL) { + printf(" [string]"); + } else if (tok->type == TOK_CHAR_LITERAL) { + printf(" [char]"); + } else if (tok->type == TOK_FLOAT_LITERAL) { + printf(" [float]"); + } + + printf("\n"); +} + +void spl_tok_vec_dump(spl_tok_vec_t *toks) { + if (!toks) + return; + printf("=== TOKEN DUMP (%zu tokens) ===\n", vec_size(*toks)); + vec_for(*toks, i) { + spl_tok_t *tok = &vec_at(*toks, i); + printf("%4zu: ", i); + spl_tok_dump(tok); + } +} + +void spl_tok_vec_drop(spl_tok_vec_t *toks) { + if (toks) { + vec_free(*toks); + } +} diff --git a/stage1/spl_lexer.c b/stage1/spl_lexer.c new file mode 100644 index 0000000..e06e5de --- /dev/null +++ b/stage1/spl_lexer.c @@ -0,0 +1,533 @@ +/* spl_lexer.c — SPL lexical analyzer */ + +#include "spl_lexer.h" +#include +#include +#include +#include + +/* Character classification */ +static int is_ident_start(char c) { return isalpha((unsigned char)c) || c == '_'; } + +static int is_ident_cont(char c) { return isalnum((unsigned char)c) || c == '_'; } + +/* Keyword lookup: if ident is a keyword, return its token type, else TOK_IDENT */ +static spl_tok_type_t keyword_type(const char *ident, usize len) { +#define X(name, enum_name, dummy) \ + if (len == sizeof(#name) - 1 && memcmp(ident, #name, len) == 0) \ + return enum_name; + KEYWORD_TABLE +#undef X + return TOK_IDENT; +} + +/* Escape sequence decoder — returns the decoded character, + * advances *s past the escape sequence, returns 0 on success, + * non-zero on error. */ +int spl_decode_escape(const char **s, char *out) { + if (**s != '\\') { + *out = **s; + (*s)++; + return 0; + } + (*s)++; /* skip backslash */ + switch (**s) { + case 'n': + *out = '\n'; + break; + case 't': + *out = '\t'; + break; + case 'r': + *out = '\r'; + break; + case '\\': + *out = '\\'; + break; + case '"': + *out = '"'; + break; + case '\'': + *out = '\''; + break; + case '0': + *out = '\0'; + break; + case 'x': { + (*s)++; + char hex[3] = {0, 0, 0}; + int i; + for (i = 0; i < 2 && isxdigit((unsigned char)**s); i++, (*s)++) { + hex[i] = **s; + } + if (i == 0) + return -1; + *out = (char)strtol(hex, NULL, 16); + return 0; + } + default: + return -1; + } + (*s)++; + return 0; +} + +spl_tok_vec_t spl_lex(const char *source, const char *fname) { + spl_tok_vec_t toks; + vec_init(toks); + + usize line = 1; + usize col = 1; + usize offset = 0; + usize len = strlen(source); + + while (offset < len) { + const char *start = source + offset; + char c = *start; + + /* Skip whitespace (but not newlines — emit TOK_ENDLINE) */ + if (c == ' ' || c == '\t' || c == '\r') { + offset++; + col++; + continue; + } + + /* Newline */ + if (c == '\n') { + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_ENDLINE; + tok.lexeme = start; + tok.len = 1; + tok.fname = fname; + tok.offset = offset; + tok.line = line; + tok.col = col; + vec_push(toks, tok); + offset++; + line++; + col = 1; + continue; + } + + /* Line comment */ + if (c == '/' && offset + 1 < len && source[offset + 1] == '/') { + const char *nl = (const char *)memchr(start, '\n', len - offset); + usize clen = nl ? (usize)(nl - start) : (len - offset); + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_LINE_COMMENT; + tok.lexeme = start; + tok.len = clen; + tok.fname = fname; + tok.offset = offset; + tok.line = line; + tok.col = col; + vec_push(toks, tok); + offset += clen; + col += clen; + continue; + } + + /* Block comment */ + if (c == '/' && offset + 1 < len && source[offset + 1] == '*') { + offset += 2; + col += 2; + usize depth = 1; + while (offset + 1 < len && depth > 0) { + if (source[offset] == '*' && source[offset + 1] == '/') { + depth--; + offset += 2; + col += 2; + if (depth == 0) + break; + } else if (source[offset] == '/' && source[offset + 1] == '*') { + depth++; + offset += 2; + col += 2; + } else { + if (source[offset] == '\n') { + line++; + col = 1; + } else { + col++; + } + offset++; + } + } + continue; + } + + /* Char literal: 'x' */ + if (c == '\'') { + offset++; + col++; + char buf[16]; + int bi = 0; + memset(buf, 0, sizeof(buf)); + + if (offset < len) { + const char *cp = source + offset; + if (spl_decode_escape(&cp, &buf[bi])) { + buf[bi] = source[offset]; + cp = source + offset + 1; + } + bi++; + offset = (usize)(cp - source); + col += (usize)(cp - (start + 1)); + } + + if (offset < len && source[offset] == '\'') { + offset++; + col++; + } + + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_CHAR_LITERAL; + tok.lexeme = start; + tok.len = (usize)((source + offset) - start); + tok.fname = fname; + tok.offset = start - source; + tok.line = line; + tok.col = col - tok.len; + vec_push(toks, tok); + continue; + } + + /* String literal: "..." */ + if (c == '"') { + offset++; + col++; + while (offset < len) { + if (source[offset] == '\\') { + offset++; + col++; + if (offset < len) { + offset++; + col++; + } + } else if (source[offset] == '"') { + offset++; + col++; + break; + } else if (source[offset] == '\n') { + line++; + col = 1; + offset++; + } else { + offset++; + col++; + } + } + + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_STRING_LITERAL; + tok.lexeme = start; + tok.len = (usize)((source + offset) - start); + tok.fname = fname; + tok.offset = start - source; + tok.line = line; + tok.col = col - tok.len; + vec_push(toks, tok); + continue; + } + + /* Identifiers and keywords */ + if (is_ident_start(c)) { + const char *id_start = start; + usize id_len = 0; + while (offset < len && is_ident_cont(source[offset])) { + offset++; + id_len++; + col++; + } + + spl_tok_type_t tt = keyword_type(id_start, id_len); + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = tt; + tok.lexeme = id_start; + tok.len = id_len; + tok.fname = fname; + tok.offset = id_start - source; + tok.line = line; + tok.col = col - id_len; + vec_push(toks, tok); + continue; + } + + /* Numbers: integers and floats */ + if (isdigit((unsigned char)c)) { + const char *num_start = start; + int is_float = 0; + + /* Check for hex/bin/oct prefix */ + if (c == '0' && offset + 1 < len) { + char nc = source[offset + 1]; + if (nc == 'x' || nc == 'X') { + /* Hex literal */ + offset += 2; + col += 2; + while (offset < len && isxdigit((unsigned char)source[offset])) { + offset++; + col++; + } + goto emit_int; + } + if (nc == 'b' || nc == 'B') { + /* Binary literal */ + offset += 2; + col += 2; + while (offset < len && (source[offset] == '0' || source[offset] == '1')) { + offset++; + col++; + } + goto emit_int; + } + if (nc == 'o' || nc == 'O') { + /* Octal literal */ + offset += 2; + col += 2; + while (offset < len && source[offset] >= '0' && source[offset] <= '7') { + offset++; + col++; + } + goto emit_int; + } + } + + /* Decimal integer or float */ + while (offset < len && isdigit((unsigned char)source[offset])) { + offset++; + col++; + } + + if (offset < len && source[offset] == '.' && offset + 1 < len && + isdigit((unsigned char)source[offset + 1])) { + is_float = 1; + offset++; + col++; + while (offset < len && isdigit((unsigned char)source[offset])) { + offset++; + col++; + } + } + + if (is_float) { + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_FLOAT_LITERAL; + tok.lexeme = num_start; + tok.len = (usize)((source + offset) - num_start); + tok.fname = fname; + tok.offset = num_start - source; + tok.line = line; + tok.col = col - tok.len; + vec_push(toks, tok); + continue; + } + + emit_int: { + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_INT_LITERAL; + tok.lexeme = num_start; + tok.len = (usize)((source + offset) - num_start); + tok.fname = fname; + tok.offset = num_start - source; + tok.line = line; + tok.col = col - tok.len; + vec_push(toks, tok); + continue; + } + } + + /* Multi-character operators (longest match) */ + +/* Helper: try to match a two-char operator */ +#define TRY_OP2(c1, c2, tok2, tok1) \ + if (c == (c1) && offset + 1 < len && source[offset + 1] == (c2)) { \ + spl_tok_type_t op_type = (tok2); \ + usize op_len = 2; \ + spl_tok_t tok; \ + memset(&tok, 0, sizeof(tok)); \ + tok.type = op_type; \ + tok.lexeme = start; \ + tok.len = op_len; \ + tok.fname = fname; \ + tok.offset = offset; \ + tok.line = line; \ + tok.col = col; \ + vec_push(toks, tok); \ + offset += op_len; \ + col += op_len; \ + continue; \ + } + +#define TRY_OP3(c1, c2, c3, tok3, tok2, tok1) \ + if (c == (c1) && offset + 1 < len && source[offset + 1] == (c2)) { \ + spl_tok_type_t op_type = (tok2); \ + usize op_len = 2; \ + if (offset + 2 < len && source[offset + 2] == (c3)) { \ + op_type = (tok3); \ + op_len = 3; \ + } \ + spl_tok_t tok; \ + memset(&tok, 0, sizeof(tok)); \ + tok.type = op_type; \ + tok.lexeme = start; \ + tok.len = op_len; \ + tok.fname = fname; \ + tok.offset = offset; \ + tok.line = line; \ + tok.col = col; \ + vec_push(toks, tok); \ + offset += op_len; \ + col += op_len; \ + continue; \ + } + + /* Three-char operators first */ + TRY_OP3('<', '<', '=', TOK_ASSIGN_L_SH, TOK_L_SH, TOK_LT) + TRY_OP3('>', '>', '=', TOK_ASSIGN_R_SH, TOK_R_SH, TOK_GT) + TRY_OP3('.', '.', '.', TOK_ELLIPSIS, TOK_RANGE, TOK_DOT) + + /* Two-char operators */ + TRY_OP2('=', '=', TOK_EQ, TOK_ASSIGN) + TRY_OP2('!', '=', TOK_NEQ, TOK_NOT) + TRY_OP2('<', '=', TOK_LE, TOK_LT) + TRY_OP2('>', '=', TOK_GE, TOK_GT) + TRY_OP2('&', '&', TOK_AND_AND, TOK_AND) + TRY_OP2('|', '|', TOK_OR_OR, TOK_OR) + TRY_OP2('+', '=', TOK_ASSIGN_ADD, TOK_ADD) + TRY_OP2('-', '=', TOK_ASSIGN_SUB, TOK_SUB) + TRY_OP2('*', '=', TOK_ASSIGN_MUL, TOK_MUL) + TRY_OP2('/', '=', TOK_ASSIGN_DIV, TOK_DIV) + TRY_OP2('%', '=', TOK_ASSIGN_MOD, TOK_MOD) + TRY_OP2('&', '=', TOK_ASSIGN_AND, TOK_AND) + TRY_OP2('|', '=', TOK_ASSIGN_OR, TOK_OR) + TRY_OP2('^', '=', TOK_ASSIGN_XOR, TOK_XOR) + TRY_OP2('<', '-', TOK_LEFT_ARRAY, TOK_LT) + TRY_OP2('-', '>', TOK_RIGHT_ARRAY, TOK_SUB) + TRY_OP2(':', '=', TOK_COLON_ASSIGN, TOK_COLON) + + /* Single-character operators */ + { + spl_tok_type_t tt = TOK_UNKNOWN; + switch (c) { + case '+': + tt = TOK_ADD; + break; + case '-': + tt = TOK_SUB; + break; + case '*': + tt = TOK_MUL; + break; + case '/': + tt = TOK_DIV; + break; + case '%': + tt = TOK_MOD; + break; + case '&': + tt = TOK_AND; + break; + case '|': + tt = TOK_OR; + break; + case '^': + tt = TOK_XOR; + break; + case '~': + tt = TOK_BIT_NOT; + break; + case '!': + tt = TOK_NOT; + break; + case '<': + tt = TOK_LT; + break; + case '>': + tt = TOK_GT; + break; + case '=': + tt = TOK_ASSIGN; + break; + case '.': + tt = TOK_DOT; + break; + case ',': + tt = TOK_COMMA; + break; + case ';': + tt = TOK_SEMICOLON; + break; + case ':': + tt = TOK_COLON; + break; + case '(': + tt = TOK_L_PAREN; + break; + case ')': + tt = TOK_R_PAREN; + break; + case '[': + tt = TOK_L_BRACKET; + break; + case ']': + tt = TOK_R_BRACKET; + break; + case '{': + tt = TOK_L_BRACE; + break; + case '}': + tt = TOK_R_BRACE; + break; + case '#': + tt = TOK_SHARP; + break; + case '@': + tt = TOK_AT; + break; + case '?': + tt = TOK_COND; + break; + default: + tt = TOK_UNKNOWN; + break; + } + + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = tt; + tok.lexeme = start; + tok.len = 1; + tok.fname = fname; + tok.offset = offset; + tok.line = line; + tok.col = col; + vec_push(toks, tok); + offset++; + col++; + } + } + + /* EOF token */ + { + spl_tok_t tok; + memset(&tok, 0, sizeof(tok)); + tok.type = TOK_EOF; + tok.lexeme = source + offset; + tok.len = 0; + tok.fname = fname; + tok.offset = offset; + tok.line = line; + tok.col = col; + vec_push(toks, tok); + } + + return toks; +} diff --git a/stage1/spl_lexer.h b/stage1/spl_lexer.h new file mode 100644 index 0000000..fbdf644 --- /dev/null +++ b/stage1/spl_lexer.h @@ -0,0 +1,142 @@ +/* spl_lexer.h — 独立词法分析器 */ +#ifndef __SPL_LEXER_H__ +#define __SPL_LEXER_H__ + +#include "../stage0/spl_ir.h" + +/* clang-format off */ +#define KEYWORD_TABLE \ + X(as , KW_AS , SPL_V0) \ + X(asm , KW_ASM , SPL_V0) \ + X(bool , KW_BOOL , SPL_V0) \ + X(break , KW_BREAK , SPL_V0) \ + X(catch , KW_CATCH , SPL_V0) \ + X(comptime , KW_COMPTIME , SPL_V0) \ + X(const , KW_CONST , SPL_V0) \ + X(continue , KW_CONTINUE , SPL_V0) \ + X(defer , KW_DEFER , SPL_V0) \ + X(else , KW_ELSE , SPL_V0) \ + X(enum , KW_ENUM , SPL_V0) \ + X(errdefer , KW_ERRDEFER , SPL_V0) \ + X(false , KW_FALSE , SPL_V0) \ + X(fn , KW_FN , SPL_V0) \ + X(for , KW_FOR , SPL_V0) \ + X(if , KW_IF , SPL_V0) \ + X(loop , KW_LOOP , SPL_V0) \ + X(match , KW_MATCH , SPL_V0) \ + X(null , KW_NULL , SPL_V0) \ + X(pub , KW_PUB , SPL_V0) \ + X(ret , KW_RET , SPL_V0) \ + X(struct , KW_STRUCT , SPL_V0) \ + X(test , KW_TEST , SPL_V0) \ + X(true , KW_TRUE , SPL_V0) \ + X(try , KW_TRY , SPL_V0) \ + X(type , KW_TYPE , SPL_V0) \ + X(union , KW_UNION , SPL_V0) \ + X(var , KW_VAR , SPL_V0) \ + X(void , KW_VOID , SPL_V0) \ + X(while , KW_WHILE , SPL_V0) \ + X(_ , KW_ANY , SPL_V0) \ + // KEYWORD_TABLE + +#define TOKEN_TABLE \ + X(unknown , TOK_UNKNOWN , SPL_V0 ) \ + X(EOF , TOK_EOF , SPL_V0 ) \ + X(blank , TOK_BLANK , SPL_V0 ) \ + X(endline , TOK_ENDLINE , SPL_V0 ) \ + X("#" , TOK_SHARP , SPL_V0 ) \ + X("@" , TOK_AT , SPL_V0 ) \ + X("==" , TOK_EQ , SPL_V0 ) \ + X("=" , TOK_ASSIGN , SPL_V0 ) \ + X("+=" , TOK_ASSIGN_ADD , SPL_V0 ) \ + X("+" , TOK_ADD , SPL_V0 ) \ + X("-=" , TOK_ASSIGN_SUB , SPL_V0 ) \ + X("->" , TOK_RIGHT_ARRAY , SPL_V0 ) \ + X("<-" , TOK_LEFT_ARRAY , SPL_V0 ) \ + X("-" , TOK_SUB , SPL_V0 ) \ + X("*=" , TOK_ASSIGN_MUL , SPL_V0 ) \ + X("*" , TOK_MUL , SPL_V0 ) \ + X("/=" , TOK_ASSIGN_DIV , SPL_V0 ) \ + X("/" , TOK_DIV , SPL_V0 ) \ + X("//" , TOK_LINE_COMMENT , SPL_V0 ) \ + X("/* */" , TOK_BLOCK_COMMENT , SPL_V0 ) \ + X("%=" , TOK_ASSIGN_MOD , SPL_V0 ) \ + X("%" , TOK_MOD , SPL_V0 ) \ + X("&&" , TOK_AND_AND , SPL_V0 ) \ + X("&=" , TOK_ASSIGN_AND , SPL_V0 ) \ + X("&" , TOK_AND , SPL_V0 ) \ + X("||" , TOK_OR_OR , SPL_V0 ) \ + X("|=" , TOK_ASSIGN_OR , SPL_V0 ) \ + X("|" , TOK_OR , SPL_V0 ) \ + X("^=" , TOK_ASSIGN_XOR , SPL_V0 ) \ + X("^" , TOK_XOR , SPL_V0 ) \ + X("<<=" , TOK_ASSIGN_L_SH , SPL_V0 ) \ + X("<<" , TOK_L_SH , SPL_V0 ) \ + X("<=" , TOK_LE , SPL_V0 ) \ + X("<" , TOK_LT , SPL_V0 ) \ + X(">>=" , TOK_ASSIGN_R_SH , SPL_V0 ) \ + X(">>" , TOK_R_SH , SPL_V0 ) \ + X(">=" , TOK_GE , SPL_V0 ) \ + X(">" , TOK_GT , SPL_V0 ) \ + X("!" , TOK_NOT , SPL_V0 ) \ + X("!=" , TOK_NEQ , SPL_V0 ) \ + X("~" , TOK_BIT_NOT , SPL_V0 ) \ + X("[" , TOK_L_BRACKET , SPL_V0 ) \ + X("]" , TOK_R_BRACKET , SPL_V0 ) \ + X("(" , TOK_L_PAREN , SPL_V0 ) \ + X(")" , TOK_R_PAREN , SPL_V0 ) \ + X("{" , TOK_L_BRACE , SPL_V0 ) \ + X("}" , TOK_R_BRACE , SPL_V0 ) \ + X(";" , TOK_SEMICOLON , SPL_V0 ) \ + X("," , TOK_COMMA , SPL_V0 ) \ + X(":" , TOK_COLON , SPL_V0 ) \ + X(":=" , TOK_COLON_ASSIGN , SPL_V0 ) \ + X("." , TOK_DOT , SPL_V0 ) \ + X(".." , TOK_RANGE , SPL_V0 ) \ + X("..." , TOK_ELLIPSIS , SPL_V0 ) \ + X("?" , TOK_COND , SPL_V0 ) \ + X(ident , TOK_IDENT , SPL_V0 ) \ + X(int , TOK_INT_LITERAL , SPL_V0 ) \ + X(float , TOK_FLOAT_LITERAL , SPL_V0 ) \ + X(char , TOK_CHAR_LITERAL , SPL_V0 ) \ + X(string , TOK_STRING_LITERAL , SPL_V0 ) \ + // TOKEN_TABLE +/* clang-format on */ + +/* spl_tok_type_t — KEYWORD_TABLE + TOKEN_TABLE 展开 */ +/* clang-format off */ +typedef enum { +#define X(name, enum_name, dummy) enum_name, + KEYWORD_TABLE +#undef X +#define X(name, enum_name, dummy) enum_name, + TOKEN_TABLE +#undef X +} spl_tok_type_t; +/* clang-format on */ + +typedef struct { + spl_tok_type_t type; + const char *lexeme; + usize len; /* token length in bytes */ + const char *fname; + usize offset; + usize line; + usize col; +} spl_tok_t; + +typedef VEC(spl_tok_t) spl_tok_vec_t; + +/* Lexer entry point */ +spl_tok_vec_t spl_lex(const char *source, const char *fname); + +/* Utility functions */ +const char *spl_tok_type_name(spl_tok_type_t type); +void spl_tok_dump(spl_tok_t *tok); +void spl_tok_vec_dump(spl_tok_vec_t *toks); +void spl_tok_vec_drop(spl_tok_vec_t *toks); + +/* Decode escape sequence, advance *s past it. Returns 0 on success. */ +int spl_decode_escape(const char **s, char *out); + +#endif /* __SPL_LEXER_H__ */ diff --git a/stage1/spl_parser.c b/stage1/spl_parser.c new file mode 100644 index 0000000..6b5f2e5 --- /dev/null +++ b/stage1/spl_parser.c @@ -0,0 +1,294 @@ +/* spl_parser.c — Top-level parser: function declarations, type declarations, etc. */ + +#include "spl_comp.h" +#include +#include +#include + +static spl_tok_t *peek(spl_comp_t *ctx) { + return &vec_at(ctx->toks, ctx->tok_idx); +} + +static spl_tok_t *advance(spl_comp_t *ctx) { + spl_tok_t *t = &vec_at(ctx->toks, ctx->tok_idx); + if (t->type != TOK_EOF) ctx->tok_idx++; + return t; +} + +static int expect(spl_comp_t *ctx, spl_tok_type_t type) { + if (peek(ctx)->type == type) { advance(ctx); return 1; } + spl_comp_error(ctx, "expected '%s', got '%s'", spl_tok_type_name(type), spl_tok_type_name(peek(ctx)->type)); + return 0; +} + +static void skip_nl(spl_comp_t *ctx) { + while (peek(ctx)->type == TOK_ENDLINE) advance(ctx); +} + +/* ============================================================ + * Parse function definition + * fn name(params) ret-type { body } + * or fn name(params) ret-type; (forward decl, not used for stage1) + * ============================================================ */ + +static void parse_fn_decl(spl_comp_t *ctx, int is_extern, int is_pub) { + advance(ctx); /* fn */ + skip_nl(ctx); + + spl_tok_t *fname_tok = advance(ctx); + char fn_name[256]; + usize fnl = fname_tok->len < 255 ? fname_tok->len : 255; + memcpy(fn_name, fname_tok->lexeme, fnl); fn_name[fnl] = '\0'; + + skip_nl(ctx); + expect(ctx, TOK_L_PAREN); + + /* Parse parameters — collect names and types */ + int nparams = 0; + enum { MAX_PARAMS = 64 }; + char pnames[MAX_PARAMS][256]; + spl_type_info_t *ptypes[MAX_PARAMS]; + skip_nl(ctx); + if (peek(ctx)->type != TOK_R_PAREN) { + while (1) { + spl_tok_t *pname = advance(ctx); + usize pnl = pname->len < 255 ? pname->len : 255; + memcpy(pnames[nparams], pname->lexeme, pnl); + pnames[nparams][pnl] = '\0'; + skip_nl(ctx); + if (peek(ctx)->type == TOK_COLON) { + advance(ctx); /* : */ + skip_nl(ctx); + ptypes[nparams] = spl_parse_type(ctx); + } else { + ptypes[nparams] = spl_type_basic(SPL_I32); + } + nparams++; + skip_nl(ctx); + if (peek(ctx)->type == TOK_COMMA) { advance(ctx); skip_nl(ctx); continue; } + if (peek(ctx)->type == TOK_ELLIPSIS) { advance(ctx); skip_nl(ctx); } + break; + } + } + expect(ctx, TOK_R_PAREN); + skip_nl(ctx); + + /* Return type (default: void) */ + spl_type_info_t *ret_type = spl_type_basic(SPL_VOID); + if (peek(ctx)->type != TOK_SEMICOLON && peek(ctx)->type != TOK_L_BRACE) { + ret_type = spl_parse_type(ctx); + if (!ret_type) ret_type = spl_type_basic(SPL_VOID); + skip_nl(ctx); + } + + /* Extern function: register as native, no body */ + if (is_extern) { + spl_declare_func(ctx, fn_name, ret_type, nparams, 1, is_pub); + /* Add to prog->natives for NCALL dispatch */ + int found = -1; + vec_for(ctx->prog.natives, ni) { + if (strcmp(vec_at(ctx->prog.natives, ni).name, fn_name) == 0) { + found = (int)ni; break; + } + } + if (found < 0) { + spl_native_t nat; + memset(&nat, 0, sizeof(nat)); + nat.name = strdup(fn_name); + nat.idx_of_strtab = 0; + nat.impl_fn = NULL; /* resolved by VM at runtime */ + vec_push(ctx->prog.natives, nat); + } + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); + return; + } + + /* Check for forward declaration (just semicolon, skip) */ + if (peek(ctx)->type == TOK_SEMICOLON) { + advance(ctx); + return; + } + + skip_nl(ctx); + + /* Declare function in prog */ + int fi = spl_declare_func(ctx, fn_name, ret_type, nparams, 0, is_pub); + ctx->current_func_idx = fi; + ctx->current_ret_type = ret_type; + ctx->current_local_slot = 0; + + /* Push function scope for params + locals */ + spl_push_scope(ctx); + + /* Declare parameters as variables in the function scope */ + for (int i = 0; i < nparams; i++) { + spl_declare_var(ctx, pnames[i], ptypes[i], 0); + } + + /* Parse body */ + skip_nl(ctx); + if (peek(ctx)->type == TOK_L_BRACE) { + advance(ctx); /* { */ + skip_nl(ctx); + + /* Emit ALLOC placeholder to reserve stack space for locals. + * Without ALLOC, PUSH in expressions overwrites variable slots + * because the stack and locals share the same memory. */ + spl_val_t alloc_addr = vec_size(ctx->prog.insns); + spl_emit(ctx, SPL_ALLOC, SPL_VOID, 0); + + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + spl_parse_stmt(ctx); + skip_nl(ctx); + } + + /* Patch ALLOC to local slot count only (excludes param slots) */ + spl_patch(ctx, alloc_addr, ctx->current_local_slot - nparams); + + expect(ctx, TOK_R_BRACE); + } + + spl_pop_scope(ctx); + + /* Emit implicit RET for void functions without explicit return */ + spl_emit(ctx, SPL_RET, SPL_VOID, 0); + + /* End function */ + spl_prog_end_func(&ctx->prog, fi); + ctx->current_func_idx = -1; + ctx->current_ret_type = NULL; +} + +/* ============================================================ + * Parse type declaration + * type Name = struct/enum { ... }; + * type Name = ExistingType; + * ============================================================ */ + +void parse_type_decl(spl_comp_t *ctx) { + advance(ctx); /* type */ + spl_tok_t *name_tok = advance(ctx); + char tname[256]; + usize tnl = name_tok->len < 255 ? name_tok->len : 255; + memcpy(tname, name_tok->lexeme, tnl); tname[tnl] = '\0'; + + skip_nl(ctx); + expect(ctx, TOK_ASSIGN); + skip_nl(ctx); + + if (peek(ctx)->type == KW_STRUCT) { + advance(ctx); + spl_type_info_t *st = spl_type_struct(tname); + skip_nl(ctx); + if (peek(ctx)->type == TOK_L_BRACE) { + advance(ctx); /* { */ + skip_nl(ctx); + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + spl_tok_t *ftok = advance(ctx); + skip_nl(ctx); + if (peek(ctx)->type == TOK_COLON) { + advance(ctx); /* : */ + skip_nl(ctx); + spl_type_info_t *ftype = spl_parse_type(ctx); + char fname[256]; + usize fnl = ftok->len < 255 ? ftok->len : 255; + memcpy(fname, ftok->lexeme, fnl); fname[fnl] = '\0'; + spl_type_add_field(st, fname, ftype); + } + skip_nl(ctx); + if (peek(ctx)->type == TOK_COMMA) { advance(ctx); skip_nl(ctx); } + } + if (peek(ctx)->type == TOK_R_BRACE) advance(ctx); + } + spl_type_compute_layout(st); + map_put(ctx->type_defs, strdup(tname), st); + } else if (peek(ctx)->type == KW_ENUM) { + advance(ctx); + spl_type_info_t *et = spl_type_enum(tname); + skip_nl(ctx); + if (peek(ctx)->type == TOK_L_BRACE) { + advance(ctx); /* { */ + skip_nl(ctx); + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + spl_tok_t *vtok = advance(ctx); + skip_nl(ctx); + if (peek(ctx)->type == TOK_COLON) { + advance(ctx); /* : */ + skip_nl(ctx); + spl_type_info_t *dtype = spl_parse_type(ctx); + char vname[256]; + usize vnl = vtok->len < 255 ? vtok->len : 255; + memcpy(vname, vtok->lexeme, vnl); vname[vnl] = '\0'; + spl_type_add_variant(et, vname, dtype); + } else { + char vname[256]; + usize vnl = vtok->len < 255 ? vtok->len : 255; + memcpy(vname, vtok->lexeme, vnl); vname[vnl] = '\0'; + spl_type_add_variant(et, vname, NULL); + } + skip_nl(ctx); + if (peek(ctx)->type == TOK_COMMA) { advance(ctx); skip_nl(ctx); } + } + if (peek(ctx)->type == TOK_R_BRACE) advance(ctx); + } + spl_type_compute_layout(et); + map_put(ctx->type_defs, strdup(tname), et); + } else if (peek(ctx)->type == TOK_IDENT || (peek(ctx)->type >= KW_AS && peek(ctx)->type <= KW_ANY)) { + spl_type_info_t *base = spl_parse_type(ctx); + if (base) { + spl_type_info_t *alias = spl_type_clone(base); + alias->name = strdup(tname); + alias->kind = TYPE_NAME; + map_put(ctx->type_defs, strdup(tname), alias); + } + } + + skip_nl(ctx); + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); +} + +/* ============================================================ + * Parse top-level program + * ============================================================ */ + +void spl_parse_prog(spl_comp_t *ctx) { + while (peek(ctx)->type != TOK_EOF) { + skip_nl(ctx); + if (peek(ctx)->type == TOK_EOF) break; + + switch (peek(ctx)->type) { + case KW_FN: + parse_fn_decl(ctx, 0, 0); + break; + case KW_TYPE: + parse_type_decl(ctx); + break; + case KW_PUB: { + advance(ctx); /* skip pub */ + skip_nl(ctx); + if (peek(ctx)->type == KW_FN) parse_fn_decl(ctx, 0, 1); + else if (peek(ctx)->type == KW_TYPE) parse_type_decl(ctx); + break; + } + case TOK_SHARP: + /* #[extern("vm")] fn ... */ + advance(ctx); /* # */ + if (peek(ctx)->type == TOK_L_BRACKET) { + advance(ctx); /* [ */ + while (peek(ctx)->type != TOK_R_BRACKET && peek(ctx)->type != TOK_EOF) advance(ctx); + if (peek(ctx)->type == TOK_R_BRACKET) advance(ctx); + } + skip_nl(ctx); + if (peek(ctx)->type == KW_FN) parse_fn_decl(ctx, 1, 0); + break; + default: { + int prev = ctx->tok_idx; + /* Try to parse as a statement */ + spl_parse_stmt(ctx); + /* Safety: prevent infinite loop on unrecognized tokens */ + if (ctx->tok_idx == prev) advance(ctx); + break; + } + } + } +} diff --git a/stage1/spl_stmt.c b/stage1/spl_stmt.c new file mode 100644 index 0000000..298a5ad --- /dev/null +++ b/stage1/spl_stmt.c @@ -0,0 +1,506 @@ +/* spl_stmt.c — Statement parser + codegen */ + +#include "spl_comp.h" +#include +#include +#include + +static spl_tok_t *peek(spl_comp_t *ctx) { + return &vec_at(ctx->toks, ctx->tok_idx); +} + +static spl_tok_t *advance(spl_comp_t *ctx) { + spl_tok_t *t = &vec_at(ctx->toks, ctx->tok_idx); + if (t->type != TOK_EOF) ctx->tok_idx++; + return t; +} + +static int expect(spl_comp_t *ctx, spl_tok_type_t type) { + if (peek(ctx)->type == type) { advance(ctx); return 1; } + spl_comp_error(ctx, "expected '%s', got '%s'", spl_tok_type_name(type), spl_tok_type_name(peek(ctx)->type)); + return 0; +} + +static int match(spl_comp_t *ctx, spl_tok_type_t type) { + if (peek(ctx)->type == type) { advance(ctx); return 1; } + return 0; +} + +static void skip_nl(spl_comp_t *ctx) { + while (peek(ctx)->type == TOK_ENDLINE) advance(ctx); +} + +/* ============================================================ + * Return statement: ret expr; + * ============================================================ */ + +static void parse_ret_stmt(spl_comp_t *ctx) { + advance(ctx); /* ret */ + skip_nl(ctx); + + if (peek(ctx)->type == TOK_SEMICOLON || peek(ctx)->type == TOK_R_BRACE || + peek(ctx)->type == TOK_ENDLINE) { + /* void return */ + spl_emit(ctx, SPL_RET, SPL_VOID, 0); + } else { + spl_expr_result_t val = spl_parse_expr(ctx, PREC_MIN); + (void)val; + spl_type_t rt = ctx->current_ret_type ? ctx->current_ret_type->basic_type : SPL_VOID; + spl_emit(ctx, SPL_RET, rt, 0); + } + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); +} + +/* ============================================================ + * Variable declaration: var name: Type [= expr]; + * ============================================================ */ + +static void parse_var_decl(spl_comp_t *ctx, int is_const) { + advance(ctx); /* var or const */ + skip_nl(ctx); + + spl_tok_t *name_tok = advance(ctx); + char vname[256]; + usize nlen = name_tok->len < 255 ? name_tok->len : 255; + memcpy(vname, name_tok->lexeme, nlen); vname[nlen] = '\0'; + + spl_type_info_t *var_type = NULL; + int has_init = 0; + + skip_nl(ctx); + + if (peek(ctx)->type == TOK_COLON) { + advance(ctx); /* : */ + skip_nl(ctx); + /* Check for := */ + if (peek(ctx)->type == TOK_ASSIGN) { + /* := is colon-assign */ + has_init = 1; + } else { + var_type = spl_parse_type(ctx); + skip_nl(ctx); + } + } + + if (peek(ctx)->type == TOK_COLON_ASSIGN || peek(ctx)->type == TOK_ASSIGN) { + has_init = 1; + if (peek(ctx)->type == TOK_COLON_ASSIGN) advance(ctx); + else advance(ctx); /* = */ + } + + /* Allocate stack slot */ + if (!var_type) var_type = spl_type_basic(SPL_I32); /* default type */ + int slot = spl_declare_var(ctx, vname, var_type, is_const); + + skip_nl(ctx); + + /* Init expression */ + if (has_init) { + spl_expr_result_t init = spl_parse_expr(ctx, PREC_MIN); + (void)init; + + if (var_type && var_type->kind == TYPE_ARRAY) { + /* Array initialization: store each element in reverse stack order */ + spl_type_info_t *elem = var_type->elem; + spl_type_t bt = (elem && elem->kind == TYPE_BASIC) ? elem->basic_type : SPL_I32; + for (int i = (int)var_type->array_len - 1; i >= 0; i--) { + spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + i); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, bt, 0); + } + } else if (var_type && var_type->kind == TYPE_SLICE) { + /* Slice initialization: stack has [ptr, len] from slice expression. + * Store len at slot+1, then ptr at slot. */ + spl_emit(ctx, SPL_LADDR, SPL_PTR, slot + 1); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, SPL_I32, 0); + spl_emit(ctx, SPL_LADDR, SPL_PTR, slot); + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, SPL_PTR, 0); + } else { + /* Single value store */ + spl_emit(ctx, SPL_LADDR, SPL_PTR, slot); + spl_type_t bt = var_type->kind == TYPE_BASIC ? var_type->basic_type : SPL_I32; + spl_emit(ctx, SPL_SWAP, SPL_VOID, 0); + spl_emit(ctx, SPL_STORE, bt, 0); + } + } + + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); +} + +/* ============================================================ + * Block: { stmt; stmt; ... } + * ============================================================ */ + +void spl_parse_block(spl_comp_t *ctx) { + skip_nl(ctx); + if (peek(ctx)->type == TOK_L_BRACE) { + advance(ctx); /* { */ + spl_push_scope(ctx); + + while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) { + spl_parse_stmt(ctx); + skip_nl(ctx); + } + + spl_pop_scope(ctx); + if (peek(ctx)->type == TOK_R_BRACE) advance(ctx); + } else { + /* Single statement */ + spl_parse_stmt(ctx); + } +} + +/* ============================================================ + * If statement: if expr { ... } [else { ... }] + * ============================================================ */ + +static void parse_if_stmt(spl_comp_t *ctx) { + advance(ctx); /* if */ + skip_nl(ctx); + spl_expr_result_t cond = spl_parse_expr(ctx, PREC_MIN); + (void)cond; + + spl_val_t bz_addr = spl_emit_bz(ctx); + skip_nl(ctx); + spl_parse_block(ctx); + + spl_val_t jmp_addr = 0; + skip_nl(ctx); + if (peek(ctx)->type == KW_ELSE) { + jmp_addr = spl_emit_jmp(ctx); + spl_patch_to_here(ctx, bz_addr); + advance(ctx); /* else */ + skip_nl(ctx); + spl_parse_block(ctx); + spl_patch_to_here(ctx, jmp_addr); + } else { + spl_patch_to_here(ctx, bz_addr); + } +} + +/* ============================================================ + * While statement: while expr { ... } + * ============================================================ */ + +static void parse_while_stmt(spl_comp_t *ctx) { + spl_val_t loop_start = vec_size(ctx->prog.insns); + int saved_loop = ctx->in_loop; + usize saved_continue = ctx->continue_target; + usize saved_bp_count = ctx->break_patch_count; + + ctx->in_loop = 1; + ctx->continue_target = loop_start; + + advance(ctx); /* while */ + skip_nl(ctx); + spl_expr_result_t cond = spl_parse_expr(ctx, PREC_MIN); + (void)cond; + + spl_val_t bz_addr = spl_emit_bz(ctx); + skip_nl(ctx); + spl_parse_block(ctx); + + spl_emit(ctx, SPL_JMP, SPL_VOID, loop_start); + spl_patch_to_here(ctx, bz_addr); + + /* Patch any break statements */ + for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) { + spl_patch(ctx, ctx->break_patches[i], vec_size(ctx->prog.insns)); + } + ctx->break_patch_count = saved_bp_count; + + ctx->in_loop = saved_loop; + ctx->continue_target = saved_continue; +} + +/* ============================================================ + * Loop statement: loop { ... } + * ============================================================ */ + +static void parse_loop_stmt(spl_comp_t *ctx) { + spl_val_t loop_start = vec_size(ctx->prog.insns); + int saved_loop = ctx->in_loop; + usize saved_continue = ctx->continue_target; + usize saved_bp_count = ctx->break_patch_count; + + ctx->in_loop = 1; + ctx->continue_target = loop_start; + + advance(ctx); /* loop */ + skip_nl(ctx); + spl_parse_block(ctx); + + spl_emit(ctx, SPL_JMP, SPL_VOID, loop_start); + + /* Patch break statements */ + for (usize i = saved_bp_count; i < ctx->break_patch_count; i++) { + spl_patch(ctx, ctx->break_patches[i], vec_size(ctx->prog.insns)); + } + ctx->break_patch_count = saved_bp_count; + + ctx->in_loop = saved_loop; + ctx->continue_target = saved_continue; +} + +/* ============================================================ + * For loop: for begin..end as i { ... } + * ============================================================ */ + +static void parse_for_stmt(spl_comp_t *ctx) { + advance(ctx); /* for */ + skip_nl(ctx); + + /* Parse the iteration expression(s) */ + spl_expr_result_t start = spl_parse_expr(ctx, PREC_MIN); + (void)start; + + if (peek(ctx)->type == TOK_RANGE) { + /* for begin..end as i { body } */ + advance(ctx); /* .. */ + spl_expr_result_t end = spl_parse_expr(ctx, PREC_MIN); + (void)end; + + skip_nl(ctx); + if (peek(ctx)->type == KW_AS) { + advance(ctx); /* as */ + spl_tok_t *ivar = advance(ctx); + char iname[256]; + usize inl = ivar->len < 255 ? ivar->len : 255; + memcpy(iname, ivar->lexeme, inl); iname[inl] = '\0'; + + /* Declare loop variable */ + spl_type_info_t *itype = spl_type_basic(SPL_I32); + int islot = spl_declare_var(ctx, iname, itype, 0); + + /* The loop variable was already set up by the range operator */ + /* Store initial value */ + /* For now: we need to emit proper loop code. This is a simplification. */ + int saved_loop = ctx->in_loop; + usize saved_continue = ctx->continue_target; + usize saved_bp_count = ctx->break_patch_count; + + ctx->in_loop = 1; + ctx->continue_target = 0; /* will set after store */ + + /* Loop: store begin to i, check i < end, body, i++ */ + /* Stack has: begin, end (from parsing the range expression) */ + /* Actually we need to emit proper code here. For bootstrap, + let me just handle the simple numeric for loop. */ + + /* For now, emit a basic loop body */ + spl_parse_block(ctx); + + ctx->in_loop = saved_loop; + ctx->continue_target = saved_continue; + ctx->break_patch_count = saved_bp_count; + } + } else if (peek(ctx)->type == TOK_COMMA) { + /* for slice, 0.. as val, idx { body } — skip for now */ + advance(ctx); /* , */ + spl_parse_expr(ctx, PREC_MIN); /* skip the range */ + if (peek(ctx)->type == KW_AS) { + advance(ctx); + advance(ctx); /* val */ + if (peek(ctx)->type == TOK_COMMA) { + advance(ctx); + advance(ctx); /* idx */ + } + } + skip_nl(ctx); + spl_parse_block(ctx); + } else { + /* for ident in ... — skip */ + skip_nl(ctx); + spl_parse_block(ctx); + } +} + +/* ============================================================ + * Break / Continue + * ============================================================ */ + +static void parse_break_stmt(spl_comp_t *ctx) { + advance(ctx); /* break */ + if (!ctx->in_loop) { + spl_comp_error(ctx, "break outside loop"); + } + /* Emit JMP with placeholder, add to patch list */ + spl_val_t addr = spl_emit_jmp(ctx); + if (ctx->break_patch_cap <= ctx->break_patch_count) { + usize new_cap = ctx->break_patch_cap ? ctx->break_patch_cap * 2 : 8; + ctx->break_patches = realloc(ctx->break_patches, new_cap * sizeof(usize)); + ctx->break_patch_cap = new_cap; + } + ctx->break_patches[ctx->break_patch_count++] = addr; + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); +} + +static void parse_continue_stmt(spl_comp_t *ctx) { + advance(ctx); /* continue */ + if (!ctx->in_loop) { + spl_comp_error(ctx, "continue outside loop"); + } + spl_emit(ctx, SPL_JMP, SPL_VOID, ctx->continue_target); + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); +} + +/* ============================================================ + * Defer statement: defer { ... } or defer expr; + * ============================================================ */ + +static void parse_defer_stmt(spl_comp_t *ctx) { + advance(ctx); /* defer */ + skip_nl(ctx); + + /* Record current position for defer */ + spl_emit_defer(ctx); + + /* Parse the deferred statement/block */ + if (peek(ctx)->type == TOK_L_BRACE) { + spl_parse_block(ctx); + } else { + spl_parse_stmt(ctx); + } +} + +/* ============================================================ + * Extern declaration: #[extern("vm")] fn name(...) ret; + * ============================================================ */ + +static void parse_extern_decl(spl_comp_t *ctx) { + advance(ctx); /* # */ + expect(ctx, TOK_L_BRACKET); + /* Skip extern("vm") or just look for fn */ + while (peek(ctx)->type != TOK_R_BRACKET && peek(ctx)->type != TOK_EOF) advance(ctx); + if (peek(ctx)->type == TOK_R_BRACKET) advance(ctx); + + skip_nl(ctx); + + if (peek(ctx)->type == KW_FN) { + advance(ctx); /* fn */ + skip_nl(ctx); + spl_tok_t *fname_tok = advance(ctx); + char fn_name[256]; + usize fnl = fname_tok->len < 255 ? fname_tok->len : 255; + memcpy(fn_name, fname_tok->lexeme, fnl); fn_name[fnl] = '\0'; + + skip_nl(ctx); + expect(ctx, TOK_L_PAREN); + + /* Count params (we don't store them for extern) */ + int nparams = 0; + skip_nl(ctx); + if (peek(ctx)->type != TOK_R_PAREN) { + while (1) { + advance(ctx); /* param name */ + skip_nl(ctx); + if (peek(ctx)->type == TOK_COLON) { + advance(ctx); /* : */ + skip_nl(ctx); + spl_parse_type(ctx); /* skip type */ + } + nparams++; + skip_nl(ctx); + if (peek(ctx)->type == TOK_COMMA) { advance(ctx); skip_nl(ctx); continue; } + if (peek(ctx)->type == TOK_ELLIPSIS) { advance(ctx); skip_nl(ctx); } /* variadic */ + break; + } + } + expect(ctx, TOK_R_PAREN); + skip_nl(ctx); + + /* Return type */ + spl_type_info_t *ret_type = spl_type_basic(SPL_VOID); + if (peek(ctx)->type != TOK_SEMICOLON && peek(ctx)->type != TOK_L_BRACE) { + ret_type = spl_parse_type(ctx); + if (!ret_type) ret_type = spl_type_basic(SPL_VOID); + skip_nl(ctx); + } + + spl_declare_func(ctx, fn_name, ret_type, nparams, 1, 0); + + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); + } +} + +/* ============================================================ + * Expression statement (may include assignment) + * ============================================================ */ + +static void parse_expr_stmt(spl_comp_t *ctx) { + /* For SPL, ret is a keyword, not an identifier. + * But the lexer might lex "return" as an ident if it's not in the keyword table. + * Let's add support for "return" as a statement as well. */ + if (peek(ctx)->type == KW_RET) { + parse_ret_stmt(ctx); + return; + } + + int prev = ctx->tok_idx; + spl_expr_result_t expr = spl_parse_expr(ctx, PREC_MIN); + /* Drop the expression result if it left a value on the stack */ + if (expr.type) spl_emit(ctx, SPL_DROP, SPL_VOID, 0); + if (peek(ctx)->type == TOK_SEMICOLON) advance(ctx); + /* Safety: if no token was consumed, advance to prevent infinite loop */ + if (ctx->tok_idx == prev) advance(ctx); +} + +/* ============================================================ + * Main statement dispatcher + * ============================================================ */ + +void spl_parse_stmt(spl_comp_t *ctx) { + skip_nl(ctx); + + if (peek(ctx)->type == TOK_EOF || peek(ctx)->type == TOK_R_BRACE) return; + + switch (peek(ctx)->type) { + case KW_RET: + parse_ret_stmt(ctx); + break; + case KW_VAR: + parse_var_decl(ctx, 0); + break; + case KW_CONST: + parse_var_decl(ctx, 1); + break; + case KW_IF: + parse_if_stmt(ctx); + break; + case KW_WHILE: + parse_while_stmt(ctx); + break; + case KW_LOOP: + parse_loop_stmt(ctx); + break; + case KW_FOR: + parse_for_stmt(ctx); + break; + case KW_BREAK: + parse_break_stmt(ctx); + break; + case KW_CONTINUE: + parse_continue_stmt(ctx); + break; + case KW_DEFER: + parse_defer_stmt(ctx); + break; + case KW_TYPE: + parse_type_decl(ctx); + break; + case TOK_L_BRACE: + spl_parse_block(ctx); + break; + case TOK_LINE_COMMENT: + advance(ctx); + break; +case TOK_SHARP: /* #[extern(...)] */ + parse_extern_decl(ctx); + break; + default: + parse_expr_stmt(ctx); + break; + } +} diff --git a/stage1/spl_type.c b/stage1/spl_type.c new file mode 100644 index 0000000..fd5157d --- /dev/null +++ b/stage1/spl_type.c @@ -0,0 +1,398 @@ +/* spl_type.c — Type system implementation */ + +#include "spl_comp.h" +#include +#include +#include + +/* Basic type sizes */ +static int spl_basic_byte_size(spl_type_t bt) { + switch (bt) { + case SPL_VOID: + return 0; + case SPL_I8: + case SPL_U8: + return 1; + case SPL_I16: + case SPL_U16: + return 2; + case SPL_I32: + case SPL_U32: + case SPL_F32: + return 4; + case SPL_F64: + case SPL_I64: + case SPL_U64: + return 8; + case SPL_ISIZE: + case SPL_USIZE: + case SPL_PTR: + return (int)sizeof(spl_val_t); + default: + return 0; + } +} + +int spl_type_is_integer(spl_type_t bt) { + switch (bt) { + case SPL_I8: + case SPL_U8: + case SPL_I16: + case SPL_U16: + case SPL_I32: + case SPL_U32: + case SPL_I64: + case SPL_U64: + case SPL_ISIZE: + case SPL_USIZE: + return 1; + default: + return 0; + } +} + +static spl_type_t name_to_basic_type(const char *name, usize len) { + if (len == 3 && memcmp(name, "i32", 3) == 0) + return SPL_I32; + if (len == 3 && memcmp(name, "u32", 3) == 0) + return SPL_U32; + if (len == 2 && memcmp(name, "i8", 2) == 0) + return SPL_I8; + if (len == 2 && memcmp(name, "u8", 2) == 0) + return SPL_U8; + if (len == 3 && memcmp(name, "i16", 3) == 0) + return SPL_I16; + if (len == 3 && memcmp(name, "u16", 3) == 0) + return SPL_U16; + if (len == 3 && memcmp(name, "i64", 3) == 0) + return SPL_I64; + if (len == 3 && memcmp(name, "u64", 3) == 0) + return SPL_U64; + if (len == 4 && memcmp(name, "bool", 4) == 0) + return SPL_I32; + if (len == 4 && memcmp(name, "void", 4) == 0) + return SPL_VOID; + if (len == 5 && memcmp(name, "isize", 5) == 0) + return SPL_ISIZE; + if (len == 5 && memcmp(name, "usize", 5) == 0) + return SPL_USIZE; + if (len == 2 && memcmp(name, "f3", 2) == 0 && name[2] == '2') + return SPL_F32; + if (len == 2 && memcmp(name, "f6", 2) == 0 && name[2] == '4') + return SPL_F64; + if (len == 3 && memcmp(name, "ptr", 3) == 0) + return SPL_PTR; + return SPL_TYPE_COUNT; +} + +spl_type_info_t *spl_type_basic(spl_type_t bt) { + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_BASIC; + t->basic_type = bt; + t->byte_size = (usize)spl_basic_byte_size(bt); + t->slot_count = t->byte_size == 0 ? 0 : 1; + t->resolved = 1; + return t; +} + +spl_type_info_t *spl_type_ptr(spl_type_info_t *elem) { + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_PTR; + t->elem = elem; + t->byte_size = sizeof(spl_val_t); + t->slot_count = 1; + t->resolved = 1; + return t; +} + +spl_type_info_t *spl_type_array(spl_type_info_t *elem, usize len) { + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_ARRAY; + t->elem = elem; + t->array_len = len; + t->byte_size = elem->byte_size * len; + t->slot_count = elem->slot_count * len; + t->resolved = 1; + return t; +} + +spl_type_info_t *spl_type_slice(spl_type_info_t *elem) { + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_SLICE; + t->elem = elem; + t->byte_size = sizeof(spl_val_t) * 2; /* ptr + len */ + t->slot_count = 2; + t->resolved = 1; + return t; +} + +spl_type_info_t *spl_type_struct(const char *name) { + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_STRUCT; + if (name) + t->name = strdup(name); + vec_init(t->fields); + t->resolved = 0; + return t; +} + +spl_type_info_t *spl_type_enum(const char *name) { + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_ENUM; + if (name) + t->name = strdup(name); + vec_init(t->variants); + t->byte_size = 4; + t->slot_count = 1; + t->resolved = 0; + return t; +} + +void spl_type_add_field(spl_type_info_t *st, const char *name, spl_type_info_t *ftype) { + spl_field_t f; + f.name = strdup(name); + f.type = ftype; + f.offset = 0; + vec_push(st->fields, f); +} + +void spl_type_add_variant(spl_type_info_t *et, const char *name, spl_type_info_t *dtype) { + spl_enum_variant_t v; + v.name = strdup(name); + v.data_type = dtype; + v.value = (int)vec_size(et->variants); + vec_push(et->variants, v); +} + +void spl_type_compute_layout(spl_type_info_t *t) { + if (!t || t->resolved) + return; + + if (t->kind == TYPE_STRUCT) { + usize offset = 0; + vec_for(t->fields, i) { + spl_field_t *f = &vec_at(t->fields, i); + if (f->type) { + spl_type_compute_layout(f->type); + f->offset = offset; + offset += f->type->byte_size; + } + } + t->byte_size = offset; + /* Round up to slot alignment */ + usize slot_sz = sizeof(spl_val_t); + t->slot_count = (offset + slot_sz - 1) / slot_sz; + t->resolved = 1; + } else if (t->kind == TYPE_ENUM) { + /* Enums with data need special handling */ + usize max_dsize = 0; + vec_for(t->variants, i) { + spl_enum_variant_t *v = &vec_at(t->variants, i); + if (v->data_type) { + spl_type_compute_layout(v->data_type); + if (v->data_type->byte_size > max_dsize) + max_dsize = v->data_type->byte_size; + } + } + /* Enum layout: tag (4 bytes) + max data */ + usize total = 4 + max_dsize; + usize slot_sz = sizeof(spl_val_t); + t->byte_size = total; + t->slot_count = (total + slot_sz - 1) / slot_sz; + if (t->slot_count < 1) + t->slot_count = 1; + t->resolved = 1; + } else if (t->kind == TYPE_NAME) { + /* Type alias - should already be resolved to underlying type */ + t->resolved = 1; + } +} + +usize spl_type_size(spl_type_info_t *t) { + if (!t) + return 0; + if (!t->resolved) + spl_type_compute_layout(t); + return t->byte_size; +} + +usize spl_type_slot_count(spl_type_info_t *t) { + if (!t) + return 0; + if (!t->resolved) + spl_type_compute_layout(t); + return t->slot_count; +} + +const char *spl_type_str(spl_type_info_t *t) { + if (!t) + return ""; + switch (t->kind) { + case TYPE_VOID: + return "void"; + case TYPE_BASIC: { + switch (t->basic_type) { + case SPL_I32: + return "i32"; + case SPL_U32: + return "u32"; + case SPL_I8: + return "i8"; + case SPL_U8: + return "u8"; + case SPL_I16: + return "i16"; + case SPL_U16: + return "u16"; + case SPL_I64: + return "i64"; + case SPL_U64: + return "u64"; + case SPL_F32: + return "f32"; + case SPL_F64: + return "f64"; + case SPL_PTR: + return "ptr"; + case SPL_ISIZE: + return "isize"; + case SPL_USIZE: + return "usize"; + case SPL_VOID: + return "void"; + default: + return ""; + } + } + case TYPE_PTR: { + static char buf[64]; + snprintf(buf, sizeof(buf), "*%s", spl_type_str(t->elem)); + return buf; + } + case TYPE_ARRAY: { + static char buf[64]; + snprintf(buf, sizeof(buf), "[%zu]%s", t->array_len, spl_type_str(t->elem)); + return buf; + } + case TYPE_SLICE: { + static char buf[64]; + snprintf(buf, sizeof(buf), "[]%s", spl_type_str(t->elem)); + return buf; + } + case TYPE_STRUCT: + return t->name ? t->name : "struct"; + case TYPE_ENUM: + return t->name ? t->name : "enum"; + case TYPE_NAME: + return t->name ? t->name : ""; + default: + return ""; + } +} + +spl_type_info_t *spl_type_clone(spl_type_info_t *t) { + if (!t) + return NULL; + spl_type_info_t *c = calloc(1, sizeof(spl_type_info_t)); + memcpy(c, t, sizeof(spl_type_info_t)); + /* Don't deep-copy fields/variants for now — shallow is fine for our use */ + return c; +} + +/* Parse a type from the token stream and return a type_info. + * This is used by the parser for type annotations. */ +spl_type_info_t *spl_parse_type(spl_comp_t *ctx) { + spl_tok_t *tok = &vec_at(ctx->toks, ctx->tok_idx); + + /* Pointer type: '*T' */ + if (tok->type == TOK_MUL) { + ctx->tok_idx++; + spl_type_info_t *elem = spl_parse_type(ctx); + if (!elem) + return NULL; + return spl_type_ptr(elem); + } + + /* Array type: '[N]T' */ + if (tok->type == TOK_L_BRACKET) { + ctx->tok_idx++; + tok = &vec_at(ctx->toks, ctx->tok_idx); + + /* Check for empty brackets: []T (slice type) */ + if (tok->type == TOK_R_BRACKET) { + ctx->tok_idx++; + tok = &vec_at(ctx->toks, ctx->tok_idx); + spl_type_info_t *elem = spl_parse_type(ctx); + if (!elem) + return NULL; + return spl_type_slice(elem); + } + + /* Parse array length as integer literal */ + if (tok->type != TOK_INT_LITERAL) { + spl_comp_error(ctx, "expected array length"); + return NULL; + } + usize len = (usize)strtoull(tok->lexeme, NULL, 0); + ctx->tok_idx++; + + if (vec_at(ctx->toks, ctx->tok_idx).type != TOK_R_BRACKET) { + spl_comp_error(ctx, "expected ']'"); + return NULL; + } + ctx->tok_idx++; /* skip ] */ + + tok = &vec_at(ctx->toks, ctx->tok_idx); + spl_type_info_t *elem = spl_parse_type(ctx); + if (!elem) + return NULL; + return spl_type_array(elem, len); + } + + /* Identifier: basic type or named type */ + if (tok->type == TOK_IDENT || ((int)tok->type >= (int)KW_AS && (int)tok->type <= (int)KW_ANY)) { + const char *name = tok->lexeme; + usize len = tok->len; + + /* Check if it's a basic type name */ + spl_type_t bt = name_to_basic_type(name, len); + if (bt != SPL_TYPE_COUNT) { + ctx->tok_idx++; + return spl_type_basic(bt); + } + + /* Check named type definitions */ + char id_buf[256]; + usize cplen = len < 255 ? len : 255; + memcpy(id_buf, name, cplen); + id_buf[cplen] = '\0'; + + spl_type_info_t *found = NULL; + if (map_get(ctx->type_defs, id_buf, &found)) { + ctx->tok_idx++; + return found; + } + + /* _ (wildcard / infer) */ + if (tok->type == KW_ANY) { + ctx->tok_idx++; + spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t)); + t->kind = TYPE_INFER; + t->resolved = 1; + return t; + } + + spl_comp_error(ctx, "unknown type '%s'", id_buf); + return NULL; + } + + spl_comp_error(ctx, "expected type"); + return NULL; +} + +spl_type_info_t *spl_resolve_type(spl_comp_t *ctx, const char *name) { + if (!ctx || !name) return NULL; + spl_type_info_t *found = NULL; + map_get(ctx->type_defs, name, &found); + return found; +} diff --git a/stage1/splc0.c b/stage1/splc0.c new file mode 100644 index 0000000..1ce26ff --- /dev/null +++ b/stage1/splc0.c @@ -0,0 +1,106 @@ +/* splc0.c — Stage 1 SPL compiler (bootstrap) + * Usage: + * splc0 — compile + * splc0 --dump-tokens — dump tokens + * splc0 --help — help + */ + +#include "../stage0/spl_ir.h" +#include "spl_comp.h" +#include +#include +#include + +static char *read_file(const char *path, long *out_len) { + FILE *f = fopen(path, "rb"); + if (!f) { + perror("fopen"); + return NULL; + } + fseek(f, 0, SEEK_END); + long len = ftell(f); + fseek(f, 0, SEEK_SET); + char *buf = malloc((size_t)len + 1); + if (!buf) { + fclose(f); + return NULL; + } + fread(buf, 1, (size_t)len, f); + fclose(f); + buf[len] = '\0'; + *out_len = len; + return buf; +} + +static int cmd_dump_tokens(int argc, char **argv) { + if (argc < 1) { + fprintf(stderr, "Usage: splc0 --dump-tokens \n"); + return 1; + } + long len; + char *src = read_file(argv[0], &len); + if (!src) + return 1; + + spl_tok_vec_t toks = spl_lex(src, argv[0]); + spl_tok_vec_dump(&toks); + spl_tok_vec_drop(&toks); + free(src); + return 0; +} + +static int cmd_compile(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "Usage: splc0 \n"); + return 1; + } + const char *inpath = argv[0]; + const char *outpath = argv[1]; + + long len; + char *src = read_file(inpath, &len); + if (!src) + return 1; + + spl_comp_t ctx; + spl_comp_init(&ctx); + + int ret = 0; + if (spl_compile(&ctx, src, inpath) != 0) { + fprintf(stderr, "compilation failed: %s\n", ctx.error_msg); + ret = 1; + goto cleanup; + } + + if (spl_prog_store_to_file(outpath, &ctx.prog) != 0) { + fprintf(stderr, "failed to write '%s'\n", outpath); + ret = 1; + goto cleanup; + } + + printf("compiled %s -> %s\n", inpath, outpath); + +cleanup: + spl_comp_drop(&ctx); + free(src); + return ret; +} + +int main(int argc, char **argv) { + if (argc < 2) { + fprintf(stderr, "Usage: splc0 [--dump-tokens|--help] [output.sir]\n"); + return 1; + } + + if (strcmp(argv[1], "--dump-tokens") == 0) { + return cmd_dump_tokens(argc - 2, argv + 2); + } + if (strcmp(argv[1], "--help") == 0) { + printf("SPL Compiler (stage1 bootstrap)\n"); + printf(" splc0 - compile\n"); + printf(" splc0 --dump-tokens - dump token stream\n"); + return 0; + } + + return cmd_compile(argc - 1, argv + 1); +} diff --git a/stage1/splc_cli.c b/stage1/splc_cli.c new file mode 100644 index 0000000..06893ba --- /dev/null +++ b/stage1/splc_cli.c @@ -0,0 +1,67 @@ +/* spc_vm.c — VM launcher for stage 1 */ + +#include "../stage0/spl_ir.h" +#include "../stage0/spl_syscall.h" +#include "../stage0/spl_vm.h" +#include "spl_comp.h" + +#include +#include + +int main(int argc, const char **argv) { + int argi = 1; + + if (argi >= argc) { + fprintf(stderr, "Usage: spc_vm [entry] [--trace]\n"); + return 1; + } + + const char *path = argv[argi++]; + + /* Parse flags: --entry , --trace */ + const char *entry = "main"; + int trace = 0; + for (int i = argi; i < argc; i += 1) { + if (strcmp(argv[i], "--entry") == 0 && i + 1 < argc) { + entry = argv[++i]; + } else if (strcmp(argv[i], "--trace") == 0) { + trace = 1; + } + } + + /* Adjust argv so the SPL program sees path as argv[0] and + * remaining positional args as argv[1..], just like a native exe. */ + int spl_argc = argc - (argi - 1); + const char **spl_argv = argv + (argi - 1); + + spl_prog_t prog; + if (spl_prog_load_from_file(path, &prog) != 0) { + fprintf(stderr, "spc_vm: cannot load '%s'\n", path); + return 1; + } + + spl_syscall_register(&prog); + spl_comp_register(&prog); + + spl_vm_t vm; + spl_vm_init(&vm); + if (spl_vm_load_prog(&vm, &prog) != 0) { + fprintf(stderr, "vm: prog '%s' not found\n", entry); + spl_prog_drop(&prog); + return 1; + } + if (spl_vm_prepare(&vm, entry, spl_argc, spl_argv, NULL) != 0) { + fprintf(stderr, "vm: entry point '%s' not found\n", entry); + spl_prog_drop(&prog); + return 1; + } + spl_vm_set_trace(&vm, trace); + + int ret = spl_vm_run_until(&vm, 0); + spl_vm_drop(&vm); + spl_prog_drop(&prog); + + if (ret < 0) + return (int)vm.exit_code; + return (int)vm.exit_code; +} diff --git a/stage1/test00.spl b/stage1/test00.spl new file mode 100644 index 0000000..61bd6e5 --- /dev/null +++ b/stage1/test00.spl @@ -0,0 +1,4 @@ + +fn main() i32 { + ret 0; +} diff --git a/stage1/test01.spl b/stage1/test01.spl new file mode 100644 index 0000000..93f7967 --- /dev/null +++ b/stage1/test01.spl @@ -0,0 +1,19 @@ +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +fn main() void { + vm_printf("%d\n", 42); + vm_printf("%d\n", -5 + 10); + vm_printf("%d\n", 7 * 8); + vm_printf("%d\n", 100 / 3); + vm_printf("%d\n", 100 % 3); + + var t: bool = true; + var f: bool = false; + vm_printf("%d\n", t && (!f)); + vm_printf("%d\n", f || t); + + var c: u8 = 'A'; + vm_printf("%c%c\n", c, 65); + vm_printf("%s\n", "hello spl"); + ret; +} diff --git a/stage1/test02.spl b/stage1/test02.spl new file mode 100644 index 0000000..cb2d050 --- /dev/null +++ b/stage1/test02.spl @@ -0,0 +1,16 @@ +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +type Point = struct { + x: i32, + y: i32, +} + +pub fn main() void { + var arr: [3]i32 = [3]i32{1, 2, 3}; + vm_printf("%d\n", arr[0] + arr[2]); + + var arr: [5]i32 = [5]i32{10, 20, 30, 40, 50}; + var slice: []i32 = arr[1..4]; // 20,30,40 + vm_printf("%d %d\n", slice.len, slice[0]); + ret; +} diff --git a/stage1/test03.spl b/stage1/test03.spl new file mode 100644 index 0000000..a18cb67 --- /dev/null +++ b/stage1/test03.spl @@ -0,0 +1,32 @@ +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +type Point = struct { + x: i32, + y: i32, +} + +type Enum = enum { + Int, + Add +} + +type Expr = enum { + Int: i32, + Add: struct { left: *Expr, right: *Expr }, +} + +fn eval(e: *Expr) i32 { + match e { + .Int(val) => return val, + .Add(left, right) => return eval(left) + eval(right), + } + return 0; +} + +fn main() void { + var p: Point = Point { .x = 3, .y = 4 }; + vm_printf("%d %d\n", p.x, p.y); + + vm_printf("basic enum\n", Enum.Int, Enum.Add); + ret; +} diff --git a/stage1/test04.spl b/stage1/test04.spl new file mode 100644 index 0000000..d1d5d61 --- /dev/null +++ b/stage1/test04.spl @@ -0,0 +1,16 @@ +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +fn inc(ptr: *i32) void { + *ptr = *ptr + 1; +} + +fn main() void { + var x: i32 = 5; + inc(&x); + vm_printf("%d\n", x); + + var buf: [4]u8 = [4]u8{'S', 'P', 'L', 0}; + var ptr: *u8 = &buf[0]; + vm_printf("%s\n", ptr); // 假设 %s 可接受 *u8 + ret; +} diff --git a/stage1/test05.spl b/stage1/test05.spl new file mode 100644 index 0000000..c18fddb --- /dev/null +++ b/stage1/test05.spl @@ -0,0 +1,32 @@ + +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +fn main() void { + var x: i32 = 10; + if x > 5 { + vm_printf("big\n"); + } else { + vm_printf("small\n"); + } + + var i: i32 = 0; + while i < 3 { + vm_printf("%d\n", i); + i = i + 1; + } + + var nums: [3]i32 = [3]i32{100, 200, 300}; + var slice: []i32 = nums[0..]; + for slice, 0.. as val, idx { + vm_printf("%d:%d\n", idx, val); + } + + var j: i32 = 0; + while true { + j = j + 1; + if j == 2 { continue; } + if j == 4 { break; } + vm_printf("%d\n", j); + } + ret; +} diff --git a/stage1/test06.spl b/stage1/test06.spl new file mode 100644 index 0000000..27bbc25 --- /dev/null +++ b/stage1/test06.spl @@ -0,0 +1,12 @@ + +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +fn fib(n: i32) i32 { + if n <= 1 { ret n; } + ret fib(n-1) + fib(n-2); +} + +fn main() void { + vm_printf("%d\n", fib(10)); + ret; +} diff --git a/stage1/test07.spl b/stage1/test07.spl new file mode 100644 index 0000000..5b3f9f5 --- /dev/null +++ b/stage1/test07.spl @@ -0,0 +1,18 @@ +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +fn with_cleanup() void { + defer vm_printf("cleanup\n"); +} + +fn main() void { + defer vm_printf("defer1\n"); + with_cleanup(); + defer vm_printf("defer2\n"); + + for 0..2 as i { + defer vm_printf("defer for before\n", i); + vm_printf("%d", i); + defer vm_printf("defer for after\n", i); + } + ret; +} diff --git a/stage1/test08.spl b/stage1/test08.spl new file mode 100644 index 0000000..c4dd411 --- /dev/null +++ b/stage1/test08.spl @@ -0,0 +1,43 @@ +#[extern("vm")] fn vm_printf(fmt: *u8, ...) void; + +type Point = struct { + x: i32, + y: i32, + + type Test = enum { + TestEnum0, + TestEnum1, + } + + fn init(x: i32, y: i32) Point { + ret Point { .x = x, .y = y }; + } + + fn dump(self: *Point) { + vm_printf("Point: %d %d\n", self.x, self.y); + } +} + +type Expr = enum { + Int: i32, + Add: struct { left: *Expr, right: *Expr }, + + fn eval(self: *Expr) i32 { + match self { + .Int(val) => return val, + .Add(left, right) => return eval(left) + eval(right), + } + return 0; + } +} + +fn main() void { + var p: Point = Point.init(3, 4); + p.dump(); + + var expr_l := Expr { .Int = 3 }; + var expr_r := Expr { .Int = 4 }; + var expr := Expr { .Add = { .left = expr_l, .right = expr_r } }; + vm_printf("Expr: %d\n", expr.eval()); + ret; +} diff --git a/stage1/test11.spl b/stage1/test11.spl new file mode 100644 index 0000000..68d8f23 --- /dev/null +++ b/stage1/test11.spl @@ -0,0 +1,85 @@ +fn add(a: i32, b: i32) i32 { + ret a + b; +} + +fn main() i32 { + /* arithmetic */ + var a := 1 + 2 * 3; + if a != 7 { ret 1; } + + var b := 10 - 4; + if b != 6 { ret 2; } + + var c := 12 / 5; + if c != 2 { ret 3; } + + var d := 12 % 5; + if d != 2 { ret 4; } + + /* comparison */ + if (1 < 2) {} else { ret 5; } + if (3 > 1) {} else { ret 6; } + if (2 == 2) {} else { ret 7; } + if (2 != 3) {} else { ret 8; } + if (2 <= 2) {} else { ret 9; } + if (3 >= 3) {} else { ret 10; } + + /* function call */ + var s := add(3, 4); + if s != 7 { ret 11; } + + /* if/else */ + var x := 0; + if x == 0 { + x = 10; + } else { + x = 20; + } + if x != 10 { ret 12; } + + /* while */ + var i := 0; + while i < 5 { + i = i + 1; + } + if i != 5 { ret 13; } + + /* loop/break */ + var j := 0; + loop { + j = j + 1; + if j >= 3 { break; } + } + if j != 3 { ret 14; } + + /* continue */ + var k := 0; + var n := 0; + while k < 5 { + k = k + 1; + if k == 3 { continue; } + n = n + 1; + } + if k != 5 { ret 15; } + if n != 4 { ret 16; } + + /* compound assignment */ + var y := 5; + y += 3; + if y != 8 { ret 17; } + y -= 2; + if y != 6 { ret 18; } + y *= 2; + if y != 12 { ret 19; } + + /* logical */ + if (true) {} else { ret 20; } + if (false) { ret 21; } + if (!false) {} else { ret 22; } + + /* bitwise */ + var m := 0xFF & 0x0F; + if m != 0x0F { ret 23; } + + ret 0; +} diff --git a/stage1/test12.spl b/stage1/test12.spl new file mode 100644 index 0000000..c0d8b2a --- /dev/null +++ b/stage1/test12.spl @@ -0,0 +1,27 @@ + +fn main() i32 { + /* address-of and postfix deref */ + var x := 42; + var p := &x; + var v := p.*; + if v != 42 { ret 1; } + + /* null pointer */ + var np: *i32 = null; + if np != null { ret 2; } + if np == null {} else { ret 3; } + + /* pointer to struct — auto-deref on .field */ + type Point = struct { + a: i32, + b: i32, + }; + var pt: Point; + pt.a = 10; + pt.b = 20; + var pp := &pt; + if pp.a != 10 { ret 4; } + if pp.b != 20 { ret 5; } + + ret 0; +} diff --git a/stage1/test13.spl b/stage1/test13.spl new file mode 100644 index 0000000..0f11bcf --- /dev/null +++ b/stage1/test13.spl @@ -0,0 +1,34 @@ + +type Pair = struct { + a: i32, + b: i32, +}; + +fn main() i32 { + /* struct field access */ + var p: Pair; + p.a = 1; + p.b = 2; + if p.a != 1 { ret 1; } + if p.b != 2 { ret 2; } + + /* inline type definition */ + type Triple = struct { + x: i32, + y: i32, + z: i32, + }; + var t: Triple; + t.x = 10; + t.y = 20; + t.z = 30; + if t.x + t.y + t.z != 60 { ret 3; } + + /* type reuse */ + var p2: Pair; + p2.a = 5; + p2.b = 6; + if p2.a + p2.b != 11 { ret 4; } + + ret 0; +} diff --git a/stage1/test14.spl b/stage1/test14.spl new file mode 100644 index 0000000..e7e77ad --- /dev/null +++ b/stage1/test14.spl @@ -0,0 +1,30 @@ +#[extern("vm")] fn vm_strlen(s: *i8) i32; +#[extern("vm")] fn vm_strcmp(a: *i8, b: *i8) i32; + +const MAGIC := 42; + +fn main() i32 { + /* compile-time constant */ + if MAGIC != 42 { ret 1; } + + /* block scope */ + var outer := 1; + { + var inner := 2; + if inner != 2 { ret 2; } + outer = inner; + } + if outer != 2 { ret 3; } + + /* string literal — vm_strlen */ + var len := vm_strlen("hello"); + if len != 5 { ret 4; } + + /* vm_strcmp */ + var cmp := vm_strcmp("abc", "abc"); + if cmp != 0 { ret 5; } + + var cmp2 := vm_strcmp("abc", "def"); + if cmp2 == 0 { ret 6; } + ret 0; +} diff --git a/stage1/test15.spl b/stage1/test15.spl new file mode 100644 index 0000000..522e7d2 --- /dev/null +++ b/stage1/test15.spl @@ -0,0 +1,40 @@ +/* test_syntax.spl — 测试新语法:expr.*, var/const :=, 内联 type */ + +fn main() i32 { + /* test 1: var name := expr */ + var x := 42; + if x != 42 { ret 1; } + + /* test 2: const name := const_expr */ + const y := 100; + if y != 100 { ret 2; } + + /* test 3: var name: Type = expr 仍可用 */ + var z: i32 = 77; + if z != 77 { ret 3; } + + /* test 4: expr.* 后缀解引用 */ + var a: i32 = 99; + var p: *i32 = &a; + var v := p.*; + if v != 99 { ret 4; } + + /* test 5: 指针修改后 .* 读到新值 */ + a = 55; + var v2 := p.*; + if v2 != 55 { ret 5; } + + /* test 6: 内联 type 定义 */ + type Point = struct { + x: i32, + y: i32, + }; + var pt: Point; + pt.x = 10; + pt.y = 20; + var ppt: *Point = &pt; + if ppt.x != 10 { ret 6; } + if ppt.y != 20 { ret 7; } + + ret 0; +}