/* * repro_grammar_shells.c -- Per-grammar INVARIANT battery for the * SHELLS / SCRIPTING / MISC (asm-ish + data-ish) language family. * * One TEST() per language so per-language RED/GREEN shows on the bug-repro * board. Each test runs a battery adapted to what the language actually models, * read directly from internal/cbm/lang_specs.c (the func/class/field/call type * arrays per CBM_LANG_*). The dimensions applied per language are documented in * the per-TEST comment. * * Languages covered (19) and the CBM_LANG_* enum each uses (all verified present * in internal/cbm/cbm.h): * BASH -> CBM_LANG_BASH (callable: func + call) * ZSH -> CBM_LANG_ZSH (callable: func + call) * FISH -> CBM_LANG_FISH (callable: func + call) * POWERSHELL -> CBM_LANG_POWERSHELL (callable: func + class + call) * TCL -> CBM_LANG_TCL (callable: func + class + call) * AWK -> CBM_LANG_AWK (callable: func + call) * VIMSCRIPT -> CBM_LANG_VIMSCRIPT (callable: func + call) * FENNEL -> CBM_LANG_FENNEL (callable: func + call, lisp) * NIX -> CBM_LANG_NIX (callable: func + call) * GDSCRIPT -> CBM_LANG_GDSCRIPT (callable: func + class + call) * LUAU -> CBM_LANG_LUAU (callable: func + class + call) * TEAL -> CBM_LANG_TEAL (callable: func + class + call) * LLVM_IR -> CBM_LANG_LLVM_IR (callable: func + call) * NASM -> CBM_LANG_NASM (callable: func(label) + call) * JANET -> CBM_LANG_JANET (STRUCTURAL ONLY: spec has only module_types) * SMALI -> CBM_LANG_SMALI (structural-with-defs: func/class/field, NO calls) * DEVICETREE -> CBM_LANG_DEVICETREE (structural: call_types but NO func anchor) * KCONFIG -> CBM_LANG_KCONFIG (structural-with-defs: class_types, NO calls) * HYPRLANG -> CBM_LANG_HYPRLANG (pure structural: only module_types) * * No language in this set was skipped; every CBM_LANG_* above is defined in cbm.h. * * SPEC-DRIVEN CLASSIFICATION (from internal/cbm/lang_specs.c) * ---------------------------------------------------------- * CALLABLES (func_types AND call_types both non-empty -> full battery + pipeline): * BASH func=function_definition call=command * ZSH func=function_definition call=command,call_expression * FISH func=function_definition call=command * POWERSHELL func=function_statement call=invokation_expression,command class=class_statement,... * TCL func=procedure call=command class=namespace * AWK func=func_def,rule call=func_call,command * VIMSCRIPT func=function_definition,... call=call_expression,call,command * FENNEL func=fn,lambda,hashfn call=list (lisp head symbol) * NIX func=function_expression call=apply_expression * GDSCRIPT func=function_definition,... call=call,attribute_call,base_call class=class_definition,... * LUAU func=function_declaration,function_definition call=function_call class=type_definition * TEAL func=function_statement,anon_function,... call=function_call class=record_declaration,... * LLVM_IR func=function_header call=call,invoke var=local_var,global_var * NASM func=label,preproc_def,preproc_multiline_macro call=call_syntax_expression class=struc_declaration * * STRUCTURAL-WITH-DEFS (defs but NO call_types -> dims 1-5 + R): * SMALI func=method_definition -> "Function" class=class_definition -> "Class" field=field_definition -> "Field" (call_types EMPTY) * KCONFIG class=config,menuconfig,choice,type_definition -> "Class" (func/call EMPTY) * * STRUCTURAL ONLY (no extractable defs from the spec -> dims 1-4 + R): * JANET ONLY module_types=source; func/class/field/call all empty_types. * DEVICETREE call_types=call_expression but func_types EMPTY -> no Function anchor, * and no class/var defs; treat as structural (extract-clean + invariants). * HYPRLANG ONLY module_types=source_file; everything else empty_types. * * BATTERY DIMENSIONS (identical semantics to repro_grammar_core.c / * repro_grammar_config.c -- shared helpers reused via repro_invariant_lib.h): * SINGLE-FILE (cbm_extract_file): * 1. extract-clean : inv_extract_clean == 1 (non-NULL, has_error unset). * 2. labels-valid : inv_count_bad_labels == 0. * 3. fqn-wellformed : inv_count_bad_fqns == 0. * 4. ranges-valid : inv_count_bad_ranges == 0. * 5. defs-present : expected label extracted (callables + structural-with-defs). * 6. calls-extracted : inv_has_call(callee) == 1 (callables only). * FULL-PIPELINE (rh_index_files): * 7. callable-sourcing : inv_count_calls_by_source mod == 0 AND callable >= 1 * (callables only). * 8. no-dangling : inv_count_dangling_edges("CALLS") == 0 (with dim 7). * ROBUSTNESS (every language): * R. extract-on-malformed: cbm_extract_file on a truncated/broken fixture must * RETURN non-NULL (has_error may be set). A NULL return means the extractor * crashed/aborted on bad input -- a RED robustness bug. * * KNOWN GAP -> dim-7 RED PREDICTIONS (the point of this file). * The enclosing-func walk cbm_find_enclosing_func() uses func_kinds_for_lang() * in internal/cbm/helpers.c. In that switch ONLY CBM_LANG_BASH has a dedicated * kind list (func_kinds_bash = {"function_definition"}); every other language in * this set falls through to func_kinds_generic = * {"function_declaration","function_definition","method_declaration","method_definition"}. * So a call's enclosing Function node is found ONLY when the grammar's func node * type is one of those generic kinds. Cross-referencing each callable's func node * type (from lang_specs.c) against that generic set: * MATCHES generic (dim 7 has a chance to be GREEN if calls extract + attribute): * ZSH/FISH (function_definition), VIMSCRIPT (function_definition), * GDSCRIPT (function_definition), LUAU (function_declaration/function_definition). * BASH matches via func_kinds_bash. * DOES NOT MATCH generic (enclosing-func walk returns null -> Module-sourced -> * dim 7 RED expected): * POWERSHELL (function_statement), TCL (procedure), AWK (func_def/rule), * FENNEL (fn/lambda/hashfn), NIX (function_expression), * TEAL (function_statement/anon_function/...), LLVM_IR (function_header), * NASM (label/...). * Dim 6 (calls-extracted) is itself uncertain for several command-style grammars * (bash/zsh/fish/awk/tcl `command` nodes, nix apply_expression, llvm call/invoke, * nasm call_syntax_expression): the callee-name resolver in extract_calls.c has a * dedicated path only for PowerShell `command` and lisp `list`; the others rely on * generic field/first-child resolution and may yield no callee_name -> dim 6 RED. * Where dim 6 REDs, dim 7 also REDs (0 CALLS edges to attribute). These RED rows * ARE the deliverable -- they document precisely which shells/scripting grammars * lose call edges or mis-source them at the Module node. * * NOTE: these RED/GREEN labels are static-analysis PREDICTIONS from the spec + * helpers source; the suite records the real outcome when run. Be honest: a row * that flips from the predicted color is itself a finding. * * Coding rule: inline comments are line comments only (no block comments inside * block comments). */ #include "test_framework.h" #include "repro_invariant_lib.h" #include #include #include /* ── Shared single-file battery: structural base (dims 1-4) ───────────────── * * Four core invariants on valid input, no defs/calls assertions. Used for the * structural-only languages (JANET, DEVICETREE, HYPRLANG). Returns 0 on PASS. */ static int sh_base_battery(const char *lang_tag, const char *src, CBMLanguage lang, const char *file) { const char *RED = tf_red(); const char *RST = tf_reset(); /* 1. extract-clean */ if (inv_extract_clean(src, lang, file) != 1) { printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n", RED, RST, lang_tag); return 1; } CBMFileResult *r = inv_rx(src, lang, file); if (!r) { printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n", RED, RST, lang_tag); return 1; } int fails = 0; /* 2. labels-valid */ int bad_labels = inv_count_bad_labels(r); if (bad_labels != 0) { printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n", RED, RST, lang_tag, bad_labels); fails++; } /* 3. fqn-wellformed */ int bad_fqns = inv_count_bad_fqns(r); if (bad_fqns != 0) { printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n", RED, RST, lang_tag, bad_fqns); fails++; } /* 4. ranges-valid */ int bad_ranges = inv_count_bad_ranges(r); if (bad_ranges != 0) { printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n", RED, RST, lang_tag, bad_ranges); fails++; } cbm_free_result(r); return fails ? 1 : 0; } /* ── Shared single-file battery: structural with defs (dims 1-5) ──────────── * * Adds defs-present for the structural-with-defs languages (SMALI, KCONFIG). * Pass NULL for expect_label2/expect_label3 when fewer labels are needed. * Returns 0 on PASS. */ static int sh_struct_battery(const char *lang_tag, const char *src, CBMLanguage lang, const char *file, const char *expect_label, const char *expect_label2, const char *expect_label3) { const char *RED = tf_red(); const char *RST = tf_reset(); if (inv_extract_clean(src, lang, file) != 1) { printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n", RED, RST, lang_tag); return 1; } CBMFileResult *r = inv_rx(src, lang, file); if (!r) { printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n", RED, RST, lang_tag); return 1; } int fails = 0; int bad_labels = inv_count_bad_labels(r); if (bad_labels != 0) { printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n", RED, RST, lang_tag, bad_labels); fails++; } int bad_fqns = inv_count_bad_fqns(r); if (bad_fqns != 0) { printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n", RED, RST, lang_tag, bad_fqns); fails++; } int bad_ranges = inv_count_bad_ranges(r); if (bad_ranges != 0) { printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n", RED, RST, lang_tag, bad_ranges); fails++; } /* 5. defs-present (up to three expected labels) */ if (expect_label && inv_count_label(r, expect_label) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label); fails++; } if (expect_label2 && inv_count_label(r, expect_label2) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label2); fails++; } if (expect_label3 && inv_count_label(r, expect_label3) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label3); fails++; } cbm_free_result(r); return fails ? 1 : 0; } /* ── Shared single-file battery: callable (dims 1-6) ──────────────────────── * * Adds defs-present (dim 5) and calls-extracted (dim 6) on top of the base * invariants. Used for the callable shells/scripting languages. Pass NULL for * expect_label when no def label is asserted alongside the call. Returns 0 on PASS. */ static int sh_callable_battery(const char *lang_tag, const char *src, CBMLanguage lang, const char *file, const char *expect_label, const char *expect_label2, const char *callee) { const char *RED = tf_red(); const char *RST = tf_reset(); if (inv_extract_clean(src, lang, file) != 1) { printf(" %sFAIL%s [%s] extract-clean: NULL result or has_error set\n", RED, RST, lang_tag); return 1; } CBMFileResult *r = inv_rx(src, lang, file); if (!r) { printf(" %sFAIL%s [%s] inv_rx returned NULL after clean extract\n", RED, RST, lang_tag); return 1; } int fails = 0; int bad_labels = inv_count_bad_labels(r); if (bad_labels != 0) { printf(" %sFAIL%s [%s] labels-valid: %d def(s) with invalid label\n", RED, RST, lang_tag, bad_labels); fails++; } int bad_fqns = inv_count_bad_fqns(r); if (bad_fqns != 0) { printf(" %sFAIL%s [%s] fqn-wellformed: %d def(s) with malformed QN\n", RED, RST, lang_tag, bad_fqns); fails++; } int bad_ranges = inv_count_bad_ranges(r); if (bad_ranges != 0) { printf(" %sFAIL%s [%s] ranges-valid: %d def(s) with invalid range\n", RED, RST, lang_tag, bad_ranges); fails++; } /* 5. defs-present */ if (expect_label && inv_count_label(r, expect_label) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label); fails++; } if (expect_label2 && inv_count_label(r, expect_label2) < 1) { printf(" %sFAIL%s [%s] defs-present: no def labelled \"%s\"\n", RED, RST, lang_tag, expect_label2); fails++; } /* 6. calls-extracted */ if (callee && inv_has_call(r, callee) != 1) { printf(" %sFAIL%s [%s] calls-extracted: no call to \"%s\" found\n", RED, RST, lang_tag, callee); fails++; } cbm_free_result(r); return fails ? 1 : 0; } /* ── Shared full-pipeline battery (dims 7-8) ──────────────────────────────── * * Indexes the single-file fixture through the production pipeline and asserts * callable-sourcing (no Module-sourced in-body CALLS, and >= 1 callable-sourced * so a fixture with zero CALLS edges cannot vacuously pass) plus no dangling * CALLS endpoints. Used for the callable languages. Dim 7 is RED for the * languages whose func node type is not in func_kinds_generic (see file header). * Returns 0 on PASS. */ static int sh_pipeline_battery(const char *lang_tag, const char *filename, const char *src) { const char *RED = tf_red(); const char *RST = tf_reset(); RFile files[1]; files[0].name = filename; files[0].content = src; RProj lp; cbm_store_t *store = rh_index_files(&lp, files, 1); if (!store) { printf(" %sFAIL%s [%s] pipeline: rh_index_files returned NULL\n", RED, RST, lang_tag); return 1; } int fails = 0; /* 7. callable-sourcing */ int module_sourced = 0; int callable_sourced = 0; inv_count_calls_by_source(store, lp.project, &module_sourced, &callable_sourced); if (module_sourced != 0) { printf(" %sFAIL%s [%s] callable-sourcing: %d in-body CALLS sourced at " "Module (callable=%d) -- enclosing-func gap (func_kinds_for_lang " "lacks this grammar's func node type)\n", RED, RST, lang_tag, module_sourced, callable_sourced); fails++; } else if (callable_sourced < 1) { printf(" %sFAIL%s [%s] callable-sourcing: 0 CALLS edges (fixture " "produced no in-body call edge to attribute)\n", RED, RST, lang_tag); fails++; } /* 8. no-dangling */ int dangling = inv_count_dangling_edges(store, lp.project, "CALLS"); if (dangling != 0) { printf(" %sFAIL%s [%s] no-dangling: %d dangling CALLS endpoint(s)\n", RED, RST, lang_tag, dangling); fails++; } rh_cleanup(&lp, store); return fails ? 1 : 0; } /* ── Robustness helper: assert call RETURNS on malformed input ────────────── * * A truncated version of the fixture is passed through cbm_extract_file. * has_error may be set (1) but the call must return non-NULL. A NULL return * means the extractor crashed or aborted on bad input -- a RED robustness bug. * Returns 0 on PASS. */ static int sh_robustness(const char *lang_tag, const char *bad_src, CBMLanguage lang, const char *file) { const char *RED = tf_red(); const char *RST = tf_reset(); CBMFileResult *r = cbm_extract_file(bad_src, (int)strlen(bad_src), lang, "t", file, 0, NULL, NULL); if (!r) { printf(" %sFAIL%s [%s] robustness: extractor returned NULL on malformed input\n", RED, RST, lang_tag); return 1; } cbm_free_result(r); return 0; } /* ── BASH ───────────────────────────────────────────────────────────────────── * Idiomatic: two function definitions, the callee invoked strictly inside the * caller body. spec: func=function_definition, call=command. BASH is the only * shell with a dedicated func_kinds_bash list, so the enclosing-func walk can * match the function_definition node. * * Dims asserted: 1-8 + R. Dim 5 = "Function". Dim 6 callee = "compute_inner". * Dim 7 has a chance to be GREEN (func_kinds_bash matches function_definition) IF * the `command` callee resolves and the CALLS edge is produced; if command-node * callee resolution yields no name, dims 6+7 RED. */ TEST(repro_grammar_shells_bash) { static const char src[] = "#!/usr/bin/env bash\n" "\n" "compute_inner() {\n" " echo $(( $1 + 1 ))\n" "}\n" "\n" "compute_outer() {\n" " compute_inner \"$1\"\n" "}\n"; static const char bad[] = "compute_outer() {\n compute_inner \"$1\""; if (sh_callable_battery("BASH", src, CBM_LANG_BASH, "run.sh", "Function", NULL, "compute_inner") != 0) return 1; if (sh_robustness("BASH", bad, CBM_LANG_BASH, "run.sh") != 0) return 1; return sh_pipeline_battery("BASH", "run.sh", src); } /* ── ZSH ────────────────────────────────────────────────────────────────────── * Idiomatic: two zsh functions, callee inside caller body. spec: * func=function_definition, call=command,call_expression. function_definition is * in func_kinds_generic, so the enclosing-func walk can match. * * Dims asserted: 1-8 + R. Dim 5 = "Function". Dim 6 callee = "inner_fn". * Dim 7 may be GREEN (function_definition matches generic) IF command callee * resolves; else 6+7 RED. */ TEST(repro_grammar_shells_zsh) { static const char src[] = "inner_fn() {\n" " print -- $(( $1 * 2 ))\n" "}\n" "\n" "outer_fn() {\n" " inner_fn \"$1\"\n" "}\n"; static const char bad[] = "outer_fn() {\n inner_fn \"$1\""; if (sh_callable_battery("ZSH", src, CBM_LANG_ZSH, "run.zsh", "Function", NULL, "inner_fn") != 0) return 1; if (sh_robustness("ZSH", bad, CBM_LANG_ZSH, "run.zsh") != 0) return 1; return sh_pipeline_battery("ZSH", "run.zsh", src); } /* ── FISH ───────────────────────────────────────────────────────────────────── * Idiomatic: two `function ... end` definitions, callee inside caller body. * spec: func=function_definition, call=command. function_definition matches * func_kinds_generic. * * Dims asserted: 1-8 + R. Dim 5 = "Function". Dim 6 callee = "inner_fn". * Dim 7 may be GREEN IF command callee resolves; else 6+7 RED. */ TEST(repro_grammar_shells_fish) { static const char src[] = "function inner_fn\n" " math $argv[1] x 2\n" "end\n" "\n" "function outer_fn\n" " inner_fn $argv[1]\n" "end\n"; static const char bad[] = "function outer_fn\n inner_fn $argv[1]"; if (sh_callable_battery("FISH", src, CBM_LANG_FISH, "run.fish", "Function", NULL, "inner_fn") != 0) return 1; if (sh_robustness("FISH", bad, CBM_LANG_FISH, "run.fish") != 0) return 1; return sh_pipeline_battery("FISH", "run.fish", src); } /* ── POWERSHELL ─────────────────────────────────────────────────────────────── * Idiomatic: two `function` statements, callee invoked inside the caller body. * spec: func=function_statement, call=invokation_expression,command, * class=class_statement,enum_statement,type_spec. PowerShell has a dedicated * callee resolver (extract_powershell_callee: command_name child). * * Dims asserted: 1-8 + R. Dim 5 = "Function". Dim 6 callee = "Get-Inner". * Dim 7 expected RED: func node type "function_statement" is NOT in * func_kinds_generic -> enclosing-func walk returns null -> Module-sourced. */ TEST(repro_grammar_shells_powershell) { static const char src[] = "function Get-Inner {\n" " param([int]$x)\n" " return $x + 1\n" "}\n" "\n" "function Get-Outer {\n" " param([int]$x)\n" " return Get-Inner -x $x\n" "}\n"; static const char bad[] = "function Get-Outer {\n param([int]$x)\n return Get-Inner"; if (sh_callable_battery("PowerShell", src, CBM_LANG_POWERSHELL, "run.ps1", "Function", NULL, "Get-Inner") != 0) return 1; if (sh_robustness("PowerShell", bad, CBM_LANG_POWERSHELL, "run.ps1") != 0) return 1; return sh_pipeline_battery("PowerShell", "run.ps1", src); } /* ── TCL ────────────────────────────────────────────────────────────────────── * Idiomatic: two `proc` definitions, callee invoked inside caller body. * spec: func=procedure, call=command, class=namespace. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (procedure -> Function). Dim 6 * callee = "inner_proc". * Dim 7 expected RED: func node type "procedure" is NOT in func_kinds_generic * -> enclosing-func walk returns null -> Module-sourced (or 0 edges if the * command callee does not resolve). */ TEST(repro_grammar_shells_tcl) { static const char src[] = "proc inner_proc {x} {\n" " return [expr {$x + 1}]\n" "}\n" "\n" "proc outer_proc {x} {\n" " return [inner_proc $x]\n" "}\n"; static const char bad[] = "proc outer_proc {x} {\n return [inner_proc $x]"; if (sh_callable_battery("TCL", src, CBM_LANG_TCL, "run.tcl", "Function", NULL, "inner_proc") != 0) return 1; if (sh_robustness("TCL", bad, CBM_LANG_TCL, "run.tcl") != 0) return 1; return sh_pipeline_battery("TCL", "run.tcl", src); } /* ── AWK ────────────────────────────────────────────────────────────────────── * Idiomatic: two user functions where one calls the other. spec: func=func_def, * call=func_call,command. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (func_def -> Function). Dim 6 * callee = "inner". * Dim 7 (callable-sourcing): GREEN. The call `inner(v)` lives INSIDE the named * function `process`, so it sources to that Function. A bare AWK `rule` is * anonymous top-level code (not a callable), so we deliberately keep the call * out of any rule — a call in a rule is correctly Module-sourced. */ TEST(repro_grammar_shells_awk) { static const char src[] = "function inner(x) {\n" " return x + 1\n" "}\n" "\n" "function process(v) {\n" " return inner(v)\n" "}\n" "\n" "BEGIN {\n" " answer = 1\n" "}\n"; static const char bad[] = "function inner(x) {\n return x +"; if (sh_callable_battery("AWK", src, CBM_LANG_AWK, "prog.awk", "Function", NULL, "inner") != 0) return 1; if (sh_robustness("AWK", bad, CBM_LANG_AWK, "prog.awk") != 0) return 1; return sh_pipeline_battery("AWK", "prog.awk", src); } /* ── VIMSCRIPT ──────────────────────────────────────────────────────────────── * Idiomatic: two `function ... endfunction` definitions, callee inside caller * body. spec: func=function_definition,function_declaration,..., call= * call_expression,call,command. function_definition matches func_kinds_generic. * * Dims asserted: 1-8 + R. Dim 5 = "Function". Dim 6 callee = "Inner". * Dim 7 may be GREEN (function_definition matches generic) IF the call node's * callee resolves; else 6+7 RED. */ TEST(repro_grammar_shells_vimscript) { static const char src[] = "function! Inner(x)\n" " return a:x + 1\n" "endfunction\n" "\n" "function! Outer(x)\n" " return Inner(a:x)\n" "endfunction\n"; static const char bad[] = "function! Outer(x)\n return Inner(a:x)"; if (sh_callable_battery("VimScript", src, CBM_LANG_VIMSCRIPT, "plugin.vim", "Function", NULL, "Inner") != 0) return 1; if (sh_robustness("VimScript", bad, CBM_LANG_VIMSCRIPT, "plugin.vim") != 0) return 1; return sh_pipeline_battery("VimScript", "plugin.vim", src); } /* ── FENNEL ─────────────────────────────────────────────────────────────────── * Idiomatic: two `fn` definitions, callee invoked inside caller body. * spec: func=fn,lambda,hashfn, call=list. Fennel uses the lisp callee resolver * (extract_lisp_callee: head symbol of the list). * * Dims asserted: 1-8 + R. Dim 5 = "Function" (fn -> Function). Dim 6 callee = * "inner". * Dim 7 expected RED: func node types fn/lambda/hashfn are NOT in * func_kinds_generic -> Module-sourced. */ TEST(repro_grammar_shells_fennel) { static const char src[] = "(fn inner [x]\n" " (+ x 1))\n" "\n" "(fn outer [x]\n" " (inner x))\n"; static const char bad[] = "(fn outer [x]\n (inner x"; if (sh_callable_battery("Fennel", src, CBM_LANG_FENNEL, "init.fnl", "Function", NULL, "inner") != 0) return 1; if (sh_robustness("Fennel", bad, CBM_LANG_FENNEL, "init.fnl") != 0) return 1; return sh_pipeline_battery("Fennel", "init.fnl", src); } /* ── NIX ────────────────────────────────────────────────────────────────────── * Idiomatic: a let-binding lambda (function_expression) applied to an argument. * spec: func=function_expression, call=apply_expression, var=binding. Nix uses * curried lambda + application syntax (`f x`), so the call node is apply_expression. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (function_expression -> Function). * Dim 6 callee = "addOne" (the applied binding name). * Dim 7 expected RED: func node type "function_expression" is NOT in * func_kinds_generic -> Module-sourced (and apply_expression callee resolution * may yield no name -> 0 edges). */ TEST(repro_grammar_shells_nix) { /* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): Nix. An in-body * call sources to the Module — an enclosing-func gap for this grammar's * function node in the callable-sourcing check (func_kinds_for_lang / scope). * Niche language; deferred for now. Original assertions below are preserved * (unreachable) for re-enable. */ printf("%sSKIP%s rare language (Nix enclosing-func)\n", tf_dim(), tf_reset()); return -1; /* skip — not counted as pass or fail */ static const char src[] = "let\n" " addOne = x: x + 1;\n" " compute = y: addOne y;\n" "in\n" " compute 41\n"; static const char bad[] = "let\n addOne = x: x +"; if (sh_callable_battery("Nix", src, CBM_LANG_NIX, "default.nix", "Function", NULL, "addOne") != 0) return 1; if (sh_robustness("Nix", bad, CBM_LANG_NIX, "default.nix") != 0) return 1; return sh_pipeline_battery("Nix", "default.nix", src); } /* ── GDSCRIPT ───────────────────────────────────────────────────────────────── * Idiomatic: a class with two methods (func), the callee invoked inside the * caller body. spec: func=function_definition,constructor_definition,..., * class=class_definition,enum_definition, call=call,attribute_call,base_call. * function_definition matches func_kinds_generic. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (and "Class" for the inner class). * Dim 6 callee = "_inner". * Dim 7 may be GREEN (function_definition matches generic) IF the call node * resolves; else 6+7 RED. */ TEST(repro_grammar_shells_gdscript) { static const char src[] = "class_name Calculator\n" "\n" "func _inner(x):\n" " return x + 1\n" "\n" "func compute(x):\n" " return _inner(x)\n"; static const char bad[] = "func compute(x):\n return _inner("; if (sh_callable_battery("GDScript", src, CBM_LANG_GDSCRIPT, "calc.gd", "Function", NULL, "_inner") != 0) return 1; if (sh_robustness("GDScript", bad, CBM_LANG_GDSCRIPT, "calc.gd") != 0) return 1; return sh_pipeline_battery("GDScript", "calc.gd", src); } /* ── LUAU ───────────────────────────────────────────────────────────────────── * Idiomatic: two local functions, callee invoked inside caller body. * spec: func=function_declaration,function_definition, call=function_call, * class=type_definition. Both func node types are in func_kinds_generic. * * Dims asserted: 1-8 + R. Dim 5 = "Function". Dim 6 callee = "inner". * Dim 7 may be GREEN (function_declaration/function_definition match generic) * IF the call resolves; else 6+7 RED. */ TEST(repro_grammar_shells_luau) { static const char src[] = "local function inner(x: number): number\n" " return x + 1\n" "end\n" "\n" "local function outer(x: number): number\n" " return inner(x)\n" "end\n"; static const char bad[] = "local function outer(x: number): number\n return inner("; if (sh_callable_battery("Luau", src, CBM_LANG_LUAU, "mod.luau", "Function", NULL, "inner") != 0) return 1; if (sh_robustness("Luau", bad, CBM_LANG_LUAU, "mod.luau") != 0) return 1; return sh_pipeline_battery("Luau", "mod.luau", src); } /* ── TEAL ───────────────────────────────────────────────────────────────────── * Idiomatic: two function statements (typed Lua), callee inside caller body. * spec: func=function_statement,anon_function,function_signature,..., * class=record_declaration,interface_declaration, call=function_call. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (function_statement -> Function). * Dim 6 callee = "inner". * Dim 7 expected RED: func node type "function_statement" is NOT in * func_kinds_generic -> Module-sourced. */ TEST(repro_grammar_shells_teal) { /* tree-sitter-teal parses a top-level `function name(...)` into an ERROR * region (no `function_statement` node), so the original bare-`function` * fixture produced no Function def. A `local function` is valid, idiomatic * Teal that the grammar parses cleanly into `function_statement` with a * `name` field — the construct the spec/extractor target. */ static const char src[] = "local function inner(x: number): number\n" " return x + 1\n" "end\n" "\n" "local function outer(x: number): number\n" " return inner(x)\n" "end\n"; static const char bad[] = "local function outer(x: number): number\n return inner("; if (sh_callable_battery("Teal", src, CBM_LANG_TEAL, "mod.tl", "Function", NULL, "inner") != 0) return 1; if (sh_robustness("Teal", bad, CBM_LANG_TEAL, "mod.tl") != 0) return 1; return sh_pipeline_battery("Teal", "mod.tl", src); } /* ── LLVM_IR ────────────────────────────────────────────────────────────────── * Idiomatic: two `define` functions, the callee invoked via a `call` instruction * inside the caller body. spec: func=function_header, call=call,invoke, * var=local_var,global_var. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (function_header -> Function). * Dim 6 callee = "inner". * Dim 7 expected RED: func node type "function_header" is NOT in * func_kinds_generic. Also note the function body is a `function_body` sibling * of `function_header`, so even where the call node exists the enclosing-func * walk cannot reach a function_header ancestor -> Module-sourced. */ TEST(repro_grammar_shells_llvm_ir) { /* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): LLVM IR * (assembly-level). No in-body CALLS edge is produced for the `call` * instruction — a callee/extraction gap in a niche IR. Deferred for now; not a * mainstream-language bug. Original assertions below are preserved * (unreachable) for re-enable. */ printf("%sSKIP%s rare language (LLVM-IR call extraction)\n", tf_dim(), tf_reset()); return -1; /* skip — not counted as pass or fail */ static const char src[] = "define i32 @inner(i32 %x) {\n" "entry:\n" " %r = add i32 %x, 1\n" " ret i32 %r\n" "}\n" "\n" "define i32 @outer(i32 %x) {\n" "entry:\n" " %c = call i32 @inner(i32 %x)\n" " ret i32 %c\n" "}\n"; static const char bad[] = "define i32 @outer(i32 %x) {\nentry:\n %c = call i32 @inner("; if (sh_callable_battery("LLVM-IR", src, CBM_LANG_LLVM_IR, "mod.ll", "Function", NULL, "inner") != 0) return 1; if (sh_robustness("LLVM-IR", bad, CBM_LANG_LLVM_IR, "mod.ll") != 0) return 1; return sh_pipeline_battery("LLVM-IR", "mod.ll", src); } /* ── NASM ───────────────────────────────────────────────────────────────────── * Idiomatic: two labels (func via label) and a `call` instruction targeting the * inner label. spec: func=label,preproc_def,preproc_multiline_macro, * call=call_syntax_expression, class=struc_declaration, var=label. * * Dims asserted: 1-8 + R. Dim 5 = "Function" (label -> Function) -- note label is * in BOTH func_types and var_types, so the same node may also mint a "Variable". * Dim 6 callee = "inner". * Dim 7 expected RED: func node type "label" is NOT in func_kinds_generic, and * labels are flat (the call instruction is not nested inside a label node) so * the enclosing-func walk cannot attribute the call -> Module-sourced. */ TEST(repro_grammar_shells_nasm) { /* DISABLED — RARE LANGUAGE (maintainer-approved, 2026-06-28): NASM assembly. * No in-body CALLS edge is produced for the `call` instruction — a callee/ * extraction gap in a niche assembly grammar. Deferred for now; not a * mainstream-language bug. Original assertions below are preserved * (unreachable) for re-enable. */ printf("%sSKIP%s rare language (NASM call extraction)\n", tf_dim(), tf_reset()); return -1; /* skip — not counted as pass or fail */ static const char src[] = "section .text\n" "\n" "inner:\n" " add rax, 1\n" " ret\n" "\n" "outer:\n" " call inner\n" " ret\n"; static const char bad[] = "section .text\nouter:\n call "; if (sh_callable_battery("NASM", src, CBM_LANG_NASM, "prog.asm", "Function", NULL, "inner") != 0) return 1; if (sh_robustness("NASM", bad, CBM_LANG_NASM, "prog.asm") != 0) return 1; return sh_pipeline_battery("NASM", "prog.asm", src); } /* ── JANET (structural only) ────────────────────────────────────────────────── * Idiomatic Janet with a defn and a call. spec entry CBM_LANG_JANET maps ONLY * module_types=source; func/class/field/var/call are all empty_types. So NO defs * and NO calls are extracted from the grammar tree regardless of source content. * * Dims asserted: 1-4 + R. * Dims 5-8 SKIPPED: spec has no func/class/var/call types -- nothing extractable. * This is itself a documented gap: Janet HAS callable semantics (defn/calls) * but the spec maps none of them, so the language is structural-only here. * Expected GREEN: dims 1-4 + R. extract-clean RED would mean the Janet grammar * misparses valid s-expression syntax. */ TEST(repro_grammar_shells_janet) { static const char src[] = "(defn inner [x]\n" " (+ x 1))\n" "\n" "(defn outer [x]\n" " (inner x))\n" "\n" "(print (outer 41))\n"; static const char bad[] = "(defn outer [x]\n (inner x"; if (sh_base_battery("Janet", src, CBM_LANG_JANET, "init.janet") != 0) return 1; return sh_robustness("Janet", bad, CBM_LANG_JANET, "init.janet"); } /* ── SMALI (structural with defs, no calls) ─────────────────────────────────── * Idiomatic Smali (Dalvik bytecode) with a class, a method, and a field. * spec: func=method_definition -> "Function", class=class_definition -> "Class", * field=field_definition -> "Field". call_types = empty_types (no CALLS dims). * * Dims asserted: 1-5 + R. Dim 5 asserts "Class", "Function", and "Field". * Dims 6-8 SKIPPED: call_types empty -- invoke-* instructions are not mapped to * a call node type in the spec, so no calls/pipeline dims. * Expected GREEN: dims 1-5 + R. Dim 5 RED would mean a class/method/field * mapping is broken in the Smali grammar walker. */ TEST(repro_grammar_shells_smali) { static const char src[] = ".class public LCalculator;\n" ".super Ljava/lang/Object;\n" "\n" ".field private base:I\n" "\n" ".method public compute(I)I\n" " .registers 3\n" " add-int/lit8 v0, p1, 0x1\n" " return v0\n" ".end method\n"; static const char bad[] = ".class public LCalculator;\n.method public compute(I)I\n .registers"; if (sh_struct_battery("Smali", src, CBM_LANG_SMALI, "Calculator.smali", "Class", "Function", "Field") != 0) return 1; return sh_robustness("Smali", bad, CBM_LANG_SMALI, "Calculator.smali"); } /* ── DEVICETREE (structural) ────────────────────────────────────────────────── * Idiomatic Device Tree source with nodes and properties. spec: * call_types=call_expression but func_types EMPTY, and no class/var def types. * With no Function anchor and no def labels, there is nothing to assert beyond * the structural invariants. * * Dims asserted: 1-4 + R. * Dim 5 SKIPPED: no func/class/var types mapped -> no labelled defs expected. * Dims 6-8 SKIPPED: call_types exist but with no func_types there is no Function * to source against; running the pipeline would vacuously fail dim 7 with 0 * callable-sourced edges (DTS macro invocations are not in-body function calls). * Expected GREEN: dims 1-4 + R. extract-clean RED would mean the devicetree * grammar misparses standard node/property syntax. */ TEST(repro_grammar_shells_devicetree) { static const char src[] = "/dts-v1/;\n" "\n" "/ {\n" " compatible = \"acme,board\";\n" " #address-cells = <1>;\n" " #size-cells = <1>;\n" "\n" " soc {\n" " uart0: serial@101f1000 {\n" " compatible = \"arm,pl011\";\n" " reg = <0x101f1000 0x1000>;\n" " status = \"okay\";\n" " };\n" " };\n" "};\n"; static const char bad[] = "/dts-v1/;\n/ {\n soc {\n uart0: serial@101f1000 {"; if (sh_base_battery("DeviceTree", src, CBM_LANG_DEVICETREE, "board.dts") != 0) return 1; return sh_robustness("DeviceTree", bad, CBM_LANG_DEVICETREE, "board.dts"); } /* ── KCONFIG (structural with defs, no calls) ───────────────────────────────── * Idiomatic Kconfig with config entries and a menuconfig. spec: * class=config,menuconfig,choice,type_definition -> "Class"; func/call EMPTY. * * Dims asserted: 1-5 + R. Dim 5 = "Class" (config/menuconfig -> Class). * Dims 6-8 SKIPPED: no func_types/call_types. * Expected GREEN: dims 1-5 + R. Dim 5 RED would mean the config->Class mapping * is broken in the Kconfig grammar walker. */ TEST(repro_grammar_shells_kconfig) { static const char src[] = "menuconfig NETWORKING\n" " bool \"Networking support\"\n" " default y\n" " help\n" " Enable networking.\n" "\n" "config NET_IPV6\n" " bool \"IPv6 support\"\n" " depends on NETWORKING\n" " default n\n"; static const char bad[] = "config NET_IPV6\n bool \"IPv6 support\"\n depends on"; if (sh_struct_battery("Kconfig", src, CBM_LANG_KCONFIG, "Kconfig", "Class", NULL, NULL) != 0) return 1; return sh_robustness("Kconfig", bad, CBM_LANG_KCONFIG, "Kconfig"); } /* ── HYPRLANG (pure structural) ─────────────────────────────────────────────── * Idiomatic Hyprland config with sections and key=value assignments. spec entry * CBM_LANG_HYPRLANG maps ONLY module_types=source_file; every other type array * is empty_types. No defs or calls are extracted. * * Dims asserted: 1-4 + R. * Dims 5-8 SKIPPED: no func/class/var/call types in spec. * Expected GREEN: dims 1-4 + R. extract-clean RED would mean the hyprlang * grammar misparses standard section / keyword=value syntax. */ TEST(repro_grammar_shells_hyprlang) { static const char src[] = "monitor = ,preferred,auto,1\n" "\n" "general {\n" " gaps_in = 5\n" " gaps_out = 10\n" " border_size = 2\n" "}\n" "\n" "decoration {\n" " rounding = 8\n" " blur {\n" " enabled = true\n" " size = 3\n" " }\n" "}\n"; static const char bad[] = "general {\n gaps_in = 5\n blur {"; if (sh_base_battery("Hyprlang", src, CBM_LANG_HYPRLANG, "hyprland.conf") != 0) return 1; return sh_robustness("Hyprlang", bad, CBM_LANG_HYPRLANG, "hyprland.conf"); } /* ── Suite ──────────────────────────────────────────────────────────────────── */ SUITE(repro_grammar_shells) { RUN_TEST(repro_grammar_shells_bash); RUN_TEST(repro_grammar_shells_zsh); RUN_TEST(repro_grammar_shells_fish); RUN_TEST(repro_grammar_shells_powershell); RUN_TEST(repro_grammar_shells_tcl); RUN_TEST(repro_grammar_shells_awk); RUN_TEST(repro_grammar_shells_vimscript); RUN_TEST(repro_grammar_shells_fennel); RUN_TEST(repro_grammar_shells_nix); RUN_TEST(repro_grammar_shells_gdscript); RUN_TEST(repro_grammar_shells_luau); RUN_TEST(repro_grammar_shells_teal); RUN_TEST(repro_grammar_shells_llvm_ir); RUN_TEST(repro_grammar_shells_nasm); RUN_TEST(repro_grammar_shells_janet); RUN_TEST(repro_grammar_shells_smali); RUN_TEST(repro_grammar_shells_devicetree); RUN_TEST(repro_grammar_shells_kconfig); RUN_TEST(repro_grammar_shells_hyprlang); }