;; Filter/preprocess an input file with Scheme. ;; ;; Call with ;; ;; ./beastie0 scheme-macro-filter foo.c.in ;; ;; This file is part of Beastie ;; SPDX-FileCopyrightText: 2023 Norman Gray ;; SPDX-License-Identifier: BSD-2-Clause ;; copy characters from port 'in' to current-output-port, except where ;; we encounter #(...). That form should evaluate to a list of ;; lists-or-strings, which are sent to the output. (define (filter/port in) (define (display-sexp x) (cond ((pair? x) (for-each display-sexp x)) ((or (not x) (null? x))) ;do nothing (else (display x)))) (let loop ((at-next-newline #f)) (let ((c (read-char in))) (cond ((eof-object? c)) ;do nothing ((and (char=? c #\#) (char=? (peek-char in) #\()) (display-sexp (eval (read in))) (loop (format #f "#line ~a \"~a\"~%" (+ (port-line-number in) 1) (port-filename in)))) (else (write-char c) (if (and (char=? c #\newline) at-next-newline) (begin (display at-next-newline) (loop #f)) (loop at-next-newline))))))) ;; Given a file foo.scm, expand to ;; ;; const char foo_scm[] = {...}; ;; const size_t foo_scm_len = N; ;; ;; where foo_scm is an array of N bytes. ;; ;; Note: this routine is OK with encoding a program with Unicode in it, ;; since (string->list s) turns s into a UTF-8 sequence of bytes (this isn't documented). (define* (file->strings fn (name #f) (static? #f)) (let ((dotidx (- (string-length fn) 4))) (if (string=? (substring fn dotidx) ".scm") (let ((fn-root (or name (string-append (substring fn 0 dotidx) "_scm"))) (prog ;the program, as a list of write strings (let-temporarily (((*s7* 'print-length) (*s7* 'most-positive-fixnum))) (call-with-input-file fn (lambda (in) (let loop ((e (read in)) (lines '())) (cond ((eof-object? e) (reverse! lines)) ((or (unspecified? e) (null? e)) ;ignore this (loop (read in) lines)) (else (loop (read in) (cons (object->string e :write) lines)))))))))) (dependency-add! fn) (list (format #f "~%~%/* contents of ~s */" fn) (prog->strings prog fn-root static?))) (format (current-error-port) "file->strings: file ~a ignored -- must be foo.scm~%" fn)))) (define* (prog->strings/strings prog name (static? #f) (comment-length 40)) ;; Given a program as a list of strings, write this in a way which ;; can be loaded with ;; ;; s7_load_c_string(S7, , _len); ;; ;; The prog argument should be a list of strings, each of which is one ;; scheme expression. ;; ;; The return value is also a list of strings, which can be written ;; in sequence to the output port. (list (format #f "~% ~aconst char* const ~a =" (if static? "static " "") name) (map (lambda (line) (with-output-to-string (lambda () (newline) (write line)))) prog) (format #f ";~% ~aconst size_t ~a_len = ~a;" (if static? "static " "") name (apply + (map string-length prog))))) ;; The following is a variant of the above, which produces the output ;; as a C _byte array_ rather than strings. This is cute, but isn't ;; going to be any faster, and ends up being less conveniently readable. (define* (prog->strings/bytes prog name (static? #f) (comment-length 40)) ;; Given a program as a list of strings, write this in a way which ;; can be loaded with ;; ;; s7_load_c_string(S7, , _len); ;; ;; The prog argument should be a list of strings, each of which is one ;; scheme expression. (list (format #f "~% ~aconst char ~a[] = {" (if static? "static " "") name) (map (lambda (line) (list (if (< (string-length line) comment-length) (format #f "~% /* ~a */" line) (format #f "~% /* ~a ... */" (substring line 0 comment-length))) (with-output-to-string (lambda () (let next-char ((l (string->list line)) (bytes/line 0)) (unless (null? l) (when (= bytes/line 0) (format #t "~% ")) (format #t "0x~x, " (char->integer (car l))) (next-char (cdr l) (if (= bytes/line 0) 9 (- bytes/line 1))))))))) prog) (format #f "0 };~% ~aconst size_t ~a_len = ~a;" (if static? "static " "") name (apply + (map string-length prog))))) (define prog->strings prog->strings/strings) (varlet (curlet) (let ((*deps* '())) (define (add! fn) (set! *deps* (cons fn *deps*)) #) (define (get-line target) (apply string-append `(,target ":" . ,(map (lambda (f) (format #f " ~a" f)) *deps*)))) (inlet 'dependency-add! add! 'dependencies-line get-line))) (let ((input-file (and (> (length *command-line*) 1) (list-ref *command-line* 1))) (output-file (and (> (length *command-line*) 2) (list-ref *command-line* 2)))) (define (process-input) ;; Include a #line number in the generated output. ;; This goes wrong after #(...) are expanded, but that turns out ;; to be hard to fix. (if input-file (begin (dependency-add! input-file) (format #t "// GENERATED~%// from ~a~%~%#line 1 \"~a\"~%" input-file input-file) (call-with-input-file input-file filter/port)) (begin (format #t "// GENERATED~%~%") (filter/port (current-input-port))))) (if output-file (begin (with-output-to-file output-file process-input) (call-with-output-file (string-append output-file ".d") (lambda (p) (format p "~a~%" (dependencies-line output-file))))) (process-input)))