/* * test_grammar_regression.c — Per-language extraction regression net. * * Guards against silent extraction breakage when vendored tree-sitter grammars * are refreshed. Every CBM_LANG_* enum is exercised: a minimal sample is * extracted and checked for (a) no crash (non-NULL result) and (b) a * catastrophic-break floor (defs >= min_defs) plus, for confident cases, * expected definition names. A future grammar upgrade that renames/removes the * node types extraction depends on (e.g. the fwcd kotlin `name`-field drop that * produced 0 defs) fails this suite loudly. * * min_defs convention: code languages use >=1 (a sample with a function/type * must yield at least one def); data/config/markup languages use 0 (the check * is "parses without crashing"). Tighten a row's min_defs to lock in a count. */ #include "test_framework.h" #include "cbm.h" #include "grammar_cases.h" static int reg_has_def_any(CBMFileResult *r, const char *name) { for (int i = 0; i < r->defs.count; i++) { if (strcmp(r->defs.items[i].name, name) == 0) return 1; } return 0; } static CBMFileResult *extract(const char *src, CBMLanguage lang, const char *proj, const char *path) { return cbm_extract_file(src, (int)strlen(src), lang, proj, path, 0, NULL, NULL); } const GrammarCase CBM_GRAMMAR_CASES[] = { /* ── LSP-backed / mainstream code languages (expect named defs) ── */ {"go", CBM_LANG_GO, "a.go", "package m\nfunc Foo() {}\nfunc Bar() int { return 0 }\n", 2, {"Foo", "Bar", NULL}}, {"c", CBM_LANG_C, "a.c", "int foo(void){return 0;}\nint bar(void){return 1;}\n", 2, {"foo", "bar", NULL}}, {"cpp", CBM_LANG_CPP, "a.cpp", "struct A {};\nint foo(){return 0;}\n", 2, {"A", "foo", NULL}}, {"cuda", CBM_LANG_CUDA, "a.cu", "__global__ void foo(){}\nint bar(){return 0;}\n", 1, {"bar", NULL}}, {"python", CBM_LANG_PYTHON, "a.py", "def foo():\n pass\nclass A:\n pass\n", 2, {"foo", "A", NULL}}, {"javascript", CBM_LANG_JAVASCRIPT, "a.js", "function foo(){}\nclass A{}\n", 2, {"foo", "A", NULL}}, {"typescript", CBM_LANG_TYPESCRIPT, "a.ts", "function foo(): number { return 1; }\nclass A {}\n", 2, {"foo", "A", NULL}}, {"tsx", CBM_LANG_TSX, "a.tsx", "function foo(): number { return 1; }\n", 1, {"foo", NULL}}, {"java", CBM_LANG_JAVA, "A.java", "class A {\n void foo() {}\n}\n", 2, {"A", "foo", NULL}}, {"kotlin", CBM_LANG_KOTLIN, "a.kt", "fun foo() {}\nclass A\n", 2, {"foo", "A", NULL}}, {"rust", CBM_LANG_RUST, "a.rs", "fn foo() {}\nstruct A;\n", 2, {"foo", "A", NULL}}, {"ruby", CBM_LANG_RUBY, "a.rb", "def foo\nend\nclass A\nend\n", 2, {"foo", "A", NULL}}, {"php", CBM_LANG_PHP, "a.php", "=1), names vary by grammar ── */ {"ada", CBM_LANG_ADA, "a.adb", "procedure Foo is\nbegin\n null;\nend Foo;\n", 1, {NULL}}, {"agda", CBM_LANG_AGDA, "a.agda", "module M where\nfoo : Set\nfoo = Set\n", 0, {NULL}}, {"apex", CBM_LANG_APEX, "A.cls", "public class A {\n void foo() {}\n}\n", 1, {NULL}}, {"awk", CBM_LANG_AWK, "a.awk", "function foo() { print 1 }\nfunction bar() { print 2 }\n", 1, {NULL}}, {"cairo", CBM_LANG_CAIRO, "a.cairo", "fn foo() {}\nfn bar() {}\n", 1, {NULL}}, {"clojure", CBM_LANG_CLOJURE, "a.clj", "(defn foo [] 1)\n(defn bar [] 2)\n", 1, {NULL}}, {"commonlisp", CBM_LANG_COMMONLISP, "a.lisp", "(defun foo () 1)\n(defun bar () 2)\n", 1, {NULL}}, {"emacslisp", CBM_LANG_EMACSLISP, "a.el", "(defun foo () 1)\n(defun bar () 2)\n", 1, {NULL}}, {"crystal", CBM_LANG_CRYSTAL, "a.cr", "def foo\nend\nclass A\nend\n", 1, {NULL}}, {"d", CBM_LANG_DLANG, "a.d", "void foo() {}\nvoid bar() {}\n", 1, {NULL}}, {"elixir", CBM_LANG_ELIXIR, "a.ex", "defmodule A do\n def foo do\n end\nend\n", 1, {NULL}}, {"erlang", CBM_LANG_ERLANG, "a.erl", "-module(a).\nfoo() -> ok.\nbar() -> ok.\n", 1, {NULL}}, {"fennel", CBM_LANG_FENNEL, "a.fnl", "(fn foo [] 1)\n(fn bar [] 2)\n", 1, {NULL}}, {"fish", CBM_LANG_FISH, "a.fish", "function foo\n echo hi\nend\n", 1, {NULL}}, {"fortran", CBM_LANG_FORTRAN, "a.f90", "subroutine foo()\nend subroutine\n", 1, {NULL}}, {"fsharp", CBM_LANG_FSHARP, "a.fs", "let foo () = 1\nlet bar () = 2\n", 1, {NULL}}, {"gleam", CBM_LANG_GLEAM, "a.gleam", "pub fn foo() { 1 }\npub fn bar() { 2 }\n", 1, {NULL}}, {"glsl", CBM_LANG_GLSL, "a.glsl", "void foo() {}\nvoid main() {}\n", 1, {NULL}}, {"hare", CBM_LANG_HARE, "a.ha", "fn foo() void = void;\nfn bar() void = void;\n", 1, {NULL}}, {"haskell", CBM_LANG_HASKELL, "a.hs", "foo :: Int\nfoo = 1\n", 1, {NULL}}, {"hlsl", CBM_LANG_HLSL, "a.hlsl", "void foo() {}\nvoid bar() {}\n", 1, {NULL}}, {"ispc", CBM_LANG_ISPC, "a.ispc", "void foo() {}\nvoid bar() {}\n", 1, {NULL}}, {"objc", CBM_LANG_OBJC, "a.m", "@implementation A\n- (void)foo {}\n@end\n", 0, {NULL}}, {"ocaml", CBM_LANG_OCAML, "a.ml", "let foo () = 1\nlet bar () = 2\n", 1, {NULL}}, {"odin", CBM_LANG_ODIN, "a.odin", "foo :: proc() {}\nbar :: proc() {}\n", 1, {NULL}}, {"pascal", CBM_LANG_PASCAL, "a.pas", "procedure Foo;\nbegin\nend;\n", 1, {NULL}}, {"pony", CBM_LANG_PONY, "a.pony", "actor Main\n fun foo(): U32 => 1\n", 2, {"Main", "foo", NULL}}, {"purescript", CBM_LANG_PURESCRIPT, "a.purs", "foo :: Int\nfoo = 1\n", 0, {NULL}}, {"racket", CBM_LANG_RACKET, "a.rkt", "(define (foo) 1)\n(define (bar) 2)\n", 1, {NULL}}, {"rescript", CBM_LANG_RESCRIPT, "a.res", "let foo = () => 1\nlet bar = () => 2\n", 1, {NULL}}, {"scheme", CBM_LANG_SCHEME, "a.scm", "(define (foo) 1)\n(define (bar) 2)\n", 1, {NULL}}, {"slang", CBM_LANG_SLANG, "a.slang", "void foo() {}\nvoid bar() {}\n", 1, {NULL}}, {"squirrel", CBM_LANG_SQUIRREL, "a.nut", "function foo() {}\nfunction bar() {}\n", 1, {NULL}}, {"starlark", CBM_LANG_STARLARK, "a.bzl", "def foo():\n pass\ndef bar():\n pass\n", 1, {NULL}}, {"sway", CBM_LANG_SWAY, "a.sw", "fn foo() {}\nfn bar() {}\n", 1, {NULL}}, {"teal", CBM_LANG_TEAL, "a.tl", "local function foo() end\nlocal function bar() end\n", 1, {NULL}}, {"vimscript", CBM_LANG_VIMSCRIPT, "a.vim", "function! Foo()\nendfunction\n", 1, {NULL}}, {"elm", CBM_LANG_ELM, "a.elm", "module M exposing (..)\nfoo = 1\n", 0, {NULL}}, {"func", CBM_LANG_FUNC, "a.fc", "() foo() {\n}\n", 0, {NULL}}, {"lean", CBM_LANG_LEAN, "a.lean", "def foo : Nat := 1\ndef bar : Nat := 2\n", 0, {NULL}}, {"move", CBM_LANG_MOVE, "a.move", "module 0x1::m {\n public fun foo() {}\n}\n", 1, {"foo", NULL}}, {"mojo", CBM_LANG_MOJO, "a.mojo", "fn foo() -> Int:\n return 1\n\nstruct A:\n fn bar(self) -> Int:\n return foo()\n", 2, {"foo", "A", NULL}}, {"smali", CBM_LANG_SMALI, "A.smali", ".class public LA;\n.super Ljava/lang/Object;\n.method public foo()V\n.end method\n", 2, {"foo", NULL}}, {"systemverilog", CBM_LANG_SYSTEMVERILOG, "a.sv", "module m;\n function int foo(); return 0; endfunction\nendmodule\n", 0, {NULL}}, {"verilog", CBM_LANG_VERILOG, "a.v", "module m;\nendmodule\n", 0, {NULL}}, {"vhdl", CBM_LANG_VHDL, "a.vhd", "entity foo is\nend foo;\n", 0, {NULL}}, {"wgsl", CBM_LANG_WGSL, "a.wgsl", "fn foo() {}\nfn bar() {}\n", 1, {NULL}}, {"tlaplus", CBM_LANG_TLAPLUS, "a.tla", "---- MODULE M ----\nFoo == 1\n====\n", 0, {NULL}}, {"llvm", CBM_LANG_LLVM_IR, "a.ll", "define void @foo() {\n ret void\n}\n", 0, {NULL}}, {"tablegen", CBM_LANG_TABLEGEN, "a.td", "def Foo {}\n", 0, {NULL}}, {"puppet", CBM_LANG_PUPPET, "a.pp", "class foo {\n}\n", 0, {NULL}}, /* ── first-party / self-maintained grammars ── */ {"assembly", CBM_LANG_ASSEMBLY, "a.s", ".global foo\nfoo:\n ret\n", 0, {NULL}}, {"nasm", CBM_LANG_NASM, "a.asm", "global foo\nfoo:\n ret\n", 0, {NULL}}, {"cfml", CBM_LANG_CFML, "a.cfm", "\n", 0, {NULL}}, {"cfscript", CBM_LANG_CFSCRIPT, "a.cfc", "component {\n function foo() {}\n}\n", 0, {NULL}}, {"cobol", CBM_LANG_COBOL, "a.cob", " IDENTIFICATION DIVISION.\n PROGRAM-ID. FOO.\n PROCEDURE DIVISION.\n " " MAIN-PARA.\n STOP RUN.\n", 1, {"FOO", NULL}}, {"janet", CBM_LANG_JANET, "a.janet", "(defn foo [] 1)\n(defn bar [] 2)\n", 0, {NULL}}, {"magma", CBM_LANG_MAGMA, "a.magma", "function foo()\n return 1;\nend function;\n", 0, {NULL}}, {"qml", CBM_LANG_QML, "a.qml", "import QtQuick\nItem {\n function foo() {}\n}\n", 0, {NULL}}, {"wolfram", CBM_LANG_WOLFRAM, "a.wl", "foo[x_] := x + 1\n", 0, {NULL}}, {"pine", CBM_LANG_PINE, "a.pine", "foo() =>\n 1\n", 0, {NULL}}, {"form", CBM_LANG_FORM, "a.frm", "Symbol x;\nLocal F = x;\n", 0, {NULL}}, {"protobuf", CBM_LANG_PROTOBUF, "a.proto", "syntax = \"proto3\";\n\nmessage Foo {\n int32 id = 1;\n}\n", 1, {"Foo", NULL}}, {"soql", CBM_LANG_SOQL, "a.soql", "SELECT Id FROM Account\n", 0, {NULL}}, {"sosl", CBM_LANG_SOSL, "a.sosl", "FIND {test} IN ALL FIELDS\n", 0, {NULL}}, {"dotenv", CBM_LANG_DOTENV, "a.env", "FOO=bar\nBAZ=qux\n", 0, {NULL}}, /* ── data / config / markup / template languages (no-crash floor) ── */ {"json", CBM_LANG_JSON, "a.json", "{\"a\": 1, \"b\": [2, 3]}\n", 0, {NULL}}, {"json5", CBM_LANG_JSON5, "a.json5", "{a: 1, b: 2}\n", 0, {NULL}}, {"jsonnet", CBM_LANG_JSONNET, "a.jsonnet", "{ a: 1, b: 2 }\n", 0, {NULL}}, {"jsdoc", CBM_LANG_JSDOC, "a.jsdoc", "/** @param {number} x */\n", 0, {NULL}}, {"yaml", CBM_LANG_YAML, "a.yaml", "a: 1\nb:\n - 2\n - 3\n", 0, {NULL}}, {"k8s", CBM_LANG_K8S, "a.yaml", "apiVersion: v1\nkind: Pod\n", 0, {NULL}}, {"kustomize", CBM_LANG_KUSTOMIZE, "kustomization.yaml", "resources:\n - a.yaml\n", 0, {NULL}}, {"toml", CBM_LANG_TOML, "a.toml", "[section]\nkey = 1\n", 0, {NULL}}, {"ini", CBM_LANG_INI, "a.ini", "[section]\nkey=1\n", 0, {NULL}}, {"csv", CBM_LANG_CSV, "a.csv", "a,b,c\n1,2,3\n", 0, {NULL}}, {"sql", CBM_LANG_SQL, "a.sql", "CREATE TABLE t (id INT);\nSELECT * FROM t;\n", 0, {NULL}}, {"xml", CBM_LANG_XML, "a.xml", "x\n", 0, {NULL}}, {"html", CBM_LANG_HTML, "a.html", "

