Compare commits

...

15 Commits

Author SHA1 Message Date
zzy
53ae30f1ca stage1 重制18测试用例替换成match 完成现有全部测试 2026-07-11 21:24:52 +08:00
zzy
147f26e063 stage1 修复bug 删除递归测试 2026-07-08 11:45:45 +08:00
zzy
463177d3be stage1 修改部分测试 完成17-19测试 2026-07-07 12:11:49 +08:00
zzy
1df3e3bcb4 stage1 将数组和聚合类型使用byte offset而不是slot idx 2026-07-07 11:38:31 +08:00
zzy
3cf11f922e stage1 优化代码 完成16测试 2026-07-07 10:40:30 +08:00
zzy
69cea030dc stage1 完成15测试 2026-07-06 21:26:27 +08:00
zzy
0892c084ee stage1 完成14测试 2026-07-06 15:18:17 +08:00
zzy
ad473f245c stage1 完成11-13测试 2026-07-06 13:00:55 +08:00
zzy
0182b8ed5c stage1 完成09,10测试 2026-07-06 10:52:31 +08:00
zzy
777b6b42d1 stage1 完成07,08测试 2026-07-06 10:44:01 +08:00
zzy
5dadf6d6ee stage1 重构到 USIZE, 使得 vm 支持 usize, isize 2026-07-06 00:07:39 +08:00
zzy
67c8a137dd stage1 实现06测试 2026-07-05 22:47:00 +08:00
zzy
e2e0ebc21f stage1 重构测试且成功00-05 2026-07-05 21:45:43 +08:00
zzy
51d8510b79 stage1 部分功能测试00,01,02,06成功 2026-07-05 18:23:33 +08:00
zzy
50b07074fb stage0 2026-07-03 22:27:50 +08:00
48 changed files with 13732 additions and 0 deletions

417
build.py Normal file
View File

@@ -0,0 +1,417 @@
#!/usr/bin/env python3
"""SPL build system — .c → .o → exe, .spl → .sir, deps auto-resolved."""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent
BUILD_DIR = ROOT / "build"
CACHE_FILE = BUILD_DIR / ".build_cache.json"
DESC_FILE = ROOT / "project_desc.py"
EXE_EXT = ".exe" if os.name == "nt" else ""
CC = os.environ.get("CC", "gcc")
CFLAGS = os.environ.get("CFLAGS", "-Wall -Wextra -O0 -g").split()
def load_desc():
ns = {"__builtins__": __builtins__}
exec(DESC_FILE.read_text(), ns)
return {
"exe": ns.get("exe", {}),
"spl": ns.get("spl", {}),
"pipeline": ns.get("pipeline", {}),
}
# ── Helpers ─────────────────────────────────────────────────────────────
def obj_key(src):
return src.rsplit(".", 1)[0].replace("/", "_").replace("\\", "_")
def obj_path(src):
return BUILD_DIR / (obj_key(src) + ".o")
def exe_path(name):
return BUILD_DIR / (name + EXE_EXT)
def sir_path(name):
return BUILD_DIR / (name + ".sir")
# ── Cache ───────────────────────────────────────────────────────────────
def load_cache():
if CACHE_FILE.is_file():
try:
return json.loads(CACHE_FILE.read_text())
except (json.JSONDecodeError, OSError):
pass
return {}
def save_cache(cache):
BUILD_DIR.mkdir(parents=True, exist_ok=True)
CACHE_FILE.write_text(json.dumps(cache, indent=2))
def hash_file(p):
h = hashlib.sha256()
with p.open("rb") as f:
while chunk := f.read(65536):
h.update(chunk)
return h.hexdigest()
# ── C compilation ───────────────────────────────────────────────────────
_built_objs = set()
def compile_c(src, cache, force):
key = f"obj:{src}"
dst = obj_path(src)
src_path = ROOT / src
if not src_path.is_file():
print(f" ERR {src} not found"); return False
h = hashlib.sha256()
h.update(hash_file(src_path).encode())
h.update(" ".join(CFLAGS).encode())
ch = h.hexdigest()
cached = cache.get(key)
if not force and cached and cached["h"] == ch and dst.is_file():
_built_objs.add(src); return True
BUILD_DIR.mkdir(parents=True, exist_ok=True)
cmd = [CC] + CFLAGS + ["-c", str(src_path), "-o", str(dst)]
print(f" CC {src}")
if subprocess.run(cmd).returncode: return False
cache[key] = {"h": ch}
_built_objs.add(src)
return True
def link_exe(name, sources, cache, force):
key = f"exe:{name}"
dst = exe_path(name)
# hash = concat of all input obj hashes + name
h = hashlib.sha256(name.encode())
for src in sources:
cached = cache.get(f"obj:{src}", {})
h.update(cached.get("h", "?").encode())
ch = h.hexdigest()
cached = cache.get(key)
if not force and cached and cached["h"] == ch and dst.is_file():
return True
cmd = [CC] + CFLAGS + [str(obj_path(s)) for s in sources] + ["-o", str(dst)]
print(f" LINK {name}")
if subprocess.run(cmd).returncode: return False
cache[key] = {"h": ch}
return True
# ── SPL compilation ─────────────────────────────────────────────────────
def find_imports(src_path):
"""Scan source for @import("path") directives, return resolved paths."""
deps = []
try:
text = src_path.read_text(errors="replace")
except OSError:
return deps
idx = 0
while True:
idx = text.find('@import("', idx)
if idx < 0:
break
start = idx + 9
end = text.find('")', start)
if end < 0:
break
imp_path = text[start:end]
imp_file = (src_path.parent / imp_path).resolve()
if imp_file.is_file():
deps.append(imp_file)
idx = end + 2
return deps
def collect_deps(src_path):
"""Collect all transitive @import dependencies."""
seen = set()
result = []
def walk(p):
p = p.resolve()
if p in seen:
return
seen.add(p)
for dep in find_imports(p):
walk(dep)
result.append(dep)
walk(src_path)
return result
def compile_spl(name, src, compiler, desc, cache, force):
key = f"spl:{name}"
dst = sir_path(name)
src_path = ROOT / src
if not src_path.is_file():
print(f" ERR {src} not found"); return False
h = hashlib.sha256()
h.update(hash_file(src_path).encode())
# Hash @import dependencies (transitive)
for dep in collect_deps(src_path):
h.update(hash_file(dep).encode())
# Determine how to run the compiler
if compiler in desc.get("spl", {}):
# SPL-based compiler: run via VM runner
cinfo = desc["spl"][compiler]
pl = desc.get("pipeline", {}).get(cinfo.get("pipeline", ""), {})
runner = pl.get("runner") or cinfo.get("runner", "spl_cli")
runner_exe = exe_path(runner)
sir = sir_path(compiler)
if runner_exe.is_file():
h.update(hash_file(runner_exe).encode())
if os.path.isfile(sir):
h.update(hash_file(Path(sir)).encode())
ch = h.hexdigest()
cached = cache.get(key)
if not force and cached and cached["h"] == ch and dst.is_file():
return True
BUILD_DIR.mkdir(parents=True, exist_ok=True)
cmd = [str(runner_exe), str(sir), str(src_path), str(dst)]
print(f" SPL {name} ({compiler} via {runner})")
else:
# Native exe compiler
ce = exe_path(compiler)
if ce.is_file():
h.update(hash_file(ce).encode())
ch = h.hexdigest()
cached = cache.get(key)
if not force and cached and cached["h"] == ch and dst.is_file():
return True
BUILD_DIR.mkdir(parents=True, exist_ok=True)
cmd = [str(ce), str(src_path), str(dst)]
print(f" SPL {name} ({compiler})")
if subprocess.run(cmd).returncode: return False
cache[key] = {"h": ch}
return True
# ── Dependency-aware build ──────────────────────────────────────────────
_built = set()
def build(name, desc, cache, force):
"""Build target + all deps (auto-resolved DAG traversal)."""
if name in _built:
return True
if name in desc.get("exe", {}):
sources = desc["exe"][name]
for src in sources:
if not compile_c(src, cache, force):
return False
if not link_exe(name, sources, cache, force):
return False
elif name in desc.get("spl", {}):
info = desc["spl"][name]
pl = desc.get("pipeline", {}).get(info.get("pipeline", ""), {})
compiler = pl.get("compiler") or info.get("compiler")
if not compiler:
print(f" ERR '{name}': no compiler in pipeline '{info.get('pipeline')}'")
return False
if not build(compiler, desc, cache, force):
return False
if not compile_spl(name, info["src"], compiler, desc, cache, force):
return False
else:
print(f" ERR unknown target '{name}'")
return False
_built.add(name)
return True
def build_all(desc, cache, force):
targets = list(desc.get("exe", {})) + list(desc.get("spl", {}))
for t in targets:
if not build(t, desc, cache, force):
return False
return True
# ── Run ─────────────────────────────────────────────────────────────────
def run_target(name, desc, cache, force, extra_args):
if name in desc.get("exe", {}):
if not build(name, desc, cache, force):
return 1
exe = exe_path(name)
print(f" RUN {name}")
return subprocess.run([str(exe)] + extra_args).returncode
if name in desc.get("spl", {}):
info = desc["spl"][name]
pl = desc.get("pipeline", {}).get(info.get("pipeline", ""), {})
runner = pl.get("runner") or info.get("runner", "spl_cli")
if not runner:
print(f" ERR '{name}': no runner"); return 1
if not build(name, desc, cache, force):
return 1
if not build(runner, desc, cache, force):
return 1
print(f" RUN {name} (via {runner})")
return subprocess.run(
[str(exe_path(runner)), str(sir_path(name))] + extra_args
).returncode
if name in desc.get("pipeline", {}):
pl = desc["pipeline"][name]
compiler = pl.get("compiler")
runner = pl.get("runner")
if not compiler or not runner:
print(f" ERR pipeline '{name}' missing compiler or runner"); return 1
if not extra_args:
print(" ERR usage: run <pipeline> <src> [args]"); return 1
src = extra_args[0]
if not build(compiler, desc, cache, force): return 1
if not build(runner, desc, cache, force): return 1
sir_name = "pipeline_" + hashlib.sha256(str(ROOT / src).encode()).hexdigest()[:8]
if not compile_spl(sir_name, src, compiler, desc, cache, force):
return 1
print(f" RUN pipeline {name} ({src})")
return subprocess.run(
[str(exe_path(runner)), str(sir_path(sir_name))] + extra_args[1:]
).returncode
print(f" ERR unknown target '{name}'"); return 1
# ── Clean / List ────────────────────────────────────────────────────────
def clean():
if BUILD_DIR.is_dir():
shutil.rmtree(BUILD_DIR)
print(f" CLEAN {BUILD_DIR}")
def list_targets(desc):
print("Executables (.c → .o → exe):")
for name, sources in desc.get("exe", {}).items():
print(f" {name}")
for s in sources:
print(f" {s}")
if desc.get("spl"):
print("\nSPL programs (.spl → .sir via pipeline):")
for name, info in desc["spl"].items():
pl_name = info.get("pipeline", "-")
print(f" {name} pipeline={pl_name} src={info['src']}")
if desc.get("pipeline"):
print("\nPipelines:")
for name, pl in desc["pipeline"].items():
print(f" {name} compiler={pl['compiler']} runner={pl.get('runner', '-')}")
# ── CLI ─────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="SPL build system")
sub = parser.add_subparsers(dest="command")
bp = sub.add_parser("build", help="build targets (default: all)")
bp.add_argument("targets", nargs="*")
bp.add_argument("-f", "--force", action="store_true")
rp = sub.add_parser("run", help="build and run a target, or run pipeline <name> <src> [args]")
rp.add_argument("target")
rp.add_argument("extra", nargs="*")
sub.add_parser("test", help="build and run VM unit tests")
sub.add_parser("clean", help="remove build artifacts")
sub.add_parser("list", help="list all targets")
args = parser.parse_args()
if not DESC_FILE.is_file():
print(f"ERR {DESC_FILE} not found"); return 1
# Default: build all
if args.command is None:
desc = load_desc(); cache = load_cache()
ok = build_all(desc, cache, False)
save_cache(cache)
return 0 if ok else 1
# Build
if args.command == "build":
desc = load_desc(); cache = load_cache()
if args.targets:
ok = all(build(t, desc, cache, args.force) for t in args.targets)
else:
ok = build_all(desc, cache, args.force)
save_cache(cache)
return 0 if ok else 1
# Run
if args.command == "run":
desc = load_desc(); cache = load_cache()
rc = run_target(args.target, desc, cache, False, args.extra)
save_cache(cache)
return rc
# Pipeline: build compiler → compile .spl → run via runner
if args.command == "pipeline":
desc = load_desc(); cache = load_cache()
pl = desc.get("pipeline", {}).get(args.name)
if not pl:
print(f" ERR unknown pipeline '{args.name}'"); return 1
compiler = pl.get("compiler")
runner = pl.get("runner")
if not compiler or not runner:
print(f" ERR pipeline '{args.name}' missing compiler or runner"); return 1
src_path = ROOT / args.src
if not src_path.is_file():
print(f" ERR source not found: {args.src}"); return 1
# Build compiler + runner
if not build(compiler, desc, cache, False): return 1
if not build(runner, desc, cache, False): return 1
# Compile .spl → .sir (temp name = src path hash)
sir_name = "pipeline_" + hashlib.sha256(str(src_path).encode()).hexdigest()[:8]
if not compile_spl(sir_name, args.src, compiler, desc, cache, False):
return 1
# Run .sir via runner
print(f" PIPELINE {args.name} ({args.src})")
rc = subprocess.run(
[str(exe_path(runner)), str(sir_path(sir_name))] + args.extra
).returncode
save_cache(cache)
return rc
# Test
if args.command == "test":
desc = load_desc(); cache = load_cache()
ok = build("test", desc, cache, False)
save_cache(cache)
if not ok: return 1
return subprocess.run([str(exe_path("test"))]).returncode
# Clean / List
if args.command == "clean":
clean(); return 0
if args.command == "list":
list_targets(load_desc()); return 0
return 0
if __name__ == "__main__":
sys.exit(main())

52
project_desc.py Normal file
View File

@@ -0,0 +1,52 @@
vm = [
"stage0/spl_ir.c",
"stage0/spl_syscall.c",
"stage0/spl_vm.c",
]
splc0_part = [
"stage1/spl_comp.c",
"stage1/spl_lexer.c",
"stage1/spl_type.c",
"stage1/spl_parser.c",
"stage1/spl_lex_util.c",
"stage1/spl_expr.c",
"stage1/spl_stmt.c",
]
exe = {
"spl_cli": ["stage0/spl_cli.c"] + vm,
"splc_cli": ["stage1/splc_cli.c"] + vm + splc0_part,
"splc0": ["stage1/splc0.c"] + vm + splc0_part,
"test": ["stage0/test_spl_vm.c"] + vm,
"spl_disasm": ["stage0/spl_disasm.c"] + vm,
}
pipeline = {
"splc0b": {"compiler": "splc0", "runner": "spl_cli"},
"splc0r": {"compiler": "splc0", "runner": "splc_cli"},
"splc0d": {"compiler": "splc0", "runner": "spl_disasm"},
"splc1b": {"compiler": "splc0", "runner": "splc_cli"},
"splc1r": {"compiler": "splc1", "runner": "splc_cli"},
"splc1d": {"compiler": "splc1", "runner": "spl_disasm"},
"splc2b": {"compiler": "splc1", "runner": "splc_cli"},
"splc2r": {"compiler": "splc2", "runner": "splc_cli"},
"splc2d": {"compiler": "splc2", "runner": "spl_disasm"},
"splc3b": {"compiler": "splc2", "runner": "splc_cli"},
"splc3r": {"compiler": "splc3", "runner": "splc_cli"},
"splc3d": {"compiler": "splc3", "runner": "spl_disasm"},
"splc4b": {"compiler": "splc3", "runner": "splc_cli"},
"splc4r": {"compiler": "splc4", "runner": "splc_cli"},
"splc4d": {"compiler": "splc4", "runner": "spl_disasm"},
}
spl = {
"splc1": {"src": "stage2/splc1.spl", "pipeline": "splc0r"},
"splc2": {"src": "stage2/splc2.spl", "pipeline": "splc1r"},
"splc3": {"src": "stage3/splc3.spl", "pipeline": "splc2r"},
"splc4": {"src": "stage4/splc4.spl", "pipeline": "splc3r"},
}

1994
stage0/include/acutest.h Normal file

File diff suppressed because it is too large Load Diff

175
stage0/include/core_map.h Normal file
View File

@@ -0,0 +1,175 @@
#ifndef __CORE_MAP_H__
#define __CORE_MAP_H__
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifndef nullptr
#define nullptr NULL
#endif
typedef size_t usize;
#define MAP_TYPEOF __typeof__
/* 状态常量 */
#define __MAP_SLOT_EMPTY 0
#define __MAP_SLOT_OCCUPIED 1
#define __MAP_SLOT_DELETED 2
/* 默认负载因子 factor/128 */
#define MAP_DEFAULT_LOAD_FACTOR 70
/* ---------- 默认哈希/比较函数 ---------- */
#define MAP_HASH_INT(k) ((usize)((k) * 2654435761U))
#define MAP_CMP_INT(a, b) ((a) != (b))
static inline usize map_hash_str(const char *s) {
usize h = 5381;
while (*s)
h = ((h << 5) + h) + (unsigned char)*s++;
return h;
}
#define MAP_HASH_STR map_hash_str
#define MAP_CMP_STR strcmp
/* ---------- 数据结构宏 ---------- */
#define MAP_SLOT(key_t, val_t) \
struct { \
key_t key; \
val_t val; \
char state; \
}
#define MAP(key_t, val_t) \
struct { \
usize size; \
usize cap; \
MAP_SLOT(key_t, val_t) * data; \
usize (*hash)(key_t); \
int (*cmp)(key_t, key_t); \
}
/* ---------- 操作宏 ---------- */
/** 初始化,必须提供哈希和比较函数 */
#define map_init(map, hash_fn, cmp_fn) \
do { \
(map).size = 0; \
(map).cap = 0; \
(map).data = nullptr; \
(map).hash = (hash_fn); \
(map).cmp = (cmp_fn); \
} while (0)
/** 释放内部数组 */
#define map_free(map) \
do { \
free((map).data); \
(map).data = nullptr; \
(map).size = (map).cap = 0; \
} while (0)
/** 遍历所有有效元素 */
#define map_for(map, idx) \
for (usize(idx) = 0; (idx) < (map).cap; ++(idx)) \
if ((map).data[(idx)].state == __MAP_SLOT_OCCUPIED)
/**
* 插入(若键已存在则更新值)
* 注意:扩容使用 realloc失败会 abort可自行修改错误处理
*/
#define map_put(map, _key, _val) \
do { \
/* 扩容 */ \
if ((map).cap == 0 || \
(map).size * 128 / (map).cap >= MAP_DEFAULT_LOAD_FACTOR) { \
usize new_cap = (map).cap == 0 ? 8 : (map).cap * 2; \
MAP_SLOT(MAP_TYPEOF((map).data->key), \
MAP_TYPEOF((map).data->val)) *new_data = \
calloc(new_cap, sizeof(*new_data)); \
if (!new_data) \
abort(); \
/* 重新插入旧元素 */ \
for (usize _i = 0; _i < (map).cap; ++_i) { \
if ((map).data[_i].state == __MAP_SLOT_OCCUPIED) { \
usize _h = (map).hash((map).data[_i].key) & (new_cap - 1); \
while (new_data[_h].state == __MAP_SLOT_OCCUPIED) \
_h = (_h + 1) & (new_cap - 1); \
new_data[_h].key = (map).data[_i].key; \
new_data[_h].val = (map).data[_i].val; \
new_data[_h].state = __MAP_SLOT_OCCUPIED; \
} \
} \
free((map).data); \
(map).data = (void *)new_data; \
(map).cap = new_cap; \
} \
/* 查找或插入 */ \
usize _mask = (map).cap - 1; \
usize _idx = (map).hash(_key) & _mask; \
usize _first_del = (usize) - 1; \
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
(map).cmp((map).data[_idx].key, _key) == 0) { \
(map).data[_idx].val = _val; \
break; \
} \
if ((map).data[_idx].state == __MAP_SLOT_DELETED && \
_first_del == (usize) - 1) \
_first_del = _idx; \
_idx = (_idx + 1) & _mask; \
} \
if ((map).data[_idx].state == __MAP_SLOT_EMPTY) { \
usize _target = (_first_del != (usize) - 1) ? _first_del : _idx; \
(map).data[_target].key = _key; \
(map).data[_target].val = _val; \
(map).data[_target].state = __MAP_SLOT_OCCUPIED; \
++(map).size; \
} \
} while (0)
/**
* 查询:若找到,*out_val 被赋值为对应值并返回 1否则返回 0
*/
#define map_get(map, _key, out_val) \
(({ \
int _found = 0; \
if ((map).cap > 0) { \
usize _mask = (map).cap - 1; \
usize _idx = (map).hash(_key) & _mask; \
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
(map).cmp((map).data[_idx].key, _key) == 0) { \
*out_val = (map).data[_idx].val; \
_found = 1; \
break; \
} \
_idx = (_idx + 1) & _mask; \
} \
} \
_found; \
}))
/**
* 删除指定键
*/
#define map_del(map, _key) \
do { \
if ((map).cap == 0) \
break; \
usize _mask = (map).cap - 1; \
usize _idx = (map).hash(_key) & _mask; \
while ((map).data[_idx].state != __MAP_SLOT_EMPTY) { \
if ((map).data[_idx].state == __MAP_SLOT_OCCUPIED && \
(map).cmp((map).data[_idx].key, _key) == 0) { \
(map).data[_idx].state = __MAP_SLOT_DELETED; \
--(map).size; \
break; \
} \
_idx = (_idx + 1) & _mask; \
} \
} while (0)
#endif /* __CORE_MAP_H__ */

258
stage0/include/core_vec.h Normal file
View File

