run.sh (10693B)
1 #!/bin/sh 2 # test/gram/run.sh — gram subsystem functional + diagnostic tests. 3 # 4 # Drives `kit gram` to generate parsers/lexers/machines, compiles the generated 5 # C plus the test drivers against libkit.a, runs them, and checks the error 6 # fixtures (a grammar must be rejected with the expected diagnostic substring). 7 # Run from the repo root (some drivers fopen() their grammar by relative path). 8 # 9 # Env (set by the Makefile target; sensible defaults for ad-hoc runs): 10 # KIT path to the kit binary (required) 11 # KIT_GRAM_CC host C compiler (default: cc) 12 # KIT_GRAM_CFLAGS compile flags (sysroot, sanitizers, -Iinclude, ...) 13 # KIT_GRAM_LIB libkit.a (default: build/libkit.a) 14 # KIT_GRAM_BUILD scratch dir for generated C + binaries 15 set -u 16 17 KIT=${KIT:?set KIT to the kit binary} 18 CC=${KIT_GRAM_CC:-cc} 19 CFLAGS=${KIT_GRAM_CFLAGS:--std=c11 -Wall -Wextra -Werror -Iinclude} 20 LIB=${KIT_GRAM_LIB:-build/libkit.a} 21 DIR=${KIT_GRAM_BUILD:-build/test/gram} 22 SRC=test/gram 23 24 mkdir -p "$DIR" 25 pass=0 26 fail=0 27 fails="" 28 29 # Table-free link checks compile sanitizer-free with section GC + dead-strip so 30 # the unused table-parser glue (which references the parser runtime) is dropped, 31 # proving the standalone scanner alone needs nothing from libkit. 32 case "$(uname)" in 33 Darwin) DEADSTRIP="-Wl,-dead_strip" ;; 34 *) DEADSTRIP="-Wl,--gc-sections" ;; 35 esac 36 SA_CFLAGS="-std=c11 -O2 -ffunction-sections -fdata-sections -Iinclude ${KIT_GRAM_SYSROOT:-}" 37 38 note_fail() { 39 fail=$((fail + 1)) 40 fails="$fails $1" 41 } 42 43 # gen <prefix> <out-base> <grammar> [extra `kit gram` flags...] 44 # Always passes --prefix (even when empty, for %machine codegen-only output). 45 gen() { 46 _pfx=$1 47 _base=$2 48 _gram=$3 49 shift 3 50 if "$KIT" gram --prefix "$_pfx" "$@" -o "$DIR/$_base.c" \ 51 --header "$DIR/$_base.h" "$_gram" >"$DIR/$_base.gen.out" \ 52 2>"$DIR/$_base.gen.err"; then 53 return 0 54 fi 55 printf ' FAIL gen %s\n' "$_base" 56 sed 's/^/ /' "$DIR/$_base.gen.err" 57 note_fail "gen:$_base" 58 return 1 59 } 60 61 # build <name> <yes|no link libkit> <cc args: flags + sources...> 62 build() { 63 _name=$1 64 _link=$2 65 shift 2 66 set -- $CFLAGS -I"$DIR" -I"$SRC" "$@" 67 if [ "$_link" = yes ]; then set -- "$@" "$LIB"; fi 68 if "$CC" "$@" -o "$DIR/$_name" 2>"$DIR/$_name.cc.err"; then 69 return 0 70 fi 71 printf ' FAIL compile %s\n' "$_name" 72 sed 's/^/ /' "$DIR/$_name.cc.err" | tail -15 73 note_fail "cc:$_name" 74 return 1 75 } 76 77 # build_linkcheck <name> <sources...> : sanitizer-free, dead-stripped, NO libkit. 78 build_linkcheck() { 79 _name=$1 80 shift 81 if "$CC" $SA_CFLAGS -I"$DIR" -I"$SRC" $DEADSTRIP -o "$DIR/$_name" "$@" \ 82 2>"$DIR/$_name.cc.err"; then 83 return 0 84 fi 85 printf ' FAIL link-check %s\n' "$_name" 86 sed 's/^/ /' "$DIR/$_name.cc.err" | tail -15 87 note_fail "cc:$_name" 88 return 1 89 } 90 91 # run <name> <binary> 92 run() { 93 if "$2" >"$DIR/$1.out" 2>"$DIR/$1.err"; then 94 pass=$((pass + 1)) 95 printf ' ok %s\n' "$1" 96 else 97 _rc=$? 98 printf ' FAIL %s (exit %d)\n' "$1" "$_rc" 99 sed 's/^/ /' "$DIR/$1.err" | tail -10 100 note_fail "run:$1" 101 fi 102 } 103 104 echo "== gram functional tests ==" 105 106 # calc: hand-written tables, then generated tables (same driver, -D switch). 107 build calc_hand yes "$SRC/calc.c" "$SRC/calc_test.c" && run calc_hand "$DIR/calc_hand" 108 gen calc_ generated_calc "$SRC/calc.ebnf" && 109 build calc_gen yes -DKIT_GRAM_GENERATED "$DIR/generated_calc.c" "$SRC/calc_test.c" && 110 run calc_gen "$DIR/calc_gen" 111 112 # features grammar: parser actions + the table lexer over the same tables. 113 if gen features_ generated_features "$SRC/features.ebnf"; then 114 build features yes "$DIR/generated_features.c" "$SRC/features_test.c" && run features "$DIR/features" 115 build lexer yes "$DIR/generated_features.c" "$SRC/lexer_test.c" && run lexer "$DIR/lexer" 116 fi 117 118 gen desugar_ generated_desugar "$SRC/desugar.ebnf" && 119 build desugar yes "$DIR/generated_desugar.c" "$SRC/desugar_test.c" && run desugar "$DIR/desugar" 120 gen pratt_calc_ generated_pratt_calc "$SRC/pratt_calc.ebnf" && 121 build pratt_calc yes "$DIR/generated_pratt_calc.c" "$SRC/pratt_calc_test.c" && run pratt_calc "$DIR/pratt_calc" 122 gen pratt_mixfix_ generated_pratt_mixfix "$SRC/pratt_mixfix.ebnf" && 123 build pratt_mixfix yes "$DIR/generated_pratt_mixfix.c" "$SRC/pratt_mixfix_test.c" && run pratt_mixfix "$DIR/pratt_mixfix" 124 gen keywords_ generated_keywords "$SRC/keywords.ebnf" && 125 build keywords yes "$DIR/generated_keywords.c" "$SRC/keywords_test.c" && run keywords "$DIR/keywords" 126 gen unicode_ generated_unicode "$SRC/unicode.ebnf" && 127 build unicode_lexer yes "$DIR/generated_unicode.c" "$SRC/unicode_lexer_test.c" && run unicode_lexer "$DIR/unicode_lexer" 128 gen utf8_runtime_ generated_utf8_runtime "$SRC/utf8_runtime.ebnf" && 129 build utf8_runtime yes "$DIR/generated_utf8_runtime.c" "$SRC/utf8_runtime_test.c" && run utf8_runtime "$DIR/utf8_runtime" 130 131 # Realistic JSON / C-like, generated with --parser-codegen (one file carries the 132 # table parser + table lexer + the generated pull parser). 133 gen json_ generated_rjson "$SRC/realistic/json.ebnf" --parser-codegen && 134 build rjson yes "$DIR/generated_rjson.c" "$SRC/realistic/test_json.c" && run rjson "$DIR/rjson" 135 gen clike_ generated_clike "$SRC/realistic/clike.ebnf" --parser-codegen && 136 build rclike yes "$DIR/generated_clike.c" "$SRC/realistic/test_clike.c" && run rclike "$DIR/rclike" 137 138 # Pure in-process gramgen API / runtime tests (no codegen step). 139 build gramgen_api yes "$SRC/gramgen_api_test.c" && run gramgen_api "$DIR/gramgen_api" 140 build match yes "$SRC/match_test.c" && run match "$DIR/match" 141 build sublexer_table yes "$SRC/sublexer_table_test.c" && run sublexer_table "$DIR/sublexer_table" 142 build json_frontend yes "$SRC/realistic/test_json_frontend.c" && run json_frontend "$DIR/json_frontend" 143 # unicode_support reaches the private generated property header. 144 build unicode_support yes -Isrc/gram "$SRC/unicode_support_test.c" && run unicode_support "$DIR/unicode_support" 145 146 # Token machines (%machine): codegen-only, linked with NO libkit (table-free). 147 gen "" generated_machine "$SRC/machine.ebnf" && 148 build machine no "$DIR/generated_machine.c" "$SRC/machine_test.c" && run machine "$DIR/machine" 149 150 # Pull recursive-descent parser codegen (RD + error-recovery variants). 151 if gen jsonrd_ jsonrd "$SRC/realistic/json.ebnf" --parser-codegen && 152 gen clikerd_ clikerd "$SRC/realistic/clike.ebnf" --parser-codegen && 153 gen mixfixrd_ mixfixrd "$SRC/mixfix.ebnf" --parser-codegen && 154 gen clikerec_ clikerec "$SRC/realistic/clike.ebnf" --parser-codegen --parser-recover && 155 gen mixfixrec_ mixfixrec "$SRC/mixfix.ebnf" --parser-codegen --parser-recover; then 156 build parser_codegen yes "$DIR/jsonrd.c" "$DIR/clikerd.c" "$DIR/mixfixrd.c" \ 157 "$DIR/clikerec.c" "$DIR/mixfixrec.c" "$SRC/parser_codegen_test.c" && 158 run parser_codegen "$DIR/parser_codegen" 159 fi 160 161 # Standalone table-free scanners (--lexer-standalone), incl. fold-keywords, 162 # utf8, anchors, match, and the fused parser+standalone-scanner variants. 163 if gen jsonsa_ jsonsa "$SRC/realistic/json.ebnf" --lexer-standalone && 164 gen clikesa_ clikesa "$SRC/realistic/clike.ebnf" --lexer-standalone && 165 gen clikesaf_ clikesaf "$SRC/realistic/clike.ebnf" --lexer-standalone --fold-keywords && 166 gen kwutf8sa_ kwutf8sa "$SRC/keywords_utf8.ebnf" --lexer-standalone && 167 gen anchstartsa_ anchstartsa "$SRC/anchors_start.ebnf" --lexer-standalone --multiline && 168 gen anchsa_ anchsa "$SRC/anchors.ebnf" --lexer-standalone --multiline && 169 gen possa_ possa "$SRC/standalone/pos.ebnf" --lexer-standalone --multiline && 170 gen matchsa_ matchsa "$SRC/standalone/match.ebnf" --lexer-standalone --multiline && 171 gen jsonsr_ jsonsr "$SRC/realistic/json.ebnf" --lexer-standalone --parser-codegen && 172 gen clikesr_ clikesr "$SRC/realistic/clike.ebnf" --lexer-standalone --parser-codegen; then 173 # The driver deliberately type-puns the generated per-grammar scanner entry 174 # points to one local signature, so drop UBSan's -fsanitize=function check 175 # (fatal under -fno-sanitize-recover) for this one binary. 176 build lexer_standalone yes -fno-sanitize=function "$DIR/jsonsa.c" "$DIR/clikesa.c" \ 177 "$DIR/clikesaf.c" "$DIR/kwutf8sa.c" "$DIR/anchstartsa.c" "$DIR/anchsa.c" \ 178 "$DIR/possa.c" "$DIR/matchsa.c" "$DIR/jsonsr.c" "$DIR/clikesr.c" \ 179 "$SRC/lexer_standalone_test.c" && 180 run lexer_standalone "$DIR/lexer_standalone" 181 fi 182 183 # Table-free link check: a standalone scanner + a tiny driver, NO libkit 184 # (dead-strip drops the unused table-parser glue). 185 build_linkcheck sa_link "$DIR/jsonsa.c" "$SRC/standalone/link.c" && run sa_link "$DIR/sa_link" 186 187 # Sub-lexers from one standalone grammar, NO libkit. 188 gen sublex_ sublexersa "$SRC/standalone/sublexer.ebnf" --lexer-standalone && 189 build_linkcheck sublexer "$DIR/sublexersa.c" "$SRC/standalone/test_sublexer.c" && run sublexer "$DIR/sublexer" 190 191 # Lazy line/col positions (grampos.h) over normal + --position-lazy scanners. 192 if gen posb_ posb "$SRC/pos_byte.ebnf" --lexer-standalone && 193 gen posbl_ posbl "$SRC/pos_byte.ebnf" --lexer-standalone --position-lazy && 194 gen posu_ posu "$SRC/pos_utf8.ebnf" --lexer-standalone && 195 gen posul_ posul "$SRC/pos_utf8.ebnf" --lexer-standalone --position-lazy; then 196 build position yes "$DIR/posb.c" "$DIR/posbl.c" "$DIR/posu.c" "$DIR/posul.c" \ 197 "$SRC/position_test.c" && run position "$DIR/position" 198 fi 199 200 # --- diagnostic fixtures: each grammar must be rejected with the expected msg --- 201 check_errors() { 202 _label=$1 203 _glob=$2 204 for ebnf in $_glob; do 205 [ -e "$ebnf" ] || continue 206 _b=$(basename "$ebnf" .ebnf) 207 # Prefer the precise meta-parser message (.meta_expect) when present; the 208 # meta grammar is the only parse path now, so it is the message produced. 209 _exp="${ebnf%.ebnf}.meta_expect" 210 [ -f "$_exp" ] || _exp="${ebnf%.ebnf}.expect" 211 [ -f "$_exp" ] || continue 212 if "$KIT" gram -o "$DIR/$_b.bad.c" --header "$DIR/$_b.bad.h" "$ebnf" \ 213 >"$DIR/$_b.err.out" 2>"$DIR/$_b.err.err"; then 214 printf ' FAIL %s/%s: accepted invalid grammar\n' "$_label" "$_b" 215 note_fail "$_label:$_b" 216 elif grep -F -f "$_exp" "$DIR/$_b.err.err" >/dev/null 2>&1; then 217 pass=$((pass + 1)) 218 printf ' ok %s/%s\n' "$_label" "$_b" 219 else 220 printf ' FAIL %s/%s: diagnostic mismatch\n' "$_label" "$_b" 221 printf ' expected substring: '; cat "$_exp"; echo 222 printf ' actual: '; cat "$DIR/$_b.err.err" 223 note_fail "$_label:$_b" 224 fi 225 done 226 } 227 228 echo "== gram diagnostic fixtures ==" 229 check_errors errors "$SRC/errors/*.ebnf" 230 check_errors machine_errors "$SRC/machine_errors/*.ebnf" 231 232 echo 233 echo "== gram: $pass passed, $fail failed ==" 234 if [ "$fail" -ne 0 ]; then 235 echo "FAILURES:$fails" 236 exit 1 237 fi 238 exit 0