Compare commits
15 Commits
main
...
53ae30f1ca
| Author | SHA1 | Date | |
|---|---|---|---|
| 53ae30f1ca | |||
| 147f26e063 | |||
| 463177d3be | |||
| 1df3e3bcb4 | |||
| 3cf11f922e | |||
| 69cea030dc | |||
| 0892c084ee | |||
| ad473f245c | |||
| 0182b8ed5c | |||
| 777b6b42d1 | |||
| 5dadf6d6ee | |||
| 67c8a137dd | |||
| e2e0ebc21f | |||
| 51d8510b79 | |||
| 50b07074fb |
417
build.py
Normal file
417
build.py
Normal 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
52
project_desc.py
Normal 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
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
175
stage0/include/core_map.h
Normal 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
258
stage0/include/core_vec.h
Normal 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
55
stage0/spl_cli.c
Normal 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
76
stage0/spl_disasm.c
Normal 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
478
stage0/spl_ir.c
Normal 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
222
stage0/spl_ir.h
Normal 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
397
stage0/spl_syscall.c
Normal 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
22
stage0/spl_syscall.h
Normal 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
1115
stage0/spl_vm.c
Normal file
File diff suppressed because it is too large
Load Diff
67
stage0/spl_vm.h
Normal file
67
stage0/spl_vm.h
Normal 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
876
stage0/test_spl_vm.c
Normal 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
452
stage1/spl.md
Normal file
@@ -0,0 +1,452 @@
|
||||
# SPL — 语法与语义
|
||||
|
||||
## 概述
|
||||
|
||||
类 C 语法的系统编程语言,受 Rust/Zig 启发。编译为 SIR(SPL 中间表示),一种基于栈的字节码。多阶段引导:
|
||||
|
||||
```
|
||||
C → splc0(C) → splc1(SPL) → splc2(SPL) → ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 词法结构
|
||||
|
||||
### 注释
|
||||
```
|
||||
// 行注释
|
||||
/* 块注释 */
|
||||
```
|
||||
|
||||
### 标识符
|
||||
`[a-zA-Z_][a-zA-Z0-9_]*`
|
||||
|
||||
### 关键字
|
||||
```
|
||||
as asm bool break catch comptime const continue
|
||||
defer else enum errdefer false fn for
|
||||
if loop match null ret struct
|
||||
test true try type union var void
|
||||
while _
|
||||
```
|
||||
|
||||
### 字面量
|
||||
|
||||
| 种类 | 示例 | 类型 |
|
||||
|-------------|---------------------------------|---------|
|
||||
| 整数 | `42` `0xFF` `0b1010` `0o77` | i32 |
|
||||
| 字符 | `'A'` `'\n'` `'\\'` | i32 |
|
||||
| 字符串 | `"hello"` `"line\n"` | i8* |
|
||||
| 布尔 | `true` `false` | bool |
|
||||
| 空 | `null` | null/0/undefinded |
|
||||
|
||||
### 运算符
|
||||
|
||||
| 类别 | 符号 |
|
||||
|------------|------------------------------------------------------------|
|
||||
| 算术 | `+` `-` `*` `/` `%` |
|
||||
| 位运算 | `&` `\|` `^` `~` `<<` `>>` |
|
||||
| 比较 | `==` `!=` `<` `<=` `>` `>=` |
|
||||
| 逻辑 | `&&` `\|\|` `!` |
|
||||
| 赋值 | `=` `+=` `-=` `*=` `/=` `%=` `&=` `\|=` `^=` `<<=` `>>=` |
|
||||
| 其他 | `&`(取地址) `*`(解引用) `.` `->` `<-` `..` `...` `:` `:=` `?` |
|
||||
|
||||
---
|
||||
|
||||
## 类型系统
|
||||
|
||||
### 基本类型
|
||||
|
||||
| 名称 | SIR 类型 | 大小(字节) |
|
||||
|--------|-------------|-------------|
|
||||
| void | SPL_VOID | 0 |
|
||||
| bool | SPL_I32 | 4 |
|
||||
| i8 | SPL_I8 | 1 |
|
||||
| u8 | SPL_U8 | 1 |
|
||||
| i16 | SPL_I16 | 2 |
|
||||
| u16 | SPL_U16 | 2 |
|
||||
| i32 | SPL_I32 | 4 |
|
||||
| u32 | SPL_U32 | 4 |
|
||||
| i64 | SPL_I64 | 8 |
|
||||
| u64 | SPL_U64 | 8 |
|
||||
| isize | SPL_ISIZE | sizeof(ptr) |
|
||||
| usize | SPL_USIZE | sizeof(ptr) |
|
||||
| f32 | SPL_F32 | 4 |
|
||||
| f64 | SPL_F64 | 8 |
|
||||
| ptr | SPL_PTR | 8 |
|
||||
| _ | SPL_ ... | (推断) |
|
||||
|
||||
`_` 是通配符类型——用作类型推断的占位符。在大多数声明上下文中无效。
|
||||
|
||||
### 指针类型
|
||||
写作 `*T`。示例:`*i32`、`*u8`、`*void`。
|
||||
|
||||
同类型指针会去重。
|
||||
|
||||
### 数组类型
|
||||
写作 `[N]T`。示例:`[10]i32`。数组是值类型(存在于栈槽或全局数据中)。`T` 可以是任意类型。
|
||||
|
||||
数组/复合类型 在VM堆上申请内存 `N * sizeof(T)` 。
|
||||
|
||||
### 切片类型
|
||||
写作 `[]T`。示例:`[]i32`、`[]u8`。切片是数组的一段连续视图,底层实现为胖指针:
|
||||
|
||||
```
|
||||
[]T = struct { ptr: *T, len: i32 }
|
||||
```
|
||||
|
||||
栈上占 **2 个槽位**(指针 + 长度)。通过切片表达式从数组创建:
|
||||
|
||||
```
|
||||
var arr: [10]i32 = ...;
|
||||
var slice: []i32 = arr[3..7]; // 取 arr[3..7) 视图
|
||||
var full: []i32 = arr[0..]; // 省略结束值 = 到末尾
|
||||
```
|
||||
|
||||
此外,切片也可以从另一个切片再切片得到,长度限制为原切片的 len。
|
||||
|
||||
### 聚合类型
|
||||
```
|
||||
type Name = struct { field: Type, ... };
|
||||
type Name = union { field: Type, ... };
|
||||
type Name = enum { A, B, C, ... };
|
||||
```
|
||||
|
||||
- **struct**:字段按顺序排列(默认由 SPL 定义布局,可通过 `#[extern("vm")]` 覆盖)
|
||||
- **union**:所有字段共享同一偏移(sizeof = 最大字段大小)
|
||||
- **enum**:无字段,值为从 0 开始的整数常量
|
||||
|
||||
聚合类型在堆上分配,如果需要栈分配那么栈上占用 `size` 个槽位(struct 为各字段大小之和,union 为最大字段大小,enum 为 1)。
|
||||
|
||||
`type Name = ExistingType;` 形式用于定义简单类型别名,但目前仅支持聚合类型的别名定义。
|
||||
|
||||
### 聚合类型拓展与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` 必须求值为 bool(i32),或者成功语义。`statement` 可以是块 `{ }`。
|
||||
|
||||
实现:`BZ` 跳转到 else 分支,`JMP` 跳过 else 分支。
|
||||
|
||||
### While
|
||||
```
|
||||
while expr { ... }
|
||||
```
|
||||
实现:在循环顶部求值 `expr`,`BZ` 跳转到循环结束,循环体末尾 `JMP` 跳回顶部。
|
||||
|
||||
### Loop
|
||||
```
|
||||
loop { ... }
|
||||
```
|
||||
无限循环。使用 `break` 退出,`continue` 重新开始。
|
||||
|
||||
### Break / Continue
|
||||
```
|
||||
break;
|
||||
continue;
|
||||
```
|
||||
break 通过链表记录所有跳出位置,在循环结束后统一回填目标地址。
|
||||
|
||||
### Defer
|
||||
```
|
||||
defer { ... }
|
||||
defer statement;
|
||||
```
|
||||
作用域退出时延迟执行
|
||||
|
||||
### For Range
|
||||
```
|
||||
for 0..N as i { body }
|
||||
for slice as val { body }
|
||||
for slice, 0.. as val, idx { body }
|
||||
```
|
||||
Range for 循环。支持三种形式:
|
||||
|
||||
1. **数值区间** `for begin..end as i`:`i` 从 `begin` 到 `end-1`,步长 1
|
||||
2. **切片遍历** `for slice as val`:遍历切片的每个元素
|
||||
3. **带索引的切片遍历** `for slice, 0.. as val, idx`:同时获得元素值和索引
|
||||
|
||||
展开实现:
|
||||
```
|
||||
// for 0..N as i { body }
|
||||
var i: i32 = 0;
|
||||
while i < N {
|
||||
// body...
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
// for slice, 0.. as val, idx { body }
|
||||
var idx: i32 = 0;
|
||||
var _len: i32 = slice.len;
|
||||
var _ptr: *T = slice.ptr;
|
||||
while idx < _len {
|
||||
var val: T = _ptr[idx];
|
||||
// body...
|
||||
idx = idx + 1;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 表达式(优先级爬升)
|
||||
|
||||
### 运算符优先级表
|
||||
|
||||
| 优先级 | 运算符 | 结合性 |
|
||||
|--------|-------------------------|----------|
|
||||
| 1 | `\|\|` | 左结合 |
|
||||
| 2 | `&&` | 左结合 |
|
||||
| 3 | `\|` | 左结合 |
|
||||
| 4 | `^` | 左结合 |
|
||||
| 5 | `&` | 左结合 |
|
||||
| 6 | `==` `!=` | 左结合 |
|
||||
| 7 | `<` `<=` `>` `>=` | 左结合 |
|
||||
| 8 | `<<` `>>` | 左结合 |
|
||||
| 9 | `+` `-` | 左结合 |
|
||||
| 10 | `*` `/` `%` | 左结合 |
|
||||
| 前缀 | `-` `!` `~` `&` `*` | 右结合 |
|
||||
| 后缀 | `.field` `[expr]` | 左结合 |
|
||||
|
||||
**`||` 和 `&&` 的短路求值**:`||` 用 `BNZ` 左侧为真时跳过右侧;`&&` 用 `BZ` 左侧为假时跳过右侧。
|
||||
|
||||
### 主要表达式
|
||||
```
|
||||
字面量 → PUSH 立即数
|
||||
标识符 → LADDR 局部变量地址(可选 LD64 取值)
|
||||
标识符(args) → 函数调用
|
||||
(expr) → 分组
|
||||
```
|
||||
|
||||
- 标识符引用局部变量时:数组/聚合类型压入地址,其他类型压入值(LADDR + LD64)
|
||||
- 函数调用时 push 参数(从左到右),push 函数地址,CALL nargs
|
||||
|
||||
### 后缀表达式
|
||||
```
|
||||
expr.field → 结构体成员访问(计算字节偏移)(可以单层解引用即 推断c语言的 -> 但是只能单层)
|
||||
expr[expr] → 数组/指针索引
|
||||
expr[begin..end] → 切片表达式(从数组/切片创建视图)
|
||||
expr.* → 解引用(LD64)
|
||||
```
|
||||
|
||||
- **`.field`**:根据类型布局计算字节偏移。结构体:地址 + 偏移,嵌套结构体保留地址。指针指向的结构体:先 LD64 解引用获取堆地址,再加字段偏移。
|
||||
- **`[expr]`**:指针索引用元素字节大小做乘法,数组索引用槽位大小做乘法。最后 ADD 得到元素地址,LD* 加载值。
|
||||
|
||||
### 前缀表达式
|
||||
```
|
||||
-expr → 算术取反(NEG)
|
||||
!expr → 逻辑非(expr == 0 → EQ + PUSH 0)
|
||||
~expr → 按位取反(NOT)
|
||||
&expr → 取地址(LADDR,仅限标识符)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 调用约定
|
||||
|
||||
> 见 ../stage0/spl_ir.h
|
||||
|
||||
---
|
||||
|
||||
## SIR 指令集
|
||||
|
||||
> 见 ../stage0/spl_ir.h
|
||||
|
||||
---
|
||||
|
||||
## 内存模型
|
||||
|
||||
- **栈**:向上增长。每个槽位为 `spl_val_t`(uintptr_t,8 字节)。
|
||||
- **帧指针**(fp):当前函数参数的基址。
|
||||
- **栈指针**(sp):栈顶。
|
||||
- **金丝雀**:存储在 `data[fp-1]`(若 fp > 0)——对编译后的代码不可见。
|
||||
- **全局数据**(gdata):按索引引用的字节块。字符串、静态数据等。
|
||||
|
||||
---
|
||||
|
||||
## 编译期执行(Comptime)
|
||||
|
||||
```
|
||||
comptime { ... }
|
||||
```
|
||||
在编译时执行代码,通过将编译后的 .sir 文件加载到子 VM 中。通过以下系统调用实现:
|
||||
|
||||
```
|
||||
vm_new() → ptr // 创建子 VM
|
||||
vm_drop(vm) // 销毁子 VM
|
||||
vm_load(vm, path) → ptr // 加载 .sir,返回 prog
|
||||
vm_push(vm, val) // 将参数压入子 VM 栈
|
||||
vm_call(vm, name, nargs) // 调用函数
|
||||
vm_run(vm) → i32 // 运行子 VM,返回退出码
|
||||
```
|
||||
|
||||
(splc0 中尚未实现。)
|
||||
347
stage1/spl_comp.c
Normal file
347
stage1/spl_comp.c
Normal 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
305
stage1/spl_comp.h
Normal 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
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
115
stage1/spl_lex_util.c
Normal 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
17
stage1/spl_lex_util.h
Normal 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
532
stage1/spl_lexer.c
Normal 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
143
stage1/spl_lexer.h
Normal 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
619
stage1/spl_parser.c
Normal 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
1013
stage1/spl_stmt.c
Normal file
File diff suppressed because it is too large
Load Diff
501
stage1/spl_type.c
Normal file
501
stage1/spl_type.c
Normal 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
106
stage1/splc0.c
Normal file
@@ -0,0 +1,106 @@
|
||||
/* splc0.c — Stage 1 SPL compiler (bootstrap)
|
||||
* Usage:
|
||||
* splc0 <input.spl> <output.sir> — compile
|
||||
* splc0 --dump-tokens <input.spl> — dump tokens
|
||||
* splc0 --help — help
|
||||
*/
|
||||
|
||||
#include "../stage0/spl_ir.h"
|
||||
#include "spl_comp.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static char *read_file(const char *path, long *out_len) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) {
|
||||
perror("fopen");
|
||||
return NULL;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
char *buf = malloc((size_t)len + 1);
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
fread(buf, 1, (size_t)len, f);
|
||||
fclose(f);
|
||||
buf[len] = '\0';
|
||||
*out_len = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int cmd_dump_tokens(int argc, char **argv) {
|
||||
if (argc < 1) {
|
||||
fprintf(stderr, "Usage: splc0 --dump-tokens <file.spl>\n");
|
||||
return 1;
|
||||
}
|
||||
long len;
|
||||
char *src = read_file(argv[0], &len);
|
||||
if (!src)
|
||||
return 1;
|
||||
|
||||
spl_tok_vec_t toks = spl_lex(src, argv[0]);
|
||||
spl_tok_vec_dump(&toks);
|
||||
spl_tok_vec_drop(&toks);
|
||||
free(src);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmd_compile(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: splc0 <input.spl> <output.sir>\n");
|
||||
return 1;
|
||||
}
|
||||
const char *inpath = argv[0];
|
||||
const char *outpath = argv[1];
|
||||
|
||||
long len;
|
||||
char *src = read_file(inpath, &len);
|
||||
if (!src)
|
||||
return 1;
|
||||
|
||||
spl_comp_t ctx;
|
||||
spl_comp_init(&ctx);
|
||||
|
||||
int ret = 0;
|
||||
if (spl_compile(&ctx, src, inpath) != 0) {
|
||||
fprintf(stderr, "compilation failed: %s\n", ctx.error_msg);
|
||||
ret = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (spl_prog_store_to_file(outpath, &ctx.prog) != 0) {
|
||||
fprintf(stderr, "failed to write '%s'\n", outpath);
|
||||
ret = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("compiled %s -> %s\n", inpath, outpath);
|
||||
|
||||
cleanup:
|
||||
spl_comp_drop(&ctx);
|
||||
free(src);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "Usage: splc0 [--dump-tokens|--help] <input.spl> [output.sir]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (strcmp(argv[1], "--dump-tokens") == 0) {
|
||||
return cmd_dump_tokens(argc - 2, argv + 2);
|
||||
}
|
||||
if (strcmp(argv[1], "--help") == 0) {
|
||||
printf("SPL Compiler (stage1 bootstrap)\n");
|
||||
printf(" splc0 <input.spl> <output.sir> - compile\n");
|
||||
printf(" splc0 --dump-tokens <file.spl> - dump token stream\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return cmd_compile(argc - 1, argv + 1);
|
||||
}
|
||||
67
stage1/splc_cli.c
Normal file
67
stage1/splc_cli.c
Normal file
@@ -0,0 +1,67 @@
|
||||
/* spc_vm.c — VM launcher for stage 1 */
|
||||
|
||||
#include "../stage0/spl_ir.h"
|
||||
#include "../stage0/spl_syscall.h"
|
||||
#include "../stage0/spl_vm.h"
|
||||
#include "spl_comp.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
int argi = 1;
|
||||
|
||||
if (argi >= argc) {
|
||||
fprintf(stderr, "Usage: spc_vm <file.sir> [entry] [--trace]\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *path = argv[argi++];
|
||||
|
||||
/* Parse flags: --entry <name>, --trace */
|
||||
const char *entry = "main";
|
||||
int trace = 0;
|
||||
for (int i = argi; i < argc; i += 1) {
|
||||
if (strcmp(argv[i], "--entry") == 0 && i + 1 < argc) {
|
||||
entry = argv[++i];
|
||||
} else if (strcmp(argv[i], "--trace") == 0) {
|
||||
trace = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Adjust argv so the SPL program sees path as argv[0] and
|
||||
* remaining positional args as argv[1..], just like a native exe. */
|
||||
int spl_argc = argc - (argi - 1);
|
||||
const char **spl_argv = argv + (argi - 1);
|
||||
|
||||
spl_prog_t prog;
|
||||
if (spl_prog_load_from_file(path, &prog) != 0) {
|
||||
fprintf(stderr, "spc_vm: cannot load '%s'\n", path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
spl_syscall_register(&prog);
|
||||
spl_comp_register(&prog);
|
||||
|
||||
spl_vm_t vm;
|
||||
spl_vm_init(&vm);
|
||||
if (spl_vm_load_prog(&vm, &prog) != 0) {
|
||||
fprintf(stderr, "vm: prog '%s' not found\n", entry);
|
||||
spl_prog_drop(&prog);
|
||||
return 1;
|
||||
}
|
||||
if (spl_vm_prepare(&vm, entry, spl_argc, spl_argv, NULL) != 0) {
|
||||
fprintf(stderr, "vm: entry point '%s' not found\n", entry);
|
||||
spl_prog_drop(&prog);
|
||||
return 1;
|
||||
}
|
||||
spl_vm_set_trace(&vm, trace);
|
||||
|
||||
int ret = spl_vm_run_until(&vm, 0);
|
||||
spl_vm_drop(&vm);
|
||||
spl_prog_drop(&prog);
|
||||
|
||||
if (ret < 0)
|
||||
return (int)vm.exit_code;
|
||||
return (int)vm.exit_code;
|
||||
}
|
||||
8
stage1/test00_basic.spl
Normal file
8
stage1/test00_basic.spl
Normal file
@@ -0,0 +1,8 @@
|
||||
/* ===== 模块1:核心基础 =====
|
||||
* test00_basic — 最小程序 / 返回码
|
||||
* 难度:1/5
|
||||
* 验证点:函数定义、整型返回、ret 语句
|
||||
*/
|
||||
fn main() i32 {
|
||||
ret 0;
|
||||
}
|
||||
43
stage1/test01_literals.spl
Normal file
43
stage1/test01_literals.spl
Normal 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;
|
||||
}
|
||||
51
stage1/test02_variables.spl
Normal file
51
stage1/test02_variables.spl
Normal 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;
|
||||
}
|
||||
56
stage1/test03_arithmetic.spl
Normal file
56
stage1/test03_arithmetic.spl
Normal 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;
|
||||
}
|
||||
66
stage1/test04_operators.spl
Normal file
66
stage1/test04_operators.spl
Normal 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
79
stage1/test05_control.spl
Normal 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
48
stage1/test06_for.spl
Normal 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;
|
||||
}
|
||||
64
stage1/test07_pointers.spl
Normal file
64
stage1/test07_pointers.spl
Normal 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
38
stage1/test08_arrays.spl
Normal 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
43
stage1/test09_slices.spl
Normal 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
64
stage1/test10_struct.spl
Normal 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
29
stage1/test11_enum.spl
Normal 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;
|
||||
}
|
||||
55
stage1/test12_functions.spl
Normal file
55
stage1/test12_functions.spl
Normal 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
35
stage1/test13_extern.spl
Normal 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;
|
||||
}
|
||||
56
stage1/test14_assignment.spl
Normal file
56
stage1/test14_assignment.spl
Normal 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
45
stage1/test15_defer.spl
Normal 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
48
stage1/test16_methods.spl
Normal 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;
|
||||
}
|
||||
95
stage1/test17_integration.spl
Normal file
95
stage1/test17_integration.spl
Normal 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
488
stage1/test18_match.spl
Normal file
@@ -0,0 +1,488 @@
|
||||
/* ===== 模块4:match 语句 =====
|
||||
* 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
184
stage1/test19_hardarray.spl
Normal 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
320
stage1/test20_complex.spl
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user