@@ -0,0 +1,258 @@
/**
* @file vec.h
* @brief 动态数组Dynamic Array实现
*
* 提供类型安全的动态数组容器实现,支持自动扩容和基本操作
*/
#ifndef __CORE_VEC_H__
#define __CORE_VEC_H__
#define __CORE_VEC_USE_STD__
#ifndef __CORE_VEC_USE_STD__
#include "core_log.h"
#include "core_impl.h"
#include "core_type.h"
#define __vec_realloc realloc
#define __vec_free free
#define __vec_memcpy memcpy
#else
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#ifndef nullptr
#define nullptr NULL
#endif
typedef size_t usize;
#define __vec_realloc realloc
#define __vec_free free
#define __vec_memcpy memcpy
#ifndef LOG_FATAL
#include <stdio.h>
#define LOG_FATAL(...) \
do { \
printf(__VA_ARGS__); \
abort(); \
} while (0)
#endif
#ifndef Assert
#include <assert.h>
#define Assert(cond) assert(cond)
#endif
#endif
/** @defgroup vec_struct 数据结构定义 */
/**
* @def VEC(type)
* @brief 声明向量结构体
* @param type 存储的数据类型
*
* 生成包含size/cap/data三个字段的结构体定义
* - size: 当前元素数量
* - cap: 数组容量
* - data: 存储数组指针
* @example
* VEC(char) string; <=> char[dynamic_array] string;
* struct people { VEC(char) name; int age; VEC(struct people) children;
* };
*/
#define VEC(type) \
struct { \
usize size; \
usize cap; \
type *data; \
}
/** @defgroup vec_operations 动态数组操作宏 */
/**
* @def vec_init(vec)
* @brief 初始化向量结构体
* @param vec 要初始化的向量结构体变量
*
* @note 此宏不会分配内存,仅做零初始化
*/
#define vec_init(vec) \
do { \
(vec).size = 0, (vec).cap = 0, (vec).data = 0; \
} while (0)
#define vec_realloc(vec, new_cap) \
do { \
void *data = __vec_realloc((vec).data, new_cap * sizeof(*(vec).data)); \
if (!data) { \
LOG_FATAL("vector_push: realloc failed\n"); \
} \
(vec).cap = new_cap; \
(vec).data = data; \
} while (0)
#define vec_size(vec) ((vec).size)
#define vec_cap(vec) ((vec).cap)
#define vec_for(vec, idx) for (usize idx = 0; idx < vec_size(vec); idx += 1)
/**
* @def vec_push(vec, value)
* @brief 添加元素到向量末尾
* @param vec 目标向量结构体
* @param value 要添加的值(需匹配存储类型)
*
* @note 当容量不足时自动扩容为2倍初始容量为4
* @warning 内存分配失败时会触发LOG_FATAL
*/
#define vec_push(vec, value) \
do { \
if ((vec).size >= (vec).cap) { \
usize cap = (vec).cap ? (vec).cap * 2 : 4; \
vec_realloc(vec, cap); \
} \
Assert((vec).data != nullptr); \
(vec).data[(vec).size++] = value; \
} while (0)
/**
* @def vec_pop(vec)
* @brief 弹出最后一个元素
* @param vec 目标向量结构体
* @return 最后元素的引用
* @warning 需确保size > 0时使用
*/
#define vec_pop(vec) ((vec).data[--(vec).size])
/**
* @def vec_at(vec, idx)
* @brief 获取指定索引元素
* @param vec 目标向量结构体
* @param idx 元素索引0 <= idx < size
* @return 对应元素的引用
*/
#define vec_at(vec, idx) (((vec).data)[idx])
/**
* @def vec_idx(vec, ptr)
* @brief 获取元素指针对应的索引
* @param vec 目标向量结构体
* @param ptr 元素指针需在data数组范围内
* @return 元素索引值
*/
#define vec_idx(vec, ptr) ((ptr) - (vec).data)
/**
* @def vec_free(vec)
* @brief 释放向量内存
* @param vec 目标向量结构体
*
* @note 释放后需重新初始化才能再次使用
*/
#define vec_free(vec) \
do { \
if ((vec).data == nullptr) \
break; \
__vec_free((vec).data); \
(vec).data = nullptr; \
(vec).size = (vec).cap = 0; \
} while (0)
#define vec_unsafe_get_data(vec) ((vec).data)
#define vec_unsafe_from_buffer(vec, buffer, buffer_size) \
do { \
(vec).size = buffer_size; \
(vec).cap = (vec).size; \
(vec).data = buffer; \
} while (0)
#define vec_unsafe_from_static_array(vec, array) \
do { \
(vec).size = sizeof(array) / sizeof((array)[0]); \
(vec).cap = (vec).size; \
(vec).data = array; \
} while (0)
/**
* @def vec_sized_realloc(vec, elem_size, new_cap)
* @brief 内部宏:按 elem_size 重新分配内存
*/
#define vec_sized_realloc(vec, elem_size, new_cap) \
do { \
void *new_data = __vec_realloc((vec).data, (new_cap) * (elem_size)); \
if (!new_data) \
LOG_FATAL("vec_sized_realloc: failed\n"); \
(vec).data = new_data; \
(vec).cap = new_cap; \
} while (0)
/**
* @def vec_sized_push(vec, elem_size, src_ptr)
* @brief 添加一个元素(从 src_ptr 拷贝 elem_size 字节)
* @param vec VEC(type) 定义的向量变量type 可为 char 或 void
* @param elem_size 每个元素占用的字节数
* @param src_ptr 源数据的指针
* @param copy_size 要拷贝的字节数
*
* @note 使用前需确保 vec.data 类型与 src_ptr 无关,内部会按字节拷贝。
* 推荐声明时为 `VEC(char)` 或 `VEC(unsigned char)`。
*/
#define vec_sized_push(vec, elem_size, src_ptr, copy_size) \
do { \
if ((vec).size >= (vec).cap) { \
usize new_cap = (vec).cap ? (vec).cap * 2 : 4; \
vec_sized_realloc(vec, elem_size, new_cap); \
} \
char *slot = (char *)(vec).data + (vec).size * (elem_size); \
__vec_memcpy(slot, (src_ptr), (copy_size)); \
(vec).size++; \
} while (0)
/**
* @def vec_sized_at_ptr(vec, elem_size, idx)
* @brief 获取第 idx 个元素的指针void*
* @return 指向元素的指针,需转换为具体类型使用
*/
#define vec_sized_at_ptr(vec, elem_size, idx) \
((void *)((char *)(vec).data + (idx) * (elem_size)))
/**
* @def vec_sized_foreach(vec, elem_size, elem_ptr_var, block)
* @brief 遍历所有元素
* @param elem_ptr_var 循环内的变量名void* 类型)
* @param block 循环体语句块
*/
#define vec_sized_foreach(vec, elem_size, elem_ptr_var, block) \
do { \
for (usize __i = 0; __i < (vec).size; ++__i) { \
void *elem_ptr_var = vec_sized_at_ptr(vec, elem_size, __i); \
block; \
} \
} while (0)
/**
* @def vec_sized_pop(vec, elem_size)
* @brief 弹出最后一个元素(仅减小 size不返回数据
*/
#define vec_sized_pop(vec, elem_size) \
do { \
if ((vec).size == 0) \
LOG_FATAL("vec_sized_pop: empty\n"); \
(vec).size--; \
} while (0)
/**
* @def vec_sized_clear(vec)
* @brief 清空向量(重置 size = 0不释放内存
*/
#define vec_sized_clear(vec) ((vec).size = 0)
/**
* @def vec_sized_free(vec)
* @brief 释放向量内存(与原始 vec_free 相同,可复用)
* @note 注意:如果元素内部有堆资源,需在释放前自行遍历调用析构函数。
*/
#define vec_sized_free(vec) vec_free(vec)
#endif /* __CORE_VEC_H__ */

55
stage0/spl_cli.c Normal file
View File

@@ -0,0 +1,55 @@
/* spl_cli.c — SIR VM launcher
*
* Loads a compiled .sir binary and runs it via the SIR VM.
* Built-in syscalls are auto-registered via spl_syscall_register().
*
* Usage:
* spl_cli <file.sir> [entry_point]
*/
#include "spl_ir.h"
#include "spl_syscall.h"
#include "spl_vm.h"
#include <stdio.h>
int main(int argc, const char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: spl_cli <file.sir> [entry_point]\n");
return 1;
}
const char *path = argv[1];
const char *entry = argc >= 3 ? argv[2] : "main";
spl_prog_t prog;
if (spl_prog_load_from_file(path, &prog) != 0) {
fprintf(stderr, "spl_cli: cannot load '%s'\n", path);
return 1;
}
spl_syscall_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, argc, argv, NULL) != 0) {
fprintf(stderr, "vm: entry point '%s' not found\n", entry);
spl_prog_drop(&prog);
return 1;
}
int ret = spl_vm_run_until(&vm, 0);
spl_vm_drop(&vm);
spl_prog_drop(&prog);
if (ret < 0) {
fprintf(stderr, "spl_cli: VM error (exit_code=%d)\n", vm.exit_code);
return (int)vm.exit_code;
}
return (int)vm.exit_code;
}

76
stage0/spl_disasm.c Normal file
View File

@@ -0,0 +1,76 @@
/* spl_disasm.c — SIR bytecode disassembler
*
* Usage: spl_disasm <file.sir>
*/
#include "spl_ir.h"
#include <stdio.h>
int main(int argc, const char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: spl_disasm <file.sir>\n");
return 1;
}
spl_prog_t prog;
if (spl_prog_load_from_file(argv[1], &prog) != 0) {
fprintf(stderr, "spl_disasm: cannot load '%s'\n", argv[1]);
return 1;
}
printf(";; SPL bytecode dump: %s\n", argv[1]);
printf(";; ninsns=%zu nfuncs=%zu nnatives=%zu nstrs=%zu\n\n", vec_size(prog.insns),
vec_size(prog.funcs), vec_size(prog.natives), vec_size(prog.strtab));
if (vec_size(prog.funcs) > 0) {
printf(";; --- functions ---\n");
for (usize i = 0; i < vec_size(prog.funcs); i++) {
spl_func_t *f = &vec_at(prog.funcs, i);
printf(" %s nargs=%zu ninsns=%zu addr=%zu\n", f->name ? f->name : "(anon)", f->nargs,
f->ninsns, f->address);
}
printf("\n");
}
if (vec_size(prog.natives) > 0) {
printf(";; --- natives ---\n");
for (int i = 0; i < (int)vec_size(prog.natives); i++) {
spl_native_t *n = &vec_at(prog.natives, i);
printf(" %s\n", n->name ? n->name : "(anon)");
}
printf("\n");
}
if (vec_size(prog.strtab) > 0) {
printf(";; --- string table ---\n");
for (int i = 0; i < (int)vec_size(prog.strtab); i++) {
printf(" %d: \"%s\"\n", i, vec_at(prog.strtab, i));
}
printf("\n");
}
printf(";; --- instructions ---\n");
for (usize i = 0; i < vec_size(prog.insns); i++) {
spl_ins_t *ins = &vec_at(prog.insns, i);
printf("%4zd: %s", i, spl_opcode_name((spl_opcode_t)ins->opcode));
if (ins->type != SPL_VOID)
printf(" %s", spl_type_name((spl_type_t)ins->type));
printf(" %zd", ins->imm);
/* annotate jumps / calls */
if (ins->opcode == SPL_JMP || ins->opcode == SPL_BZ || ins->opcode == SPL_BNZ) {
long long target = (long long)i + 1 + (long long)ins->imm;
printf(" ; -> %zd", target);
} else if (ins->opcode == SPL_CALL) {
printf(" ; nargs=%zd, from stack", (long long)ins->imm);
} else if (ins->opcode == SPL_CALLI) {
printf(" ; indirect call");
} else if (ins->opcode == SPL_GADDR) {
printf(" ; gdata[%zd]", (long long)ins->imm);
}
printf("\n");
}
spl_prog_drop(&prog);
return 0;
}

478
stage0/spl_ir.c Normal file
View File

@@ -0,0 +1,478 @@
/* spl_ir.c — SIR binary serialization, deserialization, and utilities
*
* Binary format (all metadata fields are spl_val_t = uint64_t LE):
* [HEADER] magic(8) nfuncs(8) ninsns(8) nnatives(8) nstrs(8) ndata(8)
* [FUNCS] each: name_len(8) name(padded to 8) idx_of_strtab(8)
* nargs(8) ninsns(8) address(8)
* [INSTRS] each: opcode(2) type(2) imm(8) = 12 bytes
* [NATIVES] each: name_len(8) name(padded to 8) idx_of_strtab(8)
* [STRTAB] each: slen(8) str(slen bytes, padded to 8)
*/
#include "spl_ir.h"
void spl_prog_init(spl_prog_t *prog) {
if (!prog)
return;
vec_init(prog->insns);
vec_init(prog->funcs);
vec_init(prog->natives);
vec_init(prog->strtab);
vec_init(prog->gdata);
map_init(prog->symtab, MAP_HASH_STR, MAP_CMP_STR);
}
void spl_prog_drop(spl_prog_t *prog) {
if (!prog)
return;
vec_free(prog->insns);
/* free each func name */
vec_for(prog->funcs, i) { free(vec_at(prog->funcs, i).name); }
vec_free(prog->funcs);
/* free each native name */
vec_for(prog->natives, i) { free(vec_at(prog->natives, i).name); }
vec_free(prog->natives);
/* free each gdata entry */
vec_for(prog->gdata, i) { free(vec_at(prog->gdata, i).data); }
vec_free(prog->gdata);
/* free each strtab entry */
vec_for(prog->strtab, i) { free((void *)vec_at(prog->strtab, i)); }
vec_free(prog->strtab);
map_free(prog->symtab);
}
/* ---- LE read/write helpers ---- */
static inline spl_val_t rd64(const unsigned char **p) {
spl_val_t v = (spl_val_t)(*p)[0] | ((spl_val_t)(*p)[1] << 8) | ((spl_val_t)(*p)[2] << 16) |
((spl_val_t)(*p)[3] << 24) | ((spl_val_t)(*p)[4] << 32) |
((spl_val_t)(*p)[5] << 40) | ((spl_val_t)(*p)[6] << 48) |
((spl_val_t)(*p)[7] << 56);
*p += 8;
return v;
}
static inline void wr64(unsigned char **p, spl_val_t v) {
*(*p)++ = (unsigned char)(v);
*(*p)++ = (unsigned char)(v >> 8);
*(*p)++ = (unsigned char)(v >> 16);
*(*p)++ = (unsigned char)(v >> 24);
*(*p)++ = (unsigned char)(v >> 32);
*(*p)++ = (unsigned char)(v >> 40);
*(*p)++ = (unsigned char)(v >> 48);
*(*p)++ = (unsigned char)(v >> 56);
}
/* Round n up to next multiple of 8 */
#define ALIGN8(n) (((n) + 7) & ~7)
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog) {
FILE *f;
unsigned char *data;
long len;
const unsigned char *p;
spl_val_t nfuncs, ninsns, nnatives, nstrs, ndata;
if (!fname || !prog)
return -1;
f = fopen(fname, "rb");
if (!f)
return -1;
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 48) {
fclose(f);
return -1;
}
data = (unsigned char *)malloc((size_t)len);
if (!data) {
fclose(f);
return -1;
}
if (fread(data, 1, (size_t)len, f) != (size_t)len) {
free(data);
fclose(f);
return -1;
}
fclose(f);
p = data;
/* magic */
if (p[0] != 'S' || p[1] != 'P' || p[2] != 'L' || p[3] != 'B' || p[4] != 'I' || p[5] != 'N' ||
p[6] != '\0' || p[7] != '\0') {
free(data);
return -1;
}
p += 8;
spl_prog_init(prog);
/* header counts */
nfuncs = rd64(&p);
ninsns = rd64(&p);
nnatives = rd64(&p);
nstrs = rd64(&p);
ndata = rd64(&p);
/* ---- function table ---- */
for (spl_val_t i = 0; i < nfuncs; i++) {
spl_func_t func = {0};
spl_val_t nlen = rd64(&p);
usize pad = ALIGN8((usize)nlen) - (usize)nlen;
func.name = (char *)malloc((usize)nlen);
if (!func.name) {
free(data);
return -1;
}
memcpy(func.name, p, (usize)nlen);
p += (usize)nlen + pad;
func.idx_of_strtab = rd64(&p);
func.nargs = rd64(&p);
func.ninsns = rd64(&p);
func.address = rd64(&p);
vec_push(prog->funcs, func);
}
/* ---- instructions ---- */
for (spl_val_t i = 0; i < ninsns; i++) {
if ((size_t)(p - data) + 12 > (size_t)len) {
free(data);
return -1;
}
spl_ins_t ins;
ins.opcode = (uint16_t)p[0] | ((uint16_t)p[1] << 8);
ins.type = (uint16_t)p[2] | ((uint16_t)p[3] << 8);
p += 4;
ins.imm = rd64(&p);
vec_push(prog->insns, ins);
}
/* ---- native table ---- */
for (spl_val_t i = 0; i < nnatives; i++) {
spl_native_t nat = {0};
spl_val_t nlen = rd64(&p);
usize pad = ALIGN8((usize)nlen) - (usize)nlen;
nat.name = (char *)malloc((usize)nlen);
if (!nat.name) {
free(data);
return -1;
}
memcpy(nat.name, p, (usize)nlen);
p += (usize)nlen + pad;
nat.idx_of_strtab = rd64(&p);
nat.impl_fn = NULL; /* function pointer can't be serialised */
vec_push(prog->natives, nat);
}
/* ---- string table ---- */
for (spl_val_t i = 0; i < nstrs; i++) {
spl_val_t slen = rd64(&p);
usize pad = ALIGN8((usize)slen) - (usize)slen;
char *s = (char *)malloc((usize)slen + 1);
if (!s) {
free(data);
return -1;
}
memcpy(s, p, (usize)slen);
s[(usize)slen] = '\0';
p += (usize)slen + pad;
vec_push(prog->strtab, s);
}
/* ---- global data ---- */
for (spl_val_t i = 0; i < ndata; i++) {
spl_gdata_t entry;
entry.size = rd64(&p);
usize pad = ALIGN8(entry.size) - entry.size;
entry.data = (unsigned char *)malloc(entry.size);
if (!entry.data) {
free(data);
return -1;
}
memcpy(entry.data, p, entry.size);
p += entry.size + pad;
vec_push(prog->gdata, entry);
}
free(data);
return 0;
}
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog) {
unsigned char *buf, *p;
spl_val_t i;
size_t total;
spl_val_t nfuncs, ninsns, nnatives, nstrs, ndata;
usize nlen, pad;
if (!fname || !prog)
return -1;
nfuncs = vec_size(prog->funcs);
ninsns = vec_size(prog->insns);
nnatives = vec_size(prog->natives);
nstrs = vec_size(prog->strtab);
ndata = vec_size(prog->gdata);
/* Calculate total size */
total = 8 /* magic */
+ 8 + 8 + 8 + 8 + 8; /* 5 counts (nfuncs+ninsns+nnatives+nstrs+ndata) */
/* funcs */
for (i = 0; i < nfuncs; i++) {
nlen = strlen(vec_at(prog->funcs, i).name) + 1; /* include null */
total +=
8 + ALIGN8(nlen) + 8 + 8 + 8 + 8; /* nlen + name(pad) + idx + nargs + ninsns + addr */
}
/* insns */
total += ninsns * 12; /* opcode(2) + type(2) + imm(8) */
/* natives */
for (i = 0; i < nnatives; i++) {
nlen = strlen(vec_at(prog->natives, i).name) + 1;
total += 8 + ALIGN8(nlen) + 8; /* nlen + name(pad) + idx_of_strtab */
}
/* strtab */
for (i = 0; i < nstrs; i++) {
nlen = strlen(vec_at(prog->strtab, i));
total += 8 + ALIGN8(nlen); /* slen + str(pad) */
}
/* gdata */
for (i = 0; i < ndata; i++) {
usize dsize = vec_at(prog->gdata, i).size;
total += 8 + ALIGN8(dsize); /* dsize + data(pad) */
}
buf = (unsigned char *)malloc(total);
if (!buf)
return -1;
p = buf;
/* magic */
memcpy(p, "SPLBIN\0\0", 8);
p += 8;
/* counts */
wr64(&p, nfuncs);
wr64(&p, ninsns);
wr64(&p, nnatives);
wr64(&p, nstrs);
wr64(&p, ndata);
/* ---- function table ---- */
for (i = 0; i < nfuncs; i++) {
const char *name = vec_at(prog->funcs, i).name;
nlen = strlen(name) + 1;
pad = ALIGN8(nlen) - nlen;
wr64(&p, nlen);
memcpy(p, name, nlen);
p += nlen;
memset(p, 0, pad);
p += pad;
wr64(&p, vec_at(prog->funcs, i).idx_of_strtab);
wr64(&p, vec_at(prog->funcs, i).nargs);
wr64(&p, vec_at(prog->funcs, i).ninsns);
wr64(&p, vec_at(prog->funcs, i).address);
}
/* ---- instructions ---- */
for (i = 0; i < ninsns; i++) {
spl_ins_t *ins = &vec_at(prog->insns, i);
*p++ = (unsigned char)(ins->opcode);
*p++ = (unsigned char)(ins->opcode >> 8);
*p++ = (unsigned char)(ins->type);
*p++ = (unsigned char)(ins->type >> 8);
wr64(&p, ins->imm);
}
/* ---- native table ---- */
for (i = 0; i < nnatives; i++) {
const char *name = vec_at(prog->natives, i).name;
nlen = strlen(name) + 1;
pad = ALIGN8(nlen) - nlen;
wr64(&p, nlen);
memcpy(p, name, nlen);
p += nlen;
memset(p, 0, pad);
p += pad;
wr64(&p, vec_at(prog->natives, i).idx_of_strtab);
}
/* ---- string table ---- */
for (i = 0; i < nstrs; i++) {
const char *s = vec_at(prog->strtab, i);
nlen = strlen(s);
pad = ALIGN8(nlen) - nlen;
wr64(&p, nlen);
memcpy(p, s, nlen);
p += nlen;
memset(p, 0, pad);
p += pad;
}
/* ---- global data ---- */
for (i = 0; i < ndata; i++) {
spl_gdata_t *entry = &vec_at(prog->gdata, i);
pad = ALIGN8(entry->size) - entry->size;
wr64(&p, entry->size);
memcpy(p, entry->data, entry->size);
p += entry->size;
memset(p, 0, pad);
p += pad;
}
/* Write file */
FILE *f = fopen(fname, "wb");
if (!f) {
free(buf);
return -1;
}
fwrite(buf, 1, total, f);
fclose(f);
free(buf);
return 0;
}
int spl_prog_add_func(spl_prog_t *prog, spl_func_t *func) {
if (!prog || !func)
return 0;
vec_push(prog->funcs, *func);
return vec_size(prog->funcs);
}
int spl_prog_add_native(spl_prog_t *prog, spl_native_t *native) {
if (!prog || !native)
return 0;
vec_push(prog->natives, *native);
return vec_size(prog->natives);
}
int spl_prog_add_data(spl_prog_t *prog, void *ptr, usize size) {
spl_gdata_t entry;
if (!prog)
return 0;
entry.data = (unsigned char *)malloc(size);
if (!entry.data)
return 0;
memcpy(entry.data, ptr, size);
entry.size = size;
vec_push(prog->gdata, entry);
return vec_size(prog->gdata);
}
spl_val_t spl_prog_emit(spl_prog_t *prog, uint16_t opcode, uint16_t type, spl_val_t imm) {
spl_ins_t ins = {opcode, type, imm};
spl_val_t addr = vec_size(prog->insns);
vec_push(prog->insns, ins);
return addr;
}
int spl_prog_add_func_simple(spl_prog_t *prog, const char *name, spl_val_t nargs) {
spl_func_t func = {0};
if (!prog || !name)
return -1;
func.name = strdup(name);
func.idx_of_strtab = 0;
func.nargs = nargs;
func.ninsns = 0;
func.address = vec_size(prog->insns);
vec_push(prog->funcs, func);
return (int)vec_size(prog->funcs) - 1;
}
void spl_prog_end_func(spl_prog_t *prog, int func_idx) {
if (!prog || func_idx < 0 || func_idx >= (int)vec_size(prog->funcs))
return;
spl_func_t *func = &vec_at(prog->funcs, func_idx);
func->ninsns = vec_size(prog->insns) - func->address;
}
int spl_prog_add_str(spl_prog_t *prog, const char *str) {
if (!prog || !str)
return -1;
vec_push(prog->strtab, strdup(str));
return (int)vec_size(prog->strtab) - 1;
}
spl_func_t *spl_prog_get_func(spl_prog_t *prog, const char *name) {
if (!prog || !name)
return NULL;
vec_for(prog->funcs, i) {
const char *match_name = vec_at(prog->funcs, i).name;
if (match_name && strcmp(match_name, name) == 0) {
return &vec_at(prog->funcs, i);
}
}
return NULL;
}
spl_native_t *spl_prog_get_native(spl_prog_t *prog, const char *name) {
if (!prog || !name)
return NULL;
vec_for(prog->natives, i) {
const char *match_name = vec_at(prog->natives, i).name;
if (match_name && strcmp(match_name, name) == 0) {
return &vec_at(prog->natives, i);
}
}
return NULL;
}
const char *opcode_name[] = {
#define X(opcode, name, argc, pop, push, desc) [opcode] = name,
SPL_OPCODES(X)
#undef X
};
const char *spl_opcode_name(spl_opcode_t opcode) { return opcode_name[opcode]; }
const char *spl_type_name(spl_type_t type) {
switch (type) {
case SPL_VOID:
return "void";
case SPL_I8:
return "i8";
case SPL_U8:
return "u8";
case SPL_I16:
return "i16";
case SPL_U16:
return "u16";
case SPL_I32:
return "i32";
case SPL_U32:
return "u32";
case SPL_I64:
return "i64";
case SPL_U64:
return "u64";
case SPL_F32:
return "f32";
case SPL_F64:
return "f64";
case SPL_USIZE:
return "usize";
case SPL_ISIZE:
return "isize";
case SPL_PTR:
return "ptr";
default:
return "???";
}
}
void spl_ins_dump(spl_ins_t *ins, spl_val_t addr) {
printf("%4zu: %s", addr, spl_opcode_name((spl_opcode_t)ins->opcode));
if (ins->type != SPL_VOID)
printf(" %s", spl_type_name((spl_type_t)ins->type));
printf(" %zd:%zx", ins->imm, ins->imm);
printf("\n");
}

