stage1 部分功能测试00,01,02,06成功
This commit is contained in:
408
stage1/spl.md
Normal file
408
stage1/spl.md
Normal file
@@ -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 中尚未实现。)
|
||||
251
stage1/spl_comp.c
Normal file
251
stage1/spl_comp.c
Normal file
@@ -0,0 +1,251 @@
|
||||
/* spl_comp.c — SPL compiler main logic and codegen helpers */
|
||||
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
/* ---- 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;
|
||||
}
|
||||
250
stage1/spl_comp.h
Normal file
250
stage1/spl_comp.h
Normal file
@@ -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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ============================================================
|
||||
* 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__ */
|
||||
764
stage1/spl_expr.c
Normal file
764
stage1/spl_expr.c
Normal file
@@ -0,0 +1,764 @@
|
||||
/* spl_expr.c — Expression parser + codegen (Pratt parser / precedence climbing) */
|
||||
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* ============================================================
|
||||
* 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 };
|
||||
}
|
||||
69
stage1/spl_lex_util.c
Normal file
69
stage1/spl_lex_util.c
Normal file
@@ -0,0 +1,69 @@
|
||||
/* spl_lex_util.c — Lexer utility functions */
|
||||
|
||||
#include "spl_lexer.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
533
stage1/spl_lexer.c
Normal file
533
stage1/spl_lexer.c
Normal file
@@ -0,0 +1,533 @@
|
||||
/* spl_lexer.c — SPL lexical analyzer */
|
||||
|
||||
#include "spl_lexer.h"
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 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;
|
||||
}
|
||||
142
stage1/spl_lexer.h
Normal file
142
stage1/spl_lexer.h
Normal file
@@ -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__ */
|
||||
294
stage1/spl_parser.c
Normal file
294
stage1/spl_parser.c
Normal file
@@ -0,0 +1,294 @@
|
||||
/* spl_parser.c — Top-level parser: function declarations, type declarations, etc. */
|
||||
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
506
stage1/spl_stmt.c
Normal file
506
stage1/spl_stmt.c
Normal file
@@ -0,0 +1,506 @@
|
||||
/* spl_stmt.c — Statement parser + codegen */
|
||||
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
398
stage1/spl_type.c
Normal file
398
stage1/spl_type.c
Normal file
@@ -0,0 +1,398 @@
|
||||
/* spl_type.c — Type system implementation */
|
||||
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 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 "<null>";
|
||||
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 "<basic>";
|
||||
}
|
||||
}
|
||||
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 : "<alias>";
|
||||
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;
|
||||
}
|
||||
106
stage1/splc0.c
Normal file
106
stage1/splc0.c
Normal file
@@ -0,0 +1,106 @@
|
||||
/* splc0.c — Stage 1 SPL compiler (bootstrap)
|
||||
* Usage:
|
||||
* splc0 <input.spl> <output.sir> — compile
|
||||
* splc0 --dump-tokens <input.spl> — dump tokens
|
||||
* splc0 --help — help
|
||||
*/
|
||||
|
||||
#include "../stage0/spl_ir.h"
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
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 <file.spl>\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 <input.spl> <output.sir>\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] <input.spl> [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 <input.spl> <output.sir> - compile\n");
|
||||
printf(" splc0 --dump-tokens <file.spl> - dump token stream\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cmd_compile(argc - 1, argv + 1);
|
||||
}
|
||||
67
stage1/splc_cli.c
Normal file
67
stage1/splc_cli.c
Normal file
@@ -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 <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
int argi = 1;
|
||||
|
||||
if (argi >= argc) {
|
||||
fprintf(stderr, "Usage: spc_vm <file.sir> [entry] [--trace]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *path = argv[argi++];
|
||||
|
||||
/* Parse flags: --entry <name>, --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;
|
||||
}
|
||||
4
stage1/test00.spl
Normal file
4
stage1/test00.spl
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
fn main() i32 {
|
||||
ret 0;
|
||||
}
|
||||
19
stage1/test01.spl
Normal file
19
stage1/test01.spl
Normal file
@@ -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;
|
||||
}
|
||||
16
stage1/test02.spl
Normal file
16
stage1/test02.spl
Normal file
@@ -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;
|
||||
}
|
||||
32
stage1/test03.spl
Normal file
32
stage1/test03.spl
Normal file
@@ -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;
|
||||
}
|
||||
16
stage1/test04.spl
Normal file
16
stage1/test04.spl
Normal file
@@ -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;
|
||||
}
|
||||
32
stage1/test05.spl
Normal file
32
stage1/test05.spl
Normal file
@@ -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;
|
||||
}
|
||||
12
stage1/test06.spl
Normal file
12
stage1/test06.spl
Normal file
@@ -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;
|
||||
}
|
||||
18
stage1/test07.spl
Normal file
18
stage1/test07.spl
Normal file
@@ -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;
|
||||
}
|
||||
43
stage1/test08.spl
Normal file
43
stage1/test08.spl
Normal file
@@ -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;
|
||||
}
|
||||
85
stage1/test11.spl
Normal file
85
stage1/test11.spl
Normal file
@@ -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;
|
||||
}
|
||||
27
stage1/test12.spl
Normal file
27
stage1/test12.spl
Normal file
@@ -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;
|
||||
}
|
||||
34
stage1/test13.spl
Normal file
34
stage1/test13.spl
Normal file
@@ -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;
|
||||
}
|
||||
30
stage1/test14.spl
Normal file
30
stage1/test14.spl
Normal file
@@ -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;
|
||||
}
|
||||
40
stage1/test15.spl
Normal file
40
stage1/test15.spl
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user