#include "cvm_core.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
static int file_exists(const char *p) {
FILE *f = fopen(p, "rb");
if (!f) return 0;
fclose(f);
return 1;
}
static int ends_with(const char *s, const char *suf) {
size_t ls = strlen(s), lf = strlen(suf);
if (ls < lf) return 0;
return strcmp(s + ls - lf, suf) == 0;
}
* 以 base 目录为起点, 按相对 spec 的 / 段推进, 规整 . 与 .. 段。
* 例: base="samples", spec="./cyc_b" -> "samples/cyc_b"
* base="samples", spec="../sib/foo" -> "sib/foo"
* 用段级规整而非朴素拼接, 避免 "/./" 这类冗余分隔符导致 fopen 失败。
*/
static void resolve_relative(const char *base, const char *spec, char *out, size_t outsz) {
char combined[512];
if (base[0] == '\0') snprintf(combined, sizeof(combined), "%s", spec);
else snprintf(combined, sizeof(combined), "%s/%s", base, spec);
char result[512];
result[0] = '\0';
const char *p = combined;
char seg[256];
while (*p) {
while (*p == '/' || *p == '\\') p++;
if (!*p) break;
int k = 0;
while (*p && *p != '/' && *p != '\\' && k < 255) seg[k++] = *p++;
seg[k] = '\0';
if (strcmp(seg, ".") == 0) continue;
else if (strcmp(seg, "..") == 0) {
char *last = strrchr(result, '/');
if (last) *last = '\0';
else result[0] = '\0';
} else {
size_t l = strlen(result);
if (l && l < sizeof(result) - 1) { result[l] = '/'; result[l + 1] = '\0'; }
l = strlen(result);
if (l + strlen(seg) + 1 < sizeof(result)) strcat(result, seg);
}
}
snprintf(out, outsz, "%s", result);
}
static void dir_of(const char *path, char *buf, size_t bufsz) {
const char *slash = strrchr(path, '/');
const char *bslash = strrchr(path, '\\');
const char *sep = NULL;
if (slash && bslash) sep = (slash > bslash) ? slash : bslash;
else if (slash) sep = slash;
else if (bslash) sep = bslash;
if (!sep) { buf[0] = '\0'; return; }
size_t n = (size_t)(sep - path);
if (n >= bufsz) n = bufsz - 1;
memcpy(buf, path, n);
buf[n] = '\0';
}
* 把 spec 解析为具体 .cvm 文件路径。base: 调用方所在脚本路径(用于相对解析), 可为空。
* 解析策略(优先级从高到低):
* 1) 显式 .cvm 后缀 -> 直接文件路径(绝对或相对 cwd), 原样使用
* 2) 相对路径 ./ 或 ../ -> 相对 base 的目录拼接, 并补 .cvm / index.cvm
* 3) 包形式(支持嵌套 a/b) -> packages/<spec>/index.cvm
* packages/<spec>.cvm
* packages/<spec>/<末段>.cvm
* buf 由调用方提供(大小 >= CVM_MAX_LOAD_PATH); 内部用 snprintf 写入,
* 因 buf 以指针传入, 不会触发 -Wformat-truncation。
*/
static void resolve_spec(const char *spec, const char *base, char *buf, size_t bufsz) {
if (ends_with(spec, ".cvm")) {
snprintf(buf, bufsz, "%s", spec);
return;
}
int is_rel = (spec[0] == '.' && (spec[1] == '/' || spec[1] == '\\' ||
(spec[1] == '.' && (spec[2] == '/' || spec[2] == '\\'))));
if (is_rel) {
char dir[256];
dir_of(base, dir, sizeof(dir));
char resolved[512];
resolve_relative(dir, spec, resolved, sizeof(resolved));
snprintf(buf, bufsz, "%s.cvm", resolved);
if (file_exists(buf)) return;
snprintf(buf, bufsz, "%s/index.cvm", resolved);
if (file_exists(buf)) return;
snprintf(buf, bufsz, "%s.cvm", resolved);
return;
}
const char *slash = strrchr(spec, '/');
const char *bslash = strrchr(spec, '\\');
const char *sep = NULL;
if (slash && bslash) sep = (slash > bslash) ? slash : bslash;
else if (slash) sep = slash;
else if (bslash) sep = bslash;
const char *base_name = sep ? sep + 1 : spec;
snprintf(buf, bufsz, "packages/%s/index.cvm", spec);
if (file_exists(buf)) return;
snprintf(buf, bufsz, "packages/%s.cvm", spec);
if (file_exists(buf)) return;
snprintf(buf, bufsz, "packages/%s/%s.cvm", spec, base_name);
}
static void set_current_file(Runtime *rt, const char *p) {
size_t n = strlen(p);
if (n > sizeof(rt->current_file) - 1) n = sizeof(rt->current_file) - 1;
memcpy(rt->current_file, p, n);
rt->current_file[n] = '\0';
}
Map *load_module(Runtime *rt, const char *spec) {
char specbuf[192];
snprintf(specbuf, sizeof(specbuf), "%s", spec);
char path[CVM_MAX_LOAD_PATH];
resolve_spec(specbuf, rt->current_file, path, sizeof(path));
for (int i = 0; i < rt->loading_n; ++i)
if (strcmp(rt->loading_paths[i], path) == 0) {
snprintf(rt->errbuf, sizeof(rt->errbuf),
"require: 检测到循环依赖 (重复加载 '%s')", specbuf);
rt->has_error = 1;
return NULL;
}
Map *cached = (Map *)map_get(&rt->modules, path);
if (cached) return cached;
if (rt->loading_n < CVM_MAX_LOAD_DEPTH)
snprintf(rt->loading_paths[rt->loading_n++], CVM_MAX_LOAD_PATH, "%s", path);
FILE *f = fopen(path, "rb");
if (!f) {
snprintf(rt->errbuf, sizeof(rt->errbuf),
"require: 找不到模块 '%s'", specbuf);
rt->has_error = 1;
rt->loading_n--;
return NULL;
}
fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET);
char *src = (char *)malloc((size_t)sz + 1);
size_t rd = fread(src, 1, (size_t)sz, f); src[rd] = '\0';
fclose(f);
Map *exp = NULL;
rt->has_error = 0; rt->errbuf[0] = '\0';
Program prog = cvm_compile(rt, src, path);
free(src);
if (!prog.has_error) {
Env *menv = env_new(rt->global, &rt->arena);
char saved_file[512];
snprintf(saved_file, sizeof(saved_file), "%s", rt->current_file);
set_current_file(rt, path);
cvm_exec_program(rt, &prog, menv);
set_current_file(rt, saved_file);
if (!rt->has_error) {
exp = (Map *)malloc(sizeof(Map));
map_init(exp);
for (int i = 0; i < prog.count; ++i) {
Stmt *s = prog.stmts[i];
if (s->kind == STMT_LET) {
Value *v = env_get(menv, s->u.let.name);
if (v) map_set(exp, s->u.let.name, (void *)v);
} else if (s->kind == STMT_FUNC) {
Value *fv = (Value *)arena_alloc(&rt->arena, sizeof(Value));
*fv = val_func(s->u.func.name);
map_set(exp, s->u.func.name, (void *)fv);
}
}
map_set(&rt->modules, path, (void *)exp);
}
}
rt->loading_n--;
return exp;
}