222
stage0/spl_ir.h Normal file
View File

@@ -0,0 +1,222 @@
/* spl_ir.h - SPL Intermediate Representation: instruction set and binary format
*/
#ifndef __SPL_IR_H__
#define __SPL_IR_H__
#include "include/core_map.h"
#include "include/core_vec.h"
#include <stddef.h>
#include <stdint.h>
typedef uintptr_t usize;
typedef intptr_t isize;
typedef enum {
SPL_VOID = 0,
SPL_I8,
SPL_U8,
SPL_I16,
SPL_U16,
SPL_I32,
SPL_U32,
SPL_I64,
SPL_U64,
SPL_ISIZE,
SPL_USIZE,
SPL_F32,
SPL_F64,
SPL_PTR,
SPL_TYPE_COUNT,
} spl_type_t;
/* clang-format off */
#define SPL_OPCODES(X) \
/* opcode, name, argc, pop, push, desc */ \
X(SPL_ERROR, "error", 0, 0, 0, "invalid opcode / internal error") \
/* 栈操作 */ \
X(SPL_PUSH, "push", 1, 0, 1, "push immediate") \
X(SPL_DUP, "dup", 0, 0, 1, "duplicate top of stack") \
X(SPL_DROP, "drop", 0, 1, 0, "discard top of stack") \
X(SPL_SWAP, "swap", 0, 2, 2, "swap top two elements") \
X(SPL_PICK, "pick", 1, 0, 1, "push copy of stack[imm] (0=TOS)") \
X(SPL_ROT, "rot", 0, 3, 3, "rotate top three (a b c -- b c a)") \
/* 算术 */ \
X(SPL_ADD, "add", 0, 2, 1, "integer addition") \
X(SPL_SUB, "sub", 0, 2, 1, "integer subtraction") \
X(SPL_MUL, "mul", 0, 2, 1, "integer multiplication") \
X(SPL_DIV_S, "div_s", 0, 2, 1, "signed division") \
X(SPL_DIV_U, "div_u", 0, 2, 1, "unsigned division") \
X(SPL_REM_S, "rem_s", 0, 2, 1, "signed remainder") \
X(SPL_REM_U, "rem_u", 0, 2, 1, "unsigned remainder") \
X(SPL_NEG, "neg", 0, 1, 1, "two's complement negation") \
/* 位运算 */ \
X(SPL_AND, "and", 0, 2, 1, "bitwise AND") \
X(SPL_OR, "or", 0, 2, 1, "bitwise OR") \
X(SPL_XOR, "xor", 0, 2, 1, "bitwise XOR") \
X(SPL_NOT, "not", 0, 1, 1, "bitwise NOT") \
X(SPL_SHL, "shl", 0, 2, 1, "left shift") \
X(SPL_SHR_U, "shr_u", 0, 2, 1, "logical right shift (zero-fill)") \
X(SPL_SHR_S, "shr_s", 0, 2, 1, "arithmetic right shift (sign-fill)") \
/* 比较 */ \
X(SPL_EQ, "eq", 0, 2, 1, "equal") \
X(SPL_NE, "ne", 0, 2, 1, "not equal") \
X(SPL_SLT, "slt", 0, 2, 1, "signed less than") \
X(SPL_SLE, "sle", 0, 2, 1, "signed less or equal") \
X(SPL_ULT, "ult", 0, 2, 1, "unsigned less than") \
X(SPL_ULE, "ule", 0, 2, 1, "unsigned less or equal") \
X(SPL_SGT, "sgt", 0, 2, 1, "signed greater than") \
X(SPL_SGE, "sge", 0, 2, 1, "signed greater or equal") \
X(SPL_UGT, "ugt", 0, 2, 1, "unsigned greater than") \
X(SPL_UGE, "uge", 0, 2, 1, "unsigned greater or equal") \
/* 类型转换 */ \
X(SPL_TRUNC, "trunc", 1, 1, 1, "truncate to low imm bits") \
X(SPL_SEXT, "sext", 1, 1, 1, "sign-extend from bit imm") \
X(SPL_ZEXT, "zext", 1, 1, 1, "zero-extend from bit imm") \
/* 控制流 */ \
X(SPL_JMP, "jmp", 1, 0, 0, "unconditional jump (offset isize)") \
X(SPL_BZ, "bz", 1, 1, 0, "pop; jump if zero (offset isize)") \
X(SPL_BNZ, "bnz", 1, 1, 0, "pop; jump if non-zero (offset isize)") \
X(SPL_HALT, "halt", 0, 0, 0, "stop execution") \
/* 函数调用 */ \
X(SPL_CALL, "call", 1, 0, 0, "call function pop function offset, call it") \
X(SPL_CALLI, "calli", 0, 1, 0, "indirect call: pop function address, call it") \
X(SPL_RET, "ret", 0, 0, 0, "return from function") \
/* 栈帧局部变量 */ \
X(SPL_ALLOC, "alloc", 1, 0, 0, "allocate imm zero-slots on stack") \
X(SPL_LADDR, "laddr", 1, 0, 1, "push address of local at fp + imm bytes") \
X(SPL_GADDR, "gaddr", 1, 0, 1, "push address of global at gp + imm") \
/* 间接内存访问 */ \
X(SPL_LOAD, "load", 0, 1, 1, "load sizeof(type)-bits zero-extended") \
X(SPL_STORE, "store", 0, 2, 0, "store low sizeof(type)-bits") \
/* 原生接口 */ \
X(SPL_NCALL, "ncall", 1, 0, 1, "call native function by index") \
X(SPL_NLIB, "nlib", 1, 0, 0, "dlopen library (name idx)") \
/* 调试 */ \
X(SPL_BK, "breakpoint", 0, 0, 0, "break point when exec will stop run") \
X(SPL_DBG, "dbg", 0, 0, 0, "print VM debug info (stack, backtrace, locals)")
/* clang-format on */
typedef enum {
#define X(opcode, name, argc, pop, push, desc) opcode,
SPL_OPCODES(X)
#undef X
} spl_opcode_t;
/*
* Binary format (all metadata fields spl_val_t LE):
* magic[8] = "SPLBIN\0\0"
* nfuncs, ninsns, nnatives, nstrs, ndata
* [func table] each: name_len, name(pad8), idx_of_strtab, nargs, ninsns, address
* [insns] each: opcode(2) type(2) imm(8) = 12 bytes
* [natives] each: name_len, name(pad8), idx_of_strtab
* [strtab] each: slen, str(pad8)
* [gdata] each: dsize(8), data(dsize bytes, padded to 8)
*
* === Calling Convention ===
*
* Before CALL:
* - args pushed left-to-right
* - target address pushed last
*
* CALL (imm = nargs):
* 1. pop target address
* 2. callstack[cp++] = {saved_fp, saved_ip, nargs}
* 3. fp = sp - nargs (fp points to arg0)
* 4. ip = target address
*
* ALLOC k:
* sp += k (slots zeroed; local[j] = stacks.data[fp + nargs + j])
*
* LADDR imm:
* push &stacks.data[fp + imm]
* (imm < nargs accesses args; imm >= nargs accesses locals)
*
* GADDR imm:
* push prog->gdata[imm] (pointer to global data blob)
*
* RET:
* 1. pop retval if non-void type
* 2. sp = fp
* 3. pop frame; fp = saved_fp, ip = saved_ip
* 4. push retval if non-void type
*
* CALLI (indirect call):
* - stack before: ..., arg0, ..., argN-1, nargs, func_addr
* 1. pop func_addr, then pop nargs
* 2. same as CALL steps 2-4
*/
#define SPL_BINFMT_MAGIC "SPLBIN\0\0"
// All SIR stack values are ptr-bit unsigned integers
typedef usize spl_val_t;
typedef struct spl_ins {
uint8_t opcode;
uint8_t type;
spl_val_t imm;
} spl_ins_t;
typedef VEC(spl_ins_t) spl_ins_vec_t;
typedef struct spl_func {
char *name;
spl_val_t idx_of_strtab;
spl_val_t nargs;
spl_val_t ninsns;
spl_val_t address;
} spl_func_t;
typedef VEC(spl_func_t) spl_func_vec_t;
/* Native function pointer type */
typedef spl_val_t (*spl_fn_t)(int nargs, spl_val_t *args);
typedef struct spl_native {
char *name;
spl_val_t idx_of_strtab;
spl_fn_t impl_fn;
} spl_native_t;
typedef VEC(spl_native_t) spl_native_vec_t;
typedef struct {
unsigned char *data;
usize size;
} spl_gdata_t;
typedef VEC(spl_gdata_t) spl_data_t;
typedef VEC(const char *) spl_strtab_t;
typedef MAP(const char *, const char *) spl_symtab_t;
/* Opaque handle for loaded program */
typedef struct spl_prog {
spl_ins_vec_t insns;
spl_func_vec_t funcs;
spl_native_vec_t natives;
spl_data_t gdata;
spl_strtab_t strtab;
spl_symtab_t symtab;
} spl_prog_t;
void spl_prog_init(spl_prog_t *prog);
void spl_prog_drop(spl_prog_t *prog);
int spl_prog_load_from_file(const char *fname, spl_prog_t *prog);
int spl_prog_store_to_file(const char *fname, spl_prog_t *prog);
int spl_prog_add_data(spl_prog_t *prog, void *ptr, usize size);
int spl_prog_add_func(spl_prog_t *prog, spl_func_t *func);
int spl_prog_add_native(spl_prog_t *prog, spl_native_t *native);
spl_func_t *spl_prog_get_func(spl_prog_t *prog, const char *name);
spl_native_t *spl_prog_get_native(spl_prog_t *prog, const char *name);
/* High-level program construction helpers */
spl_val_t spl_prog_emit(spl_prog_t *prog, uint16_t opcode, uint16_t type, spl_val_t imm);
int spl_prog_add_func_simple(spl_prog_t *prog, const char *name, spl_val_t nargs);
void spl_prog_end_func(spl_prog_t *prog, int func_idx);
int spl_prog_add_str(spl_prog_t *prog, const char *str);
/* Opcode name lookup for debugging/dumping */
const char *spl_opcode_name(spl_opcode_t opcode);
const char *spl_type_name(spl_type_t type);
void spl_ins_dump(spl_ins_t *ins, spl_val_t addr);
#endif /* __SPL_IR_H__ */

397
stage0/spl_syscall.c Normal file
View File

