Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions internal/cbm/cbm.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ typedef enum {
CBM_LANG_QML, // Qt QML (Qt Modeling Language — declarative UI + embedded JS)
CBM_LANG_CFSCRIPT, // CFML script dialect (.cfc components — Lucee/ColdFusion)
CBM_LANG_CFML, // CFML tag dialect (.cfm templates — Lucee/ColdFusion)
CBM_LANG_CHIALISP, // Chialisp (.clsp/.clib/.clinc — Chia smart-coin s-expression language)
CBM_LANG_COUNT
} CBMLanguage;

Expand Down
74 changes: 70 additions & 4 deletions internal/cbm/extract_calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,63 @@ static char *extract_erlang_callee(CBMArena *a, TSNode node, const char *source,
// Lisp dialects: a call is a list (`list` / `list_lit`) whose head (first named
// child) is the function symbol (`symbol` / `sym_lit`). Generic field/first-child
// extraction misses it because the head is not an `identifier` node.
static char *extract_lisp_callee(CBMArena *a, TSNode node, const char *source, const char *nk) {
/* Chialisp W2: a head atom that is a CLVM primitive/opcode, or a Chialisp
* syntax/binding/intrinsic/def keyword, is NOT a call — emit no CALLS edge.
* Sourced from clvm_tools_rs KW_PAIRS / clvm_rs operator set (pinned in #38).
* The def/include heads are here too so `(defun …)`/`(include …)` heads never
* mint a phantom call. Real helpers that merely *look* primitive — sha256tree
* and the curry helpers — are deliberately absent, so they pass through. */
static bool chialisp_head_is_not_call(const char *t) {
if (!t) {
return true;
}
static const char *filtered[] = {
/* --- 49 CLVM primitives (VM ops) --- */
"q", "a", "i", "c", "f", "r", "l", "x", "=", ">s", "sha256", "substr", "strlen", "concat",
"+", "-", "*", "/", "divmod", ">", "ash", "lsh", "logand", "logior", "logxor", "lognot",
"point_add", "pubkey_for_exp", "not", "any", "all", "softfork", "coinid", "g1_subtract",
"g1_multiply", "g1_negate", "g2_add", "g2_subtract", "g2_multiply", "g2_negate", "g1_map",
"g2_map", "bls_pairing_identity", "bls_verify", "modpow", "%", "secp256k1_verify",
"secp256r1_verify", "keccak256",
/* --- Chialisp syntax / binding / intrinsics (not calls or defs) --- */
"quote", "qq", "unquote", "&rest", "let", "let*", "assign", "assign-inline",
"assign-lambda", "lambda", "mod", "if", "list", "com", "opt", "@", "@*env*", "print",
/* --- def / include heads (own their own node kinds, never a call) --- */
"defun", "defun-inline", "defmacro", "defmac", "defconstant", "defconst", "namespace",
"export", "embed-file", "compile-file", "include", NULL};
for (int i = 0; filtered[i]; i++) {
if (strcmp(t, filtered[i]) == 0) {
return true;
}
}
return false;
}

/* W6: true when any ancestor list of `node` has a quote head (`q`/`quote`/`qq`)
* — quoted data must not mint calls. */
static bool chialisp_node_in_quote(CBMArena *a, TSNode node, const char *source) {
TSNode cur = ts_node_parent(node);
for (int guard = 0; guard < 256 && !ts_node_is_null(cur); guard++) {
const char *ck = ts_node_type(cur);
if ((strcmp(ck, "list") == 0 || strcmp(ck, "list_lit") == 0) &&
ts_node_named_child_count(cur) > 0) {
TSNode h = ts_node_named_child(cur, 0);
const char *hk = ts_node_type(h);
if (strcmp(hk, "symbol") == 0 || strcmp(hk, "sym_lit") == 0) {
char *ht = cbm_node_text(a, h, source);
if (ht && (strcmp(ht, "q") == 0 || strcmp(ht, "quote") == 0 ||
strcmp(ht, "qq") == 0)) {
return true;
}
}
}
cur = ts_node_parent(cur);
}
return false;
}

static char *extract_lisp_callee(CBMArena *a, TSNode node, const char *source, const char *nk,
CBMLanguage lang) {
if (strcmp(nk, "list") != 0 && strcmp(nk, "list_lit") != 0) {
return NULL;
}
Expand All @@ -503,7 +559,16 @@ static char *extract_lisp_callee(CBMArena *a, TSNode node, const char *source, c
const char *hk = ts_node_type(head);
if (strcmp(hk, "symbol") == 0 || strcmp(hk, "sym_lit") == 0 ||
strcmp(hk, "identifier") == 0) {
return cbm_node_text(a, head, source);
char *ht = cbm_node_text(a, head, source);
if (lang == CBM_LANG_CHIALISP) {
/* W2: drop CLVM ops / syntax / def-heads. W6: drop anything
* inside quoted data. Real helpers (sha256tree, curry helpers)
* survive both filters. */
if (chialisp_head_is_not_call(ht) || chialisp_node_in_quote(a, node, source)) {
return NULL;
}
}
return ht;
}
}
return NULL;
Expand Down Expand Up @@ -1061,8 +1126,9 @@ static char *extract_callee_lang_specific(CBMArena *a, TSNode node, const char *
}

if (lang == CBM_LANG_CLOJURE || lang == CBM_LANG_COMMONLISP || lang == CBM_LANG_SCHEME ||
lang == CBM_LANG_FENNEL || lang == CBM_LANG_RACKET || lang == CBM_LANG_EMACSLISP) {
return extract_lisp_callee(a, node, source, nk);
lang == CBM_LANG_FENNEL || lang == CBM_LANG_RACKET || lang == CBM_LANG_EMACSLISP ||
lang == CBM_LANG_CHIALISP) {
return extract_lisp_callee(a, node, source, nk, lang);
}
if (lang == CBM_LANG_FSHARP) {
return extract_fsharp_callee(a, node, source, nk);
Expand Down
164 changes: 149 additions & 15 deletions internal/cbm/extract_defs.c
Original file line number Diff line number Diff line change
Expand Up @@ -6024,36 +6024,157 @@ static bool lisp_is_def_head(const char *t) {
return false;
}

/* Chialisp (cl-26) definition-form heads. Distinct from the Clojure/Scheme
* lisp_is_def_head set because Chialisp shares the generic `list` node kind:
* treating e.g. `mod`/`defun`/`defconstant` as defs must NOT leak into the
* other lisp gates (in Scheme `(mod x y)` is a call, not a def). Excludes the
* expression-local binding forms (let, assign, lambda and friends) which, if
* made defs, would fragment call attribution. Mirror: chialisp_head_is_def()
* in extract_unified.c. */
static bool chialisp_is_def_head(const char *t) {
if (!t) {
return false;
}
static const char *heads[] = {"mod",
"defun",
"defun-inline",
"defmacro",
"defmac",
"defconstant",
"defconst",
"namespace",
"export",
"embed-file",
"compile-file",
NULL};
for (int i = 0; heads[i]; i++) {
if (strcmp(t, heads[i]) == 0) {
return true;
}
}
return false;
}

/* Chialisp W6: a `(q ...)`/`(quote ...)`/`(qq ...)` form is quoted DATA, not
* code — a def-head or call symbol inside it must not mint a node. True when
* any ancestor list of `node` has a quote head. Bounded walk (arena for the
* head-text read). */
static bool lisp_node_in_quote(CBMArena *a, TSNode node, const char *source) {
TSNode cur = ts_node_parent(node);
for (int guard = 0; guard < 256 && !ts_node_is_null(cur); guard++) {
const char *ck = ts_node_type(cur);
if ((strcmp(ck, "list") == 0 || strcmp(ck, "list_lit") == 0) &&
ts_node_named_child_count(cur) > 0) {
TSNode h = ts_node_named_child(cur, 0);
const char *hk = ts_node_type(h);
if (strcmp(hk, "symbol") == 0 || strcmp(hk, "sym_lit") == 0) {
char *ht = cbm_node_text(a, h, source);
if (ht && (strcmp(ht, "q") == 0 || strcmp(ht, "quote") == 0 ||
strcmp(ht, "qq") == 0)) {
return true;
}
}
}
cur = ts_node_parent(cur);
}
return false;
}

/* i-th named child skipping `comment` nodes (comments are NAMED in the
* Chialisp/Scheme grammar and count in named-child indexing). Cheap insurance
* for a comment interleaved between a def head and its name. */
static TSNode lisp_named_child_skip_comments(TSNode node, uint32_t want) {
uint32_t nc = ts_node_named_child_count(node);
uint32_t seen = 0;
for (uint32_t i = 0; i < nc; i++) {
TSNode c = ts_node_named_child(node, i);
if (strcmp(ts_node_type(c), "comment") == 0) {
continue;
}
if (seen == want) {
return c;
}
seen++;
}
TSNode null_node = {0};
return null_node;
}

/* Basename stem (no dir, no extension) of a path, into an arena string. */
static char *lisp_path_stem(CBMArena *a, const char *path) {
if (!path) {
return NULL;
}
const char *slash = strrchr(path, '/');
const char *base = slash ? slash + 1 : path;
const char *dot = strrchr(base, '.');
size_t len = (dot && dot != base) ? (size_t)(dot - base) : strlen(base);
return cbm_arena_strndup(a, base, len);
}

static void extract_lisp_def(CBMExtractCtx *ctx, TSNode node) {
CBMArena *a = ctx->arena;
bool chialisp = (ctx->language == CBM_LANG_CHIALISP);
if (ts_node_named_child_count(node) < 2) {
return;
}
char *head = cbm_node_text(a, ts_node_named_child(node, 0), ctx->source);
if (!lisp_is_def_head(head)) {
TSNode head_node = chialisp ? lisp_named_child_skip_comments(node, 0) : ts_node_named_child(node, 0);
if (ts_node_is_null(head_node)) {
return;
}
TSNode target = ts_node_named_child(node, 1);
const char *tk = ts_node_type(target);
TSNode name_node = target;
// (define (foo args) ...) — the name is the head symbol of the nested list.
if ((strcmp(tk, "list") == 0 || strcmp(tk, "list_lit") == 0) &&
ts_node_named_child_count(target) > 0) {
name_node = ts_node_named_child(target, 0);
char *head = cbm_node_text(a, head_node, ctx->source);
bool is_def = chialisp ? chialisp_is_def_head(head) : lisp_is_def_head(head);
if (!is_def) {
return;
}
if (ts_node_is_null(name_node)) {
/* W6: skip def-heads that live inside quoted data (cl-26 macros embed
* puzzle-shaped literals under `(q ...)`). */
if (chialisp && lisp_node_in_quote(a, node, ctx->source)) {
return;
}
char *name = cbm_node_text(a, name_node, ctx->source);
/* W1: a top-level `(mod (ARGS) ...)` is the puzzle entry point; its
* named_child(1) is the arg-LIST, so the generic nested-name path would
* name the module after the first curried arg. Name it by the filename. */
char *name = NULL;
if (chialisp && strcmp(head, "mod") == 0) {
name = lisp_path_stem(a, ctx->rel_path);
} else {
TSNode target = chialisp ? lisp_named_child_skip_comments(node, 1) : ts_node_named_child(node, 1);
if (ts_node_is_null(target)) {
return;
}
const char *tk = ts_node_type(target);
TSNode name_node = target;
// (define (foo args) ...) — the name is the head symbol of the nested list.
if ((strcmp(tk, "list") == 0 || strcmp(tk, "list_lit") == 0) &&
ts_node_named_child_count(target) > 0) {
name_node = ts_node_named_child(target, 0);
}
if (ts_node_is_null(name_node)) {
return;
}
name = cbm_node_text(a, name_node, ctx->source);
}
if (!name || !name[0]) {
return;
}
/* struct/record/type defining forms produce a type node, not a callable
* (Racket `(struct point ...)`, Clojure `(defrecord ...)`, etc.). */
const char *lisp_label = "Function";
if (strcmp(head, "struct") == 0 || strcmp(head, "define-struct") == 0 ||
strcmp(head, "define-record-type") == 0 || strcmp(head, "defrecord") == 0 ||
strcmp(head, "deftype") == 0) {
if (chialisp) {
/* W3/W4 label map: constants, macros, module entry, embedded artifacts. */
if (strcmp(head, "mod") == 0 || strcmp(head, "export") == 0 ||
strcmp(head, "namespace") == 0) {
lisp_label = "Module";
} else if (strcmp(head, "defconstant") == 0 || strcmp(head, "defconst") == 0 ||
strcmp(head, "embed-file") == 0 || strcmp(head, "compile-file") == 0) {
lisp_label = "Constant";
} else if (strcmp(head, "defmacro") == 0 || strcmp(head, "defmac") == 0) {
lisp_label = "Macro";
}
} else if (strcmp(head, "struct") == 0 || strcmp(head, "define-struct") == 0 ||
strcmp(head, "define-record-type") == 0 || strcmp(head, "defrecord") == 0 ||
strcmp(head, "deftype") == 0) {
lisp_label = "Struct";
} else if (strcmp(head, "definterface") == 0 || strcmp(head, "defprotocol") == 0) {
lisp_label = "Interface";
Expand Down Expand Up @@ -6242,9 +6363,22 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec,
}

if ((ctx->language == CBM_LANG_CLOJURE || ctx->language == CBM_LANG_RACKET ||
ctx->language == CBM_LANG_SCHEME) &&
ctx->language == CBM_LANG_SCHEME || ctx->language == CBM_LANG_CHIALISP) &&
(strcmp(kind, "list") == 0 || strcmp(kind, "list_lit") == 0)) {
extract_lisp_def(ctx, node);
/* Chialisp nests every helper `(defun …)` inside the top-level
* `(mod …)` (and `.clib` files wrap defuns in an enclosing list —
* W7). The generic function_node_types match below fires on every
* `list` and `continue`s WITHOUT descending, so those nested defs
* would be lost. Push children and continue here so the whole tree
* is walked. Gated to Chialisp — other lisps keep prior behavior. */
if (ctx->language == CBM_LANG_CHIALISP) {
uint32_t cc = ts_node_child_count(node);
for (int i = (int)cc - SKIP_CHAR; i >= 0; i--) {
wd_push(&s, ts_node_child(node, (uint32_t)i), frame.enclosing_class_qn);
}
continue;
}
// fall through: descend into children so nested defs are captured too
}

Expand Down
37 changes: 37 additions & 0 deletions internal/cbm/extract_imports.c
Original file line number Diff line number Diff line change
Expand Up @@ -1788,6 +1788,42 @@ static void lisp_process_list(CBMExtractCtx *ctx, TSNode node) {
return;
}

/* Chialisp (cl-26) include/embed forms. W3: `(include *standard-cl-26*)`
* and any `*…*` dialect sigil is a compiler directive, NOT a file
* dependency — mirror the compiler's own `!name.starts_with("*")` filter
* and drop it (record only real `(include "file.clib")` edges). W4:
* `(embed-file NAME kind "file")` / `(compile-file NAME "file")` create a
* file dependency on the embedded/compiled artifact — emit it as an import
* edge (no dedicated EMBEDS edge type; see task note). */
if (ctx->language == CBM_LANG_CHIALISP) {
if (strcmp(hn, "include") == 0) {
for (uint32_t j = 1; j < nc; j++) {
TSNode mod_node = ts_node_named_child(node, j);
const char *mk = ts_node_type(mod_node);
if (strcmp(mk, "symbol") == 0 || strcmp(mk, "sym_lit") == 0) {
char *sig = cbm_node_text(ctx->arena, mod_node, ctx->source);
if (sig && sig[0] == '*') {
continue; /* dialect sigil, not a file */
}
}
lisp_push_module(ctx, mod_node);
}
return;
}
if (strcmp(hn, "embed-file") == 0 || strcmp(hn, "compile-file") == 0) {
/* the embedded/compiled path is the last string arg. */
for (uint32_t j = nc; j-- > 1;) {
TSNode arg = ts_node_named_child(node, j);
const char *ak = ts_node_type(arg);
if (strcmp(ak, "string") == 0 || strcmp(ak, "str_lit") == 0) {
lisp_push_module(ctx, arg);
break;
}
}
return;
}
}

/* Plain import head: `(require :util)`, `(import ...)`, `(use ...)`. */
if (strcmp(hn, "import") == 0 || strcmp(hn, "require") == 0 || strcmp(hn, "load") == 0 ||
strcmp(hn, "use") == 0 || strcmp(hn, "include") == 0) {
Expand Down Expand Up @@ -2852,6 +2888,7 @@ void cbm_extract_imports(CBMExtractCtx *ctx) {
case CBM_LANG_FENNEL:
case CBM_LANG_COMMONLISP:
case CBM_LANG_CLOJURE:
case CBM_LANG_CHIALISP:
parse_lisp_imports(ctx);
break;
case CBM_LANG_STARLARK:
Expand Down
Loading
Loading