add codegen and args tests, fix couple of bugs in C generation
This commit is contained in:
@@ -787,8 +787,32 @@
|
|||||||
(fmt-join c-expr name ", ")
|
(fmt-join c-expr name ", ")
|
||||||
(c-expr name))))))
|
(c-expr name))))))
|
||||||
|
|
||||||
|
;; MODIFIED FROM UPSTREAM fmt-c: parenthesise a binary operand.
|
||||||
|
;;
|
||||||
|
;; A cast binds tighter than every binary operator, so upstream's
|
||||||
|
;; unparenthesised (cat "(" type ")" expr) silently casts only the
|
||||||
|
;; first operand: (cast (* 2 (sizeof GLfloat)) (* void)) came out as
|
||||||
|
;; `(void *)2 * sizeof(GLfloat)'. Unary operands -- &x, *p, -n,
|
||||||
|
;; sizeof, calls, identifiers, literals -- are already
|
||||||
|
;; unary-expressions and are left alone, so this changes nothing that
|
||||||
|
;; was previously correct.
|
||||||
|
(define c-binary-operators
|
||||||
|
'(+ - * / % << >> < > <= >= == != & ^ && %and %or
|
||||||
|
= *= /= %= &= ^= <<= >>=))
|
||||||
|
|
||||||
|
(define (c-binary-expression? expr)
|
||||||
|
(and (pair? expr)
|
||||||
|
(memq (car expr) c-binary-operators)
|
||||||
|
(pair? (cdr expr))
|
||||||
|
(pair? (cddr expr)))) ; two or more operands
|
||||||
|
|
||||||
(define (c-cast type expr)
|
(define (c-cast type expr)
|
||||||
(cat "(" (c-type type) ")" (c-expr expr)))
|
(cat "(" (c-type type) ")"
|
||||||
|
(if (c-binary-expression? expr)
|
||||||
|
;; c-with-op stops the operand from parenthesising itself
|
||||||
|
;; a second time inside the parens we just added.
|
||||||
|
(cat "(" (c-with-op 'paren (c-expr expr)) ")")
|
||||||
|
(c-expr expr))))
|
||||||
|
|
||||||
(define (c-typedef type alias . o)
|
(define (c-typedef type alias . o)
|
||||||
(c-wrap-stmt
|
(c-wrap-stmt
|
||||||
@@ -861,7 +885,12 @@
|
|||||||
(define (c-switch val . clauses)
|
(define (c-switch val . clauses)
|
||||||
(c-reset-newline
|
(c-reset-newline
|
||||||
(lambda (st)
|
(lambda (st)
|
||||||
((cat "switch (" (c-in-expr val) ")" (c-open-brace st)
|
;; MODIFIED FROM UPSTREAM fmt-c: c-expr on the scrutinee.
|
||||||
|
;; Upstream passes `val' straight to cat, which only works when
|
||||||
|
;; it is an atom -- anything else (a member access, a call) is
|
||||||
|
;; displayed as a raw s-expression instead of being rendered.
|
||||||
|
;; c-while and c-for already do (c-in-test (c-expr check)).
|
||||||
|
((cat "switch (" (c-in-expr (c-expr val)) ")" (c-open-brace st)
|
||||||
(c-indent/switch st)
|
(c-indent/switch st)
|
||||||
(c-in-stmt (apply c-begin/aux #t (map c-switch-clause clauses))) fl
|
(c-in-stmt (apply c-begin/aux #t (map c-switch-clause clauses))) fl
|
||||||
(c-current-indent-string st) (c-close-brace st) fl)
|
(c-current-indent-string st) (c-close-brace st) fl)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ MODULE_FLAGS = -emit-all-import-libraries -module-registration -c
|
|||||||
MODULES = utils sex-macros reader sex-modules semen sex-fmt-c fmt-c-writer sexc
|
MODULES = utils sex-macros reader sex-modules semen sex-fmt-c fmt-c-writer sexc
|
||||||
SEX_OBJ = $(MODULES:%=%.o)
|
SEX_OBJ = $(MODULES:%=%.o)
|
||||||
|
|
||||||
TESTS = basic semen reader fmt-c-writer utils line-directives
|
TESTS = basic semen reader fmt-c-writer utils line-directives codegen args
|
||||||
TEST_SRCS = $(TESTS:%=%.scm)
|
TEST_SRCS = $(TESTS:%=%.scm)
|
||||||
|
|
||||||
sex-tests: run.scm $(TEST_SRCS) $(SEX_OBJ)
|
sex-tests: run.scm $(TEST_SRCS) $(SEX_OBJ)
|
||||||
|
|||||||
55
tests/args.scm
Normal file
55
tests/args.scm
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
;;; Splitting the command line at `--'.
|
||||||
|
;;;
|
||||||
|
;;; getopt-long cannot do this: it consumes the separator and merges
|
||||||
|
;;; everything after it into `@' alongside the input file. So sexc
|
||||||
|
;;; splits the raw argv first, and only the head is parsed as options.
|
||||||
|
;;; Everything else reaches the C compiler exactly as written --
|
||||||
|
;;; including the words that do not start with a dash, which a previous
|
||||||
|
;;; leading-dash heuristic used to drop.
|
||||||
|
|
||||||
|
(import sexc)
|
||||||
|
|
||||||
|
(define full '("foo.sex" "-o" "bar" "--" "-framework" "OpenGL" "-Wall"))
|
||||||
|
|
||||||
|
(test-group "argument separator"
|
||||||
|
|
||||||
|
(test "options and input file stay with sexc"
|
||||||
|
'("foo.sex" "-o" "bar")
|
||||||
|
(args-before-separator full))
|
||||||
|
|
||||||
|
(test "the tail reaches the compiler verbatim"
|
||||||
|
'("-framework" "OpenGL" "-Wall")
|
||||||
|
(args-after-separator full))
|
||||||
|
|
||||||
|
;; The case that motivated this: `OpenGL' has no leading dash and was
|
||||||
|
;; silently dropped, leaving `-framework' to swallow whatever flag
|
||||||
|
;; came next.
|
||||||
|
(test "a word without a dash survives"
|
||||||
|
'("-framework" "OpenGL")
|
||||||
|
(args-after-separator '("x.sex" "--" "-framework" "OpenGL")))
|
||||||
|
|
||||||
|
(test "no separator means nothing for the compiler"
|
||||||
|
'()
|
||||||
|
(args-after-separator '("foo.sex" "-o" "bar")))
|
||||||
|
|
||||||
|
(test "no separator leaves every argument with sexc"
|
||||||
|
'("foo.sex" "-o" "bar")
|
||||||
|
(args-before-separator '("foo.sex" "-o" "bar")))
|
||||||
|
|
||||||
|
(test "a trailing separator is allowed"
|
||||||
|
'()
|
||||||
|
(args-after-separator '("foo.sex" "--")))
|
||||||
|
|
||||||
|
(test "a leading separator leaves no input file"
|
||||||
|
'()
|
||||||
|
(args-before-separator '("--" "-lm")))
|
||||||
|
|
||||||
|
;; Only the first `--' separates; a later one is an ordinary compiler
|
||||||
|
;; argument (ld takes several).
|
||||||
|
(test "only the first separator counts"
|
||||||
|
'("-Wl,--as-needed" "--" "-lm")
|
||||||
|
(args-after-separator '("x.sex" "--" "-Wl,--as-needed" "--" "-lm")))
|
||||||
|
|
||||||
|
(test "an empty command line is handled"
|
||||||
|
'()
|
||||||
|
(args-before-separator '())))
|
||||||
75
tests/codegen.scm
Normal file
75
tests/codegen.scm
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
;;; Codegen details that are easy to get subtly wrong, and that the
|
||||||
|
;;; walk-* unit tests cannot see: they check the intermediate form we
|
||||||
|
;;; hand to fmt-c, not the C that fmt-c renders from it.
|
||||||
|
;;;
|
||||||
|
;;; Both cases below were found by writing an OpenGL example, not by
|
||||||
|
;;; the existing suite, because both need an operand shape that no
|
||||||
|
;;; earlier test program happened to use.
|
||||||
|
|
||||||
|
(import (chicken port)
|
||||||
|
(chicken string)
|
||||||
|
srfi-13
|
||||||
|
fmt-c-writer
|
||||||
|
reader
|
||||||
|
semen
|
||||||
|
utils)
|
||||||
|
|
||||||
|
(define (sex->c source)
|
||||||
|
"Compile SOURCE, a string of Sex, and return the generated C."
|
||||||
|
(let ((forms (with-input-from-string source
|
||||||
|
(lambda ()
|
||||||
|
(parameterize ((current-source-file "codegen.sex"))
|
||||||
|
(parse-all (current-input-port)))))))
|
||||||
|
(with-output-to-string
|
||||||
|
(lambda ()
|
||||||
|
(parameterize ((sex-line-directives 'none))
|
||||||
|
(emit-c (semen-process forms)))))))
|
||||||
|
|
||||||
|
(define (emits? source fragment)
|
||||||
|
(and (string-contains (sex->c source) fragment) #t))
|
||||||
|
|
||||||
|
(define (in-fn body)
|
||||||
|
(string-append "(fn f ((a int) (b int)) void " body ")"))
|
||||||
|
|
||||||
|
(test-group "codegen"
|
||||||
|
|
||||||
|
;; c-switch handed its scrutinee straight to `cat', which only works
|
||||||
|
;; when it is an atom. Anything else was displayed as a raw
|
||||||
|
;; s-expression: `switch ((%. e type))'.
|
||||||
|
(test-group "switch scrutinee"
|
||||||
|
(test-assert "member access"
|
||||||
|
(emits? "(struct s ((type int))) (fn f ((e (struct s))) void (switch (. e type) (case 1 (g))))"
|
||||||
|
"switch (e.type)"))
|
||||||
|
(test-assert "call"
|
||||||
|
(emits? (in-fn "(switch (g a) (case 1 (h)))")
|
||||||
|
"switch (g(a))"))
|
||||||
|
(test-assert "arithmetic"
|
||||||
|
(emits? (in-fn "(switch (+ a b) (case 1 (h)))")
|
||||||
|
"switch (a + b)")))
|
||||||
|
|
||||||
|
;; A cast binds tighter than every binary operator, so an operand
|
||||||
|
;; that is itself a binary expression has to be parenthesised --
|
||||||
|
;; otherwise the cast silently applies to the first operand only.
|
||||||
|
(test-group "cast precedence"
|
||||||
|
(test-assert "binary operand is parenthesised"
|
||||||
|
(emits? (in-fn "(var p (* void) (cast (* 2 (sizeof int)) (* void)))")
|
||||||
|
"(void *)(2 * sizeof(int))"))
|
||||||
|
(test-assert "subtraction operand is parenthesised"
|
||||||
|
(emits? (in-fn "(var f float (cast (- a b) float))")
|
||||||
|
"(float)(a - b)"))
|
||||||
|
;; ...but exactly once. The operand used to parenthesise itself
|
||||||
|
;; again inside the parens the cast had just added.
|
||||||
|
(test-assert "and not parenthesised twice"
|
||||||
|
(not (emits? (in-fn "(var f float (cast (- a b) float))")
|
||||||
|
"(float)((a - b))")))
|
||||||
|
;; Unary operands are already unary-expressions and must be left
|
||||||
|
;; alone, or every existing cast in the tree gains noise.
|
||||||
|
(test-assert "identifier is left bare"
|
||||||
|
(emits? (in-fn "(var f float (cast a float))")
|
||||||
|
"(float)a"))
|
||||||
|
(test-assert "address-of is left bare"
|
||||||
|
(emits? (in-fn "(var p (* int) (cast (& a) (* int)))")
|
||||||
|
"(int *)&a"))
|
||||||
|
(test-assert "sizeof is left bare"
|
||||||
|
(emits? (in-fn "(var n int (cast (sizeof int) int))")
|
||||||
|
"(int)sizeof(int)"))))
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
(include "fmt-c-writer.scm")
|
(include "fmt-c-writer.scm")
|
||||||
(include "utils.scm")
|
(include "utils.scm")
|
||||||
(include "line-directives.scm")
|
(include "line-directives.scm")
|
||||||
|
(include "codegen.scm")
|
||||||
|
(include "args.scm")
|
||||||
|
|
||||||
;;; Should be the last in the test suite
|
;;; Should be the last in the test suite
|
||||||
(test-exit)
|
(test-exit)
|
||||||
|
|||||||
@@ -1 +1,3 @@
|
|||||||
(module sexc (main) "../sexc.scm")
|
(module sexc
|
||||||
|
*
|
||||||
|
"../sexc.scm")
|
||||||
|
|||||||
Reference in New Issue
Block a user