@@ -0,0 +1,397 @@
/* spl_syscall.c — built-in syscall implementations + registration
*
* All syscalls validate nargs, cast spl_val_t args to the expected C types,
* execute, and return the result as spl_val_t.
*
* Simple syscalls use the SYSCALL_N macros; complex ones are written
* manually but follow the same template.
*/
#include "spl_syscall.h"
#include "include/core_map.h"
#include "include/core_vec.h"
#include "spl_ir.h"
#include "spl_vm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* =================================================================
* Macro templates
*
* Each SYSCALL_N macro:
* 1. Validates nargs (prints error and returns -1 on mismatch)
* 2. Casts args[n] from spl_val_t to the specified C type via
* (type)(uintptr_t) — this handles both integer and pointer types
* 3. Evaluates the expression and returns the result as spl_val_t
* ================================================================= */
#define CHECK_NARGS(fname, expected) \
do { \
if (nargs != (expected)) { \
fprintf(stderr, fname ": expected " #expected " args, got %d\n", nargs); \
return -1; \
} \
} while (0)
#define SYSCALL_0(name, ret_expr) \
static spl_val_t name(int nargs, spl_val_t *args) { \
CHECK_NARGS(#name, 0); \
(void)args; \
return (spl_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_1(name, t1, ret_expr) \
static spl_val_t name(int nargs, spl_val_t *args) { \
CHECK_NARGS(#name, 1); \
t1 a1 = (t1)(uintptr_t)args[0]; \
return (spl_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_2(name, t1, t2, ret_expr) \
static spl_val_t name(int nargs, spl_val_t *args) { \
CHECK_NARGS(#name, 2); \
t1 a1 = (t1)(uintptr_t)args[0]; \
t2 a2 = (t2)(uintptr_t)args[1]; \
return (spl_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_3(name, t1, t2, t3, ret_expr) \
static spl_val_t name(int nargs, spl_val_t *args) { \
CHECK_NARGS(#name, 3); \
t1 a1 = (t1)(uintptr_t)args[0]; \
t2 a2 = (t2)(uintptr_t)args[1]; \
t3 a3 = (t3)(uintptr_t)args[2]; \
return (spl_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_4(name, t1, t2, t3, t4, ret_expr) \
static spl_val_t name(int nargs, spl_val_t *args) { \
CHECK_NARGS(#name, 4); \
t1 a1 = (t1)(uintptr_t)args[0]; \
t2 a2 = (t2)(uintptr_t)args[1]; \
t3 a3 = (t3)(uintptr_t)args[2]; \
t4 a4 = (t4)(uintptr_t)args[3]; \
return (spl_val_t)(uintptr_t)(ret_expr); \
}
/* =================================================================
* OS syscalls
* ================================================================= */
/* vm_exit — terminate process with the given exit code */
SYSCALL_1(vm_exit, int, (exit(a1), (spl_val_t)0))
/* vm_putchar — write a single character to stdout */
SYSCALL_1(vm_putchar, int, putchar(a1))
/* vm_getchar — read a single character from stdin */
SYSCALL_0(vm_getchar, getchar())
/* vm_putint — print an integer to stdout */
SYSCALL_1(vm_putint, int, (fprintf(stdout, "%d", a1), (spl_val_t)0))
/* vm_putstr — print a string to stdout (no trailing newline) */
SYSCALL_1(vm_putstr, const char *, (fputs(a1, stdout), (spl_val_t)0))
/* vm_fopen — open a file, returns FILE* as spl_val_t */
SYSCALL_2(vm_fopen, const char *, const char *, (uintptr_t)fopen(a1, a2))
/* vm_fclose — close a file, returns 0 on success */
SYSCALL_1(vm_fclose, FILE *, fclose(a1))
/* vm_fread — read from file, returns number of items read */
SYSCALL_4(vm_fread, void *, size_t, size_t, FILE *, fread(a1, a2, a3, a4))
/* vm_fwrite — write to file, returns number of items written */
SYSCALL_4(vm_fwrite, const void *, size_t, size_t, FILE *, fwrite(a1, a2, a3, a4))
/* vm_fsize — get file size in bytes */
static spl_val_t vm_fsize(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_fsize", 1);
FILE *f = (FILE *)(uintptr_t)args[0];
long cur = ftell(f);
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, cur, SEEK_SET);
return (spl_val_t)(uintptr_t)sz;
}
SYSCALL_0(vm_stdin, stdin)
SYSCALL_0(vm_stdout, stdout)
SYSCALL_0(vm_stderr, stderr)
/* vm_read_file — read entire file into a malloc'd, null-terminated buffer */
static spl_val_t vm_read_file(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_read_file", 1);
const char *path = (const char *)(uintptr_t)args[0];
if (path == nullptr) {
fprintf(stderr, "filepath can't be null");
return 1;
}
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "filepath %s can't be open", path);
return 1;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = (char *)malloc((size_t)sz + 1);
if (!buf) {
fclose(f);
return 0;
}
size_t nread = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[nread] = '\0';
return (spl_val_t)(uintptr_t)buf;
}
/* vm_alloc — allocate memory (malloc) */
SYSCALL_1(vm_alloc, size_t, (uintptr_t)malloc(a1))
/* vm_free — free memory */
SYSCALL_1(vm_free, void *, (free(a1), (spl_val_t)0))
/* vm_realloc — reallocate memory (realloc) */
SYSCALL_2(vm_realloc, void *, size_t, (uintptr_t)realloc(a1, a2))
/* vm_strlen — get string length */
SYSCALL_1(vm_strlen, const char *, strlen(a1))
/* vm_strcmp — compare two strings */
SYSCALL_2(vm_strcmp, const char *, const char *, strcmp(a1, a2))
/* vm_memcpy — copy memory, returns dst */
SYSCALL_3(vm_memcpy, void *, const void *, size_t, (memcpy(a1, a2, a3), (uintptr_t)a1))
static spl_val_t vm_printf(int nargs, spl_val_t *args) {
(void)args;
if (nargs <= 0) {
return 0;
}
const char *fmt = (const char *)args[0];
usize fmt_len = strlen(fmt);
typedef VEC(char) string_builder_t;
string_builder_t buffer;
vec_init(buffer);
if (nargs <= 1) {
printf("%s", fmt);
return nargs;
}
int arg_idx = 0;
char tmp_buf[32];
memset(tmp_buf, 0, sizeof(tmp_buf));
for (usize i = 0; i < fmt_len; ++i) {
if (fmt[i] != '%') {
vec_push(buffer, fmt[i]);
continue;
}
arg_idx += 1;
if (++i >= fmt_len) {
continue;
}
switch (fmt[i]) {
case 'd':
snprintf(tmp_buf, sizeof(tmp_buf), "%zd", args[arg_idx]);
for (usize j = 0; j < strlen(tmp_buf); ++j) {
vec_push(buffer, tmp_buf[j]);
}
break;
case 'c':
vec_push(buffer, (char)args[arg_idx]);
break;
case 's':
for (usize j = 0; j < strlen((const char *)args[arg_idx]); ++j) {
vec_push(buffer, ((const char *)args[arg_idx])[j]);
}
break;
default:
continue;
}
}
vec_push(buffer, '\0');
printf("%s", buffer.data);
vec_free(buffer);
return nargs;
}
/* =================================================================
* VM syscalls (for comptime — compile-time code execution)
*
* These let SPL code create isolated sub-VMs, load compiled .sir
* programs into them, push arguments, call functions, and run.
* ================================================================= */
/* vm_new — create a new sub-VM instance, returns spl_vm_t* */
static spl_val_t vm_new(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_new", 0);
(void)args;
spl_vm_t *vm = (spl_vm_t *)malloc(sizeof(spl_vm_t));
if (!vm)
return 0;
spl_vm_init(vm);
return (spl_val_t)(uintptr_t)vm;
}
/* vm_drop — destroy a sub-VM and its loaded program */
static spl_val_t vm_drop(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_drop", 1);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
if (!vm)
return 0;
if (vm->prog) {
spl_prog_drop(vm->prog);
free(vm->prog);
vm->prog = NULL;
}
spl_vm_drop(vm);
free(vm);
return 0;
}
/* vm_load — load a .sir file, register syscalls, return spl_prog_t* */
static spl_val_t vm_load(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_load", 2);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
const char *path = (const char *)(uintptr_t)args[1];
if (!vm || !path)
return -1;
spl_prog_t *prog = (spl_prog_t *)malloc(sizeof(spl_prog_t));
if (!prog)
return -1;
if (spl_prog_load_from_file(path, prog) != 0) {
free(prog);
return -1;
}
spl_syscall_register(prog);
if (spl_vm_load_prog(vm, prog) != 0) {
spl_prog_drop(prog);
free(prog);
return -1;
}
return (spl_val_t)(uintptr_t)prog;
}
/* vm_push — push a value onto the sub-VM's stack (for passing arguments) */
static spl_val_t vm_push(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_push", 2);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
spl_val_t val = args[1];
if (!vm)
return -1;
if (vm->sp >= vm->config.max_stack_depth) {
fprintf(stderr, "vm_push: stack overflow\n");
return -1;
}
vm->stacks.data[vm->sp++] = val;
return 0;
}
/* vm_call — call a function by name with pre-pushed arguments
*
* Args (3): vm, function_name, nargs
* The arguments should already be on the sub-VM's stack via vm_push.
*
* Mirrors the CALL instruction's frame setup:
* - Pushes a sentinel frame (saved_ip=-1) if cp==0 so RET halts properly
* - Pushes the actual call frame
* - Sets fp = sp - nargs (so arg0 = data[fp], arg1 = data[fp+1], ...)
* - Sets ip to the function address
*/
static spl_val_t vm_call(int nargs, spl_val_t *args) {
CHECK_NARGS("vm_call", 3);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
const char *name = (const char *)(uintptr_t)args[1];
spl_val_t call_nargs = args[2];
if (!vm || !name)
return -1;
spl_func_t *func = spl_prog_get_func(vm->prog, name);
if (!func) {
fprintf(stderr, "vm_call: function '%s' not found\n", name);
return -1;
}
if (vm->cp >= vm->config.max_call_depth - 1) {
fprintf(stderr, "vm_call: call stack overflow\n");
return -1;
}
/* Sentinel frame so the entry function's RET halts cleanly */
if (vm->cp == 0) {
vm->frames.data[vm->cp].saved_fp = 0;
vm->frames.data[vm->cp].saved_ip = (uintptr_t)-1;
vm->frames.data[vm->cp].nargs = 0;
vm->cp++;
}
/* Actual call frame (same logic as the CALL instruction) */
vm->frames.data[vm->cp].saved_fp = vm->fp;
vm->frames.data[vm->cp].saved_ip = vm->ip;
vm->frames.data[vm->cp].nargs = call_nargs;
vm->cp++;
vm->fp = vm->sp - call_nargs;
vm->ip = (uintptr_t)func->address;
return 0;
}
/* vm_run — run a sub-VM to completion, returns exit_code */
SYSCALL_1(vm_run, spl_vm_t *, spl_vm_run_until(a1, 0))
/* =================================================================
* Registration
*
* Called after loading a .sir file. Matches native declarations in
* prog->natives against known syscall names and hooks up impl_fn.
* ================================================================= */
void spl_syscall_register(spl_prog_t *prog) {
static spl_native_t table[] = {
/* OS operations */
{"vm_exit", 0, vm_exit},
{"vm_putchar", 0, vm_putchar},
{"vm_getchar", 0, vm_getchar},
{"vm_putint", 0, vm_putint},
{"vm_putstr", 0, vm_putstr},
{"vm_fopen", 0, vm_fopen},
{"vm_fclose", 0, vm_fclose},
{"vm_fread", 0, vm_fread},
{"vm_fwrite", 0, vm_fwrite},
{"vm_fsize", 0, vm_fsize},
{"vm_stdin", 0, vm_stdin},
{"vm_stdout", 0, vm_stdout},
{"vm_stderr", 0, vm_stderr},
{"vm_read_file", 0, vm_read_file},
{"vm_alloc", 0, vm_alloc},
{"vm_free", 0, vm_free},
{"vm_realloc", 0, vm_realloc},
{"vm_strlen", 0, vm_strlen},
{"vm_strcmp", 0, vm_strcmp},
{"vm_memcpy", 0, vm_memcpy},
{"vm_printf", 0, vm_printf},
/* VM operations (comptime) */
{"vm_new", 0, vm_new},
{"vm_drop", 0, vm_drop},
{"vm_load", 0, vm_load},
{"vm_push", 0, vm_push},
{"vm_call", 0, vm_call},
{"vm_run", 0, vm_run},
};
int n = (int)(sizeof(table) / sizeof(table[0]));
if (!prog)
return;
vec_for(prog->natives, i) {
spl_native_t *nat = &vec_at(prog->natives, i);
if (!nat->name || nat->impl_fn)
continue;
for (int j = 0; j < n; j++) {
if (strcmp(nat->name, table[j].name) == 0) {
nat->impl_fn = table[j].impl_fn;
break;
}
}
}
}

22
stage0/spl_syscall.h Normal file
View File

@@ -0,0 +1,22 @@
/* spl_syscall.h — built-in syscall layer for SIR VM
*
* Provides I/O native functions (putchar, getchar, file ops, etc.)
* that compiled SPL programs can call via NCALL.
*
* spl_syscall_register() must be called AFTER spl_prog_load_from_file()
* and BEFORE spl_vm_run_until(). It matches native declarations in the
* loaded program against known syscall names and hooks up the C
* implementations.
*/
#ifndef __SPL_SYSCALL_H__
#define __SPL_SYSCALL_H__
#include "spl_ir.h"
/* Register all known built-in syscalls into prog->natives[].
* Entries whose name matches a known syscall get their impl_fn set;
* unmatched entries remain NULL (will error at runtime if called). */
void spl_syscall_register(spl_prog_t *prog);
#endif /* __SPL_SYSCALL_H__ */

1115
stage0/spl_vm.c Normal file

File diff suppressed because it is too large Load Diff

67
stage0/spl_vm.h Normal file
View File

@@ -0,0 +1,67 @@
/* spl_vm.h — SIR interpreter */
#ifndef __SPL_VM_H__
#define __SPL_VM_H__
#include "include/core_vec.h"
#include "spl_ir.h"
#include <stdint.h>
#define SPL_STACK_CANARY ((spl_val_t)0xDEADBEEFCAFEBABEull)
typedef struct {
uintptr_t saved_sp;
uintptr_t saved_fp;
uintptr_t saved_ip;
spl_val_t nargs;
} spl_callframe_t;
typedef VEC(spl_val_t) spl_stack_vec_t;
typedef VEC(spl_callframe_t) spl_frame_vec_t;
typedef struct {
spl_stack_vec_t stacks;
spl_frame_vec_t frames;
uintptr_t gp; // global pointer
uintptr_t sp; // stack pointer
uintptr_t fp; // frame pointer
uintptr_t cp; // call pointer
uintptr_t ip; // instr pointer
int exit_code;
int trace; /* non-zero to print each instruction */
int debug; /* non-zero to enable canary checks */
spl_prog_t *prog;
char error_msg[1024];
struct {
uintptr_t max_stack_depth;
uintptr_t max_call_depth;
} config;
} spl_vm_t;
/* Initialize VM with default sizes (SPL_DEFAULT_STACK_SIZE /
* SPL_DEFAULT_CALL_DEPTH) */
void spl_vm_init(spl_vm_t *vm);
/* Initialize VM with custom sizes (pass 0 to use defaults) */
void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth);
/* Free dynamically allocated memory in VM */
void spl_vm_drop(spl_vm_t *vm);
int spl_vm_load_prog(spl_vm_t *vm, spl_prog_t *prog);
int spl_vm_prepare(spl_vm_t *vm, const char *entry, int argc, const char **argv, const char **envp);
/* Enable/disable instruction-level tracing */
void spl_vm_set_trace(spl_vm_t *vm, int enabled);
/* Enable/disable debug mode (stack canary protection) */
void spl_vm_set_debug(spl_vm_t *vm, int enabled);
int spl_vm_run_once(spl_vm_t *vm);
int spl_vm_run_until(spl_vm_t *vm, size_t step);
void spl_vm_dump_instr(spl_vm_t *vm, spl_val_t ip);
void spl_vm_stackdump(spl_vm_t *vm, spl_val_t sp);
int spl_vm_backtrace(spl_vm_t *vm, spl_val_t fp);
#endif /* __SPL_VM_H__ */

876
stage0/test_spl_vm.c Normal file
View File

@@ -0,0 +1,876 @@
/* test_spl_vm.c unit tests for SPL VM using acutest.h
*
* Constructs SIR binaries, writes to temp files, loads via
* spl_prog_load_from_file, and runs via the spl_vm_t API.
*/
#include "include/acutest.h"
#include "spl_ir.h"
#include "spl_vm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* ================================================================
* Binary-writing helpers (mirrors spl_ir.c internal format)
* ================================================================ */
/* Write spl_val_t (8 LE bytes), advance pointer */
#define LE64(p, v) \
do { \
unsigned char *_p = (p); \
spl_val_t _v = (spl_val_t)(v); \
*_p++ = (unsigned char)(_v); \
*_p++ = (unsigned char)(_v >> 8); \
*_p++ = (unsigned char)(_v >> 16); \
*_p++ = (unsigned char)(_v >> 24); \
*_p++ = (unsigned char)(_v >> 32); \
*_p++ = (unsigned char)(_v >> 40); \
*_p++ = (unsigned char)(_v >> 48); \
*_p++ = (unsigned char)(_v >> 56); \
(p) = _p; \
} while (0)
/* Convenience: build spl_ins_t from raw fields */
static spl_ins_t ins(uint16_t op, uint16_t type, spl_val_t imm) {
spl_ins_t x;
x.opcode = op;
x.type = type;
x.imm = imm;
return x;
}
/* Build a single-function SIR binary in malloc'd memory.
* Returns buffer that must be freed by caller. */
static unsigned char *build_binary(const char *fname, spl_val_t nargs, const spl_ins_t *instns,
int ninsns, size_t *out_len) {
size_t nlen = strlen(fname) + 1;
size_t npad = ((nlen + 7) / 8) * 8 - nlen;
size_t sz = 8 + 8 + 8 + 8 + 8 + 8 /* magic + 5 counts */
+ 8 + nlen + npad + 8 + 8 + 8 + 8 /* func entry */
+ (size_t)ninsns * 12; /* instructions */
unsigned char *buf = (unsigned char *)malloc(sz);
unsigned char *p = buf;
memcpy(p, "SPLBIN\0\0", 8);
p += 8; /* magic */
LE64(p, 1); /* nfuncs */
LE64(p, (spl_val_t)ninsns); /* ninsns */
LE64(p, 0); /* nnatives */
LE64(p, 0); /* nstrs */
LE64(p, 0); /* ndata */
/* func entry */
LE64(p, (spl_val_t)nlen); /* name_len */
memcpy(p, fname, nlen);
p += nlen;
memset(p, 0, npad);
p += npad;
LE64(p, 0); /* idx_of_strtab */
LE64(p, nargs);
LE64(p, (spl_val_t)ninsns);
LE64(p, 0); /* address = 0 */
for (int i = 0; i < ninsns; i++) {
*p++ = (unsigned char)(instns[i].opcode);
*p++ = (unsigned char)(instns[i].opcode >> 8);
*p++ = (unsigned char)(instns[i].type);
*p++ = (unsigned char)(instns[i].type >> 8);
LE64(p, instns[i].imm);
}
*out_len = sz;
return buf;
}
/* ================================================================
* VM test helpers (file-based prog load)
* ================================================================ */
static const char *TMPFILE = "test_spl_vm_tmp.bin";
/* Write binary buffer to temp file */
static int write_temp(const unsigned char *bin, size_t len) {
FILE *f = fopen(TMPFILE, "wb");
if (!f)
return -1;
fwrite(bin, 1, len, f);
fclose(f);
return 0;
}
/* Run a single-function program, return exit code (or -1 on failure). */
static int run(const spl_ins_t *instns, int ninsns) {
size_t len;
unsigned char *bin = build_binary("main", 0, instns, ninsns, &len);
spl_prog_t prog;
spl_vm_t vm;
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
if (spl_vm_load_prog(&vm, &prog) == 0) {
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1) /* halted normally */
rc = (int)vm.exit_code;
else if (ret == 0) /* still running (shouldn't happen with halt) */
rc = -2;
}
}
}
spl_vm_drop(&vm);
remove(TMPFILE);
free(bin);
return rc;
}
/* Run with native functions registered. */
static int run_with_natives(const spl_ins_t *instns, int ninsns, spl_native_t *natives,
int nnatives) {
size_t len;
unsigned char *bin = build_binary("main", 0, instns, ninsns, &len);
spl_prog_t prog;
spl_vm_t vm;
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
for (int i = 0; i < nnatives; i++)
spl_prog_add_native(&prog, &natives[i]);
if (spl_vm_load_prog(&vm, &prog) == 0) {
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1)
rc = (int)vm.exit_code;
else if (ret == 0)
rc = -2;
}
}
}
spl_vm_drop(&vm);
remove(TMPFILE);
free(bin);
return rc;
}
/* ================================================================
* Native function for NCALL tests
* ================================================================ */
static spl_val_t native_add_impl(int nargs, spl_val_t *args) {
return (nargs >= 2) ? args[0] + args[1] : 0;
}
/* ================================================================
* Stack tests
* ================================================================ */
void test_push_imm(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 2) == 42);
}
void test_dup(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 99), ins(SPL_DUP, SPL_VOID, 0),
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 198);
}
void test_drop(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_PUSH, SPL_I32, 2),
ins(SPL_DROP, SPL_VOID, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_swap(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_PUSH, SPL_I32, 2),
ins(SPL_SWAP, SPL_VOID, 0), ins(SPL_DROP, SPL_VOID, 0),
ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 5) == 2);
}
void test_pick(void) {
/* push 10, 20, 30, pick 1 -> copy 20, add -> 50 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_I32, 30), ins(SPL_PICK, SPL_VOID, 1),
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 6) == 50);
}
/* ================================================================
* Arithmetic tests
* ================================================================ */
void test_add(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 2), ins(SPL_PUSH, SPL_I32, 3), ins(SPL_ADD, SPL_I32, 0),
ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 5);
}
void test_sub(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 3),
ins(SPL_SUB, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 7);
}
void test_mul(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 6), ins(SPL_PUSH, SPL_I32, 7), ins(SPL_MUL, SPL_I32, 0),
ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 42);
}
void test_div(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 100), ins(SPL_PUSH, SPL_I32, 3),
ins(SPL_DIV_S, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 33);
}
void test_rem(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 100), ins(SPL_PUSH, SPL_I32, 3),
ins(SPL_REM_S, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_neg(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_NEG, SPL_I32, 0),
ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 3) == -42);
}
void test_i64_arith(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I64, 42), ins(SPL_RET, SPL_I64, 0)};
TEST_CHECK(run(p, 2) == 42);
}
/* ================================================================
* Bitwise tests
* ================================================================ */
void test_and(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFF00), ins(SPL_PUSH, SPL_U32, 0x0FF0),
ins(SPL_AND, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 0x0F00);
}
void test_or(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFF00), ins(SPL_PUSH, SPL_U32, 0x00FF),
ins(SPL_OR, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 0xFFFF);
}
void test_xor(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFF), ins(SPL_PUSH, SPL_U32, 0x0FF0),
ins(SPL_XOR, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 0xF00F);
}
void test_not(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFF0000), ins(SPL_NOT, SPL_U32, 0),
ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 3) == (unsigned int)0x0000FFFF);
}
void test_shl(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 1), ins(SPL_PUSH, SPL_U32, 10),
ins(SPL_SHL, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 1024);
}
void test_shr(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 1024), ins(SPL_PUSH, SPL_U32, 10),
ins(SPL_SHR_U, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_shr_s(void) {
/* arithmetic right shift: -1024 >> 5 sign-extends */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, (spl_val_t)(int32_t)-1024), ins(SPL_PUSH, SPL_U32, 5),
ins(SPL_SHR_S, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == -32);
}
/* ================================================================
* Comparison tests
* ================================================================ */
void test_eq(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_PUSH, SPL_I32, 42),
ins(SPL_EQ, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_neq(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 42), ins(SPL_PUSH, SPL_I32, 99),
ins(SPL_NE, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_lt(void) {
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_SLT, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_gt_signed(void) {
/* -1 > 1 should be 0 (false) for signed compare */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_I32, 1),
ins(SPL_SGT, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 0);
}
void test_sle(void) {
/* -1 <= 1 -> true (1) for signed */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_I32, 1),
ins(SPL_SLE, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_sge(void) {
/* -1 >= 1 -> false (0) for signed */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_I32, 1),
ins(SPL_SGE, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 4) == 0);
}
void test_ult(void) {
/* 0xFFFFFFFF < 1 -> false (0) for unsigned */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 1),
ins(SPL_ULT, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 0);
}
void test_ule(void) {
/* 0xFFFFFFFF <= 0xFFFFFFFF -> true (1) */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF),
ins(SPL_ULE, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_ugt(void) {
/* 0xFFFFFFFF > 1 -> true (1) for unsigned */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 1),
ins(SPL_UGT, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
void test_uge(void) {
/* 0xFFFFFFFF >= 1 -> true (1) */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFFFFFF), ins(SPL_PUSH, SPL_U32, 1),
ins(SPL_UGE, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
/* ================================================================
* Control flow tests (relative offset)
* ================================================================ */
void test_jmp(void) {
/* push 1, jmp +3 (skip next 2 insns), push 2 (skipped), push 3, add, ret -> 4 */
/* jmp at ip=1, after fetch ip=2, target ip=3 (push 3) => offset = 1 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_JMP, SPL_VOID, 1),
ins(SPL_PUSH, SPL_I32, 2), ins(SPL_PUSH, SPL_I32, 3),
ins(SPL_ADD, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 6) == 4);
}
void test_bz_bnz(void) {
/* push 0, bz +2 (skip to ret with 1) -> return 1 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0), ins(SPL_BZ, SPL_VOID, 2),
ins(SPL_PUSH, SPL_I32, 99), ins(SPL_RET, SPL_I32, 0),
ins(SPL_PUSH, SPL_I32, 1), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 6) == 1);
/* push 1, bnz +2 (skip to ret with 42) -> return 42 */
spl_ins_t q[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_BNZ, SPL_VOID, 2),
ins(SPL_PUSH, SPL_I32, 99), ins(SPL_RET, SPL_I32, 0),
ins(SPL_PUSH, SPL_I32, 42), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(q, 6) == 42);
}
/* ================================================================
* Memory tests (ALLOC, LADDR, LD, ST)
* ================================================================ */
void test_alloc_laddr_st64_ld64(void) {
/* alloc 1 local, laddr + st64 42, laddr + ld64 back -> 42 */
spl_ins_t p[] = {
ins(SPL_ALLOC, SPL_VOID, 1), /* 0: alloc 1 local */
ins(SPL_LADDR, SPL_VOID, 0), /* 1: addr of local[0] */
ins(SPL_PUSH, SPL_I64, 42), /* 2: value */
ins(SPL_STORE, SPL_I64, 0), /* 3: store */
ins(SPL_LADDR, SPL_VOID, 0), /* 4: addr of local[0] */
ins(SPL_LOAD, SPL_I64, 0), /* 5: load */
ins(SPL_RET, SPL_I64, 0) /* 6: ret */
};
TEST_CHECK(run(p, 7) == 42);
}
void test_alloc_zeroed(void) {
/* alloc 1 local, laddr + ld64 -> should be 0 (zero-initialized) */
spl_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 1), ins(SPL_LADDR, SPL_VOID, 0),
ins(SPL_LOAD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
TEST_CHECK(run(p, 4) == 0);
}
/* ================================================================
* Indirect memory load/store tests (stack-based, no heap)
* ================================================================ */
void test_ld_st64(void) {
/* alloc 8 slots, st64 42, ld64 back -> 42 */
spl_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 42),
ins(SPL_STORE, SPL_I64, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_RET, SPL_I64, 0)};
TEST_CHECK(run(p, 7) == 42);
}
void test_ld_st32(void) {
spl_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 0xAABBCCDD),
ins(SPL_STORE, SPL_U32, 0), ins(SPL_LOAD, SPL_U32, 0),
ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 7) == (int)0xAABBCCDD);
}
void test_ld_st16(void) {
spl_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 0xBEEF),
ins(SPL_STORE, SPL_U16, 0), ins(SPL_LOAD, SPL_U16, 0),
ins(SPL_RET, SPL_U16, 0)};
TEST_CHECK(run(p, 7) == 0xBEEF);
}
void test_ld_st8(void) {
spl_ins_t p[] = {ins(SPL_ALLOC, SPL_VOID, 8), ins(SPL_LADDR, SPL_VOID, 0),
ins(SPL_DUP, SPL_VOID, 0), ins(SPL_PUSH, SPL_I64, 0xAB),
ins(SPL_STORE, SPL_U8, 0), ins(SPL_LOAD, SPL_U8, 0),
ins(SPL_RET, SPL_U8, 0)};
TEST_CHECK(run(p, 7) == 0xAB);
}
/* ================================================================
* Function call tests (relative offset CALL)
* ================================================================ */
void test_call_add(void) {
/*
* add(a, b) address 0, 6 insns, 2 args
* main() address 6, 5 insns, 0 args
*
* add (ip=0):
* laddr 0 ip=0 arg0 addr
* ld64 ip=1 load arg0
* laddr 1 ip=2 arg1 addr
* ld64 ip=3 load arg1
* add i64 ip=4
* ret i64 ip=5
*
* main (ip=6):
* push 10 ip=6
* push 20 ip=7
* push 0 ip=8 target address = add at 0
* call 2 ip=9 nargs=2
* ret i64 ip=10
*/
spl_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
spl_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_CALL, SPL_VOID, 2),
ins(SPL_RET, SPL_I64, 0)};
int nadd = (int)(sizeof(add_insns) / sizeof(add_insns[0]));
int nmain = (int)(sizeof(main_insns) / sizeof(main_insns[0]));
int ninsns_t = nadd + nmain;
size_t nlen0 = strlen("add") + 1;
size_t npad0 = ((nlen0 + 7) / 8) * 8 - nlen0;
size_t nlen1 = strlen("main") + 1;
size_t npad1 = ((nlen1 + 7) / 8) * 8 - nlen1;
size_t sz = 8 + 8 + 8 + 8 + 8 + 8 /* magic + 5 counts */
+ 8 + nlen0 + npad0 + 8 + 8 + 8 + 8 /* func 0 */
+ 8 + nlen1 + npad1 + 8 + 8 + 8 + 8 /* func 1 */
+ (size_t)ninsns_t * 12;
unsigned char *bin = (unsigned char *)malloc(sz);
unsigned char *p = bin;
memcpy(p, "SPLBIN\0\0", 8);
p += 8;
LE64(p, 2);
LE64(p, (spl_val_t)ninsns_t);
LE64(p, 0);
LE64(p, 0);
LE64(p, 0);
/* func[0]: "add" */
LE64(p, (spl_val_t)nlen0);
memcpy(p, "add", nlen0);
p += nlen0;
memset(p, 0, npad0);
p += npad0;
LE64(p, 0);
LE64(p, 2);
LE64(p, (spl_val_t)nadd);
LE64(p, 0);
/* func[1]: "main" */
LE64(p, (spl_val_t)nlen1);
memcpy(p, "main", nlen1);
p += nlen1;
memset(p, 0, npad1);
p += npad1;
LE64(p, 0);
LE64(p, 0);
LE64(p, (spl_val_t)nmain);
LE64(p, (spl_val_t)nadd); /* address */
for (int i = 0; i < nadd; i++) {
*p++ = (unsigned char)(add_insns[i].opcode);
*p++ = (unsigned char)(add_insns[i].opcode >> 8);
*p++ = (unsigned char)(add_insns[i].type);
*p++ = (unsigned char)(add_insns[i].type >> 8);
LE64(p, add_insns[i].imm);
}
for (int i = 0; i < nmain; i++) {
*p++ = (unsigned char)(main_insns[i].opcode);
*p++ = (unsigned char)(main_insns[i].opcode >> 8);
*p++ = (unsigned char)(main_insns[i].type);
*p++ = (unsigned char)(main_insns[i].type >> 8);
LE64(p, main_insns[i].imm);
}
spl_prog_t prog;
spl_vm_t vm;
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, sz) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
spl_vm_load_prog(&vm, &prog);
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1)
rc = (int)vm.exit_code;
}
spl_vm_drop(&vm);
}
remove(TMPFILE);
free(bin);
TEST_CHECK(rc == 30);
}
/* ================================================================
* CALLI test
* ================================================================ */
void test_calli(void) {
/*
* add (ip=0, 2 args):
* laddr 0 ip=0
* ld64 ip=1
* laddr 1 ip=2
* ld64 ip=3
* add i64 ip=4
* ret i64 ip=5
*
* main (ip=6, 0 args):
* push 10 ip=6
* push 20 ip=7
* push 2 ip=8 (nargs)
* push 0 ip=9 (address of add)
* calli ip=10
* ret i64 ip=11
*/
spl_ins_t add_insns[] = {ins(SPL_LADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_LADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_I64, 0),
ins(SPL_ADD, SPL_I64, 0), ins(SPL_RET, SPL_I64, 0)};
spl_ins_t main_insns[] = {ins(SPL_PUSH, SPL_I32, 10), ins(SPL_PUSH, SPL_I32, 20),
ins(SPL_PUSH, SPL_VOID, 2), ins(SPL_PUSH, SPL_VOID, 0),
ins(SPL_CALLI, SPL_VOID, 0), ins(SPL_RET, SPL_I64, 0)};
int ninsns_t = 6 + 6;
size_t nlen0 = strlen("add") + 1;
size_t npad0 = ((nlen0 + 7) / 8) * 8 - nlen0;
size_t nlen1 = strlen("main") + 1;
size_t npad1 = ((nlen1 + 7) / 8) * 8 - nlen1;
size_t sz = 8 + 8 + 8 + 8 + 8 + 8 + 8 + nlen0 + npad0 + 8 + 8 + 8 + 8 + 8 + nlen1 + npad1 + 8 +
8 + 8 + 8 + (size_t)ninsns_t * 12;
unsigned char *bin = (unsigned char *)malloc(sz);
unsigned char *p = bin;
memcpy(p, "SPLBIN\0\0", 8);
p += 8;
LE64(p, 2);
LE64(p, (spl_val_t)ninsns_t);
LE64(p, 0);
LE64(p, 0);
LE64(p, 0);
/* func[0]: add */
LE64(p, (spl_val_t)nlen0);
memcpy(p, "add", nlen0);
p += nlen0;
memset(p, 0, npad0);
p += npad0;
LE64(p, 0);
LE64(p, 2);
LE64(p, 6);
LE64(p, 0);
/* func[1]: main */
LE64(p, (spl_val_t)nlen1);
memcpy(p, "main", nlen1);
p += nlen1;
memset(p, 0, npad1);
p += npad1;
LE64(p, 0);
LE64(p, 0);
LE64(p, 6);
LE64(p, 6);
for (int i = 0; i < 6; i++) {
*p++ = (unsigned char)(add_insns[i].opcode);
*p++ = (unsigned char)(add_insns[i].opcode >> 8);
*p++ = (unsigned char)(add_insns[i].type);
*p++ = (unsigned char)(add_insns[i].type >> 8);
LE64(p, add_insns[i].imm);
}
for (int i = 0; i < 6; i++) {
*p++ = (unsigned char)(main_insns[i].opcode);
*p++ = (unsigned char)(main_insns[i].opcode >> 8);
*p++ = (unsigned char)(main_insns[i].type);
*p++ = (unsigned char)(main_insns[i].type >> 8);
LE64(p, main_insns[i].imm);
}
spl_prog_t prog;
spl_vm_t vm;
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, sz) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
spl_vm_load_prog(&vm, &prog);
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1)
rc = (int)vm.exit_code;
}
spl_vm_drop(&vm);
}
remove(TMPFILE);
free(bin);
TEST_CHECK(rc == 30);
}
/* ================================================================
* Global data (GADDR) test
* ================================================================ */
void test_gaddr_ld32(void) {
/* Load program, add global data, GADDR + LD32 to read it back */
spl_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 0), ins(SPL_LOAD, SPL_U32, 0),
ins(SPL_RET, SPL_U32, 0)};
size_t len;
unsigned char *bin = build_binary("main", 0, p, 3, &len);
spl_prog_t prog;
spl_vm_t vm;
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
uint32_t val = 0xDEADBEEF;
spl_prog_add_data(&prog, &val, sizeof(val));
spl_vm_load_prog(&vm, &prog);
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1)
rc = (int)vm.exit_code;
}
spl_vm_drop(&vm);
}
remove(TMPFILE);
free(bin);
TEST_CHECK(rc == (int)0xDEADBEEF);
}
void test_gaddr_multi(void) {
/* Two global data entries: read second one via GADDR 1 */
spl_ins_t p[] = {ins(SPL_GADDR, SPL_VOID, 1), ins(SPL_LOAD, SPL_U32, 0),
ins(SPL_RET, SPL_U32, 0)};
size_t len;
unsigned char *bin = build_binary("main", 0, p, 3, &len);
spl_prog_t prog;
spl_vm_t vm;
int rc = -1;
spl_vm_init(&vm);
if (write_temp(bin, len) == 0 && spl_prog_load_from_file(TMPFILE, &prog) == 0) {
uint32_t a = 0xAAAAAAAA, b = 0xBBBBBBBB;
spl_prog_add_data(&prog, &a, sizeof(a));
spl_prog_add_data(&prog, &b, sizeof(b));
spl_vm_load_prog(&vm, &prog);
if (spl_vm_prepare(&vm, "main", 0, NULL, NULL) == 0) {
int ret = spl_vm_run_until(&vm, 0);
if (ret == 1)
rc = (int)vm.exit_code;
}
spl_vm_drop(&vm);
}
remove(TMPFILE);
free(bin);
TEST_CHECK(rc == (int)0xBBBBBBBB);
}
/* ================================================================
* Native call test
* ================================================================ */
void test_ncall(void) {
/* main() -> i32 { return native_add(30, 12); } */
/* NCALL imm = nargs, native index is pushed separately */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 30), ins(SPL_PUSH, SPL_I32, 12),
ins(SPL_PUSH, SPL_VOID, 0), ins(SPL_NCALL, SPL_I32, 2),
ins(SPL_RET, SPL_I32, 0)};
spl_native_t nat[] = {{"native_add", 0, native_add_impl}};
TEST_CHECK(run_with_natives(p, 5, nat, 1) == 42);
}
/* ================================================================
* Type conversion tests
* ================================================================ */
void test_trunc(void) {
/* push 0xABCD, trunc to 8 bits -> 0xCD */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xABCD), ins(SPL_TRUNC, SPL_U8, 8),
ins(SPL_RET, SPL_U8, 0)};
TEST_CHECK(run(p, 3) == 0xCD);
}
void test_sext(void) {
/* push 0x80, sext from 8 bits -> 0xFFFFFF80 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 0x80), ins(SPL_SEXT, SPL_I32, 8),
ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 3) == (int)(int8_t)(0x80));
}
void test_zext(void) {
/* push 0xFFFF, zext from 8 bits -> 0xFF */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 0xFFFF), ins(SPL_ZEXT, SPL_U32, 8),
ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 3) == 0xFF);
}
/* ================================================================
* Extended arithmetic tests (unsigned div/rem)
* ================================================================ */
void test_div_u(void) {
/* unsigned: 100 / 3 = 33 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 100), ins(SPL_PUSH, SPL_U32, 3),
ins(SPL_DIV_U, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 33);
}
void test_rem_u(void) {
/* unsigned: 100 % 3 = 1 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_U32, 100), ins(SPL_PUSH, SPL_U32, 3),
ins(SPL_REM_U, SPL_U32, 0), ins(SPL_RET, SPL_U32, 0)};
TEST_CHECK(run(p, 4) == 1);
}
/* ================================================================
* Edge case tests
* ================================================================ */
void test_many_ops(void) {
/* (1+2) * (3+4) = 21 */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 1), ins(SPL_PUSH, SPL_I32, 2), ins(SPL_ADD, SPL_I32, 0),
ins(SPL_PUSH, SPL_I32, 3), ins(SPL_PUSH, SPL_I32, 4), ins(SPL_ADD, SPL_I32, 0),
ins(SPL_MUL, SPL_I32, 0), ins(SPL_RET, SPL_I32, 0)};
TEST_CHECK(run(p, 8) == 21);
}
void test_halt(void) {
/* push 77, halt -> exit code 0 (HALT doesn't pop) */
spl_ins_t p[] = {ins(SPL_PUSH, SPL_I32, 77), ins(SPL_HALT, SPL_VOID, 0)};
TEST_CHECK(run(p, 2) == 0);
}
/* ================================================================
* Bad program tests
* ================================================================ */
void test_bad_magic(void) {
unsigned char bad[8] = {0, 0, 0, 0, 0, 0, 0, 0};
spl_prog_t prog;
if (write_temp(bad, 8) == 0) {
TEST_CHECK(spl_prog_load_from_file(TMPFILE, &prog) != 0);
remove(TMPFILE);
}
}
void test_empty(void) {
TEST_CHECK(spl_prog_load_from_file("nonexistent_file_xyz.bin", NULL) != 0);
}
/* ================================================================
* Test list
* ================================================================ */
TEST_LIST = {
{"push", test_push_imm},
{"dup", test_dup},
{"drop", test_drop},
{"swap", test_swap},
{"pick", test_pick},
{"add", test_add},
{"sub", test_sub},
{"mul", test_mul},
{"div", test_div},
{"rem", test_rem},
{"neg", test_neg},
{"i64_arith", test_i64_arith},
{"and", test_and},
{"or", test_or},
{"xor", test_xor},
{"not", test_not},
{"shl", test_shl},
{"shr", test_shr},
{"shr_s", test_shr_s},
{"eq", test_eq},
{"neq", test_neq},
{"lt", test_lt},
{"gt_signed", test_gt_signed},
{"sle", test_sle},
{"sge", test_sge},
{"ult", test_ult},
{"ule", test_ule},
{"ugt", test_ugt},
{"uge", test_uge},
{"jmp", test_jmp},
{"bz_bnz", test_bz_bnz},
{"alloc_laddr_ld_st", test_alloc_laddr_st64_ld64},
{"alloc_zeroed", test_alloc_zeroed},
{"call_add", test_call_add},
{"calli", test_calli},
{"gaddr_ld32", test_gaddr_ld32},
{"gaddr_multi", test_gaddr_multi},
{"ncall", test_ncall},
{"div_u", test_div_u},
{"rem_u", test_rem_u},
{"trunc", test_trunc},
{"sext", test_sext},
{"zext", test_zext},
{"ld_st8", test_ld_st8},
{"ld_st16", test_ld_st16},
{"ld_st32", test_ld_st32},
{"ld_st64", test_ld_st64},
{"many_ops", test_many_ops},
{"halt", test_halt},
{"bad_magic", test_bad_magic},
{"empty", test_empty},
{NULL, NULL},
};

