implement compound and designated literals support
This commit was merged in pull request #38.
This commit is contained in:
2
Makefile
2
Makefile
@@ -94,7 +94,7 @@ sextest:
|
|||||||
cp ./tools/sextest/sextest .
|
cp ./tools/sextest/sextest .
|
||||||
|
|
||||||
SEX_TEST_PROGRAMS = hello-world lists comments unicode serialize features \
|
SEX_TEST_PROGRAMS = hello-world lists comments unicode serialize features \
|
||||||
feature-flags lambdas
|
feature-flags lambdas compound-literals
|
||||||
|
|
||||||
# Multi-module linking is checked end to end; see tests/modules/Makefile.
|
# Multi-module linking is checked end to end; see tests/modules/Makefile.
|
||||||
check-modules: sexc
|
check-modules: sexc
|
||||||
|
|||||||
32
Readme.org
32
Readme.org
@@ -175,6 +175,38 @@ e.g. for checking output for other platform:
|
|||||||
sexc example/sdl3-triangle.sex -C --no-platform-features --features=linux,unix,x86-64
|
sexc example/sdl3-triangle.sex -C --no-platform-features --features=linux,unix,x86-64
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
** Aggregate initializers and compound literals
|
||||||
|
~#(...)~ is a brace initializer. On its own it has no type and takes one
|
||||||
|
from where it is written:
|
||||||
|
|
||||||
|
#+begin_src scheme
|
||||||
|
(var p (struct point) #(1 2))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
A ~:~ inside one ends a type and makes the whole thing a compound
|
||||||
|
literal --- an unnamed object of that type, usable anywhere an
|
||||||
|
expression is:
|
||||||
|
|
||||||
|
#+begin_src scheme
|
||||||
|
(var q (struct point) #(struct point : 3 4))
|
||||||
|
(var a (* int) #([int 3] : 10 20 30))
|
||||||
|
(draw-line ui #(struct point : 0 0) end)
|
||||||
|
(var p (* struct point) (& #(struct point : 9 9))) ; an lvalue, so `&' works
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
The type is written as bare words, the way it is everywhere else in the
|
||||||
|
language; ~:~ is what ends it.
|
||||||
|
|
||||||
|
A leading ~.~ names a field, so initializers may be designated, given in
|
||||||
|
any order, and mixed with positional ones:
|
||||||
|
|
||||||
|
#+begin_src scheme
|
||||||
|
(var r (struct named) #(struct named : .n 7 .first-name "zoe"))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
A compound literal written inside a block lives until the end of that
|
||||||
|
block and no longer, so returning its address is a dangling pointer.
|
||||||
|
|
||||||
** Syntactic macros
|
** Syntactic macros
|
||||||
Sex has support for syntactic macros. Macro definitions look like
|
Sex has support for syntactic macros. Macro definitions look like
|
||||||
functions: they have a name, an argument list and a body. Macro should
|
functions: they have a name, an argument list and a body. Macro should
|
||||||
|
|||||||
@@ -172,9 +172,7 @@ forms, and what remains."
|
|||||||
|
|
||||||
(define (walk-expr form)
|
(define (walk-expr form)
|
||||||
(match form
|
(match form
|
||||||
((? vector?)
|
((? vector?) (walk-initializer (vector->list form)))
|
||||||
(list->vector
|
|
||||||
(walk-expr (vector->list form))))
|
|
||||||
((? atom?)
|
((? atom?)
|
||||||
(atom-to-fmt-c form))
|
(atom-to-fmt-c form))
|
||||||
;; (comment "text") -> /* text */. `%comment' is the fmt-c directive.
|
;; (comment "text") -> /* text */. `%comment' is the fmt-c directive.
|
||||||
@@ -234,6 +232,48 @@ forms, and what remains."
|
|||||||
;; Drop comments so they will not generate additional comma
|
;; Drop comments so they will not generate additional comma
|
||||||
(else (map walk-expr (remove comment-form? form)))))
|
(else (map walk-expr (remove comment-form? form)))))
|
||||||
|
|
||||||
|
;;; #(a b c) is a brace initializer. A `:' inside one ends a type and
|
||||||
|
;;; turns the whole thing into a C99 compound literal:
|
||||||
|
;;; #(struct point : 1 2) is (struct point){1, 2}, and the type is
|
||||||
|
;;; written as bare words, the way it is everywhere else in the
|
||||||
|
;;; language. `:' is the separator because it is the one thing that can
|
||||||
|
;;; be neither a type word nor an expression -- a named form would be a
|
||||||
|
;;; C identifier, and so shadowable (see issue #36).
|
||||||
|
(define (walk-initializer elements)
|
||||||
|
(let ((parts (list-split (remove comment-form? elements) ':)))
|
||||||
|
(if (null? (cdr parts))
|
||||||
|
(list->vector (walk-designators (car parts)))
|
||||||
|
(cons* '%compound
|
||||||
|
(walk-type (maybe-unwrap-type (car parts)))
|
||||||
|
(walk-designators (cadr parts))))))
|
||||||
|
|
||||||
|
;;; `.field value' is a designated initializer; anything else is
|
||||||
|
;;; positional. C lets the two be mixed, and nothing here stops it. A
|
||||||
|
;;; leading `.' cannot begin a C identifier, so a field name needs no
|
||||||
|
;;; keyword to introduce it and cannot collide with one.
|
||||||
|
(define (walk-designators elements)
|
||||||
|
(let loop ((es elements) (acc (list)))
|
||||||
|
(match es
|
||||||
|
(() (reverse acc))
|
||||||
|
(((? designator? d))
|
||||||
|
(sex-error elements "designated initializer without a value" d))
|
||||||
|
(((? designator? d) value . rest)
|
||||||
|
(loop rest
|
||||||
|
(cons (list '%designate
|
||||||
|
(atom-to-fmt-c (designator-field d))
|
||||||
|
(walk-expr value))
|
||||||
|
acc)))
|
||||||
|
((e . rest) (loop rest (cons (walk-expr e) acc))))))
|
||||||
|
|
||||||
|
(define (designator? x)
|
||||||
|
(and (symbol? x)
|
||||||
|
(let ((s (symbol->string x)))
|
||||||
|
(and (> (string-length s) 1)
|
||||||
|
(char=? #\. (string-ref s 0))))))
|
||||||
|
|
||||||
|
(define (designator-field d)
|
||||||
|
(string->symbol (substring (symbol->string d) 1)))
|
||||||
|
|
||||||
(define (walk-var form)
|
(define (walk-var form)
|
||||||
;; (var a int) -> (%var int a)
|
;; (var a int) -> (%var int a)
|
||||||
;; (var a (const int) 32) -> (%var (const int) a 32)
|
;; (var a (const int) 32) -> (%var (const int) a 32)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
c-in-expr c-in-stmt c-in-test
|
c-in-expr c-in-stmt c-in-test
|
||||||
c-paren c-maybe-paren c-type c-literal? c-literal char->c-char
|
c-paren c-maybe-paren c-type c-literal? c-literal char->c-char
|
||||||
c-struct c-union c-class c-enum c-typedef c-cast
|
c-struct c-union c-class c-enum c-typedef c-cast
|
||||||
|
c-braced-list c-compound c-designate
|
||||||
c-expr c-expr/sexp c-apply c-op c-indent c-current-indent-string
|
c-expr c-expr/sexp c-apply c-op c-indent c-current-indent-string
|
||||||
c-wrap-stmt c-open-brace c-close-brace
|
c-wrap-stmt c-open-brace c-close-brace
|
||||||
c-block c-braced-block c-begin
|
c-block c-braced-block c-begin
|
||||||
@@ -277,6 +278,8 @@
|
|||||||
((%comment) ((apply c-comment (cdr x)) st))
|
((%comment) ((apply c-comment (cdr x)) st))
|
||||||
((:) ((apply c-label (cdr x)) st))
|
((:) ((apply c-label (cdr x)) st))
|
||||||
((%cast) ((apply c-cast (cdr x)) st))
|
((%cast) ((apply c-cast (cdr x)) st))
|
||||||
|
((%compound) ((apply c-compound (cdr x)) st))
|
||||||
|
((%designate) ((apply c-designate (cdr x)) st))
|
||||||
((+ - & * / % ! ~ ^ && < > <= >= == != << >>
|
((+ - & * / % ! ~ ^ && < > <= >= == != << >>
|
||||||
= *= /= %= &= ^= >>= <<=) ; |\|| |\|\|| |\|=|
|
= *= /= %= &= ^= >>= <<=) ; |\|| |\|\|| |\|=|
|
||||||
((apply c-op x) st))
|
((apply c-op x) st))
|
||||||
@@ -306,17 +309,7 @@
|
|||||||
((apply c-op "-=" (cdr x)) st))
|
((apply c-op "-=" (cdr x)) st))
|
||||||
(else ((c-apply x) st))))))
|
(else ((c-apply x) st))))))
|
||||||
((vector? x)
|
((vector? x)
|
||||||
((c-wrap-stmt
|
((c-wrap-stmt (c-braced-list (vector->list x))) st))
|
||||||
(fmt-try-fit
|
|
||||||
(fmt-let 'no-wrap? #t
|
|
||||||
(cat "{" (fmt-join c-expr (vector->list x) ", ") "}"))
|
|
||||||
(lambda (st)
|
|
||||||
(let* ((col (fmt-col st))
|
|
||||||
(sep (string-append "," (make-nl-space col))))
|
|
||||||
((cat "{" (fmt-join c-expr (vector->list x) sep)
|
|
||||||
"}" nl)
|
|
||||||
st)))))
|
|
||||||
st))
|
|
||||||
(else
|
(else
|
||||||
((c-literal x) st))))))
|
((c-literal x) st))))))
|
||||||
|
|
||||||
@@ -832,6 +825,25 @@
|
|||||||
(cat "(" (c-with-op 'paren (c-expr expr)) ")")
|
(cat "(" (c-with-op 'paren (c-expr expr)) ")")
|
||||||
(c-expr expr))))
|
(c-expr expr))))
|
||||||
|
|
||||||
|
;; { a, b, c } -- on one line if it fits, one element per line if not.
|
||||||
|
(define (c-braced-list ls)
|
||||||
|
(fmt-try-fit
|
||||||
|
(fmt-let 'no-wrap? #t (cat "{" (fmt-join c-expr ls ", ") "}"))
|
||||||
|
(lambda (st)
|
||||||
|
(let* ((col (fmt-col st))
|
||||||
|
(sep (string-append "," (make-nl-space col))))
|
||||||
|
((cat "{" (fmt-join c-expr ls sep) "}" nl) st)))))
|
||||||
|
|
||||||
|
;; (T){ ... } -- a C99 compound literal, not a cast: the result is an
|
||||||
|
;; unnamed object and an lvalue, so `&' on it is legal. At block scope
|
||||||
|
;; it lives until the end of the enclosing block and no longer.
|
||||||
|
(define (c-compound type . init)
|
||||||
|
(cat "(" (c-type type) ")" (c-braced-list init)))
|
||||||
|
|
||||||
|
;; .field = value, inside a braced list
|
||||||
|
(define (c-designate field value)
|
||||||
|
(cat "." (c-expr field) " = " (c-expr value)))
|
||||||
|
|
||||||
(define (c-typedef type alias . o)
|
(define (c-typedef type alias . o)
|
||||||
(c-wrap-stmt
|
(c-wrap-stmt
|
||||||
(cat "typedef " (c-type type alias) (fmt-join/prefix c-expr o " "))))
|
(cat "typedef " (c-type type alias) (fmt-join/prefix c-expr o " "))))
|
||||||
|
|||||||
@@ -101,6 +101,39 @@
|
|||||||
'(%var (struct suc *) s (hoge piyo))
|
'(%var (struct suc *) s (hoge piyo))
|
||||||
(walk-var '(var s (* struct suc) (hoge piyo))))
|
(walk-var '(var s (* struct suc) (hoge piyo))))
|
||||||
|
|
||||||
|
;;; Initializers and compound literals
|
||||||
|
(test "a bare initializer is unchanged"
|
||||||
|
'#(1 2)
|
||||||
|
(walk-expr '#(1 2)))
|
||||||
|
|
||||||
|
(test "`:' ends the type and makes it a compound literal"
|
||||||
|
'(%compound (struct point) 3 4)
|
||||||
|
(walk-expr '#(struct point : 3 4)))
|
||||||
|
|
||||||
|
(test "the type is bare words, as everywhere else"
|
||||||
|
'(%compound (const char *) 65)
|
||||||
|
(walk-expr '#(* const char : 65)))
|
||||||
|
|
||||||
|
(test "and may be an array type"
|
||||||
|
'(%compound (%array int 3) 10 20 30)
|
||||||
|
(walk-expr '#(¤ int 3 : 10 20 30)))
|
||||||
|
|
||||||
|
(test "a grouped type is unwrapped the way a declaration's is"
|
||||||
|
'(%compound (%array int 3) 10)
|
||||||
|
(walk-expr '#((¤ int 3) : 10)))
|
||||||
|
|
||||||
|
(test "`.field value' is a designated initializer, kebab and all"
|
||||||
|
'(%compound (struct named) (%designate n 7) (%designate first_name "zoe"))
|
||||||
|
(walk-expr '#(struct named : .n 7 .first-name "zoe")))
|
||||||
|
|
||||||
|
(test "positional and designated may be mixed"
|
||||||
|
'(%compound (struct point) 1 (%designate y 5))
|
||||||
|
(walk-expr '#(struct point : 1 .y 5)))
|
||||||
|
|
||||||
|
(test "designators work in an untyped initializer too"
|
||||||
|
'#((%designate y 5))
|
||||||
|
(walk-expr '#(.y 5)))
|
||||||
|
|
||||||
;;; Fn defs
|
;;; Fn defs
|
||||||
(test
|
(test
|
||||||
'(%fun void puk ((int) (%array float 8)))
|
'(%fun void puk ((int) (%array float 8)))
|
||||||
|
|||||||
49
tests/sex-programs/compound-literals.sex
Normal file
49
tests/sex-programs/compound-literals.sex
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
(input)
|
||||||
|
(output "plain 1 2"
|
||||||
|
"literal 3 4"
|
||||||
|
"designated zoe 7"
|
||||||
|
"through a pointer 9"
|
||||||
|
"array 10 20 30"
|
||||||
|
"argument 6"
|
||||||
|
"mixed 1 5")
|
||||||
|
(return 0)
|
||||||
|
|
||||||
|
;;; `:' inside #(...) ends a type and makes the rest a C99 compound
|
||||||
|
;;; literal. Without one, #(...) is the brace initializer it always was.
|
||||||
|
|
||||||
|
(include stdio.h)
|
||||||
|
|
||||||
|
(struct point ((x int) (y int)))
|
||||||
|
(struct named ((first-name (* const char)) (n int)))
|
||||||
|
|
||||||
|
(fn sum ((p (struct point))) int
|
||||||
|
(return (+ (. p x) (. p y))))
|
||||||
|
|
||||||
|
(pub fn main () int
|
||||||
|
;; unchanged: a bare initializer has no type of its own
|
||||||
|
(var p (struct point) #(1 2))
|
||||||
|
(printf "plain %d %d\n" (. p x) (. p y))
|
||||||
|
|
||||||
|
(var q (struct point) #(struct point : 3 4))
|
||||||
|
(printf "literal %d %d\n" (. q x) (. q y))
|
||||||
|
|
||||||
|
;; designated, out of declaration order, and kebab-cased
|
||||||
|
(var r (struct named) #(struct named : .n 7 .first-name "zoe"))
|
||||||
|
(printf "designated %s %d\n" (. r first-name) (. r n))
|
||||||
|
|
||||||
|
;; a compound literal is an lvalue, so its address can be taken --
|
||||||
|
;; until the end of the enclosing block, and no longer
|
||||||
|
(var pp (* struct point) (& #(struct point : 9 9)))
|
||||||
|
(printf "through a pointer %d\n" (-> pp x))
|
||||||
|
|
||||||
|
;; an array literal decays the way an array does
|
||||||
|
(var a (* int) #([int 3] : 10 20 30))
|
||||||
|
(printf "array %d %d %d\n" (¤ a 0) (¤ a 1) (¤ a 2))
|
||||||
|
|
||||||
|
(printf "argument %d\n" (sum #(struct point : 2 4)))
|
||||||
|
|
||||||
|
;; positional and designated may be mixed, as in C
|
||||||
|
(var m (struct point) #(struct point : 1 .y 5))
|
||||||
|
(printf "mixed %d %d\n" (. m x) (. m y))
|
||||||
|
|
||||||
|
(return 0))
|
||||||
Reference in New Issue
Block a user