hi

\n", 0, {NULL}}, {"css", CBM_LANG_CSS, "a.css", "a { color: red; }\n.x { width: 1px; }\n", 0, {NULL}}, {"scss", CBM_LANG_SCSS, "a.scss", "$c: red;\na { color: $c; }\n", 0, {NULL}}, {"markdown", CBM_LANG_MARKDOWN, "a.md", "# Title\n\nSome text.\n", 0, {NULL}}, {"rst", CBM_LANG_RST, "a.rst", "Title\n=====\n\ntext\n", 0, {NULL}}, {"dockerfile", CBM_LANG_DOCKERFILE, "Dockerfile", "FROM alpine\nRUN echo hi\n", 0, {NULL}}, {"makefile", CBM_LANG_MAKEFILE, "Makefile", "all:\n\techo hi\n", 0, {NULL}}, {"cmake", CBM_LANG_CMAKE, "CMakeLists.txt", "function(foo)\nendfunction()\n", 0, {NULL}}, {"meson", CBM_LANG_MESON, "meson.build", "project('x', 'c')\n", 0, {NULL}}, {"gn", CBM_LANG_GN, "a.gn", "executable(\"foo\") {\n}\n", 0, {NULL}}, {"just", CBM_LANG_JUST, "justfile", "foo:\n\techo hi\n", 0, {NULL}}, {"hcl", CBM_LANG_HCL, "a.hcl", "resource \"x\" \"y\" {\n a = 1\n}\n", 0, {NULL}}, {"nix", CBM_LANG_NIX, "a.nix", "{ foo = 1; bar = 2; }\n", 0, {NULL}}, {"gomod", CBM_LANG_GOMOD, "go.mod", "module example.com/x\n\ngo 1.21\n", 0, {NULL}}, {"gotemplate", CBM_LANG_GOTEMPLATE, "a.tmpl", "{{ if .X }}{{ .Y }}{{ end }}\n", 0, {NULL}}, {"graphql", CBM_LANG_GRAPHQL, "a.graphql", "type Foo {\n id: ID\n}\n", 0, {NULL}}, {"prisma", CBM_LANG_PRISMA, "a.prisma", "model Foo {\n id Int @id\n}\n", 0, {NULL}}, {"thrift", CBM_LANG_THRIFT, "a.thrift", "service Foo {\n void ping()\n}\n", 0, {NULL}}, {"capnp", CBM_LANG_CAPNP, "a.capnp", "struct Foo {\n id @0 :Int32;\n}\n", 0, {NULL}}, {"smithy", CBM_LANG_SMITHY, "a.smithy", "namespace example\n\nstructure Foo {\n id: Integer\n}\n", 1, {"Foo", NULL}}, {"wit", CBM_LANG_WIT, "a.wit", "interface i {\n record r { x: u32 }\n enum e { a, b }\n f: func() -> u32;\n}\n", 3, {"r", "e", "f", NULL}}, {"kdl", CBM_LANG_KDL, "a.kdl", "node \"x\" {\n}\n", 0, {NULL}}, {"ron", CBM_LANG_RON, "a.ron", "(a: 1, b: 2)\n", 0, {NULL}}, {"nickel", CBM_LANG_NICKEL, "a.ncl", "{ foo = 1, bar = 2 }\n", 0, {NULL}}, {"pkl", CBM_LANG_PKL, "a.pkl", "foo = 1\nbar = 2\n", 0, {NULL}}, {"bicep", CBM_LANG_BICEP, "a.bicep", "param foo string\n", 0, {NULL}}, {"bitbake", CBM_LANG_BITBAKE, "a.bb", "DESCRIPTION = \"x\"\n", 0, {NULL}}, {"beancount", CBM_LANG_BEANCOUNT, "a.beancount", "2020-01-01 open Assets:Cash\n", 0, {NULL}}, {"bibtex", CBM_LANG_BIBTEX, "a.bib", "@article{key,\n title = {X}\n}\n", 0, {NULL}}, {"po", CBM_LANG_PO, "a.po", "msgid \"x\"\nmsgstr \"y\"\n", 0, {NULL}}, {"diff", CBM_LANG_DIFF, "a.diff", "--- a\n+++ b\n@@ -1 +1 @@\n-x\n+y\n", 0, {NULL}}, {"regex", CBM_LANG_REGEX, "a.re", "(foo|bar)+\n", 0, {NULL}}, {"requirements", CBM_LANG_REQUIREMENTS, "requirements.txt", "flask==1.0\nrequests>=2\n", 0, {NULL}}, {"properties", CBM_LANG_PROPERTIES, "a.properties", "foo=1\nbar=2\n", 0, {NULL}}, {"gitignore", CBM_LANG_GITIGNORE, ".gitignore", "*.o\nbuild/\n", 0, {NULL}}, {"gitattributes", CBM_LANG_GITATTRIBUTES, ".gitattributes", "*.c text\n", 0, {NULL}}, {"sshconfig", CBM_LANG_SSHCONFIG, "config", "Host x\n HostName y\n", 0, {NULL}}, {"hyprlang", CBM_LANG_HYPRLANG, "a.conf", "general {\n gaps_in = 5\n}\n", 0, {NULL}}, {"kconfig", CBM_LANG_KCONFIG, "Kconfig", "config FOO\n bool \"foo\"\n", 0, {NULL}}, {"linkerscript", CBM_LANG_LINKERSCRIPT, "a.ld", "SECTIONS {\n .text : {}\n}\n", 0, {NULL}}, {"devicetree", CBM_LANG_DEVICETREE, "a.dts", "/dts-v1/;\n/ {\n};\n", 0, {NULL}}, {"jinja2", CBM_LANG_JINJA2, "a.j2", "{% if x %}{{ y }}{% endif %}\n", 0, {NULL}}, {"liquid", CBM_LANG_LIQUID, "a.liquid", "{% if x %}{{ y }}{% endif %}\n", 0, {NULL}}, {"blade", CBM_LANG_BLADE, "a.blade.php", "@if($x)\n{{ $y }}\n@endif\n", 0, {NULL}}, {"vue", CBM_LANG_VUE, "a.vue", "\n", 0, {NULL}}, {"svelte", CBM_LANG_SVELTE, "a.svelte", "\n

