;;; nyacc/lang/c99/util2.scm - C processing code ;;; ;;; Copyright (C) 2015-2017 Matthew R. Wette ;;; ;;; This program is free software: you can redistribute it and/or modify ;;; it under the terms of the GNU General Public License as published by ;;; the Free Software Foundation, either version 3 of the License, or ;;; (at your option) any later version. ;;; ;;; This program 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 General Public License for more details. ;;; ;;; You should have received a copy of the GNU General Public License ;;; along with this program. If not, see . ;; utilities for processing output trees ;; The idea is to convert declarations into something like ;; @example ;; const char *args[21]; /* command arguments */ ;; @end example ;; @noindent ;; into ;; @example ;; ("args" (comment " command arguments ") ;; (array-of 21) (pointer-to) (fixed "char")) ;; @end example ;; @noindent ;; or without the comment. It is a question whether we need the fixed part. ;; In addition, we want to reduce to a set of canonical types. So something ;; like @code{foo_t} should be expanded. ;; KEEPING STRUCTS ENUMS etc ;; if have typename and want to keep it, then change ;; (typename "foo_t") ;; to ;; (typename (@ (base "struct")) "foo_t") ;; ALSO ;; (make-proxy comp-udecl) => udecl ;; (revert-proxy udecl) => comp-udecl (define-module (nyacc lang c99 util2) #:export (tree->udict stripdown stripdown-2 udecl->mspec udecl->mspec/comm unwrap-decl canize-enum-def-list fix-fields fixed-width-int-names match-decl match-comp-decl match-param-decl declr->ident expand-decl-typerefs ) #:use-module (nyacc lang c99 pprint) #:use-module (ice-9 pretty-print) #:use-module (srfi srfi-1) #:use-module ((sxml fold) #:select (foldts foldts*)) #:use-module (sxml match) #:use-module (nyacc lang util) #:use-module (nyacc lang c99 pprint) ) ;; Use the term @dfn{udecl}, or unit-declaration, for a declaration which has ;; only one decl-item. That is where, ;; @example ;; @end example ;; (decl (decl-spec-list ...) (init-declr-list (init-declr ...) ...)) ;; @noindent ;; has been replaced by ;; (decl (decl-spec-list ...) (init-declr ...)) ;; ... ;; @example ;; @end example ;; mspec is ;; ("foo" (pointer-to) (array-of 3) (fixed-type "unsigned int")) ;; which can be converted to ;; ("(*foo) (array-of 3) (fixed-type "unsigned int")) ;; which can be converted to ;; (("((*foo)[0])" (fixed-type "unsigned int")) ;; ("((*foo)[1])" (fixed-type "unsigned int")) ;; ("((*foo)[2])" (fixed-type "unsigned int")) ;; may need to replace (typename "int32_t") with (fixed-type "int32_t") ;; @deffn declr->ident declr => (ident "name") ;; just match the declarator ;; (init-declr []) ;; See also: declr->id-name in body.scm. (define (declr->ident declr) (sxml-match declr ((init-declr ,declr . ,rest) (declr->ident declr)) ((comp-declr ,declr) (declr->ident declr)) ((param-declr ,declr) (declr->ident declr)) ((ident ,name) declr) ((array-of ,dir-declr ,array-spec) (declr->ident dir-declr)) ((array-of ,dir-declr) (declr->ident dir-declr)) ((ptr-declr ,pointer ,dir-declr) (declr->ident dir-declr)) ((ftn-declr ,dir-declr ,rest ...) (declr->ident dir-declr)) ((scope ,declr) (declr->ident declr)) (,otherwise (throw 'util-error "c99/util2: unknown declarator: " declr)))) ;; @deffn unwrap-decl decl seed => seed ;; This is a fold to break up multiple declarators. ;; @example ;; (decl (decl-spec-list ...) (init-declr-list (init-declr ...) ...)) ;; => ;; ((decl (decl-spec-list ...) (init-declr ...)) ;; (decl (decl-spec-list ...) (init-declr ...)) ;; ...) ;; @end example (define (unwrap-decl decl seed) (cond ((not (eqv? 'decl (car decl))) seed) ((< (length decl) 3) seed) ; this should catch struct-ref etc. (else (let* ((tag (sx-ref decl 0)) (attr (sx-attr decl)) (spec (sx-ref decl 1)) ; (decl-spec-list ...) (id-l (sx-ref decl 2)) ; (init-declr-list ...) (tail (sx-tail decl 3))) ; comment (let iter ((res seed) (idl (cdr id-l))) (if (null? idl) res (let* ((declr (sx-ref (car idl) 1)) (ident (declr->ident declr)) (name (cadr ident))) (iter (cons (if attr (cons* tag attr spec (car idl) tail) (cons* tag spec (car idl) tail)) res) (cdr idl))))))))) ;; @deffn tree->udict tree => udict ;; Turn a C parse tree into a assoc-list of names and definitions. ;; This will unwrap @code{init-declr-list} into list of decls w/ ;; @code{init-declr}. ;; BUG: need to add struct and union defn's: struct foo { int x; }; ;; how to deal with this ;; lookup '(struct . "foo"), "struct foo", ??? ;; wanted "struct" -> dict but that is not great ;; solution: match-decl => '(struct . "foo") then filter to generate ;; ("struct" ("foo" . decl) ..) ;; ("union" ("bar" . decl) ..) (define (tree->udict tree) (if (pair? tree) (fold match-decl '() (cdr tree)) '())) ;; @deffn match-decl decl seed ;; This procedure is intended to be used with @code{fold}. It breaks up ;; up the init-declr-list into individual init-declr items and associates ;; with the identifier being declared. So this is a fold iterator to ;; provide a dictionary of declared names. ;; @example ;; (decl (decl-spec-list ...) (init-declr-list (init-declr ...) ...)) ;; @end example ;; @noindent ;; has been replaced by ;; @example ;; (decl (decl-spec-list ...) (init-declr ...)) ;; (decl (decl-spec-list ...) ...) ;; @end example ;; Here we generate a dictionary of all declared items: ;; @example ;; (let* ((sx0 (with-input-from-file src-file parse-c)) ;; (sx1 (merge-inc-trees! sx0)) ;; (name-dict (fold match-decl-1 '() (cdr sx1)))) ;; @end example ;; TODO: add enums because they are global!! ;; turn enum { ABC = 123 }; into '(ABC . (enum .. "ABC" "123" .. ) (define (match-decl decl seed) (let* ((tag (sx-ref decl 0)) (attr (sx-attr decl))) (case tag ((decl) (let* ((spec (sx-ref decl 1)) ; (decl-spec-list ...) (tbd (sx-ref decl 2))) ; (init-declr-list ...) OR ... (cond ((or (not tbd) (eqv? 'comment (sx-tag tbd))) ;; no init-declr-list => struct or union def or param-decl enum ;;(display "spec:\n") (pretty-print decl) (sxml-match spec ((decl-spec-list (type-spec (struct-def (ident ,name) . ,rest2) . ,rest1)) (acons `(struct . ,name) decl seed)) ((decl-spec-list (type-spec (union-def (ident ,name) . ,rest2) . ,rest1)) (acons `(union . ,name) decl seed)) ((decl-spec-list (type-spec (enum-def (enum-def-list (enum-defn (ident "ABC") (p-expr (fixed "123"))))))) ;; TODO seed) (,otherwise ;; e.g., enum { VAL = 1 }; ;;(simple-format #t "+++ otherwise: ~S\n" tbd) (pretty-print decl) seed))) (else ;; decl with init-declr-list (let* ((id-l tbd) (tail (sx-tail decl 3))) (let iter ((res seed) (idl (cdr id-l))) (if (null? idl) res (let* ((declr (sx-ref (car idl) 1)) (ident (declr->ident declr)) (name (cadr ident))) (iter (acons name (if attr (cons* tag attr spec (car idl) tail) (cons* tag spec (car idl) tail)) res) (cdr idl)))))))))) (else seed)))) ;; @deffn match-comp-decl decl seed ;; This will turn ;; @example ;; (comp-decl (decl-spec-list (type-spec "int")) ;; (comp-decl-list ;; (comp-declr (ident "a")) (comp-declr (ident "b")))) ;; @end example ;; @noindent ;; into ;; @example ;; ("a" . (comp-decl (decl-spec-list ...) (comp-declr (ident "a")))) ;; ("b" . (comp-decl (decl-spec-list ...) (comp-declr (ident "b")))) ;; @end example ;; @noindent ;; This is coded to be used with fold-right in order to preserve order ;; in @code{struct} and @code{union} field lists. (define (match-comp-decl decl seed) (if (not (eqv? 'comp-decl (car decl))) seed (let* ((tag (sx-ref decl 0)) (attr (sx-attr decl)) (spec (sx-ref decl 1)) ; (type-spec ...) (id-l (sx-ref decl 2)) ; (init-declr-list ...) (tail (sx-tail decl 3))) ; opt comment, different here ;;(simple-format #t "1: ~S\n" id-l) (let iter ((res seed) (idl (cdr id-l))) (if (null? idl) res (let* ((declr (sx-ref (car idl) 1)) (ident (declr->ident declr)) (name (cadr ident))) ;;(pretty-print `(comp-decl ,spec ,(car idl) . ,tail)) (acons name (if attr (cons* tag attr spec (car idl) tail) (cons* tag spec (car idl) tail)) (iter res (cdr idl))))))))) ;; @deffn match-param-decl param-decl seed ;; This will turn ;; @example ;; (param-decl (decl-spec-list (type-spec "int")) (param-declr (ident "a"))) ;; @end example ;; @noindent ;; into ;; @example ;; ("a" . (comp-decl (decl-spec-list ...) (comp-declr (ident "a")))) ;; @end example ;; @noindent ;; This is coded to be used with fold-right in order to preserve order ;; in @code{struct} and @code{union} field lists. (define (match-param-decl decl seed) (if (not (eqv? 'param-decl (car decl))) seed (let* ((tag (sx-ref decl 0)) (attr (sx-attr decl)) (spec (sx-ref decl 1)) ; (type-spec ...) (declr (sx-ref decl 2)) ; (param-declr ...) (ident (declr->ident declr)) (name (cadr ident))) (acons name decl seed)))) ;; @deffn find-special udecl-alist seed => .. ;; NOT DONE ;; @example ;; '((struct . ("foo" ...) ...) ;; (union . ("bar" ...) ...) ;; (enum . ("bar" ...) ...) ;; seed) ;; @end example (define (find-special udecl-alist seed) (let iter ((struct '()) (union '()) (enum '()) (udal udecl-alist)) (if (null? udal) (cons* (cons 'struct struct) (cons 'union union) (cons 'enum enum) seed) '()))) (define tmap-fmt '(("char" "%hhd") ("unsigned char" "%hhu") ("short int" "%hd") ("unsigned short int" "%hu") ("int" "%d") ("unsigned int" "%u") ("long int" "%ld") ("unsigned long int" "%lu") ("long long int" "%lld") ("unsigned long long int" "%llu"))) (define fixed-width-int-names '("int8_t" "uint8_t" "int16_t" "uint16_t" "int32_t" "uint32_t" "int64_t" "uint64_t")) ;; @deffn typedef-decl? decl) (define (typedef-decl? decl) (sxml-match decl ((decl (decl-spec-list (stor-spec (typedef)) . ,r1) . ,r2) #t) (,otherwise #f))) ;; @deffn splice-declarators orig-declr tdef-declr => ;; Splice the original declarator into the typedef declarator. ;; This is a helper for @code{expand-*-typename-ref} procecures. (define (splice-declarators orig-declr tdef-declr) (define (fD seed tree) ; => (values seed tree) (sxml-match tree ((param-list . ,rest) (values tree '())) ; don't process ((ident ,name) (values (reverse (cadr orig-declr)) '())) ; replace (,otherwise (values '() tree)))) (define (fU seed kseed tree) (let ((ktree (case (car kseed) ((param-list ident) kseed) (else (reverse kseed))))) (if (null? seed) ktree (cons ktree seed)))) (define (fH seed tree) (cons tree seed)) ;; This cons transfers the tag from orig-declr to the result. (cons (car orig-declr) ; init-declr or comp-declr (cdr (foldts* fD fU fH '() tdef-declr)))) ; always init-declr ;; @deffn repl-typespec decl-spec-list replacement ;; This is a helper for expand-decl-typerefs (define (repl-typespec decl-spec-list replacement) (fold-right (lambda (item seed) (cond ((symbol? item) (cons item seed)) ((eqv? 'type-spec (car item)) (if (pair? (car replacement)) (append replacement seed) (cons replacement seed))) (else (cons item seed)))) '() decl-spec-list)) ;; @deffn expand-decl-typerefs udecl udecl-dict => udecl ;; Given a declaration or component-declaration, expand all typename, ;; struct, union and enum refs. ;; @example ;; typedef const int (*foo_t)(int a, double b); ;; extern foo_t fctns[2]; ;; => ;; extern const int (*fctns[2])(int a, double b); ;; @end example ;; @noindent ;; Cool. Eh? (but is it done?) (define* (expand-decl-typerefs udecl udecl-dict #:key (keep '())) ;;(display "FIXME: some decls have no init-declr-list\n") ;; between adding (init-declr-list) to those or having predicate ;; (has-init-declr? decl) (let* ((tag (sx-tag udecl)) ; decl or comp-decl (attr (sx-attr udecl)) ; (@ ...) (specl (sx-ref udecl 1)) ; decl-spec-list (declr (or (sx-find 'init-declr udecl) (sx-find 'comp-declr udecl))) (tail (if declr (sx-tail udecl 3) (sx-tail udecl 2))) ; opt comment (tspec (cadr (sx-find 'type-spec specl)))) ;;(simple-format #t "=D> ~S\n" decl-spec-list) ;;(simple-format #t "init-declr: ~S\n" init-declr) (case (car tspec) ((typename) (cond ((member (cadr tspec) keep) udecl) (else ;; splice in the typedef (let* ((name (sx-ref tspec 1)) (decl (or (assoc-ref udecl-dict name) ; decl for typename (throw 'c99-error "util2 decl error"))) (tdef-specl (sx-ref decl 1)) ; decl-spec-list for typename (tdef-declr (sx-ref decl 2)) ; init-declr for typename ;; splice the typedef specifiers into target: (fixd-specl (repl-typespec specl (sx-tail tdef-specl 2))) (fixd-declr (splice-declarators declr tdef-declr)) (fixed-udecl (cons* tag fixd-specl fixd-declr tail))) (expand-decl-typerefs fixed-udecl udecl-dict #:keep keep))))) ((struct-ref union-ref) (simple-format (current-error-port) "+++ c99/util2: struct/union-ref: more to do?\n") ;;(simple-format #t "\nstruct-ref:\n") (pretty-print udecl) udecl) ((struct-def union-def) (let* ((ident (sx-find 'ident tspec)) (field-list (sx-find 'field-list tspec)) (orig-flds (cdr field-list)) (unit-flds (map cdr (fold-right match-comp-decl '() orig-flds))) (fixd-flds (map (lambda (fld) (expand-decl-typerefs fld udecl-dict #:keep keep)) unit-flds)) (fixd-tspec (if #f ;;ident `(type-spec (struct-def ,ident (field-list ,@fixd-flds))) `(type-spec (struct-def (field-list ,@fixd-flds))))) (fixd-specl (repl-typespec specl fixd-tspec))) (if declr (cons* tag fixd-specl declr tail) (cons* tag fixd-specl tail)))) ((enum-def) (let* ((enum-def-list (sx-find 'enum-def-list tspec)) (fixd-def-list (canize-enum-def-list enum-def-list)) (fixd-tspec `(type-spec (enum-def ,fixd-def-list))) (fixd-specl (repl-typespec specl fixd-tspec)) (fixed-decl (cons* tag fixd-specl declr tail))) ;; !!! fixed-decl)) ((enum-ref) (simple-format (current-error-port) "chack: enum-ref NOT DONE\n") udecl) (else udecl)))) ;; @deffn canize-enum-def-list ;; Fill in constants for all entries of an enum list. (define (canize-enum-def-list enum-def-list) (define (get-used edl) (let iter ((uzd '()) (edl edl)) (cond ((null? edl) uzd) ((assq-ref (cdar edl) 'p-expr) => (lambda (x) (iter (cons (string->number (cadar x)) uzd) (cdr edl)))) (else (iter uzd (cdr edl)))))) (let ((used (get-used (cdr enum-def-list)))) (let iter ((rez '()) (ix 0) (edl (cdr enum-def-list))) (cond ((null? edl) (cons (car enum-def-list) (reverse rez))) ((assq-ref (cdar edl) 'p-expr) (iter (cons (car edl) rez) ix (cdr edl))) (else (let* ((ix1 (let iter ((ix (1+ ix))) (if (memq ix used) (iter (1+ ix)) ix))) (is1 (number->string ix1))) (iter (cons (append (car edl) `((p-expr (fixed ,is1)))) rez) ix1 (cdr edl)))))))) ;; @deffn stripdown udecl decl-dict => decl ;; 1) remove stor-spec ;; 2) expand typenames ;; @example ;; typedef int *x_t; ;; x_t a[10]; ;; (spec (typename x_t)) (init-declr (array-of 10 (ident a))) ;; (spec (typedef) (fixed-type "int")) (init-declr (pointer) (ident "x_t")) ;; => ;; [TO BE DOCUMENTED] ;; @end example (define* (stripdown udecl decl-dict #:key (keep '())) ;;(define strip-list '(stor-spec type-qual comment)) (define strip-list '(stor-spec type-qual)) (define (fsD seed tree) '()) (define (fsU seed kseed tree) (if (memq (car tree) strip-list) seed (if (null? seed) (reverse kseed) (cons (reverse kseed) seed)))) (define (fsH seed tree) (cons tree seed)) (let* ((xdecl (expand-decl-typerefs udecl decl-dict #:keep keep)) (tag (sx-tag xdecl)) (attr (sx-attr xdecl)) (specl (sx-ref xdecl 1)) (declr (sx-ref xdecl 2)) (specl1 (foldts fsD fsU fsH '() specl))) (list tag specl1 declr))) ;; This one experimental for guile ffi. (define* (stripdown-2 udecl decl-dict #:key (keep '())) ;;(define strip-list '(stor-spec type-qual comment)) (define strip-list '(stor-spec type-qual)) (define (fsD seed tree) '()) (define (fsU seed kseed tree) (if (memq (car tree) strip-list) seed (if (null? seed) (reverse kseed) (cons (reverse kseed) seed)))) (define (fsH seed tree) (cons tree seed)) (let* ((speclt (sx-tail udecl 1))) ; decl-spec-list tail ;; don't expand typedefs, structure specs etc, (cond ((and (eqv? 'stor-spec (caar speclt)) (eqv? 'typedef (cadar speclt))) udecl) ;; lone struct ref (else (let* ((xdecl (expand-decl-typerefs udecl decl-dict #:keep keep)) (tag (sx-tag xdecl)) (attr (sx-attr xdecl)) (specl (sx-ref xdecl 1)) (declr (sx-ref xdecl 2)) (specl1 (foldts fsD fsU fsH '() specl))) (list tag specl1 declr)))) )) ;; @deffn udecl->mspec sudecl ;; Turn a stripped-down unit-declaration into an m-spec. ;; This assumes decls have been run through @code{stripdown}. (define (udecl->mspec decl . rest) (define (cnvt-array-size size-spec) (with-output-to-string (lambda () (pretty-print-c99 size-spec)))) (define (unwrap-specl specl) (let ((tspec (cadadr specl))) ;;(simple-format #t "tspec:\n") (pretty-print tspec) (sxml-match tspec ((xxx-struct-def (field-list . ,rest)) `(struct-def ,@rest)) (,otherwise tspec)))) (define (unwrap-declr declr) (sxml-match declr ((ident ,name) (list name)) ((init-declr ,item) (unwrap-declr item)) ((comp-declr ,item) (unwrap-declr item)) ((ptr-declr (pointer . ,r) ,dir-declr) (cons '(pointer-to) (unwrap-declr dir-declr))) ((array-of ,dir-declr ,size) (cons `(array-of ,(cnvt-array-size size)) (unwrap-declr dir-declr))) ((ftn-declr ,dir-declr ,params) (cons '(function-returning) (unwrap-declr dir-declr))) ((scope ,expr) (unwrap-declr expr)) (,otherwise (simple-format #t "unwrap-declr: OTHERWISE\n") (pretty-print otherwise) ;; failed got: (array-of (ident "foo")) FROM const char foo[]; #f))) (define (find-type-spec decl-spec-list) (let iter ((tsl (cdr decl-spec-list))) (if (eqv? 'type-spec (caar tsl)) (car tsl) (iter (cdr tsl))))) (let* ((decl-dict (if (pair? rest) (car rest) '())) (specl (sx-ref decl 1)) (declr (sx-ref decl 2)) (comm (sx-ref decl 3)) (m-specl (unwrap-specl specl)) (m-declr (unwrap-declr declr)) (m-decl (reverse (cons m-specl m-declr)))) m-decl)) ;; @deffn udecl->mspec/comm decl [dict] [#:def-comm ""] ;; Convert declaration tree to an mspec ;; @example ;; (decl ... (comment "state vector") ;; => ;; ("x" "state vector" (array-of 10) (float "double") ;; @end example (define* (udecl->mspec/comm decl #:optional (dict '()) #:key (def-comm "")) (let* ((comm (or (and=> (sx-ref decl 3) cadr) def-comm)) (spec (udecl->mspec decl dict))) (cons* (car spec) comm (cdr spec)))) ;; @deffn fix-fields flds => flds ;; This will take a list of fields from a struct and remove lone comments. ;; If a field following a lone comment has no code-comment, the lone comment ;; will be used. For example, ;; @example ;; /* foo */ ;; int x; ;; @end example ;; @noindent ;; will be treated as if it was denereed ;; @example ;; int x; /* foo */ ;; @end example ;; @noindent (define (fix-fields flds) (let iter ((rz '()) (cl '()) (fl flds)) ;;(pretty-print fl) (cond ((null? fl) (reverse rz)) ((eqv? 'comment (caar fl)) (iter rz (cons (cadar fl) cl) (cdr fl))) ((eqv? 'comp-decl (caar fl)) (if (eq? 4 (length (car fl))) (iter (cons (car fl) rz) '() (cdr fl)) ; has comment (let* ((cs (apply string-append (reverse cl))) ; add comment (fd (append (car fl) (list (list 'comment cs))))) (iter (cons fd rz) '() (cdr fl))))) (else (error "bad field"))))) ;; --- last line ---