cpp.scm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. ;;; lang/c/cpp.scm - C preprocessor
  2. ;; Copyright (C) 2015-2019 Matthew R. Wette
  3. ;;
  4. ;; This library is free software; you can redistribute it and/or
  5. ;; modify it under the terms of the GNU Lesser General Public
  6. ;; License as published by the Free Software Foundation; either
  7. ;; version 3 of the License, or (at your option) any later version.
  8. ;;
  9. ;; This library is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. ;; Lesser General Public License for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU Lesser General Public License
  15. ;; along with this library; if not, see <http://www.gnu.org/licenses/>.
  16. ;;; Notes:
  17. ;; C preprocessor macro expansion and condition text parse-and-eval
  18. ;; ref: https://gcc.gnu.org/onlinedocs/gcc-3.0.1/cpp_3.html
  19. ;;; Code:
  20. (define-module (nyacc lang c99 cpp)
  21. #:export (
  22. cpp-line->stmt
  23. eval-cpp-cond-text
  24. expand-cpp-macro-ref
  25. parse-cpp-expr
  26. find-incl-in-dirl
  27. scan-arg-literal
  28. eval-cpp-expr)
  29. #:use-module (nyacc parse)
  30. #:use-module (nyacc lex)
  31. #:use-module (nyacc lang sx-util)
  32. #:use-module ((nyacc lang util) #:select (report-error)))
  33. (cond-expand
  34. (guile-2
  35. (use-modules (rnrs arithmetic bitwise))
  36. (use-modules (system base pmatch)))
  37. (else
  38. (use-modules (ice-9 optargs))
  39. (use-modules (nyacc compat18))))
  40. (define c99-std-defs
  41. '("__DATE__" "__FILE__" "__LINE__" "__STDC__" "__STDC_HOSTED__"
  42. "__STDC_VERSION__" "__TIME__"))
  43. (define (c99-std-def? str)
  44. (let loop ((defs c99-std-defs))
  45. (cond
  46. ((null? defs) #f)
  47. ((string=? (car defs) str) #t)
  48. (else (loop (cdr defs))))))
  49. (define (c99-std-val str)
  50. (cond
  51. ((string=? str "__DATE__") "M01 01 2001")
  52. ((string=? str "__FILE__") "(unknown)")
  53. ((string=? str "__LINE__") "0")
  54. ((string=? str "__STDC__") "1")
  55. ((string=? str "__STDC_HOSTED__") "0")
  56. ((string=? str "__STDC_VERSION__") "201701")
  57. ((string=? str "__TIME__") "00:00:00")
  58. (else #f)))
  59. (define inline-whitespace (list->char-set '(#\space #\tab)))
  60. ;;.@deffn {Procedure} skip-il-ws ch
  61. ;; Skip in-line whitespace
  62. ;; @end deffn
  63. (define (skip-il-ws ch)
  64. (cond
  65. ((eof-object? ch) ch)
  66. ((char-set-contains? inline-whitespace ch) (skip-il-ws (read-char)))
  67. (else ch)))
  68. ;; This reads the rest of the input, with ch and returns a string;
  69. ;; Replaces get-string-all from (ice-9 textual-ports).
  70. (define (read-rest ch)
  71. (list->string (let loop ((ch ch))
  72. (if (eof-object? ch) '()
  73. (cons ch (loop (read-char)))))))
  74. ;; Not sure about this. We want to turn a list of tokens into a string
  75. ;; with proper escapes.
  76. (define (esc-c-str str)
  77. (list->string
  78. (string-fold-right
  79. (lambda (ch chl)
  80. (case ch
  81. ((#\\ #\") (cons* #\\ ch chl))
  82. (else (cons ch chl))))
  83. '() str)))
  84. (define ident-like? (make-ident-like-p read-c-ident))
  85. ;; @deffn {Procedure} read-ellipsis ch
  86. ;; read ellipsis
  87. ;; @end deffn
  88. (define (read-ellipsis ch)
  89. (cond
  90. ((eof-object? ch) #f)
  91. ((char=? ch #\.) (read-char) (read-char) "...") ; assumes correct syntax
  92. (else #f)))
  93. ;; @deffn {Procedure} find-incl-in-dirl file dirl [next] => path
  94. ;; Find path to include file expression, (i.e., @code{<foo.h>} or
  95. ;; @code{"foo.h"}. If @code{"foo.h"} form look in current directory first.
  96. ;; If @var{next} (default false) is true then remove current directory from
  97. ;; search path.
  98. ;; @*Refs:
  99. ;; @itemize
  100. ;; @item https://gcc.gnu.org/onlinedocs/cpp/Search-Path.html
  101. ;; @item https://gcc.gnu.org/onlinedocs/cpp/Wrapper-Headers.html
  102. ;; @end itemize
  103. ;; @end deffn
  104. (define* (find-incl-in-dirl file dirl #:optional (next #f))
  105. (let* ((cid (and=> (port-filename (current-input-port)) dirname))
  106. (file-type (string-ref file 0)) ;; #\< or #\"
  107. (file-name (substring file 1 (1- (string-length file))))
  108. (dirl (if (and cid (char=? #\" file-type)) (cons cid dirl) dirl)))
  109. (let loop ((dirl dirl))
  110. (if (null? dirl) #f
  111. (if (and next (string=? (car dirl) cid))
  112. (loop (cdr dirl))
  113. (let ((p (string-append (car dirl) "/" file-name)))
  114. (if (access? p R_OK) p (loop (cdr dirl)))))))))
  115. ;; @deffn {Procedure} cpp-define
  116. ;; Reads CPP define from current input and generates a cooresponding sxml
  117. ;; expression.
  118. ;; @example
  119. ;; (define (name "ABC") (repl "123"))
  120. ;; OR
  121. ;; (define (name "ABC") (args "X" "Y") (repl "X+Y"))
  122. ;; @example
  123. ;; @end deffn
  124. (define (cpp-define)
  125. (define (p-args la) ;; parse args
  126. (if (eq? la #\()
  127. (let loop ((args '()) (la (skip-il-ws (read-char))))
  128. (cond
  129. ((eq? la #\)) (reverse args))
  130. ((read-c-ident la) =>
  131. (lambda (arg) (loop (cons arg args) (skip-il-ws (read-char)))))
  132. ((read-ellipsis la) =>
  133. (lambda (arg) (loop (cons arg args) (skip-il-ws (read-char)))))
  134. ((eq? la #\,) (loop args (skip-il-ws (read-char))))))
  135. (begin (if (char? la) (unread-char la)) #f)))
  136. (define (p-rest la) (read-rest la))
  137. (let* ((name (let loop ((ch (skip-il-ws (read-char))))
  138. (cond
  139. ((eof-object? ch) (throw 'cpp-error "bad #define"))
  140. ((read-c-ident ch))
  141. ((cpp-comm-skipper ch) (loop (skip-il-ws (read-char))))
  142. (else (throw 'cpp-error "bad #define")))))
  143. (args (or (p-args (read-char)) '()))
  144. (repl (p-rest (skip-il-ws (read-char)))))
  145. (if (pair? args)
  146. `(define (name ,name) (args . ,args) (repl ,repl))
  147. `(define (name ,name) (repl ,repl)))))
  148. ;; @deffn {Procedure} cpp-include
  149. ;; Parse CPP include statement.
  150. (define (cpp-include)
  151. (define (loop cl ch end-ch)
  152. (if (eq? ch end-ch) (reverse-list->string (cons ch cl))
  153. (loop (cons ch cl) (read-char) end-ch)))
  154. (let ((ch (skip-il-ws (read-char))))
  155. (cond
  156. ((char=? ch #\<) (loop (list #\<) (read-char) #\>))
  157. ((char=? ch #\") (loop (list #\") (read-char) #\"))
  158. ((read-c-ident ch))
  159. (else (throw 'cpp-error "bad include")))))
  160. ;; @deffn {Procedure} cpp-line->stmt line defs => (stmt-type text)
  161. ;; Parse a line from a CPP statement and return a parse tree.
  162. ;; @example
  163. ;; (parse-cpp-stmt "define X 123") => (define "X" "123")
  164. ;; (parse-cpp-stmt "if defined(A) && defined(B) && defined(C)"
  165. ;; => (if "defined(A) && defined(B) && defined(C)")
  166. ;; @end example
  167. ;; To evaluate the @code{if} statements use @code{parse-cpp-expr} and
  168. ;; @code{eval-cpp-expr}.
  169. ;; @end deffn
  170. (define (cpp-line->stmt line)
  171. (define (rd-ident) (read-c-ident (skip-il-ws (read-char))))
  172. (define (rd-num) (and=> (read-c-num (skip-il-ws (read-char))) cdr))
  173. (define (rd-rest) (read-rest (skip-il-ws (read-char))))
  174. (with-input-from-string line
  175. (lambda ()
  176. (let ((ch (skip-il-ws (read-char))))
  177. (cond
  178. ((read-c-ident ch) =>
  179. (lambda (cmds)
  180. (let ((cmd (string->symbol cmds)))
  181. (case cmd
  182. ((include) `(include ,(cpp-include)))
  183. ((include_next) `(include-next ,(cpp-include)))
  184. ((define) (cpp-define))
  185. ((undef) `(undef ,(rd-ident)))
  186. ((ifdef)
  187. `(if ,(string-append "defined(" (rd-ident) ")" (rd-rest))))
  188. ((ifndef)
  189. `(if ,(string-append "!defined(" (rd-ident) ")" (rd-rest))))
  190. ((if elif else endif line error warning pragma)
  191. (list cmd (rd-rest)))
  192. (else
  193. (list 'warning (simple-format #f "unknown CPP: ~S" line)))))))
  194. ((read-c-num ch) => (lambda (num) `(line ,num ,(rd-rest))))
  195. (else (error "nyacc cpp-line->stmt: missing code")))))))
  196. (include-from-path "nyacc/lang/c99/mach.d/cpp-tab.scm")
  197. (include-from-path "nyacc/lang/c99/mach.d/cpp-act.scm")
  198. (define cpp-raw-parser
  199. (make-lalr-parser (acons 'act-v cpp-act-v cpp-tables)))
  200. (define (cpp-err fmt . args)
  201. (apply throw 'cpp-error fmt args))
  202. ;; Since we want to be able to get CPP statements with comment in tact
  203. ;; (e.g., for passing to @code{pretty-print-c99}) we need to remove
  204. ;; comments when parsing CPP expressions. We convert a comm-reader
  205. ;; into a comm-skipper here. And from that generate a lexer generator.
  206. (define cpp-comm-skipper
  207. (let ((reader (make-comm-reader '(("/*" . "*/")))))
  208. (lambda (ch)
  209. (reader ch #f))))
  210. ;; generate a lexical analyzer per string
  211. (define gen-cpp-lexer
  212. (make-lexer-generator cpp-mtab
  213. #:comm-skipper cpp-comm-skipper
  214. #:chlit-reader read-c-chlit
  215. #:num-reader read-c-num))
  216. ;; @deffn {Procedure} parse-cpp-expr text => tree
  217. ;; Given a string returns a cpp parse tree. This is called by
  218. ;; @code{eval-cpp-expr}. The text will have had all CPP defined symbols
  219. ;; expanded already so no identifiers should appear in the text.
  220. ;; A @code{cpp-error} will be thrown if a parse error occurs.
  221. ;; @end deffn
  222. (define (parse-cpp-expr text)
  223. (with-throw-handler
  224. 'nyacc-error
  225. (lambda ()
  226. (with-input-from-string text
  227. (lambda () (cpp-raw-parser (gen-cpp-lexer)))))
  228. (lambda (key fmt . args)
  229. (apply throw 'cpp-error fmt args))))
  230. ;; @deffn {Procedure} eval-cpp-expr tree [options] => datum
  231. ;; Evaluate a tree produced from @code{parse-cpp-expr}.
  232. ;; Options include optional dictionary for defines and values
  233. ;; and @code{#:inc-dirs} for @code{has_include} etc
  234. ;; @end deffn
  235. (define* (eval-cpp-expr tree #:optional (dict '()) #:key (inc-dirs '()))
  236. (letrec
  237. ((tx (lambda (tr ix) (sx-ref tr ix)))
  238. (tx1 (lambda (tr) (sx-ref tr 1)))
  239. (ev (lambda (ex ix) (eval-expr (sx-ref ex ix))))
  240. (ev1 (lambda (ex) (ev ex 1))) ; eval expr in arg 1
  241. (ev2 (lambda (ex) (ev ex 2))) ; eval expr in arg 2
  242. (ev3 (lambda (ex) (ev ex 3))) ; eval expr in arg 3
  243. (eval-expr
  244. (lambda (tree)
  245. (case (car tree)
  246. ((fixed) (string->number (cnumstr->scm (tx1 tree))))
  247. ((char) (char->integer (string-ref (tx1 tree) 0)))
  248. ((defined) (if (assoc-ref dict (tx1 tree)) 1 0))
  249. ((has-include)
  250. (if (find-incl-in-dirl (tx1 tree) inc-dirs #f) 1 0))
  251. ((has-include-next)
  252. (if (find-incl-in-dirl (tx1 tree) inc-dirs #t) 1 0))
  253. ((pre-inc post-inc) (1+ (ev1 tree)))
  254. ((pre-dec post-dec) (1- (ev1 tree)))
  255. ((pos) (ev1 tree))
  256. ((neg) (- (ev1 tree)))
  257. ((not) (if (zero? (ev1 tree)) 1 0))
  258. ((mul) (* (ev1 tree) (ev2 tree)))
  259. ((div) (/ (ev1 tree) (ev2 tree)))
  260. ((mod) (modulo (ev1 tree) (ev2 tree)))
  261. ((add) (+ (ev1 tree) (ev2 tree)))
  262. ((sub) (- (ev1 tree) (ev2 tree)))
  263. ((lshift) (bitwise-arithmetic-shift-left (ev1 tree) (ev2 tree)))
  264. ((rshift) (bitwise-arithmetic-shift-right (ev1 tree) (ev2 tree)))
  265. ((lt) (if (< (ev1 tree) (ev2 tree)) 1 0))
  266. ((le) (if (<= (ev1 tree) (ev2 tree)) 1 0))
  267. ((gt) (if (> (ev1 tree) (ev2 tree)) 1 0))
  268. ((ge) (if (>= (ev1 tree) (ev2 tree)) 1 0))
  269. ((eq) (if (= (ev1 tree) (ev2 tree)) 1 0))
  270. ((ne) (if (= (ev1 tree) (ev2 tree)) 0 1))
  271. ((bitwise-not) (lognot (ev1 tree)))
  272. ((bitwise-or) (logior (ev1 tree) (ev2 tree)))
  273. ((bitwise-xor) (logxor (ev1 tree) (ev2 tree)))
  274. ((bitwise-and) (logand (ev1 tree) (ev2 tree)))
  275. ((or) (if (and (zero? (ev1 tree)) (zero? (ev2 tree))) 0 1))
  276. ((and) (if (or (zero? (ev1 tree)) (zero? (ev2 tree))) 0 1))
  277. ((cond-expr) (if (zero? (ev1 tree)) (ev3 tree) (ev2 tree)))
  278. ;; in CPP if ident is not defined it should be zero
  279. ((ident) (or (and=> (assoc-ref dict (tx1 tree)) string->number) 0))
  280. ((p-expr) (ev1 tree))
  281. ((cast) (ev2 tree))
  282. (else (error "nyacc eval-cpp-expr: incomplete implementation"))))))
  283. (eval-expr tree)))
  284. ;;.@deffn {Procedure} rtokl->string reverse-token-list => string
  285. ;; Convert reverse token-list to string.
  286. ;; @end deffn
  287. (define (rtokl->string tokl)
  288. ;; Turn reverse chl into a string and insert it into the string list stl.
  289. (define (add-chl chl stl)
  290. (if (null? chl) stl (cons (list->string chl) stl)))
  291. ;; Works like this: Scan through the list of tokens (key-val pairs or
  292. ;; lone characters). Lone characters are collected in a list (@code{chl});
  293. ;; pairs are converted into strings and combined with list of characters
  294. ;; into a list of strings. When done the list of strings is combined to
  295. ;; one string. (The token 'argval is expansion of argument.)
  296. (let loop ((stl '()) ; list of strings to reverse-append
  297. (chl '()) ; char list
  298. (nxt #f) ; next string to add after chl
  299. (tkl tokl)) ; input token list
  300. (cond
  301. (nxt
  302. (loop (cons nxt (add-chl chl stl)) '() #f tkl))
  303. ((null? tkl)
  304. (apply string-append (add-chl chl stl)))
  305. ((char? (car tkl))
  306. (loop stl (cons (car tkl) chl) nxt (cdr tkl)))
  307. (else
  308. (pmatch tkl
  309. ((($ident . ,rval) $dhash ($ident . ,lval) . ,rest)
  310. (loop stl chl nxt
  311. (acons '$ident (string-append lval rval) (list-tail tkl 3))))
  312. ((($ident . ,arg) $hash . ,rest)
  313. (loop stl chl (string-append "\"" arg "\"") (list-tail tkl 2)))
  314. ((($ident . ,iden) ($ident . ,lval) . ,rest)
  315. (loop stl chl iden rest))
  316. ((($ident . ,iden) . ,rest)
  317. (loop stl chl iden rest))
  318. ((($string . ,val) . ,rest)
  319. (loop stl (cons #\" chl) (esc-c-str val) (cons #\" rest)))
  320. ((($echo . ,val) . ,rest)
  321. (loop stl chl val rest))
  322. (($space $space . ,rest)
  323. (loop stl chl nxt rest))
  324. (($space . ,rest)
  325. (loop stl (cons #\space chl) nxt rest))
  326. ((($comm . ,val) . ,rest)
  327. ;; replace comment with extra trailing space
  328. (loop stl chl (string-append "/*" val "*/ ") rest))
  329. ((,asis . ,rest)
  330. (loop stl chl asis rest))
  331. (,otherwise
  332. (error "nyacc cpp rtokl->string, no match" tkl)))))))
  333. ;; We just scanned "defined", now need to scan the arg to inhibit expansion.
  334. ;; For example, we have scanned "defined"; we now scan "(FOO)" or "FOO", and
  335. ;; return "defined(FOO)" or "defined FOO".
  336. (define (scan-defined-arg)
  337. (let* ((ch (skip-il-ws (read-char))) (no-ec (not (char=? ch #\())))
  338. (let loop ((chl (list ch)) (ch (skip-il-ws (read-char))))
  339. (cond
  340. ((eof-object? ch)
  341. (if no-ec
  342. (list->string (cons #\space (reverse chl)))
  343. (cpp-err "illegal argument to `defined'")))
  344. ((char-set-contains? c:ir ch)
  345. (loop (cons ch chl) (read-char)))
  346. (no-ec
  347. (unread-char ch)
  348. (list->string (cons #\space (reverse chl))))
  349. ((char=? #\) (skip-il-ws ch))
  350. (reverse-list->string (cons #\) chl)))
  351. (else
  352. (cpp-err "illegal argument to `defined'"))))))
  353. ;; must be (\s*<xxx>\s*) OR (\s*"xxx"\s*) => ("<xxx>") OR ("\"xxx\"")
  354. (define (scan-arg-literal)
  355. (let ((ch (read-char)))
  356. ;; if exit, then did not defined __has_include(X)=__has_include__(X)
  357. (if (or (eof-object? ch) (not (char=? #\( ch)))
  358. (throw 'cpp-error "expedcting `('")))
  359. (let loop ((chl '()) (ch (skip-il-ws (read-char))))
  360. (cond
  361. ((eof-object? ch) (cpp-err "illegal argument"))
  362. ((char=? #\) ch)
  363. (let loop2 ((res '()) (chl chl))
  364. (cond
  365. ((null? chl)
  366. (string-append "(\"" (esc-c-str (list->string res)) "\")"))
  367. ((and (null? res) (char-whitespace? (car chl))) (loop2 res (cdr chl)))
  368. (else (loop2 (cons (car chl) res) (cdr chl))))))
  369. (else (loop (cons ch chl) (read-char))))))
  370. (define* (scan-cpp-input defs used end-tok #:key (keep-comments #t))
  371. ;; Works like this: scan for tokens (comments, parens, strings, char's, etc).
  372. ;; Tokens are collected in a (reverse ordered) list (tkl) and merged together
  373. ;; to a string on return using @code{rtokl->string}. Keep track of expanded
  374. ;; identifiers and rerun if something got expanded. Also, keep track of
  375. ;; ## and spaces so that we can parse ID /* foo */ ## /* bar */ 123
  376. ;; as well as ABC/*foo*/(123,456).
  377. (define (trim-spaces tkl)
  378. (if (and (pair? tkl) (eqv? '$space (car tkl)))
  379. (trim-spaces (cdr tkl))
  380. tkl))
  381. (define (finish rr tkl)
  382. (let* ((tkl (if end-tok (trim-spaces tkl) tkl))
  383. (repl (rtokl->string tkl)))
  384. (if (pair? rr)
  385. (cpp-expand-text repl defs (append rr used)) ;; re-run
  386. repl)))
  387. (let loop ((rr '()) ; list symbols resolved
  388. (tkl '()) ; token list of
  389. (lv 0) ; level
  390. (ch (skip-il-ws (read-char)))) ; next character
  391. (cond
  392. ((eof-object? ch) (finish rr tkl))
  393. ((and (eqv? end-tok ch) (zero? lv))
  394. (unread-char ch) (finish rr tkl))
  395. ((and end-tok (char=? #\) ch) (zero? lv))
  396. (unread-char ch) (finish rr tkl))
  397. ((char-set-contains? c:ws ch) ; whitespace
  398. (loop rr (cons '$space tkl) lv (skip-il-ws (read-char))))
  399. ((read-c-comm ch #f) => ; comment
  400. (lambda (comm)
  401. ;; Normally comments in CPP def's are replaced by a space. We allow
  402. ;; comments to get passed through, hoping this does not break code.
  403. (if keep-comments
  404. (loop rr (acons '$comm (cdr comm) tkl) lv (skip-il-ws (read-char)))
  405. (loop rr (cons '$space tkl) lv (skip-il-ws (read-char))))))
  406. ((read-c-ident ch) =>
  407. (lambda (iden)
  408. (cond
  409. ((string=? iden "defined")
  410. (loop rr
  411. (acons '$echo (string-append iden (scan-defined-arg)) tkl)
  412. lv (read-char)))
  413. ((member iden '("__has_include__" "__has_include_next__"))
  414. (cond
  415. ((scan-arg-literal) =>
  416. (lambda (arg)
  417. (loop rr (acons '$echo (string-append iden arg) tkl)
  418. lv (read-char))))
  419. (else
  420. (loop rr (acons '$ident iden tkl) lv (read-char)))))
  421. (else
  422. (let ((rval (expand-cpp-macro-ref iden defs used)))
  423. (if rval
  424. (loop #t (cons rval tkl) lv (read-char))
  425. (loop rr (acons '$ident iden tkl) lv (read-char))))))))
  426. ((read-c-string ch) =>
  427. (lambda (pair) (loop rr (cons pair tkl) lv (read-char))))
  428. ((char=? #\( ch) (loop rr (cons ch tkl) (1+ lv) (read-char)))
  429. ((char=? #\) ch) (loop rr (cons ch tkl) (1- lv) (read-char)))
  430. (else
  431. (loop rr (cons ch tkl) lv (read-char))))))
  432. ;; @deffn {Procedure} collect-args argl defs used => argd
  433. ;; Collect arguments to a macro which appears in C code. If not looking at
  434. ;; @code{(} return @code{#f}, else scan and eat up to closing @code{)}.
  435. ;; If multiple whitespace characters are skipped at the front then only
  436. ;; one @code{#\space} is re-inserted.
  437. ;; @end deffn
  438. (define (collect-args argl defs used)
  439. (let loop1 ((sp #f) (ch (read-char)))
  440. (cond
  441. ((eof-object? ch) (if sp (unread-char #\space)) #f)
  442. ((char-set-contains? inline-whitespace ch) (loop1 #t (read-char)))
  443. ((char=? #\( ch)
  444. (let loop2 ((argl argl) (argv '()) (ch ch))
  445. (cond
  446. ((eqv? ch #\)) (reverse argv))
  447. ((null? argl) (cpp-err "arg count"))
  448. ((and (null? (cdr argl)) (string=? (car argl) "..."))
  449. (let ((val (scan-cpp-input defs used #\))))
  450. (loop2 (cdr argl) (acons "__VA_ARGS__" val argv) (read-char))))
  451. ((or (char=? ch #\() (char=? ch #\,))
  452. (let* ((val (scan-cpp-input defs used #\,)))
  453. (loop2 (cdr argl) (acons (car argl) val argv) (read-char))))
  454. (else
  455. (error "nyacc cpp.scm: collect-args coding error")))))
  456. (else (unread-char ch) (if sp (unread-char #\space)) #f))))
  457. ;; @deffn {Procedure} px-cpp-ftn-repl argd repl => string
  458. ;; pre-expand CPP function where @var{argd} is an a-list of arg name
  459. ;; and replacement and repl is the defined replacement
  460. ;;
  461. ;; argd is alist of arguments and token lists
  462. ;; if end-tok == #f ignore levels
  463. ;; ident space fixed float chseq hash dhash arg
  464. ;; need to decide if we should use `(space ,tkl) or `((space) ,tkl)
  465. ;; This should replace args and execute hash and double-hash ??
  466. ;; @end deffn
  467. (define (px-cpp-ftn argd repl)
  468. (with-input-from-string repl
  469. (lambda ()
  470. (px-cpp-ftn-1 argd))))
  471. (define (px-cpp-ftn-1 argd)
  472. ;; Turn reverse chl into a string and insert it into the token stream.
  473. (define (ins-chl chl stl)
  474. (if (null? chl) stl (cons (reverse-list->string chl) stl)))
  475. (define (rem-space chl)
  476. (let loop ((chl chl))
  477. (cond
  478. ((null? chl) chl)
  479. ((char-set-contains? c:ws (car chl)) (loop (cdr chl)))
  480. (else chl))))
  481. (define (mk-string str) (string-append "\"" (esc-c-str str) "\""))
  482. (let loop ((stl '()) ; string list
  483. (chl '()) ; character list
  484. (nxt #f) ; next string after char list
  485. (ch (read-char))) ; next character
  486. (cond
  487. (nxt (loop (cons nxt (ins-chl chl stl)) '() #f ch))
  488. ((eof-object? ch)
  489. (apply string-append (reverse (ins-chl chl stl))))
  490. ((char-set-contains? c:ws ch)
  491. (loop stl (cons #\space chl) nxt (skip-il-ws (read-char))))
  492. ((read-c-comm ch #f) (loop stl (cons #\space chl) nxt (read-char)))
  493. ((read-c-string ch) =>
  494. (lambda (st) (loop stl chl (mk-string (cdr st)) (read-char))))
  495. ((char=? #\( ch) (loop stl (cons ch chl) nxt (read-char)))
  496. ((char=? #\) ch) (loop stl (cons ch chl) nxt (read-char)))
  497. ((read-c-ident ch) => ; replace if aval
  498. (lambda (iden)
  499. (loop stl chl (or (assoc-ref argd iden) iden) (read-char))))
  500. ((char=? #\# ch)
  501. (let ((ch (read-char)))
  502. (if (eqv? ch #\#)
  503. (loop stl (rem-space chl) nxt (skip-il-ws (read-char)))
  504. (let* ((aref (read-c-ident (skip-il-ws ch)))
  505. (aval (assoc-ref argd aref)))
  506. (if (not aref) (cpp-err "expecting arg-ref"))
  507. (if (not aval) (cpp-err "expecting arg-val"))
  508. (loop stl chl (mk-string aval) (read-char))))))
  509. (else (loop stl (cons ch chl) nxt (read-char))))))
  510. ;; @deffn {Procedure} cpp-expand-text text defs [used] => string
  511. ;; Expand the string @var{text} using the provided CPP @var{defs} a-list.
  512. ;; Identifiers in the list of strings @var{used} will not be expanded.
  513. ;; @end deffn
  514. (define* (cpp-expand-text text defs #:optional (used '()))
  515. (with-input-from-string text
  516. (lambda () (scan-cpp-input defs used #f))))
  517. ;; === exports =======================
  518. ;; @deffn {Procedure} eval-cpp-cond-text text [defs] => string
  519. ;; Evaluate CPP condition expression (text).
  520. ;; Undefined identifiers are replaced with @code{0}.
  521. ;; @end deffn
  522. (define* (eval-cpp-cond-text text #:optional (defs '()) #:key (inc-dirs '()))
  523. (with-throw-handler
  524. 'cpp-error
  525. (lambda ()
  526. (let* ((rhs (cpp-expand-text text defs))
  527. (exp (parse-cpp-expr rhs)))
  528. (eval-cpp-expr exp defs #:inc-dirs inc-dirs)))
  529. (lambda (key fmt . args)
  530. (report-error fmt args)
  531. (throw 'c99-error "CPP error"))))
  532. ;; @deffn {Procedure} expand-cpp-macro-ref ident defs [used] => repl|#f
  533. ;; Given an identifier seen in the current input, this checks for associated
  534. ;; definition in @var{defs} (generated from CPP defines). If found as simple
  535. ;; macro, the expansion is returned as a string. If @var{ident} refers
  536. ;; to a macro with arguments, then the arguments will be read from the
  537. ;; current input. The format of the @code{defs} entries are
  538. ;; @example
  539. ;; ("ABC" . "123")
  540. ;; ("MAX" ("X" "Y") . "((X)>(Y)?(X):(Y))")
  541. ;; @end example
  542. ;; @noindent
  543. ;; Note that this routine will look in the current-input so if you want to
  544. ;; expand text,
  545. ;; @end deffn
  546. (define* (expand-cpp-macro-ref ident defs #:optional (used '()))
  547. (let ((rval (assoc-ref defs ident)))
  548. (cond
  549. ((member ident used) #f)
  550. ((string? rval)
  551. (let* ((used (cons ident used))
  552. (repl (cpp-expand-text rval defs used)))
  553. (if (ident-like? repl)
  554. (or (expand-cpp-macro-ref repl defs used) repl)
  555. repl)))
  556. ((pair? rval)
  557. ;; GNU CPP manual: "A function-like macro is only expanded if its name
  558. ;; appears with a pair of parentheses after it. If you just write the
  559. ;; name, it is left alone."
  560. (and=> (collect-args (car rval) defs used)
  561. (lambda (argd)
  562. (let* ((used (cons ident used))
  563. (prep (px-cpp-ftn argd (cdr rval)))
  564. (repl (cpp-expand-text prep defs used)))
  565. (if (ident-like? repl)
  566. (or (expand-cpp-macro-ref repl defs used) repl)
  567. repl)))))
  568. ((c99-std-val ident) => identity)
  569. (else #f))))
  570. ;;; --- last line ---