;;; lang/c99/mach.scm - C parser grammer

;; Copyright (C) 2015-2021 Matthew R. Wette
;;
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 3 of the License, or (at your option) any later version.
;;
;; This library is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public License
;; along with this library; if not, see <http://www.gnu.org/licenses/>.

;;; Notes:

;; This is a C parser, based on ISO-C99, with comments and CPP statements.

;; Currentl K&R function definitions are not supported:
;;   int f(x) int x; { ... } => syntax error
;; because they lead to an ambiguous grammar, I believe:
;;   int f(x) __attribute__((__static__)) int x; { ... }

;; See also:
;;   http://www.quut.com/c/ANSI-C-grammar-y.html - C11 grammar
;;   https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html#C-Extensions

;;; Code:

(define-module (nyacc lang c99 mach)
  #:export (c99-spec c99-mach c99x-spec c99x-mach gen-c99-files)
  #:use-module (nyacc lang c99 cpp)
  #:use-module (nyacc lang util)
  #:use-module (nyacc lalr)
  #:use-module (nyacc parse)
  #:use-module (nyacc lex)
  #:use-module (nyacc util))

;; @deffn {Variable} c99-spec
;; This variable is the specification a-list for the hacked ISO C99 language.
;; Well, actually, this does not produce pure C99 spec: it has been extended
;; to be able be used for practical purposes so it now parses forms like
;; @code{__asm__} and @code{__attribute__}.
;; Run this through @code{make-lalr-machine} to get an a-list for the
;; automaton.  The grammar is modified to parse CPP statements and comments.
;; The output of the end parser will be a SXML tree (w/o the @code{*TOP*} node.
;; @end deffn
(define c99-spec
  (lalr-spec
   (notice (string-append "Copyright (C) 2016-2020 Matthew R. Wette"
			  license-lgpl3+))

   (prec< 'then "else")	       ; "then/else" SR-conflict resolution
   (prec< (left 'imp)	       ; "implied type" SR-conflict resolution
	  (left "char" "short" "int" "long" "_Fract" "_Accum" "_Sat")
	  (left "float" "double" "_Complex"))
   (prec< 'shift-on-attr		; living on the edge ...
	  (nonassoc "__attribute__" "__packed__" "__aligned__" "__alignof__")
	  'reduce-on-attr
	  'reduce-on-semi
	  (nonassoc "*" "(" '$ident))

   (start translation-unit)
   (grammar

    ;; === expressions ========================================================

    (primary-expression			; S 6.5.1
     (identifier ($$ `(p-expr ,$1)))
     (constant ($$ `(p-expr ,$1)))
     (string-literal ($$ `(p-expr ,$1)))
     ("(" expression ")" ($$ $2))
     ("(" "{" ($$ (cpi-push)) block-item-list ($$ (cpi-pop)) "}" ")"
      ($$ `(stmt-expr (@ (extension "GNUC")) ,(tl->list $4)))))

    (postfix-expression			; S 6.5.2
     (primary-expression)
     (postfix-expression "[" expression "]" ($$ `(array-ref ,$3 ,$1)))
     (postfix-expression "(" argument-expression-list ")"
			 ($$ `(fctn-call ,$1 ,(tl->list $3))))
     (postfix-expression "(" ")" ($$ `(fctn-call ,$1 (expr-list))))
     (postfix-expression "." identifier ($$ `(d-sel ,$3 ,$1)))
     (postfix-expression "->" identifier ($$ `(i-sel ,$3 ,$1)))
     (postfix-expression "++" ($$ `(post-inc ,$1)))
     (postfix-expression "--" ($$ `(post-dec ,$1)))
     ("(" type-name ")" "{" initializer-list "}"
      ($$ `(comp-lit ,$2 ,(tl->list $5))))
     ("(" type-name ")" "{" initializer-list "," "}"
      ($$ `(comp-lit ,$2 ,(tl->list $5)))))

    (argument-expression-list
     (assignment-expression ($$ (make-tl 'expr-list $1)))
     (argument-expression-list "," assignment-expression ($$ (tl-append $1 $3)))
     ;; The following is a hack to deal with using abstract declarations
     ;; as arguments to CPP macros (e.g., see offsetof in <stddef.h>).
     (arg-expr-hack ($$ (make-tl 'expr-list $1)))
     (argument-expression-list "," arg-expr-hack ($$ (tl-append $1 $3))))
    (arg-expr-hack
     (declaration-specifiers
      abstract-declarator ($$ `(param-decl ,1 ,$2)))
     (declaration-specifiers ($$ `(param-decl ,$1))))

    (unary-expression
     (postfix-expression)		; S 6.5.3
     ("++" unary-expression ($$ `(pre-inc ,$2)))
     ("--" unary-expression ($$ `(pre-dec ,$2)))
     (unary-operator cast-expression ($$ (list $1 $2)))
     ("sizeof" unary-expression ($$ `(sizeof-expr ,$2)))
     ("sizeof" "(" type-name ")" ($$ `(sizeof-type ,$3))))
    
    (unary-operator ("&" ($$ 'ref-to)) ("*" ($$ 'de-ref))
		    ("+" ($$ 'pos)) ("-" ($$ 'neg))
		    ("~" ($$ 'bitwise-not)) ("!" ($$ 'not)))

    (cast-expression			; S 6.5.4
     (unary-expression)
     ("(" type-name ")" cast-expression ($$ `(cast ,$2 ,$4))))

    (multiplicative-expression		; S 6.5.5
     (cast-expression)
     (multiplicative-expression "*" cast-expression ($$ `(mul ,$1 ,$3)))
     (multiplicative-expression "/" cast-expression ($$ `(div ,$1 ,$3)))
     (multiplicative-expression "%" cast-expression ($$ `(mod ,$1 ,$3))))

    (additive-expression		; S 6.5.6
     (multiplicative-expression)
     (additive-expression "+" multiplicative-expression ($$ `(add ,$1 ,$3)))
     (additive-expression "-" multiplicative-expression ($$ `(sub ,$1 ,$3))))

    (shift-expression			; S 6.5.7
     (additive-expression)
     (shift-expression "<<" additive-expression ($$ `(lshift ,$1 ,$3)))
     (shift-expression ">>" additive-expression ($$ `(rshift ,$1 ,$3))))

    (relational-expression		; S 6.5.8
     (shift-expression)
     (relational-expression "<" shift-expression ($$ `(lt ,$1 ,$3)))
     (relational-expression ">" shift-expression ($$ `(gt ,$1 ,$3)))
     (relational-expression "<=" shift-expression ($$ `(le ,$1 ,$3)))
     (relational-expression ">=" shift-expression ($$ `(ge ,$1 ,$3))))
    
    (equality-expression		; S 6.5.9
     (relational-expression)
     (equality-expression "==" relational-expression ($$ `(eq ,$1 ,$3)))
     (equality-expression "!=" relational-expression ($$ `(ne ,$1 ,$3))))

    ;; called AND-expression
    (bitwise-and-expression		; S 6.5.10
     (equality-expression)
     (bitwise-and-expression "&" equality-expression
			     ($$ `(bitwise-and ,$1 ,$3))))

    ;; called exclusive-OR-expression
    (bitwise-xor-expression		; S 6.5.11
     (bitwise-and-expression)
     (bitwise-xor-expression "^" bitwise-and-expression
			     ($$ `(bitwise-xor ,$1 ,$3))))

    ;; called inclusive-OR-expression
    (bitwise-or-expression		; S 6.5.12
     (bitwise-xor-expression)
     (bitwise-or-expression "|" bitwise-xor-expression
			    ($$ `(bitwise-or ,$1 ,$3))))

    (logical-and-expression		; S 6.5.13
     (bitwise-or-expression)
     (logical-and-expression "&&" bitwise-or-expression
			     ($$ `(and ,$1 ,$3))))

    (logical-or-expression		; 6.5.14
     (logical-and-expression)
     (logical-or-expression "||" logical-and-expression
			    ($$ `(or ,$1 ,$3))))
    
    (conditional-expression
     (logical-or-expression)
     (logical-or-expression "?" expression ":" conditional-expression
			    ($$ `(cond-expr ,$1 ,$3 ,$5))))

    (assignment-expression		; S 6.5.16
     (conditional-expression)
     (unary-expression assignment-operator assignment-expression
		       ($$ `(assn-expr ,$1 (op ,$2) ,$3))))
    (assignment-operator
     ("=") ("+=") ("-=") ("*=") ("/=") ("%=")
     ("<<=") (">>=") ("&=") ("^=") ("|="))

    (expression				; S 6.5.17
     (assignment-expression)
     (expression "," assignment-expression
		 ($$ (if (eqv? 'comma-expr (sx-tag $1))
			 (append $1 (list $3))
			 `(comma-expr ,$1 ,$3)))))

    (constant-expression		; S 6.6
     (conditional-expression))

    ;; === declarations

    ;; TODO: check if we should move attributes or trap attribute-only spec's
    (declaration			; S 6.7
     (declaration-no-comment ";")
     (declaration-no-comment ";" code-comment ($$ (sx-attr-add $1 $3))))

    (declaration-no-comment
     (declaration-specifiers
      init-declarator-list
      ($$ (save-typenames `(decl ,$1 ,$2))))
     (declaration-specifiers
      ($$ `(decl ,$1))))

    ;; --- declaration specifiers

    (declaration-specifiers		; S 6.7
     (declaration-specifiers-1 ($$ (process-specs (tl->list $1)))))
    (declaration-specifiers-1
     ;; storage-class-specifiers
     (storage-class-specifier
      ($prec 'shift-on-attr) ($$ (make-tl 'decl-spec-list $1)))
     (storage-class-specifier declaration-specifiers-1 ($$ (tl-insert $2 $1)))
     ;; type-specifiers
     (type-specifier
      ($prec 'reduce-on-attr) ($$ (make-tl 'decl-spec-list $1)))
     (type-specifier declaration-specifiers-1 ($$ (tl-insert $2 $1)))
     ;; type-qualifiers
     (type-qualifier
      ($prec 'shift-on-attr) ($$ (make-tl 'decl-spec-list $1)))
     (type-qualifier declaration-specifiers-1 ($$ (tl-insert $2 $1)))
     ;; function-specifiers
     (function-specifier
      ($prec 'reduce-on-attr) ($$ (make-tl 'decl-spec-list $1)))
     (function-specifier declaration-specifiers-1 ($$ (tl-insert $2 $1)))
     ;; attribute-specifiers
     (attribute-specifier
      ($prec 'reduce-on-semi) ($$ (make-tl 'decl-spec-list $1)))
     (attribute-specifier declaration-specifiers-1 ($$ (tl-insert $2 $1))))

    (storage-class-specifier		; S 6.7.1
     ("auto" ($$ '(stor-spec (auto))))
     ("extern" ($$ '(stor-spec (extern))))
     ("register" ($$ '(stor-spec (register))))
     ("static" ($$ '(stor-spec (static))))
     ("typedef" ($$ '(stor-spec (typedef)))))

    ;; I have created fixed-, float- and complex- type specifiers to capture
    ;; combinations like "short int" "long long" etc.
    (type-specifier			; S 6.7.2
     ("void" ($$ '(type-spec (void))))
     (fixed-type-specifier ($$ `(type-spec ,$1)))
     (float-type-specifier ($$ `(type-spec ,$1)))
     (fixpt-type-specifier ($$ `(type-spec ,$1)))
     ("_Bool" ($$/ref 's5.1.5-01 '(type-spec (fixed-type "_Bool"))))
     (complex-type-specifier ($$ `(type-spec ,$1)))
     (struct-or-union-specifier ($$ `(type-spec ,$1)))
     (enum-specifier ($$ `(type-spec ,$1)))
     (typedef-name ($$ `(type-spec ,$1)))
     ("typeof" "(" unary-expression ")" ($$ `(typeof-expr ,$3)))
     ("typeof" "(" type-name ")" ($$ `(typeof-type ,$3)))
     ("__typeof" "(" unary-expression ")" ($$ `(typeof-expr ,$3)))
     ("__typeof" "(" type-name ")" ($$ `(typeof-type ,$3)))
     ("__typeof__" "(" unary-expression ")" ($$ `(typeof-expr ,$3)))
     ("__typeof__" "(" type-name ")" ($$ `(typeof-type ,$3))))

    (fixed-type-specifier
     ("short" ($prec 'imp) ($$ '(fixed-type "short")))
     ("short" "int" ($$ '(fixed-type "short int")))
     ("signed" "short" ($prec 'imp) ($$ '(fixed-type "signed short")))
     ("signed" "short" "int" ($$ '(fixed-type "signed short int")))
     ("int" ($$ '(fixed-type "int")))
     ("signed" ($prec 'imp) ($$ '(fixed-type "signed")))
     ("signed" "int" ($$ '(fixed-type "signed int")))
     ("long" ($prec 'imp) ($$ '(fixed-type "long")))
     ("long" "int" ($$ '(fixed-type "long int")))
     ("signed" "long" ($prec 'imp) ($$ '(fixed-type "signed long")))
     ("signed" "long" "int" ($$ '(fixed-type "signed long int")))
     ("long" "long" ($prec 'imp) ($$ '(fixed-type "long long")))
     ("long" "long" "int" ($$ '(fixed-type "long long int")))
     ("signed" "long" "long" ($prec 'imp)
      ($$ '(fixed-type "signed long long")))
     ("signed" "long" "long" "int" ($$ '(fixed-type "signed long long int")))
     ("unsigned" "short" "int" ($$ '(fixed-type "unsigned short int")))
     ("unsigned" "short" ($prec 'imp) ($$ '(fixed-type "unsigned short")))
     ("unsigned" "int" ($$ '(fixed-type "unsigned int")))
     ("unsigned" ($prec 'imp) ($$ '(fixed-type "unsigned")))
     ("unsigned" "long" "int" ($$ '(fixed-type "unsigned long")))
     ("unsigned" "long" ($prec 'imp) ($$ '(fixed-type "unsigned long")))
     ("unsigned" "long" "long" "int"
      ($$ '(fixed-type "unsigned long long int")))
     ("unsigned" "long" "long" ($prec 'imp)
      ($$ '(fixed-type "unsigned long long")))
     ("char" ($$ '(fixed-type "char")))
     ("signed" "char" ($$ '(fixed-type "signed char")))
     ("unsigned" "char" ($$ '(fixed-type "unsigned char"))))

    (float-type-specifier
     ("float" ($prec 'imp) ($$ '(float-type "float")))
     ("double" ($prec 'imp) ($$ '(float-type "double")))
     ("long" "double" ($$ '(float-type "long double")))
     ("_Float128" ($$ '(float-type "_Float128"))))

    (complex-type-specifier
     ("_Complex" ($$ '(complex-type "_Complex")))
     ("float" "_Complex" ($$ '(complex-type "float _Complex")))
     ("double" "_Complex" ($$ '(complex-type "double _Complex")))
     ("long" "double" "_Complex" ($$ '(complex-type "long double _Complex"))))

    (fixpt-type-specifier
     ;; http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2001/n1290.pdf
     ("short" "_Fract" ($$ '(fixpt-type "short _Fract")))
     ("_Fract" ($$ '(fixpt-type "_Fract")))
     ("long" "_Fract" ($$ '(fixpt-type "long _Fract")))
     ("signed" "short" "_Fract" ($$ '(fixpt-type "signd short _Fract")))
     ("signed" "_Fract" ($$ '(fixpt-type "signed _Fract")))
     ("signed" "long" "_Fract" ($$ '(fixpt-type "signed long _Fract")))
     ("unsigned" "short" "_Fract" ($$ '(fixpt-type "unsigned short _Fract")))
     ("unsigned" "_Fract" ($$ '(fixpt-type "unsigned _Fract")))
     ("unsigned" "long" "_Fract" ($$ '(fixpt-type "unsigned long _Fract")))
     ("short" "_Accum" ($$ '(fixpt-type "short _Accum")))
     ("_Accum" ($$ '(fixpt-type "_Accum")))
     ("long" "_Accum" ($$ '(fixpt-type "long _Accum")))
     ("signed" "short" "_Accum" ($$ '(fixpt-type "signd short _Accum")))
     ("signed" "_Accum" ($$ '(fixpt-type "signed _Accum")))
     ("signed" "long" "_Accum" ($$ '(fixpt-type "signed long _Accum")))
     ("unsigned" "short" "_Accum" ($$ '(fixpt-type "unsigned short _Accum")))
     ("unsigned" "_Accum" ($$ '(fixpt-type "unsigned _Accum")))
     ("unsigned" "long" "_Accum" ($$ '(fixpt-type "unsigned long _Accum")))
     ("_Sat" "short" "_Fract" ($$ '(fixpt-type "_Sat short _Fract")))
     ("_Sat" "_Fract" ($$ '(fixpt-type "_Sat _Fract")))
     ("_Sat" "long" "_Fract" ($$ '(fixpt-type "_Sat long _Fract")))
     ("_Sat" "signed" "short" "_Fract"
      ($$ '(fixpt-type "_Sat signd short _Fract")))
     ("_Sat" "signed" "_Fract" ($$ '(fixpt-type "_Sat signed _Fract")))
     ("_Sat" "signed" "long" "_Fract"
      ($$ '(fixpt-type "_Sat signed long _Fract")))
     ("_Sat" "unsigned" "short" "_Fract"
      ($$ '(fixpt-type "_Sat unsigned short _Fract")))
     ("_Sat" "unsigned" "_Fract" ($$ '(fixpt-type "_Sat unsigned _Fract")))
     ("_Sat" "unsigned" "long" "_Fract"
      ($$ '(fixpt-type "_Sat unsigned long _Fract")))
     ("_Sat" "short" "_Accum" ($$ '(fixpt-type "_Sat short _Accum")))
     ("_Sat" "_Accum" ($$ '(fixpt-type "_Sat _Accum")))
     ("_Sat" "long" "_Accum" ($$ '(fixpt-type "_Sat long _Accum")))
     ("_Sat" "signed" "short" "_Accum"
      ($$ '(fixpt-type "_Sat signd short _Accum")))
     ("_Sat" "signed" "_Accum" ($$ '(fixpt-type "_Sat signed _Accum")))
     ("_Sat" "signed" "long" "_Accum"
      ($$ '(fixpt-type "_Sat signed long _Accum")))
     ("_Sat" "unsigned" "short" "_Accum"
      ($$ '(fixpt-type "_Sat unsigned short _Accum")))
     ("_Sat" "unsigned" "_Accum" ($$ '(fixpt-type "_Sat unsigned _Accum")))
     ("_Sat" "unsigned" "long" "_Accum"
      ($$ '(fixpt-type "_Sat unsigned long _Accum"))))

    ;; This one modified: split out struct-or-union = "struct"|"union"
    (struct-or-union-specifier
     ("struct" opt-attr-specs ident-like "{" struct-declaration-list "}"
      ($$ (sx-list 'struct-def $2 $3 (tl->list $5))))
     ("struct" opt-attr-specs "{" struct-declaration-list "}"
      ($$ (sx-list 'struct-def $2 (tl->list $4))))
     ("struct" opt-attr-specs ident-like
      ($$ (sx-list 'struct-ref $2 $3)))
     ("union" opt-attr-specs ident-like "{" struct-declaration-list "}"
      ($$ (sx-list 'union-def $2 $3 (tl->list $5))))
     ("union" opt-attr-specs "{" struct-declaration-list "}"
      ($$ (sx-list 'union-def $2 (tl->list $4))))
     ("union" opt-attr-specs ident-like
      ($$ (sx-list 'union-ref $2 $3))))

    ;; because name following struct/union can be identifier or typeref:
    (ident-like
     (identifier)
     (typedef-name ($$ `(ident ,(sx-ref $1 1)))))

    (opt-attr-specs
     ($empty)
     (attribute-specifiers ($$ `(@ ,(attrl->attrs $1)))))

    ;; Calling this field-list in the parse tree.
    (struct-declaration-list		; S 6.7.2.1
     (struct-declaration ($$ (make-tl 'field-list $1)))
     (lone-comment ($$ (make-tl 'field-list $1)))
     ;;(cpp-statement ($$ (make-tl 'field-list $1)))
     (struct-declaration-list struct-declaration ($$ (tl-append $1 $2)))
     (struct-declaration-list lone-comment ($$ (tl-append $1 $2)))
     ;;(struct-declaration-list cpp-statement ($$ (tl-append $1 $2)))
     ;; Not in C99, but allowed by GNU, I believe:
     (";" ($$ (make-tl 'field-list)))
     (struct-declaration-list ";" ($$ $1)))

    (struct-declaration			; S 6.7.2.1
     (struct-declaration-no-comment ";")
     (struct-declaration-no-comment ";" code-comment ($$ (sx-attr-add $1 $3))))
    (struct-declaration-no-comment
     (specifier-qualifier-list
      struct-declarator-list ($$ `(comp-decl ,$1 ,(tl->list $2))))
     (specifier-qualifier-list ($$ `(comp-decl ,$1)))) ;; <= anonymous
    
    (specifier-qualifier-list		; S 6.7.2.1
     (specifier-qualifier-list-1 ($$ (process-specs (tl->list $1)))))
    (specifier-qualifier-list-1
     (type-specifier ($$ (make-tl 'decl-spec-list $1)))
     (type-specifier specifier-qualifier-list-1 ($$ (tl-insert $2 $1)))
     (type-qualifier ($$ (make-tl 'decl-spec-list $1)))
     (type-qualifier specifier-qualifier-list-1 ($$ (tl-insert $2 $1)))
     (attribute-specifier ($$ (make-tl 'decl-spec-list $1)))
     (attribute-specifier specifier-qualifier-list-1 ($$ (tl-insert $2 $1))))

    (specifier-qualifier-list/no-attr
     (specifier-qualifier-list/no-attr-1 ($$ (tl->list $1))))
    (specifier-qualifier-list/no-attr-1
     (type-specifier ($$ (make-tl 'decl-spec-list $1)))
     (type-specifier specifier-qualifier-list/no-attr-1 ($$ (tl-insert $2 $1)))
     (type-qualifier ($$ (make-tl 'decl-spec-list $1)))
     (type-qualifier specifier-qualifier-list/no-attr-1 ($$ (tl-insert $2 $1))))

    (struct-declarator-list		; S 6.7.2.1
     (struct-declarator ($$ (make-tl 'comp-declr-list $1)))
     (struct-declarator-list "," struct-declarator ($$ (tl-append $1 $3)))
     (struct-declarator-list "," attribute-specifiers
			     struct-declarator ($$ (tl-append $1 $3 $4))))

    (struct-declarator			; S 6.7.2.1
     (struct-declarator-1 ($$ (process-declr $1))))
    (struct-declarator-1
     (declarator ($$ `(comp-declr ,$1)))
     (declarator attribute-specifiers ($$ `(comp-declr ,$1 ,$2)))
     (declarator ":" constant-expression
		 ($$ `(comp-declr (bit-field ,$1 ,$3))))
     (":" constant-expression ($$ `(comp-declr (bit-field ,$2)))))

    (enum-specifier			; S 6.7.2.2
     ("enum" ident-like "{" enumerator-list "}"
      ($$ `(enum-def ,$2 ,(tl->list $4))))
     ("enum" ident-like "{" enumerator-list "," "}"
      ($$ `(enum-def ,$2 ,(tl->list $4))))
     ("enum" "{" enumerator-list "}" ($$ `(enum-def ,(tl->list $3))))
     ("enum" "{" enumerator-list "," "}" ($$ `(enum-def ,(tl->list $3))))
     ("enum" ident-like ($$ `(enum-ref ,$2))))

    ;; keeping old enum-def-list in parse tree
    (enumerator-list			; S 6.7.2.2
     (enumerator ($$ (make-tl 'enum-def-list $1)))
     (enumerator-list "," enumerator ($$ (tl-append $1 $3))))

    ;; had to change enumeration-constant => identifier
    (enumerator				; S 6.7.2.2
     (identifier ($$ `(enum-defn ,$1)))
     (identifier attribute-specifiers ($$ `(enum-defn ,$1 ,$2)))
     (identifier "=" constant-expression ($$ `(enum-defn ,$1 ,$3)))
     (identifier attribute-specifiers "=" constant-expression
		 ($$ `(enum-defn ,$1 ,$2 ,$4))))

    (type-qualifier
     ("const" ($$ `(type-qual (const))))
     ("volatile" ($$ `(type-qual (volatile))))
     ("restrict" ($$ `(type-qual (restrict)))))

    (function-specifier
     ("inline" ($$ `(fctn-spec ,$1)))
     ("_Noreturn" ($$ `(fctn-spec ,$1))))
    
    ;; Support for __attribute__(( ... )).  See the gcc documentation.
    ;; The documentation does not seem rigourous about defining where the
    ;; attribute specifier can appear.  This is my best attempt.  MW 2018
    ;; https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Attribute-Syntax.html
    ;; https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Type-Attributes.html
    ;; https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Variable-Attributes.html
    ;; https://gcc.gnu.org/onlinedocs/gcc-8.2.0/gcc/Function-Attributes.html
    
    (attribute-specifiers
     (attribute-specifier ($prec 'reduce-on-attr))
     (attribute-specifiers attribute-specifier ($$ (append $1 (cdr $2)))))

    ;; (attributes (attribute "__static__") (attribute aligned(8)" ...)
    (attribute-specifier
     ("__attribute__" "(" "(" attribute-list ")" ")" ($$ $4))
     (attr-name ($$ `(attribute-list (attribute ,$1)))))
    (attr-name
     ("__packed__" ($$ '(ident "__packed__")))
     ("__aligned__" ($$ '(ident "__aligned__")))
     ("__alignof__" ($$ '(ident "__alignof__"))))

    (attribute-list (attribute-list-1 ($$ (tl->list $1))))
    (attribute-list-1
     (attribute ($$ (make-tl 'attribute-list $1)))
     (attribute-list-1 "," attribute ($$ (tl-append $1 $3)))
     (attribute-list-1 "," ($$ $1)))

    (attribute
     (attr-word ($$ `(attribute ,$1)))
     (attr-word "(" attr-expr-list ")" ($$ `(attribute ,$1 ,$3)))
     ("const" ($$ `(attribute (ident "const")))))

    (attr-word
     (attr-name)
     (identifier))

    (attr-expr-list
     (attr-expr-list-1 ($$ (tl->list $1))))

    (attr-expr-list-1
     (attribute-expr ($$ (make-tl 'attr-expr-list $1)))
     (attr-expr-list-1 "," attribute-expr ($$ (tl-append $1 $3))))

    (attribute-expr
     (type-name)
     ($fixed ($$ `(fixed ,$1)))
     (string-literal)
     (identifier)
     (attr-word "(" attr-expr-list ")" ($$ `(attribute ,$1 ,$3)))) ;; ???

    ;; --- declarators

    (init-declarator-list		; S 6.7
     (init-declarator-list-1 ($$ (tl->list $1))))
    (init-declarator-list-1
     (init-declarator ($$ (make-tl 'init-declr-list $1)))
     (init-declarator-list-1 "," init-declarator ($$ (tl-append $1 $3)))
     (init-declarator-list-1 "," attribute-specifiers
			     init-declarator ($$ (tl-append $1 $3 $4))))

    ;; todo: move asm-expression to attribute
    (init-declarator			; S 6.7
     (init-declarator-1 ($$ (process-declr $1))))
    (init-declarator-1
     (declarator ($$ `(init-declr ,$1)))
     (declarator "=" initializer ($$ `(init-declr ,$1 ,$3)))
     (declarator asm-expression ($$ `(init-declr ,$1 ,$2)))
     (declarator asm-expression "=" initializer ($$ `(init-declr ,$1 ,$2 ,$4)))
     (declarator attribute-specifiers ($$ `(init-declr ,$1 ,$2)))
     (declarator attribute-specifiers "=" initializer
		 ($$ `(init-declr ,$1 ,$2 ,$4)))
     (declarator asm-expression attribute-specifiers
		 ($$ `(init-declr ,$1 ,$2 ,$3))))

    (declarator
     (pointer direct-declarator ($$ `(ptr-declr ,$1 ,$2)))
     (direct-declarator))

    (pointer				; S 6.7.6
     ("*" type-qualifier-list pointer ($$ `(pointer ,$2 ,$3)))
     ("*" type-qualifier-list ($$ `(pointer ,$2)))
     ("*" attribute-specifiers pointer ($$ `(pointer ,$3)))
     ("*" attribute-specifiers ($$ '(pointer)))
     ("*" pointer ($$ `(pointer ,$2)))
     ("*" ($$ '(pointer))))

    (direct-declarator			; S 6.7.6
     (identifier ($$ $1))
     ;;(ident-like ($$ $1)) ;; generates many SR RR conflicts
     ;;("(" declarator ")" ($$ `(scope ,$2)))
     ("(" declarator ")" ($$ $2))
     ("(" attribute-specifier declarator ")" ($$ $3))
     (direct-declarator
      "[" type-qualifier-list assignment-expression "]"
      ($$ `(ary-declr ,$1 ,$3 ,$4)))
     (direct-declarator
      "[" type-qualifier-list "]" ($$ `(ary-declr ,$1 ,$3)))
     (direct-declarator
      "[" assignment-expression "]" ($$ `(ary-declr ,$1 ,$3)))
     (direct-declarator
      "[" "]" ($$ `(ary-declr ,$1)))
     (direct-declarator
      "[" "static" type-qualifier-list assignment-expression "]"
      ($$ `(ary-declr ,$1 ,$4 ,$5))) ;; FIXME $4 needs "static" added
     (direct-declarator
      "[" type-qualifier-list "static" assignment-expression "]"
      ($$ `(ary-declr ,$1 (static) ,$5))) ;; FIXME $4 needs "static" added
     (direct-declarator
      "[" type-qualifier-list "*" "]"	; variable length array
      ($$ `(ary-declr ,$1 ,$3 (var-len))))
     (direct-declarator
      "[" "*" "]"			; variable length array
      ($$ `(ary-declr ,$1 (var-len))))
     (direct-declarator
      "(" parameter-type-list ")" ($$ `(ftn-declr ,$1 ,$3)))
     (direct-declarator
      "(" identifier-list ")" ($$ `(ftn-declr ,$1 ,$3)))
     (direct-declarator
      "(" ")" ($$ `(ftn-declr ,$1 (param-list)))))

    (type-qualifier-list
     (type-qualifier-list-1 ($$ (tl->list $1))))
    (type-qualifier-list-1
     (type-qualifier ($$ (make-tl 'type-qual-list $1)))
     (type-qualifier-list-1 type-qualifier ($$ (tl-append $1 $2))))

    (parameter-type-list
     (parameter-list ($$ (tl->list $1)))
     (parameter-list "," "..." ($$ (tl->list (tl-append $1 '(ellipsis))))))

    (parameter-list
     (parameter-declaration ($$ (make-tl 'param-list $1)))
     (parameter-list "," parameter-declaration ($$ (tl-append $1 $3))))

    (parameter-declaration
     (declaration-specifiers
      declarator ($$ `(param-decl ,$1 (param-declr ,$2))))
     (declaration-specifiers
      abstract-declarator ($$ `(param-decl ,$1 (param-declr ,$2))))
     (declaration-specifiers
      ($$ `(param-decl ,$1 (param-declr))))
     ;; adding attribute specifiers:
     (declaration-specifiers
      declarator attribute-specifiers ($$ `(param-decl ,$1 (param-declr ,$2)))))

    (identifier-list
     (identifier-list-1 ($$ (tl->list $1))))
    (identifier-list-1
     (identifier ($$ (make-tl 'ident-list $1)))
     (identifier-list-1 "," identifier ($$ (tl-append $1 $3))))

    (type-name				; S 6.7.6
     (specifier-qualifier-list/no-attr abstract-declarator
				       ($$ `(type-name ,$1 ,$2)))
     (specifier-qualifier-list/no-attr ($$ `(type-name ,$1)))
     ;;(declaration-specifiers ($$ `(type-name ,$1))) ; why did I have this?
     )

    (abstract-declarator		; S 6.7.6
     (pointer direct-abstract-declarator ($$ `(ptr-declr ,$1 ,$2)))
     (pointer ($$ `(abs-ptr-declr ,$1)))
     (direct-abstract-declarator))

    (direct-abstract-declarator
     ("(" abstract-declarator ")" ($$ $2))
     (direct-abstract-declarator "(" parameter-type-list ")"
				 ($$ `(ftn-declr ,$1 ,$3)))
     (direct-abstract-declarator "(" ")" ($$ `(ftn-declr ,$1 (param-list))))
     (direct-abstract-declarator
      "[" type-qualifier-list assignment-expression "]"
      ($$ `(ary-declr ,$1 ,$3 ,$4)))
     (direct-abstract-declarator
      "[" type-qualifier-list "]"
      ($$ `(ary-declr ,$1 ,$3)))
     (direct-abstract-declarator
      "[" assignment-expression "]"
      ($$ `(ary-declr ,$1 ,$3)))
     (direct-abstract-declarator
      "[" "]" ($$ `(ary-declr ,$1)))
     (direct-abstract-declarator
      "[" "static" type-qualifier-list assignment-expression "]"
      ($$ `(ary-declr ,$1 ,(tl->list (tl-insert $4 '(stor-spec (static))))
		      ,$5)))
     (direct-abstract-declarator
      "[" "static" type-qualifier-list "]"
      ($$ `(ary-declr ,$1 ,(tl->list (tl-insert $4 '(stor-spec (static)))))))
     (direct-abstract-declarator
      "[" type-qualifier-list "static" assignment-expression "]"
      ($$ `(ary-declr ,$1 ,(tl->list (tl-insert $3 '(stor-spec (static))))
		      ,$5)))
     (direct-abstract-declarator "[" "*" "]" ($$ `(star-ary-declr ,$1)))
     ;;
     ("(" parameter-type-list ")" ($$ `(abs-ftn-declr ,$2)))
     ("(" ")" ($$ '(abs-ftn-declr (param-list))))
     ("[" type-qualifier-list assignment-expression "]"
      ($$ `(abs-ary-declr ,$2 ,$3)))
     ("[" type-qualifier-list "]" ($$ `(abs-ary-declr ,$2)))
     ("[" assignment-expression "]" ($$ `(abs-ary-declr ,$2)))
     ("[" "]" ($$ `(abs-ary-declr)))
     ("[" "static" type-qualifier-list assignment-expression "]"
      ($$ `(abs-ary-declr ,(tl->list (tl-insert $3 '(stor-spec (static))))
			  ,$4)))
     ("[" "static" type-qualifier-list "]"
      ($$ `(abs-ary-declr ,(tl->list (tl-insert $3 '(stor-spec (static)))))))
     ("[" type-qualifier-list "static" assignment-expression "]"
      ($$ `(abs-ary-declr ,(tl->list (tl-insert $2 '(stor-spec (static))))
			  ,$4)))
     ("[" "*" "]" ($$ '(abs-star-ary-declr)))
     )

    ;; typedef-name is generated by the lexical analyzer
    (typedef-name ('typename ($$ `(typename ,$1))))

    ;; --------------------------------

    (initializer			; S 6.7.9
     (assignment-expression ($$ `(initzer ,$1)))
     ("{" initializer-list "}" ($$ `(initzer ,(tl->list $2))))
     ("{" initializer-list "," "}" ($$ `(initzer ,(tl->list $2)))))

    ;; The designation productions are from C99.
    (initializer-list
     (designation initializer ($$ (make-tl 'initzer-list $1 $2)))
     (initializer ($$ (make-tl 'initzer-list $1)))
     (initializer-list "," designation initializer ($$ (tl-append $1 $3 $4)))
     (initializer-list "," initializer ($$ (tl-append $1 $3))))

    (designation			; S 6.7.8
     (designator-list "=" ($$ `(desig ,$1))))

    (designator-list
     (designator ($$ (make-tl 'desgr-list $1)))
     (designator-list designator ($$ (tl-append $1 $2))))

    (designator
     ("[" constant-expression "]" ($$ `(array-dsgr ,$2)))
     ("." identifier ($$ `(sel-dsgr ,$2))))

    ;; === statements =========================================================

    (statement
     (labeled-statement)
     (compound-statement)
     (expression-statement)
     (selection-statement)
     (iteration-statement)
     (jump-statement)
     (asm-statement)
     (pragma)
     (cpp-statement))

    (labeled-statement
     (identifier ":" statement ($$ `(labeled-stmt ,$1 ,$3)))
     (identifier ":" attribute-specifier statement
		 ($$ `(labeled-stmt ,$1 ,$4)))
     ("case" constant-expression ":" statement ($$ `(case ,$2 ,$4)))
     ("default" ":" statement ($$ `(default ,$3))))

    (compound-statement
     ("{" ($$ (cpi-push)) block-item-list ($$ (cpi-pop)) "}" 
      ($$ `(compd-stmt ,(tl->list $3))))
     ("{" "}"
      ($$ `(compd-stmt (block-item-list)))))

    (block-item-list
     (block-item ($$ (make-tl 'block-item-list $1)))
     (block-item-list block-item ($$ (tl-append $1 $2))))

    (block-item
     (declaration)
     (statement))
    
    (expression-statement
     (expression ";" ($$ `(expr-stmt ,$1)))
     (";" ($$ '(expr-stmt))))

    (selection-statement
     ("if" "(" expression ")" statement ($prec 'then)
      ($$ `(if ,$3 ,$5)))
     ("if" "(" expression ")" statement "else" statement
      ($$ `(if ,$3 ,$5 ,$7)))
     ("switch" "(" expression ")" statement ($$ `(switch ,$3 ,$5))))

    (iteration-statement
     ("while" "(" expression ")" statement ($$ `(while ,$3 ,$5)))
     ("do" statement "while" "(" expression ")" ";" ($$ `(do-while ,$2 ,$5)))
     ("for" "(" initial-clause opt-expression ";" opt-expression ")" statement
      ($$ `(for ,$3 ,$4 ,$6 ,$8))))
    (initial-clause			; <= added for convenience
     (expression ";")
     (";" ($$ '(expr)))
     (declaration))
    (opt-expression			; <= added for convenience
     ($empty ($$ '(expr)))
     (expression))

    (jump-statement			; S 6.8.6
     ("goto" identifier ";" ($$ `(goto ,$2)))
     ("continue" ";" ($$ '(continue)))
     ("break" ";" ($$ '(break)))
     ("return" expression ";" ($$ `(return ,$2)))
     ("return" ";" ($$ `(return (expr)))))

    (asm-statement
     (asm-expression ";" ($$ `(expr-stmt ,$1))))
    (asm-expression
     ("__asm__" opt-asm-qualifiers "(" string-literal ")"
      ($$ `(asm-expr (@ (extension "GNUC") ,@$2) ,$4)))
     ("__asm__" opt-asm-qualifiers "(" string-literal asm-outputs ")"
      ($$ `(asm-expr (@ (extension "GNUC") ,@$2) ,$4 ,$5)))
     ("__asm__" opt-asm-qualifiers "(" string-literal asm-outputs
      asm-inputs ")"
      ($$ `(asm-expr (@ (extension "GNUC") ,@$2) ,$4 ,$5 ,$6)))
     ("__asm__" opt-asm-qualifiers "(" string-literal asm-outputs
      asm-inputs asm-clobbers ")"
      ($$ `(asm-expr (@ (extension "GNUC") ,@$2) ,$4 ,$5 ,$6 ,$7)))
     ("__asm__" opt-asm-qualifiers "(" string-literal asm-outputs
      asm-inputs asm-clobbers asm-gotos ")"
      ($$ `(asm-expr (@ (extension "GNUC") ,@$2) ,$4 (asm-outputs)
		     ,$6 ,$7 ,$8))))
    (opt-asm-qualifiers
     ($empty)
     ("__volatile__" ($$ (list '(volatile "true"))))
     ("__goto__" ($$ (list '(goto "true"))))
     ("volatile" ($$ (list '(volatile "true"))))
     ("goto" ($$ (list '(goto "true")))))
    (asm-outputs
     (asm-outputs-1 ($$ (tl->list $1))))
    (asm-outputs-1
     (":" ($$ (make-tl 'asm-outputs)))
     (":" asm-output ($$ (make-tl 'asm-outputs $2)))
     (asm-outputs-1 "," asm-output ($$ (tl-append $1 $3))))
    (asm-output
     (string-literal "(" identifier ")" ($$ `(asm-operand ,$1 ,$3)))
     ("[" identifier "]" string-literal "(" identifier ")"
      ($$ `(asm-operand ,$2 ,$4 ,$6))))
    (asm-inputs
     (asm-inputs-1 ($$ (tl->list $1))))
    (asm-inputs-1
     (":" ($$ (make-tl 'asm-inputs)))
     (":" asm-input ($$ (make-tl 'asm-inputs $2)))
     (asm-inputs-1 "," asm-input ($$ (tl-append $1 $3))))
    (asm-input
     (string-literal "(" expression ")" ($$ `(asm-operand ,$1 ,$3)))
     ("[" identifier "]" string-literal "(" expression ")"
      ($$ `(asm-operand ,$2 ,$4 ,$6))))
    (asm-clobbers
     (asm-clobbers-1 ($$ (tl->list $1))))
    (asm-clobbers-1
     (":" ($$ (make-tl 'asm-clobbers)))
     (":" string-literal ($$ (make-tl 'asm-clobbers $2)))
     (asm-clobbers-1 "," string-literal ($$ (tl-append $1 $3))))
    (asm-gotos
     (asm-gotos-1 ($$ (tl->list $1))))
    (asm-gotos-1
     (":" ($$ (make-tl 'asm-gotos)))
     (":" identifier ($$ (make-tl 'asm-gotos $2)))
     (asm-gotos-1 "," identifier ($$ (tl-append $1 $3))))

    ;; === top-level forms ====================================================

    (translation-unit			; S 6.9
     (external-declaration-list ($$ (tl->list $1))))

    (external-declaration-list
     ($empty ($$ (make-tl 'trans-unit)))
     (external-declaration-list
      external-declaration
      ;; A ``kludge'' to deal with @code{extern "C" ...}:
      ($$ (if (eqv? (sx-tag $2) 'extern-block)
	      (tl-extend $1 (sx-tail $2 1))
	      (tl-append $1 $2)))))

    (external-declaration		; S 6.9
     (function-definition)
     (declaration)
     (lone-comment)
     (cpp-statement)
     (pragma)
     ("extern" $string "{"
      ($$ (cpi-dec-blev!)) external-declaration-list ($$ (cpi-inc-blev!)) "}"
      ($$ `(extern-block
	    (extern-begin ,$2) ,@(sx-tail (tl->list $5) 1) (extern-end))))
     (";" ($$ `(decl (@ (extension "GNUC"))))))
    
    (function-definition
     (declaration-specifiers
      declarator compound-statement
      ($$ `(fctn-defn ,$1 ,$2 ,$3)))
     ;; K&R function definitions are not compatible with attribute-specifiers.
     ;;(declaration-specifiers
     ;; declarator declaration-list compound-statement
     ;; ($$ `(knr-fctn-defn ,$1 ,$2 ,$3 ,$4)))
     )

    ;; K&R function-definition parameter list
    ;;(declaration-list (declaration-list-1 ($$ (tl->list $1))))
    ;;(declaration-list-1
    ;; (declaration ($$ (make-tl 'decl-list $1)))
    ;; (declaration-list-1 declaration ($$ (tl-append $1 $2))))

    ;; non-terminal leaves
    (identifier ($ident ($$ `(ident ,$1))))
    (constant
     ($fixed ($$ `(fixed ,$1)))		; integer literal
     ($float ($$ `(float ,$1)))		; floating literal
     ($chlit ($$ `(char ,$1)))		; char literal
     ($chlit/L ($$ `(char (@ (type "wchar_t")) ,$1)))
     ($chlit/u ($$ `(char (@ (type "char16_t")) ,$1)))
     ($chlit/U ($$ `(char (@ (type "char32_t")) ,$1))))
    (string-literal (string-literal-1 ($$ (tl->list $1))))
    (string-literal-1
     ($string ($$ (make-tl 'string $1))) ; string-constant
     (string-literal-1 $string ($$ (tl-append $1 $2))))
    (code-comment ($code-comm ($$ `(comment ,$1))))
    (lone-comment ($lone-comm ($$ `(comment ,$1))))
    (cpp-statement ('cpp-stmt ($$ `(cpp-stmt ,$1))))
    (pragma
     ($pragma ($$ `(pragma ,$1)))
     ;;("_Pragma" "(" string-literal ")" ($$ `(pragma ,$3)))
     )

    )))

;;; === parsers =========================

;; We setup dev parser because circular dependence between lexer and parser
;; due to parsing include files as units for code and decl mode.
;; update: This is doable now (see parser.scm) but wait until it's needed.

(define c99-mach
  (compact-machine
   (hashify-machine
    (make-lalr-machine c99-spec))
   #:keep 2
   #:keepers '($code-comm $lone-comm $pragma cpp-stmt)))

(define c99x-spec (restart-spec c99-spec 'expression))

(define c99x-mach
  (compact-machine
   (hashify-machine
    (make-lalr-machine c99x-spec))
   #:keep 2
   ;; Sync w/ #:skip-if-unexp arg to make-lalr-parser in parser.scm.
   #:keepers '($code-comm $lone-comm $pragma cpp-stmt)))

;;; =====================================

;; @deffn {Procedure} gen-c99-files [dir] => #t
;; Update or generate the files @quot{c99act.scm} and @quot{c99tab.scm}.
;; These are the tables and actions for the C99 parser.
;; If there are no changes to existing files, no update occurs.
;; @end deffn
(define* (gen-c99-files #:optional (path "."))
  (define (mdir file) (mach-dir path file))
  (write-lalr-actions c99-mach (mdir "c99-act.scm.new") #:prefix "c99-")
  (write-lalr-tables c99-mach (mdir "c99-tab.scm.new") #:prefix "c99-")
  (write-lalr-actions c99x-mach (mdir "c99x-act.scm.new") #:prefix "c99x-")
  (write-lalr-tables c99x-mach (mdir "c99x-tab.scm.new") #:prefix "c99x-")
  (let ((a (move-if-changed (mdir "c99-act.scm.new") (mdir "c99-act.scm")))
	(b (move-if-changed (mdir "c99-tab.scm.new") (mdir "c99-tab.scm")))
	(c (move-if-changed (mdir "c99x-act.scm.new") (mdir "c99x-act.scm")))
	(d (move-if-changed (mdir "c99x-tab.scm.new") (mdir "c99x-tab.scm"))))
    (or a b c d)))

;; --- last line ---