{x}

\n", 0, {NULL}}, {"astro", CBM_LANG_ASTRO, "a.astro", "---\nconst x = 1;\n---\n
{x}
\n", 0, {NULL}}, {"templ", CBM_LANG_TEMPL, "a.templ", "templ foo() {\n
\n}\n", 0, {NULL}}, {"typst", CBM_LANG_TYPST, "a.typ", "= Title\n\nsome text\n", 0, {NULL}}, {"mermaid", CBM_LANG_MERMAID, "a.mmd", "graph TD\n A --> B\n", 0, {NULL}}, }; const size_t CBM_GRAMMAR_CASES_COUNT = sizeof(CBM_GRAMMAR_CASES) / sizeof(CBM_GRAMMAR_CASES[0]); TEST(grammar_regression_all) { int failures = 0; size_t n = CBM_GRAMMAR_CASES_COUNT; for (size_t i = 0; i < n; i++) { const GrammarCase *c = &CBM_GRAMMAR_CASES[i]; CBMFileResult *r = extract(c->src, c->lang, "reg", c->path); if (!r) { fprintf(stderr, " [REG] %-14s extract returned NULL\n", c->name); failures++; continue; } if (r->defs.count < c->min_defs) { fprintf(stderr, " [REG] %-14s defs=%d < min=%d (extraction regression?)\n", c->name, r->defs.count, c->min_defs); failures++; } for (int e = 0; c->expect[e]; e++) { if (!reg_has_def_any(r, c->expect[e])) { fprintf(stderr, " [REG] %-14s missing def '%s' (defs=%d)\n", c->name, c->expect[e], r->defs.count); failures++; } } cbm_free_result(r); } if (failures > 0) { fprintf(stderr, " [REG] %d grammar-regression check(s) failed across %zu languages\n", failures, n); } ASSERT_EQ(failures, 0); PASS(); } void suite_grammar_regression(void) { RUN_TEST(grammar_regression_all); }