. "$BST_LIB" # lemon is the SQLite project's LALR(1) parser generator (meta-oe 3.44.2, built # from tool/lemon.c in the sqlite tree). Neither busybox nor toybox ships a lemon # applet, so a bare `lemon` is this port. byacc (83-lemon's sibling suite) is the # other parser generator on the image; the two are independent tools. require lemon lemon/present work=${TMPDIR:-/tmp}/bst-lemon.$$ rm -rf "$work"; mkdir -p "$work" "$work/dst" log=$work/log # lemon locates its parser-driver template by searching $PATH for lempar.c when # invoked with no '/' in argv[0] (getenv + access), and only in dirname(argv[0]) # otherwise -- which is why the recipe installs lempar.c beside the binary in # ${bindir}. Assert the template actually shipped there: without it every # generate below fails, so this is the port's data half, not a cosmetic file. lempar=$(dirname "$(command -v lemon)")/lempar.c check lemon/lempar-shipped test -f "$lempar" check_out lemon/lempar-template "ParseARG_SDECL" cat "$lempar" # -x prints the version banner and exits 0 -- unlike the generate runs, whose # status carries meaning (the exit-status contract below). check_out lemon/version "Lemon version 1.0" lemon -x # An UNAMBIGUOUS grammar. lemon's exit status is its contract: a grammar with # conflicts exits 1 even though the parser is still written (asserted at the end), # so a conflict-free fixture is what makes rc 0 mean "generated, no errors, no # conflicts". Left recursion over a single terminal keeps it conflict-free. cat > "$work/calc.y" <<'EOF' 2>/dev/null %token_type {int} %include { #include } %syntax_error { fprintf(stderr,"LEMON-SYNTAX-ERROR\n"); } program ::= expr(A). { printf("lemon-calc-result %d\n", A); } expr(A) ::= expr(B) PLUS NUM(C). { A = B + C; } expr(A) ::= NUM(B). { A = B; } EOF # Bare `lemon` on a clean grammar: rc 0, and the $PATH+access template lookup # above resolved -- the three output files are written next to the input. ( cd "$work" && lemon calc.y ) >"$log" 2>&1; check_eq lemon/rc-generate 0 "$?" check lemon/parser-file test -f "$work/calc.c" check lemon/header-file test -f "$work/calc.h" check lemon/report-file test -f "$work/calc.out" # The generated parser carries the driver routines the template supplies... check_out lemon/parser-alloc "ParseAlloc" cat "$work/calc.c" check_out lemon/parser-free "ParseFree" cat "$work/calc.c" check_out lemon/parser-reduce "yy_reduce" cat "$work/calc.c" # ...and the grammar's own action and error-handler code, substituted into it. # Assert on literal strings inside the C blocks: lemon rewrites the symbolic # aliases (A/B/C) into yymsp[] table references, so the arithmetic itself is # deliberately not a needle. check_out lemon/action-code "lemon-calc-result %d" cat "$work/calc.c" check_out lemon/error-hook "LEMON-SYNTAX-ERROR" cat "$work/calc.c" # The token header gets one #define per declared terminal, numbered from 1. check_out lemon/header-num "#define NUM" cat "$work/calc.h" check_out lemon/header-plus "#define PLUS" cat "$work/calc.h" # The report file is the LALR(1) machine itself, state by state. check_out lemon/report-states "State 0:" cat "$work/calc.out" # #line directives are emitted by default and suppressed by -l. check_rc lemon/lines-default 0 grep -q "^#line" "$work/calc.c" mkdir -p "$work/nl"; cp "$work/calc.y" "$work/nl/" ( cd "$work/nl" && lemon -l calc.y ) >/dev/null 2>&1; check_eq lemon/rc-nolines 0 "$?" check_rc lemon/nolines-gone 1 grep -q "^#line" "$work/nl/calc.c" # -q suppresses the report file only; the parser and header are still written. mkdir -p "$work/q"; cp "$work/calc.y" "$work/q/" ( cd "$work/q" && lemon -q calc.y ) >/dev/null 2>&1; check_eq lemon/rc-quiet 0 "$?" check lemon/quiet-parser test -f "$work/q/calc.c" check_rc lemon/quiet-noreport 1 test -f "$work/q/calc.out" # -s prints the parser statistics to stdout and still exits 0. The conflict # counter is the fixture's own witness that the grammar really is unambiguous. mkdir -p "$work/s"; cp "$work/calc.y" "$work/s/" sout=$( cd "$work/s" && lemon -s calc.y 2>&1 ); check_eq lemon/rc-stats 0 "$?" case "$sout" in *"Parser statistics:"*) bst_ok lemon/stats ;; *) bst_notok lemon/stats "$(_bst_1line "$sout")" ;; esac check_eq lemon/stats-noconflict 0 \ "$(printf '%s\n' "$sout" | awk '/^ conflicts/{print $NF}')" check_eq lemon/stats-rules 3 \ "$(printf '%s\n' "$sout" | awk '/^ rules/{print $NF}')" # -d redirects all three outputs to another directory, leaving the grammar's # own directory untouched. The value is attached to the flag, not a separate word. mkdir -p "$work/dsrc"; cp "$work/calc.y" "$work/dsrc/" ( cd "$work/dsrc" && lemon -d../dst calc.y ) >/dev/null 2>&1 check_eq lemon/rc-outdir 0 "$?" check lemon/outdir-parser test -f "$work/dst/calc.c" check_rc lemon/outdir-clean 1 test -f "$work/dsrc/calc.c" # -T names the template explicitly; a template that cannot be found is an error # (rc 1) and no parser is written -- the .h and report still are, so the missing # file is what the status reports, not a total failure to run. mkdir -p "$work/t"; cp "$work/calc.y" "$work/t/" ( cd "$work/t" && lemon -T"$lempar" calc.y ) >/dev/null 2>&1 check_eq lemon/rc-template 0 "$?" check lemon/template-parser test -f "$work/t/calc.c" mkdir -p "$work/tm"; cp "$work/calc.y" "$work/tm/" ( cd "$work/tm" && lemon -T/nonexistent/lempar.c calc.y ) >"$log" 2>&1 check_eq lemon/rc-notemplate 1 "$?" check_out lemon/msg-notemplate "Can't find the parser driver template file" cat "$log" check_rc lemon/notemplate-noparser 1 test -f "$work/tm/calc.c" # -m (makeheaders mode) leaves the header to makeheaders and writes none itself. mkdir -p "$work/m"; cp "$work/calc.y" "$work/m/" ( cd "$work/m" && lemon -m calc.y ) >/dev/null 2>&1; check_eq lemon/rc-mkhdr 0 "$?" check lemon/mkhdr-parser test -f "$work/m/calc.c" check_rc lemon/mkhdr-noheader 1 test -f "$work/m/calc.h" # -S additionally emits the SQL description of the parser tables. mkdir -p "$work/sq"; cp "$work/calc.y" "$work/sq/" ( cd "$work/sq" && lemon -S calc.y ) >/dev/null 2>&1; check_eq lemon/rc-sql 0 "$?" check lemon/sql-file test -f "$work/sq/calc.sql" # -g reprints the grammar without actions and generates nothing: a report mode, # exiting 0. mkdir -p "$work/g"; cp "$work/calc.y" "$work/g/" gout=$( cd "$work/g" && lemon -g calc.y 2>&1 ); check_eq lemon/rc-dump 0 "$?" case "$gout" in *"expr ::= expr PLUS NUM."*) bst_ok lemon/dump-rule ;; *) bst_notok lemon/dump-rule "$(_bst_1line "$gout")" ;; esac case "$gout" in *"lemon-calc-result"*) bst_notok lemon/dump-noactions "action code leaked into -g output" ;; *) bst_ok lemon/dump-noactions ;; esac check_rc lemon/dump-nofiles 1 test -f "$work/g/calc.c" # %ifdef sections are resolved by -D, and -E prints the result of that # preprocessing pass (exiting 0, generating nothing). mkdir -p "$work/e" cat > "$work/e/ifd.y" <<'EOF' 2>/dev/null %token NUM. %ifdef FEATURE expr ::= NUM NUM. %endif expr ::= NUM. EOF eout=$( cd "$work/e" && lemon -E ifd.y 2>&1 ); check_eq lemon/rc-pp 0 "$?" case "$eout" in *"expr ::= NUM."*) bst_ok lemon/pp-kept ;; *) bst_notok lemon/pp-kept "$(_bst_1line "$eout")" ;; esac # Without -DFEATURE the guarded rule is blanked out of the preprocessed text... case "$eout" in *"expr ::= NUM NUM."*) bst_notok lemon/pp-ifdef-off "guarded rule survived without -D" ;; *) bst_ok lemon/pp-ifdef-off ;; esac # ...and with it, the same rule appears. dout=$( cd "$work/e" && lemon -E -DFEATURE ifd.y 2>&1 ); check_eq lemon/rc-pp-def 0 "$?" case "$dout" in *"expr ::= NUM NUM."*) bst_ok lemon/pp-ifdef-on ;; *) bst_notok lemon/pp-ifdef-on "$(_bst_1line "$dout")" ;; esac # A grammar with a genuine LALR conflict: status 1 AND the count on stderr, while # the parser is still generated. This is the half of the exit-status contract a # caller must not read as "nothing was produced". mkdir -p "$work/c" cat > "$work/c/amb.y" <<'EOF' 2>/dev/null %token NUM PLUS. program ::= expr. expr ::= expr PLUS expr. expr ::= NUM. EOF ( cd "$work/c" && lemon amb.y ) >"$log" 2>&1 check_eq lemon/rc-conflict 1 "$?" check_out lemon/msg-conflict "1 parsing conflicts." cat "$log" check lemon/conflict-parser test -f "$work/c/amb.c" check_out lemon/conflict-report "conflict" cat "$work/c/amb.out" # The error paths: each reports its own diagnostic and exits 1. ( cd "$work" && lemon ) >"$log" 2>&1 check_eq lemon/rc-noargs 1 "$?" check_out lemon/msg-noargs "Exactly one filename argument is required." cat "$log" ( cd "$work" && lemon nosuch.y ) >"$log" 2>&1 check_eq lemon/rc-noinput 1 "$?" check_out lemon/msg-noinput "Can't open this file for reading." cat "$log" : > "$work/empty.y" ( cd "$work" && lemon empty.y ) >"$log" 2>&1 check_eq lemon/rc-empty 1 "$?" check_out lemon/msg-empty "Empty grammar." cat "$log" printf '%%token NUM.\nexpr NUM.\n' > "$work/bad.y" ( cd "$work" && lemon bad.y ) >"$log" 2>&1 check_eq lemon/rc-syntax 1 "$?" check_out lemon/msg-syntax 'Expected to see a ":"' cat "$log" printf '%%token NUM.\n%%bogus_decl foo.\nexpr ::= NUM.\n' > "$work/decl.y" ( cd "$work" && lemon decl.y ) >"$log" 2>&1 check_eq lemon/rc-baddecl 1 "$?" check_out lemon/msg-baddecl 'Unknown declaration keyword: "%bogus_decl"' cat "$log" ( cd "$work" && lemon -Z calc.y ) >"$log" 2>&1 check_eq lemon/rc-badopt 1 "$?" check_out lemon/msg-badopt "undefined option" cat "$log" # Regenerating from the same grammar reproduces the parser byte-for-byte -- the # generator carries no timestamp or path nondeterminism into its output. mkdir -p "$work/i"; cp "$work/calc.y" "$work/i/" ( cd "$work/i" && lemon -q calc.y ) >/dev/null 2>&1 cp "$work/i/calc.c" "$work/i/first.c" ( cd "$work/i" && lemon -q calc.y ) >/dev/null 2>&1; check_eq lemon/rc-again 0 "$?" check_rc lemon/idempotent 0 cmp -s "$work/i/first.c" "$work/i/calc.c" rm -rf "$work"