452
stage1/spl.md Normal file
View File

@@ -0,0 +1,452 @@
# SPL — 语法与语义
## 概述
类 C 语法的系统编程语言,受 Rust/Zig 启发。编译为 SIRSPL 中间表示),一种基于栈的字节码。多阶段引导:
```
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;` 形式用于定义简单类型别名,但目前仅支持聚合类型的别名定义。
### 聚合类型拓展与match
```
type Expr = enum {
Int: i32,
Add: struct { left: *Expr, right: *Expr },
fn eval(self: *Expr) i32 {
match self {
.Int(val) => ret val,
.Add(left, right) => ret eval(left) + eval(right),
}
ret 0;
}
}
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) void {
vm_printf("Point: %d %d", self.x, self.y);
}
}
```
这里面包括聚合类型声明除了成员以外任何东西,即整个编译模块也属于聚合类型,将类型抽象化。
其中这里面还包括调用时支持第一个参数self匹配类型时自动填充。
暂时默认全部pub即全部暴露没有不暴露的。
复合enum支持match等语法解包
```
match self {
.Int(val) => ret val,
.Add(left, right) => ret eval(left) + eval(right),
}
```
---
## 声明
### 函数
```
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` 必须求值为 booli32或者成功语义。`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_t8 字节)。
- **帧指针**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 中尚未实现。)

347
stage1/spl_comp.c Normal file
View File

@@ -0,0 +1,347 @@
/* spl_comp.c — SPL compiler main logic and codegen helpers */
#include "spl_comp.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.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';
ctx->current_type_name = NULL;
}
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_bytes = 0;
ctx->peak_local_bytes = 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;
ctx->addr_of_mode = 0;
ctx->current_type_name = NULL;
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);
}
/* ---- Multi-slot copy helper ---- */
void spl_emit_copy_slots(spl_comp_t *ctx, int dest_offset, usize nslots) {
for (usize i = 0; i < nslots; i++) {
if (i < nslots - 1)
spl_emit(ctx, SPL_DUP, SPL_VOID, 0);
if (i > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, i * sizeof(spl_val_t));
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, dest_offset + (int)(i * sizeof(spl_val_t)));
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, SPL_PTR, 0);
}
}
/* ---- Uniform LOAD/STORE type helper ---- */
spl_type_t spl_type_emit_type(spl_type_info_t *type) {
if (!type)
return SPL_I32;
if (type->kind == TYPE_BASIC)
return type->basic_type;
if (type->kind == TYPE_PTR)
return SPL_PTR;
return SPL_PTR;
}
/* ---- Load from [saved_ptr+offset], store to local var ---- */
void spl_emit_load_to_var(spl_comp_t *ctx, int ptr_slot_offset, usize byte_offset,
spl_type_info_t *data_type, int var_offset) {
spl_type_t bt = spl_type_emit_type(data_type);
spl_emit(ctx, SPL_LADDR, SPL_PTR, ptr_slot_offset);
spl_emit(ctx, SPL_LOAD, SPL_PTR, 0);
if (byte_offset > 0) {
spl_emit(ctx, SPL_PUSH, SPL_USIZE, byte_offset);
spl_emit(ctx, SPL_ADD, SPL_USIZE, 0);
}
spl_emit(ctx, SPL_LOAD, bt, 0);
spl_emit(ctx, SPL_LADDR, SPL_PTR, var_offset);
spl_emit(ctx, SPL_SWAP, SPL_VOID, 0);
spl_emit(ctx, SPL_STORE, bt, 0);
}
/* ---- 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.offset = ctx->current_local_bytes;
usize var_size = spl_type_size(type);
/* Round up to sizeof(spl_val_t) alignment so params (pushed as spl_val_t) align */
usize aligned = (var_size + sizeof(spl_val_t) - 1) & ~(sizeof(spl_val_t) - 1);
if (aligned < sizeof(spl_val_t))
aligned = sizeof(spl_val_t);
ctx->current_local_bytes += (int)aligned;
if (ctx->current_local_bytes > ctx->peak_local_bytes)
ctx->peak_local_bytes = ctx->current_local_bytes;
/* 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.offset;
}
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_offset(spl_comp_t *ctx, const char *name) {
spl_var_info_t *v = spl_lookup_var(ctx, name);
return v ? v->offset : -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) {
if (ctx->defer_count >= DEFER_MAX)
return;
/* Emit JMP placeholder (will skip defer body during normal execution).
* This JMP is at the current position. The defer body starts right after. */
spl_val_t jmp_skip = spl_emit_jmp(ctx);
spl_defer_entry_t *e = &ctx->defer_stack[ctx->defer_count];
e->body_start = jmp_skip + 1; /* instruction right after the JMP = body start */
e->jmp_exit = 0; /* set after body is parsed */
e->depth = ctx->scope_depth;
e->count_at_decl = ctx->defer_count;
ctx->defer_count++;
}
void spl_emit_defer_epilogue(spl_comp_t *ctx, int depth) {
/* === Pass 1: patch skip JMPs to jump past their defer body ===
* During normal execution, the skip JMP at body_start-1 must jump
* over the defer body (to jmp_exit + 1 = the code after the defer). */
for (int i = 0; i < ctx->defer_count; i++) {
if (ctx->defer_stack[i].depth != depth)
continue;
spl_defer_entry_t *e = &ctx->defer_stack[i];
spl_val_t skip_addr = e->body_start - 1; /* the JMP L_skip instruction */
spl_val_t skip_target = e->jmp_exit + 1; /* instruction right after defer body */
spl_val_t skip_offset = skip_target - skip_addr - 1;
spl_patch(ctx, skip_addr, skip_offset);
}
/* === Pass 2: at scope exit, emit backwards JMPs to each defer body ===
* Process in reverse order so the LAST declared defer runs FIRST at scope exit.
* Each defer body's trailing JMP_exit gets patched to jump to right after
* the scope-exit JMP we just emitted, so control chains through correctly. */
for (int i = ctx->defer_count - 1; i >= 0; i--) {
if (ctx->defer_stack[i].depth != depth)
continue;
spl_defer_entry_t *e = &ctx->defer_stack[i];
/* Emit JMP backwards to the defer body */
spl_val_t here = vec_size(ctx->prog.insns);
spl_val_t jmp_offset = (spl_val_t)((isize)e->body_start - (isize)here - 1);
spl_emit(ctx, SPL_JMP, SPL_VOID, jmp_offset);
/* Patch the body's trailing JMP_exit to jump to here+1 (right after the
* backwards JMP we just emitted). This chains to the next defer or exits. */
if (e->jmp_exit > 0) {
spl_val_t exit_target = here + 1;
spl_val_t offset = exit_target - e->jmp_exit - 1;
spl_patch(ctx, e->jmp_exit, offset);
}
}
/* Remove processed defers from stack */
int new_count = 0;
for (int i = 0; i < ctx->defer_count; i++) {
if (ctx->defer_stack[i].depth != depth) {
ctx->defer_stack[new_count++] = ctx->defer_stack[i];
}
}
ctx->defer_count = new_count;
}
/* ---- 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;
}

305
stage1/spl_comp.h Normal file
View File

@@ -0,0 +1,305 @@
/* 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_UNION,
TYPE_ENUM,
TYPE_ENUM_VARIANT, /* enum variant with data */
TYPE_NAME, /* named alias */
TYPE_INFER, /* _ (to be inferred) */
} spl_type_kind_t;
/* Forward declarations */
typedef struct spl_type_info spl_type_info_t;
typedef struct spl_comp spl_comp_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;
/* Method info (struct/enum methods) */
typedef struct {
char *name;
int func_idx; /* index in ctx->funcs */
} spl_method_info_t;
typedef VEC(spl_method_info_t) spl_method_info_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 */
spl_method_info_vec_t methods; /* methods */
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_union(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_add_method(spl_type_info_t *t, const char *name, int func_idx);
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);
/* Byte stride between consecutive elements in storage (slot-based layout) */
usize spl_type_elem_stride(spl_type_info_t *elem);
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);
/* Determine the uniform type for LOAD/STORE codegen (i32→SPL_I32, ptr→SPL_PTR, else→SPL_PTR) */
spl_type_t spl_type_emit_type(spl_type_info_t *type);
/* Emit LOAD from [saved_ptr + byte_offset] and STORE to local var at var_offset.
* Stack: [] → []
* The saved_ptr is loaded from the 1-slot temp at ptr_slot_offset. */
void spl_emit_load_to_var(spl_comp_t *ctx, int ptr_slot_offset, usize byte_offset,
spl_type_info_t *data_type, int var_offset);
/* ============================================================
* Scope / symbol table
* ============================================================ */
typedef struct spl_var_info {
char *name;
spl_type_info_t *type;
int offset; /* byte 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 {
usize body_start; /* IP where defer body starts (after skip-JMP) */
usize jmp_exit; /* IP of the JMP after the body (to patch at scope exit) */
int depth; /* scope depth */
int count_at_decl; /* defer_count when declared */
} spl_defer_entry_t;
typedef struct spl_comp {
/* 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_bytes; /* next free local byte offset */
int peak_local_bytes; /* peak current_local_bytes for ALLOC sizing */
const char *current_type_name; /* name of type whose body we're parsing (for method short-name
lookup) */
/* 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 */
spl_defer_entry_t defer_stack[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;
int addr_of_mode; /* 1 = inside & operator, suppress loads */
} 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_ASSIGN = 1,
PREC_LOGOR = 2,
PREC_LOGAND = 3,
PREC_OR = 4,
PREC_XOR = 5,
PREC_AND = 6,
PREC_CMPEQ = 7,
PREC_CMP = 8,
PREC_SHIFT = 9,
PREC_ADD = 10,
PREC_MUL = 11,
PREC_PREFIX = 12,
PREC_POSTFIX = 13
};
/* 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);
/* Copy multi-slot value from TOS (temp address) to frame-relative destination.
* Stack: [..., temp_addr] → [...]
* Copies nslots slots (each sizeof(spl_val_t) bytes) from temp offset to dest_offset. */
void spl_emit_copy_slots(spl_comp_t *ctx, int dest_offset, usize nslots);
/* 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_offset(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, int depth);
/* 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);
/* Shared token helpers (defined in spl_parser.c) */
spl_tok_t *peek(spl_comp_t *ctx);
spl_tok_t *advance(spl_comp_t *ctx);
#endif /* __SPL_COMP_H__ */

1396
stage1/spl_expr.c Normal file

File diff suppressed because it is too large Load Diff

115
stage1/spl_lex_util.c Normal file
View File

@@ -0,0 +1,115 @@
/* spl_lex_util.c — Lexer utility functions */
#include "spl_lex_util.h"
#include <stdio.h>
#include <string.h>
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;
}
int match(spl_comp_t *ctx, spl_tok_type_t type) {
if (peek(ctx)->type == type) {
advance(ctx);
return 1;
}
return 0;
}
void skip_nl(spl_comp_t *ctx) {
while (peek(ctx)->type == TOK_ENDLINE)
advance(ctx);
}
int spl_parse_int_literal(spl_comp_t *ctx, int *val) {
int negate = 0;
if (peek(ctx)->type == TOK_SUB) {
negate = 1;
advance(ctx);
}
if (peek(ctx)->type != TOK_INT_LITERAL) {
if (negate)
ctx->tok_idx--; /* un-consume the SUB token */
return 0;
}
spl_tok_t *tok = advance(ctx);
long long v = strtoll(tok->lexeme, NULL, 0);
*val = negate ? -(int)v : (int)v;
return 1;
}
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);
}
}
usize spl_tok_copy_name(spl_tok_t *tok, char *buf, usize buf_size) {
if (!tok || !buf || buf_size == 0)
return 0;
usize nlen = tok->len < buf_size - 1 ? tok->len : buf_size - 1;
memcpy(buf, tok->lexeme, nlen);
buf[nlen] = '\0';
return nlen;
}

17
stage1/spl_lex_util.h Normal file
View File

@@ -0,0 +1,17 @@
/* spl_lex_util.h — Shared token helper utilities */
#ifndef __SPL_LEX_UTIL_H__
#define __SPL_LEX_UTIL_H__
#include "spl_comp.h"
/* Token matching helpers */
int expect(spl_comp_t *ctx, spl_tok_type_t type);
int match(spl_comp_t *ctx, spl_tok_type_t type);
void skip_nl(spl_comp_t *ctx);
/* Parse an integer literal value, handling optional '-' prefix for negatives.
* Returns 1 on success (value in *val), 0 if current token isn't an integer.
* On success, advances past all consumed tokens. On failure, does not advance. */
int spl_parse_int_literal(spl_comp_t *ctx, int *val);
#endif /* __SPL_LEX_UTIL_H__ */

532
stage1/spl_lexer.c Normal file
View File

@@ -0,0 +1,532 @@
/* spl_lexer.c — SPL lexical analyzer */
#include "spl_lexer.h"
#include <ctype.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;
}

143
stage1/spl_lexer.h Normal file
View File

@@ -0,0 +1,143 @@
/* 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);
usize spl_tok_copy_name(spl_tok_t *tok, char *buf, usize buf_size);
/* Decode escape sequence, advance *s past it. Returns 0 on success. */
int spl_decode_escape(const char **s, char *out);
#endif /* __SPL_LEXER_H__ */

619
stage1/spl_parser.c Normal file
View File

@@ -0,0 +1,619 @@
/* spl_parser.c — Top-level parser: function declarations, type declarations, etc. */
#include "spl_comp.h"
#include "spl_lex_util.h"
#include <string.h>
spl_tok_t *peek(spl_comp_t *ctx) { return &vec_at(ctx->toks, ctx->tok_idx); }
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;
}
/* ============================================================
* Parse function definition
* fn name(params) ret-type { body }
* or fn name(params) ret-type; (forward decl, not used for stage1)
* ============================================================ */
/* Shared helper: register a function, declare params, parse body, end function.
* Used by both top-level fn decl and methods inside type bodies. */
static int parse_fn_body(spl_comp_t *ctx, const char *fn_name, spl_type_info_t *ret_type,
int nparams, char pnames[][256], spl_type_info_t *ptypes[], int is_pub) {
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_bytes = 0;
ctx->peak_local_bytes = 0;
spl_push_scope(ctx);
for (int i = 0; i < nparams; i++) {
spl_declare_var(ctx, pnames[i], ptypes[i], 0);
}
skip_nl(ctx);
if (peek(ctx)->type == TOK_L_BRACE) {
advance(ctx); /* { */
skip_nl(ctx);
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);
}
spl_patch(ctx, alloc_addr, ctx->peak_local_bytes / (int)sizeof(spl_val_t) - nparams);
expect(ctx, TOK_R_BRACE);
}
spl_emit_defer_epilogue(ctx, ctx->scope_depth);
spl_pop_scope(ctx);
spl_emit(ctx, SPL_RET, SPL_VOID, 0);
spl_prog_end_func(&ctx->prog, fi);
ctx->current_func_idx = -1;
ctx->current_ret_type = NULL;
return fi;
}
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);
/* Use shared helper for function body parsing */
parse_fn_body(ctx, fn_name, ret_type, nparams, pnames, ptypes, is_pub);
}
/* ============================================================
* Parse struct/union body (shared for struct and union containers)
*
* Body supports:
* var name: type; — field declarations
* name: type, — field declarations (old-style)
* type Name = ...; — nested type declarations
* fn name(...) type { } — methods
* ============================================================ */
static void parse_struct_body(spl_comp_t *ctx, spl_type_info_t *st) {
if (peek(ctx)->type != TOK_L_BRACE)
return;
advance(ctx); /* { */
/* === Pass 1: Parse all nested type declarations first ===
* This allows field declarations to reference types defined later in the body. */
{
usize saved = ctx->tok_idx;
int depth = 1;
while (depth > 0 && ctx->tok_idx < vec_size(ctx->toks)) {
spl_tok_type_t tt = peek(ctx)->type;
if (tt == TOK_L_BRACE) {
depth++;
advance(ctx);
} else if (tt == TOK_R_BRACE) {
depth--;
if (depth == 0)
break;
advance(ctx);
} else if (tt == KW_TYPE && depth == 1) {
parse_type_decl(ctx);
} else {
advance(ctx);
}
}
/* Reset to start of body for second pass */
ctx->tok_idx = saved;
}
/* === Pass 2: Parse fields and methods === */
{
int depth = 1;
while (depth > 0 && ctx->tok_idx < vec_size(ctx->toks)) {
skip_nl(ctx);
spl_tok_type_t tt = peek(ctx)->type;
if (tt == TOK_L_BRACE) {
depth++;
advance(ctx);
} else if (tt == TOK_R_BRACE) {
depth--;
if (depth == 0) {
advance(ctx);
break;
}
advance(ctx);
} else if (tt == KW_TYPE && depth == 1) {
/* Nested type — already parsed in pass 1, skip by re-parsing */
parse_type_decl(ctx);
} else if (tt == KW_VAR && depth == 1) {
/* var name: type; */
advance(ctx); /* var */
skip_nl(ctx);
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_SEMICOLON || peek(ctx)->type == TOK_COMMA)
advance(ctx);
} else if (tt == TOK_IDENT && depth == 1) {
/* Old-style field: name: type, */
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_SEMICOLON || peek(ctx)->type == TOK_COMMA)
advance(ctx);
} else if (tt == KW_FN && depth == 1) {
/* Method — parse properly using parse_fn_body */
advance(ctx); /* fn */
skip_nl(ctx);
spl_tok_t *mname_tok = advance(ctx);
char mname[256];
usize mnl = mname_tok->len < 255 ? mname_tok->len : 255;
memcpy(mname, mname_tok->lexeme, mnl);
mname[mnl] = '\0';
/* Build qualified name: TypeName.method_name */
char qualified[512];
snprintf(qualified, sizeof(qualified), "%s.%s", st->name ? st->name : "anon",
mname);
skip_nl(ctx);
expect(ctx, TOK_L_PAREN);
/* Parse parameters */
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;
}
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);
}
/* Set current_type_name for short-name resolution inside method body */
const char *saved_type_name = ctx->current_type_name;
ctx->current_type_name = st->name;
int fi = parse_fn_body(ctx, qualified, ret_type, nparams, pnames, ptypes, 0);
{
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
f->param_types = calloc(nparams, sizeof(spl_type_info_t *));
f->param_names = calloc(nparams, sizeof(char *));
for (int i = 0; i < nparams; i++) {
f->param_types[i] = ptypes[i];
f->param_names[i] = strdup(pnames[i]);
}
}
ctx->current_type_name = saved_type_name;
spl_type_add_method(st, mname, fi);
} else {
advance(ctx);
}
}
}
spl_type_compute_layout(st);
}
/* ============================================================
* Parse enum body
*
* Body supports:
* Name — simple variant
* Name: Type — variant with data
* var name: type; — field-style variant
* type Name = ...; — nested type declarations
* fn name(...) type { } — methods (skipped)
* ============================================================ */
static void parse_enum_body(spl_comp_t *ctx, spl_type_info_t *et) {
if (peek(ctx)->type != TOK_L_BRACE)
return;
advance(ctx); /* { */
/* === Pass 1: Parse all nested type declarations first === */
{
usize saved = ctx->tok_idx;
int depth = 1;
while (depth > 0 && ctx->tok_idx < vec_size(ctx->toks)) {
spl_tok_type_t tt = peek(ctx)->type;
if (tt == TOK_L_BRACE) {
depth++;
advance(ctx);
} else if (tt == TOK_R_BRACE) {
depth--;
if (depth == 0)
break;
advance(ctx);
} else if (tt == KW_TYPE && depth == 1) {
parse_type_decl(ctx);
} else {
advance(ctx);
}
}
ctx->tok_idx = saved;
}
/* === Pass 2: Parse variants and methods === */
{
int depth = 1;
while (depth > 0 && ctx->tok_idx < vec_size(ctx->toks)) {
skip_nl(ctx);
spl_tok_type_t tt = peek(ctx)->type;
if (tt == TOK_L_BRACE) {
depth++;
advance(ctx);
} else if (tt == TOK_R_BRACE) {
depth--;
if (depth == 0) {
advance(ctx);
break;
}
advance(ctx);
} else if (tt == KW_TYPE && depth == 1) {
/* Already parsed in pass 1, re-parse to skip */
parse_type_decl(ctx);
} else if (tt == TOK_IDENT && depth == 1) {
/* Variant: Name or Name: Type */
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_SEMICOLON || peek(ctx)->type == TOK_COMMA)
advance(ctx);
} else if (tt == KW_FN && depth == 1) {
/* Method — parse properly using parse_fn_body */
advance(ctx); /* fn */
skip_nl(ctx);
spl_tok_t *mname_tok = advance(ctx);
char mname[256];
usize mnl = mname_tok->len < 255 ? mname_tok->len : 255;
memcpy(mname, mname_tok->lexeme, mnl);
mname[mnl] = '\0';
/* Build qualified name: TypeName.method_name */
char qualified[512];
snprintf(qualified, sizeof(qualified), "%s.%s", et->name ? et->name : "anon",
mname);
skip_nl(ctx);
expect(ctx, TOK_L_PAREN);
/* Parse parameters */
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;
}
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);
}
/* Set current_type_name for short-name resolution inside method body */
const char *saved_type_name = ctx->current_type_name;
ctx->current_type_name = et->name;
int fi = parse_fn_body(ctx, qualified, ret_type, nparams, pnames, ptypes, 0);
ctx->current_type_name = saved_type_name;
{
spl_func_info_t *f = &vec_at(ctx->funcs, fi);
f->param_types = calloc(nparams, sizeof(spl_type_info_t *));
f->param_names = calloc(nparams, sizeof(char *));
for (int i = 0; i < nparams; i++) {
f->param_types[i] = ptypes[i];
f->param_names[i] = strdup(pnames[i]);
}
}
spl_type_add_method(et, mname, fi);
} else {
advance(ctx);
}
}
}
spl_type_compute_layout(et);
}
/* ============================================================
* Parse type declaration
* type Name = struct { ... };
* type Name = union { ... };
* type Name = 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);
/* Register type early to allow self-referential fields */
map_put(ctx->type_defs, strdup(tname), st);
skip_nl(ctx);
parse_struct_body(ctx, st);
} else if (peek(ctx)->type == KW_UNION) {
advance(ctx);
spl_type_info_t *ut = spl_type_union(tname);
map_put(ctx->type_defs, strdup(tname), ut);
skip_nl(ctx);
parse_struct_body(ctx, ut);
} else if (peek(ctx)->type == KW_ENUM) {
advance(ctx);
spl_type_info_t *et = spl_type_enum(tname);
/* Register type early to allow self-referential variants */
map_put(ctx->type_defs, strdup(tname), et);
skip_nl(ctx);
parse_enum_body(ctx, 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;
}
}
}
}

1013
stage1/spl_stmt.c Normal file

File diff suppressed because it is too large Load Diff

501
stage1/spl_type.c Normal file
View File

@@ -0,0 +1,501 @@
/* spl_type.c — Type system implementation */
#include "spl_comp.h"
#include "spl_lex_util.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);
vec_init(t->methods);
t->resolved = 0;
return t;
}
spl_type_info_t *spl_type_union(const char *name) {
spl_type_info_t *t = calloc(1, sizeof(spl_type_info_t));
t->kind = TYPE_UNION;
if (name)
t->name = strdup(name);
vec_init(t->fields);
vec_init(t->methods);
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);
vec_init(t->methods);
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_add_method(spl_type_info_t *t, const char *name, int func_idx) {
spl_method_info_t m;
m.name = strdup(name);
m.func_idx = func_idx;
vec_push(t->methods, m);
}
void spl_type_compute_layout(spl_type_info_t *t) {
if (!t || t->resolved)
return;
if (t->kind == TYPE_STRUCT || t->kind == TYPE_UNION) {
usize offset = 0;
usize max_field_size = 0;
vec_for(t->fields, i) {
spl_field_t *f = &vec_at(t->fields, i);
if (f->type) {
spl_type_compute_layout(f->type);
if (t->kind == TYPE_UNION) {
/* Union: all fields at offset 0, size = max field size */
f->offset = 0;
if (f->type->byte_size > max_field_size)
max_field_size = f->type->byte_size;
} else {
/* Struct: sequential layout */
f->offset = offset;
offset += f->type->byte_size;
}
}
}
if (t->kind == TYPE_UNION) {
t->byte_size = max_field_size;
} else {
t->byte_size = offset;
}
/* Round up to slot alignment */
usize slot_sz = sizeof(spl_val_t);
t->slot_count = (t->byte_size + slot_sz - 1) / slot_sz;
if (t->slot_count < 1)
t->slot_count = 1;
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;
}
/* Byte stride between consecutive elements in storage */
usize spl_type_elem_stride(spl_type_info_t *elem) { return spl_type_size(elem); }
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_UNION:
return t->name ? t->name : "union";
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 */
int len_val;
if (!spl_parse_int_literal(ctx, &len_val)) {
spl_comp_error(ctx, "expected array length");
return NULL;
}
usize len = (usize)len_val;
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);
}
/* Inline struct/union/enum type: struct { field: type, ... } */
if (tok->type == KW_STRUCT || tok->type == KW_UNION) {
int is_union = (tok->type == KW_UNION);
ctx->tok_idx++;
spl_type_info_t *t = is_union ? spl_type_union(NULL) : spl_type_struct(NULL);
if (peek(ctx)->type == TOK_L_BRACE) {
advance(ctx); /* { */
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
spl_tok_t *ftok = advance(ctx);
if (peek(ctx)->type == TOK_COLON) {
advance(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(t, fname, ftype);
}
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
}
if (peek(ctx)->type == TOK_R_BRACE)
advance(ctx);
}
spl_type_compute_layout(t);
return t;
}
/* Inline enum type: enum { A, B, C, ... } */
if (tok->type == KW_ENUM) {
ctx->tok_idx++;
spl_type_info_t *t = spl_type_enum(NULL);
if (peek(ctx)->type == TOK_L_BRACE) {
advance(ctx); /* { */
while (peek(ctx)->type != TOK_R_BRACE && peek(ctx)->type != TOK_EOF) {
spl_tok_t *vtok = advance(ctx);
if (peek(ctx)->type == TOK_COLON) {
advance(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(t, 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(t, vname, NULL);
}
if (peek(ctx)->type == TOK_COMMA)
advance(ctx);
}
if (peek(ctx)->type == TOK_R_BRACE)
advance(ctx);
}
spl_type_compute_layout(t);
return t;
}
/* 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
View 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
View 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;
}

8
stage1/test00_basic.spl Normal file
View File

@@ -0,0 +1,8 @@
/* ===== 模块1核心基础 =====
* test00_basic — 最小程序 / 返回码
* 难度1/5
* 验证点函数定义、整型返回、ret 语句
*/
fn main() i32 {
ret 0;
}

View File

@@ -0,0 +1,43 @@
/* ===== 模块1核心基础 =====
* test01_literals — 全部字面量形式
* 难度1/5
* 验证点:十进制/十六进制/二进制/八进制整数、
* 字符字面量、字符串字面量、布尔字面量、null
*/
fn main() i32 {
/* 整数:十进制 */
var a: i32 = 42;
if a != 42 { ret 1; }
/* 整数:十六进制 */
var b: i32 = 0xFF;
if b != 255 { ret 2; }
/* 整数:二进制 */
var c: i32 = 0b1010;
if c != 10 { ret 3; }
/* 整数:八进制 */
var d: i32 = 0o77;
if d != 63 { ret 4; }
/* 字符字面量 */
var ch: i32 = 'A';
if ch != 65 { ret 5; }
/* 字符转义 */
var nl: i32 = '\n';
if nl != 10 { ret 6; }
/* 布尔字面量 */
var t: i32 = true;
if t != 1 { ret 7; }
var f: i32 = false;
if f != 0 { ret 8; }
/* null 指针 */
var np: *i32 = null;
if np != null { ret 9; }
ret 0;
}

View File

@@ -0,0 +1,51 @@
/* ===== 模块1核心基础 =====
* test02_variables — 变量与常量声明
* 难度1/5
* 验证点var 声明 + 类型注解、var 声明 + 初始化、
* const 声明、var := 短声明、const := 短声明
*/
fn main() i32 {
/* var 带类型注解与初始化 */
var a: i32 = 10;
if a != 10 { ret 1; }
/* var 先声明后赋值 */
var b: i32;
b = 20;
if b != 20 { ret 2; }
/* const 声明 */
const c: i32 = 30;
if c != 30 { ret 3; }
/* var := 短声明(类型推断) */
var d := 40;
if d != 40 { ret 4; }
/* const := 短声明 */
const e := 50;
if e != 50 { ret 5; }
/* 多类型变量 */
var i8v: i8 = 127;
if i8v != 127 { ret 6; }
var u8v: u8 = 255;
if u8v != 255 { ret 7; }
var i16v: i16 = 32767;
if i16v != 32767 { ret 8; }
var u16v: u16 = 65535;
if u16v != 65535 { ret 9; }
var u32v: u32 = 4294967295;
if u32v != 4294967295 { ret 10; }
/* 指针类型变量 */
var x: i32 = 100;
var p: *i32 = &x;
if p == null { ret 11; }
ret 0;
}

View File

@@ -0,0 +1,56 @@
/* ===== 模块2表达式 =====
* test03_arithmetic — 算术运算
* 难度1/5
* 验证点:加、减、乘、除、取模、负数、混合运算优先级
*/
fn main() i32 {
/* 加法 */
var a := 12 + 34;
if a != 46 { ret 1; }
/* 减法 */
var b := 100 - 23;
if b != 77 { ret 2; }
/* 乘法 */
var c := 7 * 8;
if c != 56 { ret 3; }
/* 除法 */
var d := 100 / 3;
if d != 33 { ret 4; }
/* 取模 */
var e := 100 % 3;
if e != 1 { ret 5; }
/* 负数 */
var f := -5;
if f != -5 { ret 6; }
/* 负数运算 */
var g := -10 + 3;
if g != -7 { ret 7; }
/* 运算优先级:乘优先于加 */
var h := 2 + 3 * 4;
if h != 14 { ret 8; }
/* 括号改变优先级 */
var i := (2 + 3) * 4;
if i != 20 { ret 9; }
/* 连续运算 */
var j := 1 + 2 + 3 + 4 + 5;
if j != 15 { ret 10; }
/* 混合运算 */
var k := 10 * 2 - 8 / 4;
if k != 18 { ret 11; }
/* 负数取模 */
var l := -7 % 3;
if l != -1 { ret 12; }
ret 0;
}

View File

@@ -0,0 +1,66 @@
/* ===== 模块2表达式 =====
* test04_operators — 比较/逻辑/位运算 + 短路求值
* 难度2/5
* 验证点:== != < <= > >=、&& || !、& | ^ ~ << >>、&&/|| 短路
*/
fn main() i32 {
/* ---- 比较运算 ---- */
if (1 < 2) {} else { ret 1; }
if (3 > 1) {} else { ret 2; }
if (2 == 2) {} else { ret 3; }
if (2 != 3) {} else { ret 4; }
if (2 <= 2) {} else { ret 5; }
if (2 <= 3) {} else { ret 6; }
if (3 >= 3) {} else { ret 7; }
if (3 >= 2) {} else { ret 8; }
/* 比较结果为 0/1 */
var eq := (42 == 42);
if eq != 1 { ret 9; }
var ne := (42 == 43);
if ne != 0 { ret 10; }
var lt := (5 < 10);
if lt != 1 { ret 11; }
/* ---- 逻辑运算 ---- */
if (true) {} else { ret 12; }
if (!false) {} else { ret 13; }
if (true && true) {} else { ret 14; }
if (false || true) {} else { ret 15; }
/* 逻辑非 */
var not_t := !true;
if not_t != 0 { ret 16; }
var not_f := !false;
if not_f != 1 { ret 17; }
// /* ---- 短路求值 ---- */
// var short1 := 0;
// var short2 := 0;
// if (false && { short1 = 1; true; }) {} else {}
// if short1 != 0 { ret 18; } /* 右侧未执行 */
/* ---- 位运算 ---- */
var band := 0xFF & 0x0F;
if band != 0x0F { ret 19; }
var bor := 0xF0 | 0x0F;
if bor != 0xFF { ret 20; }
var bxor := 0xFF ^ 0x0F;
if bxor != 0xF0 { ret 21; }
var bnot := ~0xFF;
/* ~0xFF 在 32 位下 = 0xFFFFFF00即 -256 */
if bnot != -256 { ret 22; }
/* 左移 */
var shl := 1 << 3;
if shl != 8 { ret 23; }
/* 右移 */
var shr := 16 >> 2;
if shr != 4 { ret 24; }
ret 0;
}

79
stage1/test05_control.spl Normal file
View File

@@ -0,0 +1,79 @@
/* ===== 模块3控制流 =====
* test05_control — 分支与循环
* 难度2/5
* 验证点if/else、while、loop、break、continue
*/
fn main() i32 {
/* ---- if/else ---- */
var x: i32 = 10;
if x > 5 {
x = 0;
} else {
x = 1;
}
if x != 0 { ret 1; }
/* else 分支 */
var y: i32 = 1;
if y > 5 {
y = 10;
} else {
y = 20;
}
if y != 20 { ret 2; }
/* if 不带 else */
var z: i32 = 0;
if z == 0 {
z = 99;
}
if z != 99 { ret 3; }
/* ---- while ---- */
var i: i32 = 0;
while i < 5 {
i = i + 1;
}
if i != 5 { ret 4; }
/* while 条件为 false */
var j: i32 = 0;
while j > 0 {
j = j + 1;
}
if j != 0 { ret 5; }
/* ---- loop / break ---- */
var k: i32 = 0;
loop {
k = k + 1;
if k >= 3 { break; }
}
if k != 3 { ret 6; }
/* ---- continue ---- */
var n: i32 = 0;
var m: i32 = 0;
while n < 5 {
n = n + 1;
if n == 3 { continue; }
m = m + 1;
}
if n != 5 { ret 7; }
if m != 4 { ret 8; }
/* ---- 嵌套控制流 ---- */
var total: i32 = 0;
var p: i32 = 0;
while p < 3 {
var q: i32 = 0;
while q < 4 {
total = total + 1;
q = q + 1;
}
p = p + 1;
}
if total != 12 { ret 9; }
ret 0;
}

48
stage1/test06_for.spl Normal file
View File

@@ -0,0 +1,48 @@
/* ===== 模块3控制流 =====
* test06_for — for 区间循环
* 难度2/5
* 验证点for begin..end as i、for slice as val、for slice,0.. as val,idx
*/
#[extern("vm")] fn vm_printf(fmt: *u8, ...) void;
fn main() i32 {
/* for 数值区间for 0..N as i */
var sum: i32 = 0;
for 0..5 as i {
sum = sum + i;
}
/* 0+1+2+3+4 = 10 */
if sum != 10 { ret 1; }
/* for 数值区间:非 0 起始 */
var sum2: i32 = 0;
for 3..7 as i {
sum2 = sum2 + i;
}
/* 3+4+5+6 = 18 */
if sum2 != 18 { ret 2; }
/* for 遍历切片 */
var arr: [4]i32 = [4]i32{10, 20, 30, 40};
var sl: []i32 = arr[0..];
var total: i32 = 0;
for sl as val {
total = total + val;
}
/* 10+20+30+40 = 100 */
if total != 100 { ret 3; }
/* for 遍历切片 + 索引 */
var arr2: [3]i32 = [3]i32{100, 200, 300};
var sl2: []i32 = arr2[0..];
var sum_val: i32 = 0;
var sum_idx: i32 = 0;
for sl2, 0.. as val, idx {
sum_val = sum_val + val;
sum_idx = sum_idx + idx;
}
if sum_val != 600 { ret 4; }
if sum_idx != 3 { ret 5; }
ret 0;
}

View File

@@ -0,0 +1,64 @@
/* ===== 模块4指针 =====
* test07_pointers — 指针操作
* 难度2/5
* 验证点:& 取地址、* 解引用、null 空指针、指针自动解引用(->)、.* 后缀解引用
*/
fn inc(ptr: *i32) void {
*ptr = *ptr + 1;
}
fn main() i32 {
/* ---- & 取地址和 * 解引用 ---- */
var x: i32 = 42;
var p: *i32 = &x;
var v := p.*;
if v != 42 { ret 1; }
/* 通过指针修改 */
p.* = 99;
if x != 99 { ret 2; }
/* 指针作为函数参数 */
var y: i32 = 5;
inc(&y);
if y != 6 { ret 3; }
/* ---- null 空指针 ---- */
var np: *i32 = null;
if np != null { ret 4; }
if np == null {} else { ret 5; }
/* ---- 指针自动解引用ptr.field 相当于 ptr->field ---- */
type Point = struct {
a: i32,
b: i32,
};
var pt: Point;
pt.a = 10;
pt.b = 20;
var pp: *Point = &pt;
if pp.a != 10 { ret 6; }
if pp.b != 20 { ret 7; }
/* 通过指针修改字段 */
pp.a = 30;
if pt.a != 30 { ret 8; }
/* ---- 指向数组元素的指针 ---- */
var buf: [4]i32 = [4]i32{1, 2, 3, 4};
// @dbg(); /* 数组初始化后的栈状态 */
var elem_ptr: *i32 = &buf[0];
// @dbg(); /* &buf[0] 之后:栈上应有指针值 */
var loaded := elem_ptr.*;
// @dbg(); /* 解引用后loaded 值 */
if loaded != 1 { ret 9; }
elem_ptr = &buf[1];
// @dbg(); /* &buf[1] 之后 */
var loaded2 := elem_ptr.*;
// @dbg(); /* 解引用后loaded2 值 */
if loaded2 != 2 { ret 10; }
ret 0;
}

38
stage1/test08_arrays.spl Normal file
View File

@@ -0,0 +1,38 @@
/* ===== 模块5复合类型 =====
* test08_arrays — 数组
* 难度2/5
* 验证点:数组字面量 [N]T{...}、数组索引、数组元素修改
*/
fn main() i32 {
/* 数组字面量 */
var arr: [3]i32 = [3]i32{1, 2, 3};
if arr[0] != 1 { ret 1; }
if arr[1] != 2 { ret 2; }
if arr[2] != 3 { ret 3; }
/* 数组元素修改 */
arr[1] = 99;
if arr[1] != 99 { ret 4; }
/* 数组求和 */
var nums: [5]i32 = [5]i32{10, 20, 30, 40, 50};
var sum: i32 = 0;
sum = sum + nums[0];
sum = sum + nums[1];
sum = sum + nums[2];
sum = sum + nums[3];
sum = sum + nums[4];
if sum != 150 { ret 5; }
/* 数组通过循环访问while */
var vals: [4]i32 = [4]i32{2, 4, 6, 8};
var i: i32 = 0;
var s: i32 = 0;
while i < 4 {
s = s + vals[i];
i = i + 1;
}
if s != 20 { ret 6; }
ret 0;
}

43
stage1/test09_slices.spl Normal file
View File

@@ -0,0 +1,43 @@
/* ===== 模块5复合类型 =====
* test09_slices — 切片
* 难度2/5
* 验证点arr[begin..end] 创建切片、arr[begin..] 到末尾、
* 切片索引访问、slice.len、slice.ptr
*/
fn main() i32 {
/* 从数组创建切片 */
var arr: [5]i32 = [5]i32{10, 20, 30, 40, 50};
var slice: []i32 = arr[1..4];
/* 切片长度 */
if slice.len != 3 { ret 1; }
/* 切片元素访问 */
if slice[0] != 20 { ret 2; }
if slice[1] != 30 { ret 3; }
if slice[2] != 40 { ret 4; }
/* 省略结束值arr[begin..] */
var full: []i32 = arr[0..];
if full.len != 5 { ret 5; }
if full[0] != 10 { ret 6; }
if full[4] != 50 { ret 7; }
/* 从开头到中间 */
var head: []i32 = arr[0..3];
if head.len != 3 { ret 8; }
if head[0] != 10 { ret 9; }
/* 切片遍历(配合 for*/
var nums: [3]i32 = [3]i32{100, 200, 300};
var sl: []i32 = nums[0..];
var total: i32 = 0;
var i: i32 = 0;
while i < sl.len {
total = total + sl[i];
i = i + 1;
}
if total != 600 { ret 10; }
ret 0;
}

64
stage1/test10_struct.spl Normal file
View File

@@ -0,0 +1,64 @@
/* ===== 模块5复合类型 =====
* test10_struct — 结构体
* 难度2/5
* 验证点struct 定义、字段访问、内联 type、嵌套结构体
*/
type Pair = struct {
a: i32,
b: i32,
}
fn main() i32 {
/* struct 字段访问 */
var p: Pair;
p.a = 1;
p.b = 2;
if p.a != 1 { ret 1; }
if p.b != 2 { ret 2; }
/* 同一类型复用 */
var p2: Pair;
p2.a = 10;
p2.b = 20;
if p2.a + p2.b != 30 { ret 3; }
/* 不影响其他实例 */
if p.a != 1 { ret 4; }
/* 内联 type 定义(在函数内) */
type Triple = struct {
x: i32,
y: i32,
z: i32,
}
var t: Triple;
t.x = 5;
t.y = 10;
t.z = 15;
if t.x + t.y + t.z != 30 { ret 5; }
/* 结构体字段运算 */
var calc: Pair;
calc.a = 7;
calc.b = 3;
var r := calc.a * calc.b + calc.a - calc.b;
if r != 25 { ret 6; }
/* 结构体嵌套 */
type Outer = struct {
inner: Inner,
extra: i32,
type Inner = struct {
val: i32,
}
}
var o: Outer;
o.inner.val = 42;
o.extra = 58;
if o.inner.val != 42 { ret 7; }
if o.extra != 58 { ret 8; }
ret 0;
}

29
stage1/test11_enum.spl Normal file
View File

@@ -0,0 +1,29 @@
/* ===== 模块5复合类型 =====
* test11_enum — 枚举
* 难度3/5
* 验证点enum 定义、简单枚举值、带数据枚举变体
*/
#[extern("vm")] fn vm_printf(fmt: *u8, ...) void;
type Color = enum {
Red,
Green,
Blue,
}
type Expr = enum {
val: i32,
tag: Tag,
type Tag = enum {
TagA,
TagB,
TagC,
}
}
fn main() i32 {
vm_printf("enum values: %d %d %d\n", Color.Red, Color.Green, Color.Blue);
vm_printf("enum values: %d %d %d\n", Expr.Tag.TagA, Expr.Tag.TagB, Expr.Tag.TagC);
ret 0;
}

View File

@@ -0,0 +1,55 @@
/* ===== 模块6函数 =====
* test12_functions — 函数调用
* 难度2/5
* 验证点:函数定义与调用、参数传递、返回值、多个参数
*/
fn add(a: i32, b: i32) i32 {
ret a + b;
}
fn sub(a: i32, b: i32) i32 {
ret a - b;
}
fn mul(a: i32, b: i32) i32 {
ret a * b;
}
fn identity(x: i32) i32 {
ret x;
}
fn addr(x: *i32) *i32 {
ret x;
}
fn main() i32 {
/* 函数调用 */
var r1 := add(3, 4);
if r1 != 7 { ret 1; }
var r2 := sub(10, 3);
if r2 != 7 { ret 2; }
var r3 := mul(6, 7);
if r3 != 42 { ret 3; }
/* 函数嵌套调用 */
var r4 := add(mul(2, 3), sub(10, 4));
if r4 != 12 { ret 4; }
/* 函数返回值的传递 */
var r5 := identity(99);
if r5 != 99 { ret 5; }
/* 多个参数 */
var r6 := add(add(1, 2), add(3, 4));
if r6 != 10 { ret 6; }
/* 返回地址 */
var r7 := addr(&r6);
if r7 != &r6 { ret 7; }
if r7.* != 10 { ret 8; }
ret 0;
}

35
stage1/test13_extern.spl Normal file
View File

@@ -0,0 +1,35 @@
/* ===== 模块6函数 =====
* test13_extern — 外部 VM 函数与字符串
* 难度2/5
* 验证点:#[extern("vm")] 声明、vm_printf 调用、
* 字符串字面量、字符串参数传递
*/
#[extern("vm")] fn vm_printf(fmt: *u8, ...) void;
#[extern("vm")] fn vm_strlen(s: *i8) i32;
#[extern("vm")] fn vm_strcmp(a: *i8, b: *i8) i32;
fn main() i32 {
/* vm_printf 输出测试 */
vm_printf("hello spl\n");
vm_printf("%d %d %d\n", 1, 2, 3);
vm_printf("chars: %c%c\n", 'A', 'B');
/* 字符串字面量用于 extern */
var len := vm_strlen("hello");
if len != 5 { ret 1; }
/* 字符串比较 */
var cmp := vm_strcmp("abc", "abc");
if cmp != 0 { ret 2; }
var cmp2 := vm_strcmp("abc", "def");
if cmp2 == 0 { ret 3; }
/* 字符串作为格式化参数 */
vm_printf("string test: %s %s\n", "foo", "bar");
/* vm_printf 返回 void不关心返回值*/
vm_printf("all extern tests done\n");
ret 0;
}

View File

@@ -0,0 +1,56 @@
/* ===== 模块7高级特性 =====
* test14_assignment — 复合赋值
* 难度2/5
* 验证点:+= -= *= /= %= &= |= ^= <<= >>=
*/
fn main() i32 {
var x: i32 = 10;
/* += */
x += 5;
if x != 15 { ret 1; }
/* -= */
x -= 3;
if x != 12 { ret 2; }
/* *= */
x *= 2;
if x != 24 { ret 3; }
/* /= */
x /= 4;
if x != 6 { ret 4; }
/* %= */
var y: i32 = 17;
y %= 5;
if y != 2 { ret 5; }
/* &= */
var a: i32 = 0xFF;
a &= 0x0F;
if a != 0x0F { ret 6; }
/* |= */
var b: i32 = 0xF0;
b |= 0x0F;
if b != 0xFF { ret 7; }
/* ^= */
var c: i32 = 0xFF;
c ^= 0xF0;
if c != 0x0F { ret 8; }
/* <<= */
var d: i32 = 1;
d <<= 4;
if d != 16 { ret 9; }
/* >>= */
var e: i32 = 64;
e >>= 3;
if e != 8 { ret 10; }
ret 0;
}

45
stage1/test15_defer.spl Normal file
View File

@@ -0,0 +1,45 @@
/* ===== 模块7高级特性 =====
* test15_defer — 延迟执行
* 难度3/5
* 验证点defer 语句、defer 块、多个 defer逆序执行
* 函数中 defer、块作用域 defer
*/
#[extern("vm")] fn vm_printf(fmt: *u8, ...) void;
fn with_cleanup() void {
defer vm_printf(" inner defer\n");
vm_printf(" inside with_cleanup\n");
}
fn main() i32 {
vm_printf("--- defer test ---\n");
/* defer 语句 */
defer vm_printf("defer1\n");
vm_printf("after defer1\n");
/* defer 块 */
defer {
vm_printf("defer block\n");
}
vm_printf("after defer block\n");
/* 函数内 defer */
with_cleanup();
vm_printf("after with_cleanup\n");
/* 多个 defer 应逆序执行出作用域应该立刻执行,包括循环作用域 */
{
defer vm_printf("block defer last\n");
defer vm_printf("block defer middle\n");
defer vm_printf("block defer first (should print third)\n");
}
/* 多个 defer 应逆序执行 */
defer vm_printf("defer last\n");
defer vm_printf("defer middle\n");
defer vm_printf("defer first (should print third)\n");
vm_printf("--- defer test end ---\n");
ret 0;
}

48
stage1/test16_methods.spl Normal file
View File

@@ -0,0 +1,48 @@
/* ===== 模块7高级特性 =====
* test16_methods — 类型关联方法
* 难度3/5
* 验证点struct 方法、enum 方法、self 参数自动填充、方法调用
*/
#[extern("vm")] fn vm_printf(fmt: *u8, ...) void;
type Point = struct {
x: i32,
y: i32,
fn init(x: i32, y: i32) Point {
ret Point { .x = x, .y = y };
}
fn dump(self: *Point) void {
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) => ret val,
.Add(left, right) => ret eval(left) + eval(right),
}
ret 0;
}
}
fn main() i32 {
/* struct 方法调用 */
var p: Point = Point.init(3, 4);
p.dump();
/* enum 方法 + match */
var expr_l := Expr { .Int = 3 };
var expr_r := Expr { .Int = 4 };
var expr := Expr { .Add = { .left = expr_l, .right = expr_r } };
var result := expr.eval(&expr);
vm_printf("eval result: %d\n", result);
if result != 7 { ret 1; }
ret 0;
}

View File

@@ -0,0 +1,95 @@
/* ===== 模块8综合 =====
* test17_integration — 综合测试
* 难度3/5
* 验证点:多特性组合——指针 + 结构体 + 函数 + 循环 + 数组 + 切片
*/
#[extern("vm")] fn vm_printf(fmt: *u8, ...) void;
type Point = struct {
x: i32,
y: i32,
}
/* 计算点积 */
fn dot(a: *Point, b: *Point) i32 {
ret a.x * b.x + a.y * b.y;
}
/* 递归斐波那契 */
fn fib(n: i32) i32 {
if n <= 1 { ret n; }
ret fib(n - 1) + fib(n - 2);
}
fn main() i32 {
/* 综合:指针 + 结构体 + 函数 */
var p1: Point;
p1.x = 3;
p1.y = 4;
var p2: Point;
p2.x = 5;
p2.y = 6;
var d := dot(&p1, &p2);
/* 3*5 + 4*6 = 15 + 24 = 39 */
if d != 39 { ret 1; }
/* 综合:数组 + while 循环 */
var arr: [5]i32 = [5]i32{1, 2, 3, 4, 5};
var sum: i32 = 0;
var i: i32 = 0;
while i < 5 {
sum = sum + arr[i];
i = i + 1;
}
if sum != 15 { ret 2; }
/* 综合:切片 + for */
var sl: []i32 = arr[1..4];
var sl_sum: i32 = 0;
for sl as val {
sl_sum = sl_sum + val;
}
if sl_sum != 9 { ret 3; } /* 2+3+4 */
/* 综合:递归 */
var f := fib(10);
if f != 55 { ret 4; }
/* 综合loop/break/continue */
var n: i32 = 0;
var count: i32 = 0;
loop {
n = n + 1;
if n == 2 { continue; }
count = count + 1;
if n >= 5 { break; }
}
/* 执行 n=1,3,4,5 共 4 次 (n=2 被 continue) */
if count != 4 { ret 5; }
/* 综合:指针修改结构体 */
var pp: *Point = &p1;
pp.x = 10;
pp.y = 20;
if p1.x != 10 { ret 6; }
if p1.y != 20 { ret 7; }
/* 综合:嵌套控制流 */
var mat: [3]i32 = [3]i32{1, 2, 3};
var outer_sum: i32 = 0;
var j: i32 = 0;
while j < 3 {
var k: i32 = 0;
while k < 3 {
if mat[j] > mat[k] {
outer_sum = outer_sum + 1;
}
k = k + 1;
}
j = j + 1;
}
if outer_sum != 3 { ret 8; }
ret 0;
}

488
stage1/test18_match.spl Normal file
View File

@@ -0,0 +1,488 @@
/* ===== 模块4match 语句 =====
* test18_match — match 枚举匹配 + 整数匹配(类似 switch
* 难度3/5
* 验证点:枚举匹配、带数据绑定、结构体字段绑定、枚举指针、
* 整数自变量匹配、默认分支 _
*/
/* ---- 简单枚举(无数据) ---- */
type Color = enum {
Red;
Green;
Blue;
}
/* ---- 带 i32 数据的枚举 ---- */
type Optional = enum {
Some: i32;
None;
}
/* ---- 带结构体数据的枚举 ---- */
type Point = struct {
var x: i32;
var y: i32;
}
type Shape = enum {
Circle: i32;
Rect: Point;
}
/* ---- 多变体枚举 ---- */
type ActionResult = enum {
Success: i32;
NotFound;
Timeout: i32;
Error: *u8;
}
/* ============================================================
* 测试 1: 无数据枚举匹配
* ============================================================ */
fn test_color_match() i32 {
var c: Color = Color { .Red };
var val: i32 = 0;
match c {
.Red => { val = 1; },
.Green => { val = 2; },
.Blue => { val = 3; }
}
if val != 1 { ret 1; }
c = Color { .Green };
match c {
.Red => { val = 0; },
.Green => { val = 2; },
.Blue => { val = 0; }
}
if val != 2 { ret 2; }
c = Color { .Blue };
match c {
.Red => { val = 0; },
.Green => { val = 0; },
.Blue => { val = 3; }
}
if val != 3 { ret 3; }
ret 0;
}
/* ============================================================
* 测试 2: 带 i32 数据枚举匹配
* ============================================================ */
fn test_optional_match() i32 {
var o: Optional = Optional { .Some = 42 };
match o {
.Some(val) => {
if val != 42 { ret 1; }
},
.None => {
ret 2;
}
}
o = Optional { .None };
var is_none: i32 = 0;
match o {
.Some(val) => {},
.None => { is_none = 1; }
}
if is_none != 1 { ret 3; }
/* 多次提取不同值 */
o = Optional { .Some = 99 };
match o {
.Some(val) => {
if val != 99 { ret 4; }
},
.None => { ret 5; }
}
ret 0;
}
/* ============================================================
* 测试 3: 带结构体数据枚举匹配(多字段绑定)
* ============================================================ */
fn test_shape_match() i32 {
/* Circle: 单数据 */
var s: Shape = Shape { .Circle = 10 };
match s {
.Circle(r) => {
if r != 10 { ret 1; }
},
.Rect(w, h) => {
ret 2;
}
}
/* Rect: 结构体数据,绑定为 (x, y) 对应 Point 的字段 */
s = Shape { .Rect = Point { .x = 3, .y = 4 } };
match s {
.Circle(r) => { ret 3; },
.Rect(w, h) => {
if w != 3 { ret 4; }
if h != 4 { ret 5; }
}
}
ret 0;
}
/* ============================================================
* 测试 4: 多数据变体枚举匹配
* ============================================================ */
fn test_action_result_match() i32 {
var r: ActionResult = ActionResult { .Success = 200 };
match r {
.Success(code) => {
if code != 200 { ret 1; }
},
.NotFound => {
ret 2;
},
.Timeout(ms) => {
ret 3;
},
.Error(msg) => {
ret 4;
}
}
r = ActionResult { .NotFound };
var found: i32 = 1;
match r {
.Success(code) => { found = 0; },
.NotFound => { },
.Timeout(ms) => { found = 0; },
.Error(msg) => { found = 0; }
}
if found != 1 { ret 5; }
r = ActionResult { .Timeout = 5000 };
match r {
.Success(code) => { ret 6; },
.NotFound => { ret 7; },
.Timeout(ms) => {
if ms != 5000 { ret 8; }
},
.Error(msg) => { ret 9; }
}
ret 0;
}
/* ============================================================
* 测试 5: 枚举指针匹配
* ============================================================ */
fn test_ptr_match() i32 {
var c: Color = Color { .Green };
var p: *Color = &c;
match p {
.Red => { ret 1; },
.Green => { },
.Blue => { ret 2; }
}
/* 修改后通过指针匹配 */
c = Color { .Blue };
match p {
.Red => { ret 3; },
.Green => { ret 4; },
.Blue => { }
}
ret 0;
}
/* ============================================================
* 测试 6: match 作为函数返回值
* ============================================================ */
fn classify_color(c: Color) i32 {
match c {
.Red => { ret 1; },
.Green => { ret 2; },
.Blue => { ret 3; }
}
ret 0;
}
fn test_match_in_func() i32 {
var r: i32;
r = classify_color(Color { .Red });
if r != 1 { ret 1; }
r = classify_color(Color { .Green });
if r != 2 { ret 2; }
r = classify_color(Color { .Blue });
if r != 3 { ret 3; }
ret 0;
}
/* ============================================================
* 测试 7: match 嵌套在循环中
* ============================================================ */
fn test_match_in_loop() i32 {
var i: i32 = 0;
var sum: i32 = 0;
while i < 3 {
var o: Optional;
if i == 0 {
o = Optional { .Some = 10 };
} else if i == 1 {
o = Optional { .Some = 20 };
} else {
o = Optional { .Some = 30 };
}
match o {
.Some(val) => {
sum = sum + val;
},
.None => { }
}
i = i + 1;
}
if sum != 60 { ret 1; }
ret 0;
}
/* ============================================================
* 测试 8: 整数 match类似 switch
* ============================================================ */
fn test_int_match() i32 {
var x: i32 = 2;
var result: i32 = 0;
match x {
1 => { result = 10; },
2 => { result = 20; },
3 => { result = 30; }
}
if result != 20 { ret 1; }
/* 匹配第一个值 */
x = 1;
match x {
1 => { result = 100; },
2 => { result = 200; },
3 => { result = 300; }
}
if result != 100 { ret 2; }
/* 匹配最后一个值 */
x = 3;
match x {
1 => { result = 1000; },
2 => { result = 2000; },
3 => { result = 3000; }
}
if result != 3000 { ret 3; }
ret 0;
}
/* ============================================================
* 测试 9: 整数 match 带默认分支 _
* ============================================================ */
fn test_int_match_default() i32 {
var x: i32 = 99;
var result: i32 = 0;
match x {
1 => { result = 1; },
2 => { result = 2; },
_ => { result = 99; }
}
if result != 99 { ret 1; }
/* 默认分支未触发 */
x = 1;
match x {
1 => { result = 1; },
2 => { result = 2; },
_ => { result = 99; }
}
if result != 1 { ret 2; }
ret 0;
}
/* ============================================================
* 测试 10: 整数 match 多个值跳转到相同逻辑
* ============================================================ */
fn test_int_match_multi() i32 {
var x: i32 = 2;
var result: i32 = 0;
/* 每个分支独立 */
match x {
0 => { result = 0; },
1 => { result = 1; },
2 => { result = 2; },
3 => { result = 3; }
}
if result != 2 { ret 1; }
/* 负数和零 */
x = -1;
match x {
-1 => { result = -1; },
0 => { result = 0; },
1 => { result = 1; }
}
if result != -1 { ret 2; }
x = 0;
match x {
-1 => { result = -1; },
0 => { result = 0; },
1 => { result = 1; }
}
if result != 0 { ret 3; }
ret 0;
}
/* ============================================================
* 测试 11a: match 多值 fallthrough: 1, 2, 3 => body
* ============================================================ */
fn test_int_match_fallthrough() i32 {
var x: i32;
var r: i32;
/* 三个值映射到同一个 body */
x = 1; r = 0;
match x {
1, 2, 3 => { r = 10; },
4, 5 => { r = 20; }
}
if r != 10 { ret 1; }
x = 3;
match x {
1, 2, 3 => { r = 10; },
4, 5 => { r = 20; }
}
if r != 10 { ret 2; }
x = 5;
match x {
1, 2, 3 => { r = 10; },
4, 5 => { r = 20; }
}
if r != 20 { ret 3; }
/* 单一值(非fallthrough)仍然正常 */
x = 7; r = 0;
match x {
1 => { r = 1; },
7 => { r = 7; }
}
if r != 7 { ret 4; }
/* 多个负数值 */
x = -2; r = 0;
match x {
-3, -2, -1 => { r = 100; },
0, 1 => { r = 200; }
}
if r != 100 { ret 5; }
ret 0;
}
/* ============================================================
* 测试 11b: match with body 中修改变量
* ============================================================ */
fn test_match_with_side_effects() i32 {
var x: i32 = 3;
var acc: i32 = 0;
match x {
1 => { acc = acc + 1; },
2 => { acc = acc + 2; },
3 => { acc = acc + 3; },
4 => { acc = acc + 4; }
}
if acc != 3 { ret 1; }
/* 再次 match 同一个变量 */
match x {
1 => { acc = acc + 1; },
2 => { acc = acc + 2; },
3 => { acc = acc + 3; },
4 => { acc = acc + 4; }
}
if acc != 6 { ret 2; }
ret 0;
}
/* ============================================================
* 主函数
* ============================================================ */
fn main() i32 {
var r: i32;
r = test_color_match();
if r != 0 { ret r; }
r = test_optional_match();
if r != 0 { ret r + 10; }
r = test_shape_match();
if r != 0 { ret r + 20; }
r = test_action_result_match();
if r != 0 { ret r + 30; }
r = test_ptr_match();
if r != 0 { ret r + 40; }
r = test_match_in_func();
if r != 0 { ret r + 50; }
r = test_match_in_loop();
if r != 0 { ret r + 60; }
r = test_int_match();
if r != 0 { ret r + 70; }
r = test_int_match_default();
if r != 0 { ret r + 80; }
r = test_int_match_multi();
if r != 0 { ret r + 90; }
r = test_match_with_side_effects();
if r != 0 { ret r + 100; }
r = test_int_match_fallthrough();
if r != 0 { ret r + 110; }
ret 0;
}

184
stage1/test19_hardarray.spl Normal file
View File

@@ -0,0 +1,184 @@
/* ===== 进阶数组与切片 =====
* test19_hardarray — 多维数组、多维切片、切片构造、字符串测试
* 难度4/5
* 验证点:多维数组索引与修改、多维切片、切片字段读写、扁平指针访问、字符串切片
*/
fn test_string() i32 {
/* ============================
* String test: str is []u8
* ============================ */
var data: *u8 = "hello";
var s: []u8 = { .ptr = data, .len = 5 };
if s.len != 5 { ret 100; }
if s[0] != 104 { ret 101; } /* 'h' */
if s[1] != 101 { ret 102; } /* 'e' */
if s[4] != 111 { ret 103; } /* 'o' */
/* Slice the string slice */
var sub: []u8 = s[1..4];
if sub.len != 3 { ret 104; }
if sub[0] != 101 { ret 105; } /* 'e' */
if sub[2] != 108 { ret 106; } /* 'l' */
/* Modify through slice → original changes */
s[0] = 72; /* 'H' */
if data[0] != 72 { ret 107; }
/* Full slice */
var full: []u8 = s[0..];
if full.len != 5 { ret 108; }
if full[0] != 72 { ret 109; }
/* Construct slice from pointer via field assignment */
var s2: []u8;
s2.ptr = data;
s2.len = 3;
if s2.len != 3 { ret 110; }
if s2[0] != 72 { ret 111; }
ret 0;
}
fn main() i32 {
/* ============================
* Part 1: 多维数组(逐元素初始化)
* ============================ */
var matrix: [2][3]i32;
matrix[0][0] = 1; matrix[0][1] = 2; matrix[0][2] = 3;
matrix[1][0] = 4; matrix[1][1] = 5; matrix[1][2] = 6;
if matrix[0][0] != 1 { ret 1; }
if matrix[0][1] != 2 { ret 2; }
if matrix[0][2] != 3 { ret 3; }
if matrix[1][0] != 4 { ret 4; }
if matrix[1][1] != 5 { ret 5; }
if matrix[1][2] != 6 { ret 6; }
/* 元素修改 */
matrix[0][0] = 10;
matrix[1][2] = 60;
if matrix[0][0] != 10 { ret 7; }
if matrix[1][2] != 60 { ret 8; }
/* ============================
* Part 2: 嵌套循环遍历多维数组
* ============================ */
var big: [3][4]i32;
big[0][0] = 1; big[0][1] = 2; big[0][2] = 3; big[0][3] = 4;
big[1][0] = 5; big[1][1] = 6; big[1][2] = 7; big[1][3] = 8;
big[2][0] = 9; big[2][1] = 10; big[2][2] = 11; big[2][3] = 12;
var total: i32 = 0;
var i: i32 = 0;
while i < 3 {
var j: i32 = 0;
while j < 4 {
total = total + big[i][j];
j = j + 1;
}
i = i + 1;
}
if total != 78 { ret 9; }
/* ============================
* Part 3: &取地址 + 扁平指针访问
* ============================ */
var flat: *i32 = &matrix[0][0];
if flat[0] != 10 { ret 10; }
if flat[1] != 2 { ret 11; }
if flat[2] != 3 { ret 12; }
if flat[3] != 4 { ret 13; }
if flat[4] != 5 { ret 14; }
if flat[5] != 60 { ret 15; }
/* ============================
* Part 4: 多维数组切片
* ============================ */
var row: []i32 = matrix[0][0..3];
if row.len != 3 { ret 16; }
if row[0] != 10 { ret 17; }
if row[1] != 2 { ret 18; }
if row[2] != 3 { ret 19; }
/* 切取第二行 */
var row2: []i32 = matrix[1][0..];
if row2.len != 3 { ret 20; }
if row2[0] != 4 { ret 21; }
if row2[2] != 60 { ret 22; }
/* ============================
* Part 5: 切片的切片
* ============================ */
var sub: []i32 = row[1..3];
if sub.len != 2 { ret 23; }
if sub[0] != 2 { ret 24; }
if sub[1] != 3 { ret 25; }
/* ============================
* Part 6: 切片字段读写
* ============================ */
var arr: [4]i32;
arr[0] = 100; arr[1] = 200; arr[2] = 300; arr[3] = 400;
var custom: []i32;
custom.ptr = &arr[1];
custom.len = 2;
if custom.len != 2 { ret 26; }
if custom[0] != 200 { ret 27; }
if custom[1] != 300 { ret 28; }
/* 修改切片长度 */
custom.len = 3;
if custom.len != 3 { ret 29; }
if custom[2] != 400 { ret 30; }
/* ============================
* Part 7: 空范围 / 全切片
* ============================ */
var full: []i32 = arr[0..];
if full.len != 4 { ret 31; }
if full[0] != 100 { ret 32; }
if full[3] != 400 { ret 33; }
/* ============================
* Part 8: 从指针构造切片
* ============================ */
var data: [5]i32;
data[0] = 10; data[1] = 20; data[2] = 30; data[3] = 40; data[4] = 50;
var p: *i32 = &data[2];
var from_ptr: []i32 = { .ptr = p, .len = 2 };
var from_ptr2: []i32;
from_ptr2.ptr = p;
from_ptr2.len = 2;
if from_ptr.len != 2 { ret 34; }
if from_ptr[0] != 30 { ret 35; }
if from_ptr[1] != 40 { ret 36; }
if from_ptr2.len != 2 { ret 37; }
if from_ptr2[0] != 30 { ret 38; }
if from_ptr2[1] != 40 { ret 39; }
/* ============================
* Part 9: 切片元素修改反映到原始数组
* ============================ */
row2[0] = 99;
if matrix[1][0] != 99 { ret 40; }
/* ============================
* Part 10: 1D 数组字面量仍然正常
* ============================ */
var literal: [3]i32 = [3]i32{10, 20, 30};
if literal[0] != 10 { ret 41; }
if literal[1] != 20 { ret 42; }
if literal[2] != 30 { ret 43; }
/* ============================
* Part 11: 字符串切片测试
* ============================ */
var r: i32 = test_string();
if r != 0 { ret r; }
ret 0;
}

320
stage1/test20_complex.spl Normal file
View File

@@ -0,0 +1,320 @@
/* ===== 复杂类型嵌套综合测试 =====
* test20_complex — 结构体嵌套、切片、数组、方法、类型别名、枚举等
* 难度5/5
*/
/* ---- 基础结构体 ---- */
type Point = struct {
var x: i32;
var y: i32;
}
/* ---- 嵌套结构体 ---- */
type Rect = struct {
var min: Point;
var max: Point;
}
/* ---- 含切片字段的结构体 (核心 bug 测试) ---- */
type Buffer = struct {
var data: []u8;
var len: usize;
}
/* ---- 含数组字段的结构体 ---- */
type MatrixRow = struct {
var items: [4]i32;
}
/* ---- 含指针字段的结构体 ---- */
type Node = struct {
var ptr: *i32;
var val: i32;
}
/* ---- 多层级嵌套:结构体里的结构体里的切片 ---- */
type Bundle = struct {
var name: *u8;
var buf: Buffer;
var row: MatrixRow;
var pt: Point;
}
/* ---- 枚举含数据 ---- */
type Status = enum {
Active: i32;
Inactive;
Pending: Point;
}
/* ---- 含方法的结构体 (方法定义在结构体内部) ---- */
type Counter = struct {
var val: i32;
fn inc(self: *Counter) i32 {
self.val = self.val + 1;
ret self.val;
}
fn add(self: *Counter, n: i32) i32 {
self.val = self.val + n;
ret self.val;
}
fn reset(self: *Counter) {
self.val = 0;
}
fn get(self: *Counter) i32 {
ret self.val;
}
}
/* ============================================================
* 测试 1: 切片在结构体内部初始化 (修复的核心 bug)
* ============================================================ */
fn test_slice_in_struct() i32 {
var raw: [4]u8;
raw[0] = 65; raw[1] = 66; raw[2] = 67; raw[3] = 68;
/* Bug fix: { .ptr = ..., .len = ... } inside struct literal */
var b: Buffer = Buffer { .data = { .ptr = &raw[0], .len = 4 }, .len = 4 };
if b.len != 4 { ret 1; }
if b.data[0] != 65 { ret 2; }
if b.data[1] != 66 { ret 3; }
if b.data[3] != 68 { ret 4; }
/* Modify through slice — verify reflection */
b.data[0] = 90;
if raw[0] != 90 { ret 5; }
/* Initialize with shorter slice */
var b2: Buffer = Buffer { .data = { .ptr = &raw[2], .len = 2 }, .len = 2 };
if b2.len != 2 { ret 6; }
if b2.data[0] != 67 { ret 7; }
/* Slice field assignment via field access */
b2.data.ptr = &raw[0];
b2.data.len = 4;
if b2.data[0] != 90 { ret 8; }
if b2.data.len != 4 { ret 9; }
ret 0;
}
/* ============================================================
* 测试 2: 结构体含数组字段
* ============================================================ */
fn test_struct_with_array() i32 {
var mr: MatrixRow = MatrixRow { .items = [4]i32{10, 20, 30, 40} };
if mr.items[0] != 10 { ret 1; }
if mr.items[1] != 20 { ret 2; }
if mr.items[2] != 30 { ret 3; }
if mr.items[3] != 40 { ret 4; }
/* 修改数组元素 */
mr.items[2] = 99;
if mr.items[2] != 99 { ret 5; }
ret 0;
}
/* ============================================================
* 测试 3: 嵌套结构体初始化
* ============================================================ */
fn test_nested_struct() i32 {
var p: Point = Point { .x = 5, .y = 10 };
if p.x != 5 { ret 1; }
if p.y != 10 { ret 2; }
/* 嵌套结构体字面量 */
var r: Rect = Rect {
.min = Point { .x = 1, .y = 2 },
.max = Point { .x = 3, .y = 4 }
};
if r.min.x != 1 { ret 3; }
if r.min.y != 2 { ret 4; }
if r.max.x != 3 { ret 5; }
if r.max.y != 4 { ret 6; }
/* 修改嵌套字段 */
r.min.x = 100;
if r.min.x != 100 { ret 7; }
if r.min.y != 2 { ret 8; } /* unchanged */
ret 0;
}
/* ============================================================
* 测试 4: 结构体成员方法 (实例方法调用)
* ============================================================ */
fn test_struct_method() i32 {
var c: Counter = Counter { .val = 0 };
/* 实例方法调用 c.inc() */
var r1: i32 = c.inc();
if r1 != 1 { ret 1; }
if c.val != 1 { ret 2; }
/* 带参数方法调用 c.add(n) */
var r2: i32 = c.add(5);
if r2 != 6 { ret 3; }
if c.val != 6 { ret 4; }
/* 连续调用 */
c.reset();
if c.val != 0 { ret 5; }
c.add(10);
c.inc();
var r3: i32 = c.get();
if r3 != 11 { ret 6; }
ret 0;
}
/* ============================================================
* 测试 5: 结构体含指针字段
* ============================================================ */
fn test_ptr_in_struct() i32 {
var v: i32 = 42;
var n: Node = Node { .ptr = &v, .val = 99 };
if n.val != 99 { ret 1; }
if n.ptr[0] != 42 { ret 2; }
/* 通过指针修改 */
v = 100;
if n.ptr[0] != 100 { ret 3; }
/* 通过指针在结构体内修改 */
n.ptr[0] = 200;
if v != 200 { ret 4; }
ret 0;
}
/* ============================================================
* 测试 6: 多层级复杂嵌套
* ============================================================ */
fn test_complex_nesting() i32 {
var str_data: [5]u8;
str_data[0] = 72; str_data[1] = 101;
str_data[2] = 108; str_data[3] = 108; str_data[4] = 111;
var bundle: Bundle = Bundle {
.name = &str_data[0],
.buf = Buffer { .data = { .ptr = &str_data[1], .len = 3 }, .len = 3 },
.row = MatrixRow { .items = [4]i32{1, 2, 3, 4} },
.pt = Point { .x = -5, .y = 15 }
};
/* Verify name */
if bundle.name[0] != 72 { ret 1; }
if bundle.name[4] != 111 { ret 2; }
/* Verify nested slice */
if bundle.buf.len != 3 { ret 3; }
if bundle.buf.data[0] != 101 { ret 4; }
if bundle.buf.data[2] != 108 { ret 5; }
/* Verify array field */
if bundle.row.items[0] != 1 { ret 6; }
if bundle.row.items[3] != 4 { ret 7; }
/* Verify nested struct field */
if bundle.pt.x != -5 { ret 8; }
if bundle.pt.y != 15 { ret 9; }
/* Modify nested slice */
bundle.buf.data[1] = 87; /* 'W' */
if str_data[2] != 87 { ret 10; }
/* Modify nested array */
bundle.row.items[2] = 33;
if bundle.row.items[2] != 33 { ret 11; }
ret 0;
}
/* ============================================================
* 测试 7: 枚举含结构体数据
* ============================================================ */
fn test_enum_complex() i32 {
var s: Status = Status { .Active = 42 };
/* Verify active variant */
match s {
.Active(val) => {
if val != 42 { ret 1; }
},
.Inactive => {
ret 2;
},
.Pending(px, py) => {
ret 3;
}
}
/* Test Inactive variant */
var s2: Status = Status { .Inactive };
var is_inactive: i32 = 0;
match s2 {
.Active(val) => {},
.Inactive => { is_inactive = 1; },
.Pending(px, py) => {}
}
if is_inactive != 1 { ret 4; }
/* Test Pending variant with struct data */
var s3: Status = Status { .Pending = Point { .x = 7, .y = 8 } };
match s3 {
.Active(val) => { ret 5; },
.Inactive => { ret 6; },
.Pending(px, py) => {
if px != 7 { ret 7; }
if py != 8 { ret 8; }
}
}
ret 0;
}
/* ============================================================
* 主函数
* ============================================================ */
fn main() i32 {
var r: i32;
r = test_slice_in_struct();
if r != 0 { ret r; }
r = test_struct_with_array();
if r != 0 { ret r + 100; }
r = test_nested_struct();
if r != 0 { ret r + 200; }
r = test_struct_method();
if r != 0 { ret r + 300; }
r = test_ptr_in_struct();
if r != 0 { ret r + 400; }
r = test_complex_nesting();
if r != 0 { ret r + 500; }
r = test_enum_complex();
if r != 0 { ret r + 600; }
ret 0;
}