3 Commits

Author SHA1 Message Date
Pavel Kulyov
63801b47b2 deps: lock versions
All checks were successful
Sex CI / build-linux (pull_request) Successful in 7m12s
2026-09-18 00:37:56 +03:00
Pavel Kulyov
82d9402857 readme: update installation docs
Some checks failed
Sex CI / build-linux (pull_request) Failing after 3m56s
2026-09-18 00:27:08 +03:00
Pavel Kulyov
22936e68e3 infra: pepper some GNU on top of Makefile 2026-09-18 00:27:07 +03:00
22 changed files with 170 additions and 511 deletions

View File

@@ -57,9 +57,20 @@ jobs:
hash -r
csc -version
# Eggs are pinned in eggs.lock and installed by the Makefile into .eggs/.
- name: Install eggs
run: |
set -eu
mkdir -p .eggs
repo="$(chicken-install -repository)"
echo "CHICKEN_INSTALL_REPOSITORY=${PWD}/.eggs" >> "$GITHUB_ENV"
echo "CHICKEN_REPOSITORY_PATH=${PWD}/.eggs:${repo}" >> "$GITHUB_ENV"
export CHICKEN_INSTALL_REPOSITORY="${PWD}/.eggs"
export CHICKEN_REPOSITORY_PATH="${PWD}/.eggs:${repo}"
# shellcheck disable=SC2046
chicken-install $(cat dependencies.txt)
- name: Build sexc
run: make && ./sexc --help
run: make sexc && ./sexc --help
- name: Run tests
run: make check
run: make run-tests

View File

@@ -90,8 +90,7 @@ sextest: $(EGGS_STAMP)
$(MAKE) -C ./tools/sextest sextest
cp ./tools/sextest/sextest .
SEX_TEST_PROGRAMS = hello-world lists comments unicode serialize features \
feature-flags
SEX_TEST_PROGRAMS = hello-world lists comments unicode serialize features
# Multi-module linking is checked end to end; see tests/modules/Makefile.
check-modules: sexc
@@ -101,7 +100,7 @@ check-modules: sexc
check-exit-code: sexc
@$(MAKE) --no-print-directory -C ./tests/exit-code check SEXC=../../sexc
check run-tests: sexc sex-tests sextest
run-tests: sexc sex-tests sextest
./sex-tests && ./sextest --sexc=./sexc $(SEX_TEST_PROGRAMS:%=./tests/sex-programs/%.sex) && $(MAKE) check-modules && $(MAKE) check-exit-code
install: all installdirs
@@ -146,6 +145,5 @@ clean:
deps-clean:
rm -rf $(EGGS_DIR)
.PHONY: all check run-tests check-modules check-exit-code \
install install-strip installdirs uninstall \
deps deps-update deps-clean clean sex-tests sextest
.PHONY: all check run-tests check-modules check-exit-code install install-strip installdirs
uninstall deps deps-update clean depsclean sex-tests sextest

View File

@@ -135,6 +135,9 @@ forms, and what remains."
(car type)
type))
(define (comment-form? f)
(and (pair? f) (eq? (car f) 'comment)))
;;; Strip ;s so ;;; Foo becomes /* Foo */ and not /* ;; Foo */
(define (strip-comment-marker text)
(string-trim-both (string-trim text #\;)))
@@ -276,7 +279,7 @@ forms, and what remains."
;; it's a strong semantic cue
`(%array ,(walk-type (maybe-unwrap-type array-type)))))
(('fn arglist ret-type)
`(%fun ,(walk-type ret-type) ,(walk-arg-types arglist)))
`(%fun ,(walk-type ret-type) ,(walk-arglist arglist)))
(('fn . _)
(sex-error form "malformed function type" form))
@@ -288,13 +291,8 @@ forms, and what remains."
(else
(type-convert-to-c form))))
;;; An array or a function type
(define (structured-type? form)
(and (pair? form) (memq (car form) '(¤ fn))))
(define (has-pointer-star? form)
(and (pair? form)
(not (structured-type? form))
(or (memq '* form)
(any has-pointer-star? (filter pair? form)))))
@@ -306,10 +304,6 @@ forms, and what remains."
(and (pair? type)
(any has-pointer-star? (filter pair? type))))
(define (nested-structured-type type)
(and (pair? type)
(find structured-type? (filter pair? type))))
(define (type-convert-to-c type)
;; Our pointers to C pointers
;; int -> int
@@ -317,12 +311,6 @@ forms, and what remains."
;; const * const char -> const char * const
(when (nested-pointer? type)
(sex-error type "pointer chains are written flat, as (* * T), not nested" type))
(let ((inner (nested-structured-type type)))
(when inner
(if (eq? (car inner) 'fn)
;; (fn ...) is spelled as the pointer it already is in C
(sex-error type "a fn type is a function pointer already: write (fn ...), not (* (fn ...))" type)
(sex-error type "a pointer to an array is not supported" type))))
(if (atom? type) (atom-to-fmt-c type)
(flatten
(tree-map atom-to-fmt-c
@@ -346,38 +334,25 @@ forms, and what remains."
((¤ * const volatile struct union) #t)
(else #f)))
;;; Does the parameter name itself?
;;; (f1 float) does
;;; (float), (const char) and (¤ float 4) do not
(define (named-arg? arg)
(and (pair? arg)
(pair? (cdr arg)) ; 1 element args are always type
(not (eq? (car arg) '¤))
(not (is-probably-type arg))))
;;; The type of one parameter
(define (arg-type arg)
(if (named-arg? arg)
(walk-type (maybe-unwrap-type (cdr arg)))
;; A lone type may arrive wrapped in parens of its own, and those
;; are not part of it: ((* const char))
;; Plain names e.g. (int) are left as is
(walk-type (if (and (pair? arg) (null? (cdr arg)) (pair? (car arg)))
(car arg)
arg))))
(define (walk-arglist form)
;; E.g.:
;; ((float) (int) (const char) (* const char) (¤ (* const struct res) 32))
;; ((f1 float) (f2 float) (f3 float) (res (¤ float 4)))
(map (lambda (arg)
(if (named-arg? arg)
(list (arg-type arg) (walk-type (car arg)))
(arg-type arg)))
(remove comment-form? form)))
(map (fn
(match x
(('¤ . _) (walk-type x))
(define (walk-arg-types form)
(map arg-type (remove comment-form? form)))
;; yeah shitty, but I don't know yet how to determine if the
;; first entry is part of the type and not an argument name
;; :(
((? is-probably-type) (walk-type x))
;; 1 element args are always type
((_) (walk-type x))
((var . type) (append (list (walk-type (maybe-unwrap-type type)))
(list (walk-type var))))))
(remove comment-form? form)))
(define (walk-function form)
;; (fn ret-type name arglist body) -> normal function

View File

@@ -115,21 +115,6 @@
((eq? tok dot-token) (error "Unexpected ."))
(else tok))))
;;; A token that stands for a datum
(define (datum-token? tok)
(not (or (eof-object? tok)
(eq? tok close-paren)
(eq? tok close-bracket)
(eq? tok dot-token))))
;;; next-token, sans the comments
(define (next-code-token port)
(let loop ()
(let ((tok (next-token port)))
(if (comment-form? tok)
(loop)
tok))))
;;; Read list elements up to close-paren or close-bracket,
;;; honoring dotted-pair notation (a b . c)
(define (read-list port closer)
@@ -223,24 +208,13 @@
(else (error "Malformed feature expression" test))))
;;; The #-/#+ preceded datum is always read -- there is no other way
;;; to know where it ends -- and then either returned or dropped. What
;;; follows a dropped datum is read in its place: `#+x #+y (a) (b)'
;;; with only x is (b).
;;;
;;; That next thing may be nothing: the end of the file, or the
;;; paren closing the list we are in
;;; to know where it ends -- and then either returned or dropped
(define (read-conditional port keep-when)
(let* ((test (next-code-token port))
(keep (begin
(unless (datum-token? test)
(error "Unexpected end of input in feature expression"))
(eq? keep-when (feature-true? test))))
(guarded (next-code-token port)))
(cond
(keep guarded)
;; A datum was dropped, so the next one stands in for it
((datum-token? guarded) (next-token port))
(else guarded))))
(let ((keep (eq? keep-when (feature-true? (read-datum port)))))
(if keep
(read-datum port)
(begin (read-datum port)
(next-token port)))))
;;; #t / #true / #f / #false. `val' is the boolean; consume any
;;; trailing name characters and validate

160
semen.scm
View File

@@ -140,91 +140,43 @@
(cons (copy-form-source! form `(typedef ,target ,new-type)) acc))
;;; Fn processing
;;;
;;; A string as the first body form is a docstring. In the generated
;;; C code it will be placed as a C commentary just before the function
;;; definition (actually that works for all blocky things: enum, struct, union as well).
(define (fn-header-length fn-form)
(if (memq (first fn-form) '(pub extern)) 5 4))
(define (fn-core form)
;; The (fn name args rettype . body) list, without pub/extern
(if (memq (first form) '(pub extern))
(cdr form)
form))
(define (take-leading-docstring forms)
;; If FORMS starts with a string, possibly after comment forms, return
;; that string and FORMS without it. Otherwise #f and FORMS unchanged
(let loop ((fs forms) (prefix (list)))
(match fs
(() (values #f forms))
(((and cmt ('comment . _)) . rest)
(loop rest (cons cmt prefix)))
(((? string? doc) . rest)
(values doc (append (reverse prefix) rest)))
(_ (values #f forms)))))
(define (extract-fn-docstring fn-form)
(let ((lift
(lambda (proto body)
(let-values (((doc rest) (take-leading-docstring body)))
(if doc
(values doc (copy-form-source! fn-form (append proto rest)))
(values #f fn-form))))))
(match fn-form
(('pub 'fn name args ret . body)
(lift `(pub fn ,name ,args ,ret) body))
(('extern 'fn name args ret . body)
(lift `(extern fn ,name ,args ,ret) body))
(('fn name args ret . body)
(lift `(fn ,name ,args ,ret) body))
(_ (values #f fn-form)))))
(define (extract-aggregate-docstring form)
;; A string immediately after the name is the docstring; comments
;; between name and fields are not skipped, they already confuse the
;; writer
(match form
(('pub (and kind (or 'struct 'union 'enum))
(? symbol? name) (? string? doc) . rest)
(values doc (copy-form-source! form `(pub ,kind ,name ,@rest))))
(((and kind (or 'struct 'union 'enum))
(? symbol? name) (? string? doc) . rest)
(values doc (copy-form-source! form `(,kind ,name ,@rest))))
(_ (values #f form))))
(define (with-docstring doc form acc)
;; acc is newest-first; FORM is consed last so the final reverse
;; emits the comment immediately before the declaration
(cons form
(if doc
(cons (list 'comment doc) acc)
acc)))
(define (comment-form? f)
(and (pair? f) (eq? (car f) 'comment)))
(define (strip-fn-header-comments fn-form)
;; ([pub|extern] fn name arglist rettype). Comments in the body are
;; left in place as ordinary statements and preserved into the
;; generated C.
(strip-header-comments fn-form (fn-header-length fn-form)))
;; Remove comment forms from the function header
;; ([pub|extern] fn name arglist rettype) so the positional accessors
;; below are not shifted. Comments in the body are left in place as
;; ordinary statements and preserved into the generated C.
(let ((header-count (if (memq (car fn-form) '(pub extern)) 5 4)))
;; This always rebuilds the list, so the location has to be carried
;; over explicitly -- otherwise every function loses it
(copy-form-source!
fn-form
(let loop ((form fn-form) (kept 0) (acc (list)))
(cond
((null? form) (reverse acc))
((= kept header-count) (append (reverse acc) form))
((comment-form? (car form)) (loop (cdr form) kept acc))
(else (loop (cdr form) (+ kept 1) (cons (car form) acc))))))))
(define (process-fn sex-fn-raw acc)
(let-values (((doc sex-fn)
(extract-fn-docstring (strip-fn-header-comments sex-fn-raw))))
(let* ((expanded (macro-expand sex-fn))
(env (make-hash-table))
(processed
(walk-form
expanded
fn-walker
(begin
(set! (hash-table-ref env :fn-name) (sex-fn-name expanded))
(set! (hash-table-ref env :lambda-counter) 0)
(set! (hash-table-ref env :lambda-aux-code) (list))
env))))
(with-docstring doc processed
(append (hash-table-ref env :lambda-aux-code) acc)))))
(let* ((sex-fn (strip-fn-header-comments sex-fn-raw))
(expanded (macro-expand sex-fn))
(env (make-hash-table))
(processed
(walk-form
expanded
fn-walker
(begin
(set! (hash-table-ref env :fn-name) (sex-fn-name expanded))
(set! (hash-table-ref env :lambda-counter) 0)
(set! (hash-table-ref env :lambda-aux-code) (list))
env))))
(cons processed
(append (hash-table-ref env :lambda-aux-code) acc))))
(define (fn-walker form env)
(if (eq? 'lambda (car form))
@@ -255,9 +207,8 @@
;;; Record the named structs, unions and enums in the type database
(define (process-struct sex-struct acc)
(let-values (((doc form) (extract-aggregate-docstring sex-struct)))
(register-aggregate! form)
(with-docstring doc form acc)))
(register-aggregate! sex-struct)
(cons sex-struct acc))
(define (register-aggregate! form)
(let* ((f (if (eq? (car form) 'pub) (cdr form) form))
@@ -282,36 +233,45 @@
"The `form` must be toplevel.
Returns #f if the form is not a function, returns the form otherwise"
(match form
((or ('fn . _)
('pub 'fn . _)
('extern 'fn . _)) form)
((fn . _) form)
((pub fn . _) form)
(else #f)))
(define (sex-fn-public? fn-form)
(eq? (first fn-form) 'pub))
(define (sex-fn-name fn-form)
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(second (fn-core fn-form)))
(define (sex-fn-arglist fn-form)
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(third (fn-core fn-form)))
(eq? (car fn-form) 'pub))
(define (sex-fn-return-type fn-form)
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(fourth (fn-core fn-form)))
(if (sex-fn-public? fn-form)
(third fn-form)
(second fn-form)))
(define (sex-fn-name fn-form)
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(if (sex-fn-public? fn-form)
(fourth fn-form)
(third fn-form)))
(define (sex-fn-arglist fn-form)
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(if (sex-fn-public? fn-form)
(fifth fn-form)
(fourth fn-form)))
(define (sex-fn-prototype fn-form)
"Returns all except body"
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(take fn-form (fn-header-length fn-form)))
(if (sex-fn-public? fn-form)
(take fn-form 5)
(take fn-form 4)))
(define (sex-fn-body fn-form)
(assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function"))
(drop fn-form (fn-header-length fn-form)))
(if (sex-fn-public? fn-form)
(drop fn-form 5)
(drop fn-form 4)))

View File

@@ -737,13 +737,10 @@
(cat (c-type (cadr type) #f)
" (*" (or name "") ")("
(fmt-join (lambda (x) (c-type x #f)) (caddr type) ", ") ")"))
;; MODIFIED FROM UPSTREAM fmt-c: a nameless declarator -- an
;; array parameter of a function type, where C has no room
;; for a name -- arrives as #f, and upstream printed it
((%array)
(let ((name (cat (or name "") "[" (if (pair? (cddr type))
(c-expr (caddr type))
"")
(let ((name (cat name "[" (if (pair? (cddr type))
(c-expr (caddr type))
"")
"]")))
(c-type (cadr type) name)))
((%pointer *)
@@ -776,13 +773,10 @@
(cat (c-type (cadr type) #f)
" (*" (or name "") ")("
(fmt-join (lambda (x) (c-type x #f)) (caddr type) ", ") ")"))
;; MODIFIED FROM UPSTREAM fmt-c: a nameless declarator -- an
;; array parameter of a function type, where C has no room
;; for a name -- arrives as #f, and upstream printed it
((%array)
(let ((name (cat (or name "") "[" (if (pair? (cddr type))
(c-expr (caddr type))
"")
(let ((name (cat name "[" (if (pair? (cddr type))
(c-expr (caddr type))
"")
"]")))
(c-type (cadr type) name)))
((%pointer *)

View File

@@ -26,8 +26,8 @@
(import scheme
(scheme base)
(only sex-macros cat comment)
(only types get-type-info get-tag-info get-fields
get-underlying-type type-match map-fields))
(only types get-type-info get-fields get-underlying-type
type-match map-fields))
,@body)))
(define (get-macro name)

View File

@@ -8,7 +8,6 @@
(chicken process-context)
(chicken string)
fmt
matchable
reader
srfi-1
utils)
@@ -72,35 +71,22 @@
(list)
raw-forms)))
(define (public-fn-interface raw-form)
;; (pub fn name args ret . body) -> prototype, keeping a docstring so
;; the importer can emit it above the declaration
(let ((form (strip-header-comments raw-form 5)))
(match form
(('pub 'fn name args ret)
form)
(('pub 'fn name args ret ('comment . _) . rest)
(public-fn-interface `(pub fn ,name ,args ,ret ,@rest)))
(('pub 'fn name args ret (? string? doc) . _)
`(pub fn ,name ,args ,ret ,doc))
(('pub 'fn name args ret . _)
`(pub fn ,name ,args ,ret)))))
;;; TODO: use semen facilities to analyze modules
(define (process-public-interface-form form acc)
(match form
;; Reduced to a prototype, still `pub', so the importer declares it
;; with external linkage
(('pub 'fn . _)
(cons (copy-form-source! form (public-fn-interface form)) acc))
(('pub 'var . _)
(match-let ((('pub 'var name type . _) (strip-header-comments form 4)))
(cons (copy-form-source! form `(extern var ,name ,type)) acc)))
(('pub (or 'define 'defmacro 'enum 'import 'include 'struct 'typedef 'union) . _)
(cons (copy-form-source! form (cdr form)) acc))
(('pub . _)
(sex-error form "pub must be followed by a definition" form))
(_ acc)))
(case (car form)
((pub)
(case (cadr form)
;; A function is reduced to a prototype and keeps its `pub', so
;; the importing unit declares it with external linkage
((fn)
(cons (copy-form-source! form (take form 5)) acc))
;; A variable becomes an `extern' declaration
((var)
(cons (copy-form-source! form (cons 'extern (take (cdr form) 3))) acc))
((define defmacro enum import include struct typedef union)
(cons (copy-form-source! form (cdr form)) acc))
(else (sex-error form "pub must be followed by a definition" form))))
(else acc)))
(define (load-persistent-module-paths)
(let ((sex-module-path-env-var

View File

@@ -230,33 +230,4 @@ compiles."
(test-assert "grouping sublist still accepted"
(emits? (in-fn "(var s (* (const struct suc)) 0)") "const struct suc * s"))
(test-assert "flat chain still accepted"
(emits? (in-fn "(var q (* * const char) 0)") "const char * * q")))
;; A string as the first body form (or after the name of a struct,
;; union or enum) is a docstring: it becomes a comment immediately
;; before the declaration, not a statement inside it.
(test-group "docstrings"
(test-assert "appears before the function"
(emits? "(fn greet ((name (* char))) void \"Greet NAME.\" (printf \"hi\" name))"
"/* Greet NAME. */"))
(test-assert "and not inside the body as a statement"
(not (emits? "(fn greet ((name (* char))) void \"Greet NAME.\" (printf \"hi\" name))"
"\"Greet NAME.\"")))
(test-assert "multiline keeps its paragraphs"
(emits? "(pub fn main () int \"Entry point.\n\nARGC and ARGV.\" (return 0))"
"Entry point."))
(test-assert "and the second paragraph too"
(emits? "(pub fn main () int \"Entry point.\n\nARGC and ARGV.\" (return 0))"
"ARGC and ARGV."))
(test-assert "a prototype with only a docstring stays a prototype"
(emits? "(fn helper ((a int)) int \"Forward.\")"
"helper (int a);"))
(test-assert "a string after the first statement is left alone"
(emits? "(fn f () void (g) \"not a docstring\")"
"\"not a docstring\""))
(test-assert "a struct docstring sits above the struct"
(emits? "(struct point \"A 2D point.\" ((x int) (y int)))"
"/* A 2D point. */"))
(test-assert "and an enum docstring too"
(emits? "(enum color \"RGB.\" (red green blue))"
"/* RGB. */"))))
(emits? (in-fn "(var q (* * const char) 0)") "const char * * q"))))

View File

@@ -11,9 +11,6 @@
# It also checks what only a second translation unit can check: that an
# imported type reaches the type database, by expanding a macro that
# reads the imported struct's fields.
#
# The public forms carry comments in their headers, which the reduction
# to a prototype and to an extern both have to look past.
SEXC ?= ../../sexc

View File

@@ -7,15 +7,9 @@
(include stdio.h)
(pub var greet-count ;; a comment in the header of a public form is
;; not part of it: what the importer is given has
;; to be `extern int greet-count', not a form
;; counted off by one
int 0)
(pub var greet-count int 0)
(pub struct greeting
"A greeting to print."
((text (* const char)) (times int)))
(pub struct greeting ((text (* const char)) (times int)))
(pub enum mood (cheerful grumpy))
@@ -29,9 +23,7 @@
(lambda (name field-type)
`(printf "%s " ,(symbol->string name))))))
(pub fn greet ;; ...and here the prototype would lose its return type
((name (* const char))) void
"Print a greeting for NAME."
(pub fn greet ((name (* const char))) void
(++ greet-count)
(printf "hello, %s\n" name))

View File

@@ -80,22 +80,6 @@
;; guards nest
(feature-test '((a)) (x y) "#+x #+y (a)")
(feature-test '((b)) (x) "#+x #+y (a) (b)")
;; ...and the inner one may leave nothing behind: the end of the
;; file, or the paren closing the list, is what the outer one then
;; produces, and neither is an error
(feature-test '() (x) "#+x #+y (a)")
(feature-test '((f)) (x) "(f #+x #+y 1)")
(feature-test '((f 2)) (x) "(f #+x #+y 1 2)")
(feature-test '((a)) (x) "(a) #+x #+y (b)")
;; a comment between a guard and the form it guards describes the
;; guard. Taking it for the guarded datum would leave the form itself
;; unconditional
(feature-test '((a)) (x) "#+x ;; why\n (a)")
(feature-test '() (y) "#+x ;; why\n (a)")
(feature-test '((b)) (y) "#+x ;; why\n (a) (b)")
;; a comment after the guarded form is an ordinary form, and stays
(feature-test '((comment "; tail")) (y) "#+x (a) ;; tail")
;; a feature the program was not given is simply absent
(feature-test '() () "#+anything (a)")

View File

@@ -1,35 +1,30 @@
(import srfi-69
semen
types)
semen)
(define print-str-fn
'(fn print-str ((s string)) void
'(fn void print-str ((string s))
(printf "%s" s)))
(define sum-fn
'(pub fn sum ((a int) (b int)) float
(return (cast (+ a b) float))))
'(pub fn float sum ((int a) (int b))
(return (cast float (+ a b)))))
(test-group "semen"
(test-assert (sex-fn? print-str-fn))
(test #f (sex-fn-public? print-str-fn))
(test 'void (sex-fn-return-type print-str-fn))
(test 'print-str (sex-fn-name print-str-fn))
(test '((s string)) (sex-fn-arglist print-str-fn))
(test '(fn print-str ((s string)) void) (sex-fn-prototype print-str-fn))
(test '((string s)) (sex-fn-arglist print-str-fn))
(test '(fn void print-str ((string s))) (sex-fn-prototype print-str-fn))
(test '((printf "%s" s)) (sex-fn-body print-str-fn))
(test-assert (sex-fn? sum-fn))
(test #t (sex-fn-public? sum-fn))
(test 'float (sex-fn-return-type sum-fn))
(test 'sum (sex-fn-name sum-fn))
(test '((a int) (b int)) (sex-fn-arglist sum-fn))
(test '(pub fn sum ((a int) (b int)) float) (sex-fn-prototype sum-fn))
(test '((return (cast (+ a b) float))) (sex-fn-body sum-fn))
(test-assert (sex-fn? '(extern fn foo () void)))
(test 'foo (sex-fn-name '(extern fn foo () void)))
(test #f (sex-fn? '(struct point ((x int)))))
(test '((int a) (int b)) (sex-fn-arglist sum-fn))
(test '(pub fn float sum ((int a) (int b))) (sex-fn-prototype sum-fn))
(test '((return (cast float (+ a b)))) (sex-fn-body sum-fn))
(let ((sex-code
'((defmacro (sum-var name a b c)
@@ -54,65 +49,9 @@
'((defmacro (x10 a)
`(* 10 ,a))
(fn foo ((a int) (b int)) void
(fn void foo ((int a) (int b))
(return (+ a (x10 b)))))))
(test '((fn foo ((a int) (b int)) void
(test '((fn void foo ((int a) (int b))
(return (+ a (* 10 b)))))
(semen-process sex-code-macro)))
;;; Docstrings are lifted out as comment forms sitting before the
;;; declaration. A string later in a body is left alone.
(test '((comment "Greet NAME.")
(fn greet ((name (* char))) void
(printf "Hello %s!\n" name)))
(semen-process
'((fn greet ((name (* char))) void
"Greet NAME."
(printf "Hello %s!\n" name)))))
(test '((comment "Public entry.")
(pub fn main () int
(return 0)))
(semen-process
'((pub fn main () int
"Public entry."
(return 0)))))
;; A prototype whose only "body" is a docstring stays a prototype
(test '((comment "Forward.")
(fn helper ((a int)) int))
(semen-process
'((fn helper ((a int)) int
"Forward."))))
(test '((fn f () void (g) "not a docstring"))
(semen-process
'((fn f () void (g) "not a docstring"))))
;; `;' comments before the string are skipped when looking for it,
;; and stay in the body
(test '((comment "Kept.")
(fn f () void (comment " note") (g)))
(semen-process
'((fn f () void (comment " note") "Kept." (g)))))
(test '((comment "A 2D point.")
(struct t-doc-pt ((x int) (y int))))
(semen-process
'((struct t-doc-pt "A 2D point." ((x int) (y int))))))
(test '((x int) (y int))
(get-fields 't-doc-pt))
(test '((comment "RGB.")
(enum t-doc-color (red green blue)))
(semen-process
'((enum t-doc-color "RGB." (red green blue)))))
(test '((comment "Either.")
(union t-doc-val ((i int) (f float))))
(semen-process
'((union t-doc-val "Either." ((i int) (f float))))))
)
(semen-process sex-code-macro))))

View File

@@ -1,25 +0,0 @@
(compilation "-f alpha -f beta,gamma --features=delta --no-platform-features")
(input)
(output "alpha" "beta" "gamma" "delta" "elsewhere")
(return 0)
;;; The flags naming the features, in every spelling sexc takes. This
;;; is not pedantry about the command line: sextest reads the program
;;; itself and prints the surviving forms to sexc, so a spelling it
;;; does not recognise leaves the guards below resolved against the
;;; wrong set -- quietly, since the test then checks the output of a
;;; program it did not mean to compile.
;;;
;;; --no-platform-features is what makes `#-unix' true wherever this is
;;; compiled, and it has to be honoured on both sides for that to hold.
(include stdio.h)
(pub fn main () int
#+alpha (puts "alpha")
#+beta (puts "beta")
#+gamma (puts "gamma")
#+delta (puts "delta")
#-unix (puts "elsewhere")
#+unix (puts "here")
(return 0))

View File

@@ -9,7 +9,6 @@
map-fields
get-type-info
get-tag-info
get-fields
get-underlying-type)
"../types.scm")

View File

@@ -79,38 +79,6 @@
#f
(get-fields 't-u8))
;; C keeps typedefs and ordinary identifiers apart, and the canonical way
;; to declare a struct uses both names at once. Neither declaration
;; may stand on the other.
(add-struct 't-node '(struct t-node ((next (* t-node)) (v int))))
(add-typedef 't-node '(typedef t-node (struct t-node)))
(test "a typedef of a struct's own name keeps the struct reachable"
'((next (* t-node)) (v int))
(get-fields 't-node))
(test "and the tag is there under its own name"
'(struct t-node ((next (* t-node)) (v int)))
(get-tag-info 't-node))
(add-struct 't-rect '(struct t-rect ((w int) (h int))))
(add-define 't-rect '(define t-rect 3))
(test "a define of a tag's name does not hide the fields"
'((w int) (h int))
(get-fields 't-rect))
;; A type built over a struct is not that struct. Handing back the
;; element's fields would have a macro write v->x for an array
(add-typedef 't-points '(typedef t-points (¤ t-point 4)))
(test "an array of a struct has no fields of its own"
#f
(get-fields 't-points))
(add-typedef 't-point-p '(typedef t-point-p (* t-point)))
(test "nor does a pointer to one"
#f
(get-fields 't-point-p))
(add-typedef 't-cb '(typedef t-cb (fn ((t-point)) void)))
(test "nor a function type over one"
#f
(get-fields 't-cb))
;; A typedef can be written to lead back to itself. Resolving it must
;; stop rather than spin
(add-typedef 't-loop-a '(typedef t-loop-a t-loop-b))

View File

@@ -1,5 +1,4 @@
(import scheme
(scheme base) ; let-values
brev-separate
(chicken base)
(chicken file)
@@ -34,45 +33,27 @@
(cons (list) (list))
contents))
;;; The feature flags of the (compilation ...) form, which we have to
;;; honour ourselves: the program is read here and printed back out for
;;; sexc, so #+ and #- are resolved on this side.
;;;
;;; Returns the named features and whether the host's own are in play
;;; --features from the (compilation ...) form, which we have to honour
;;; ourselves: the program is read here and printed back out for sexc,
;;; so #+ and #- are resolved on this side
(define (compilation-features settings)
(let ((compilation (assoc 'compilation settings)))
(let loop ((flags (if compilation
(string-split (cadr compilation))
(if compilation
(append-map (lambda (flag)
(if (string-prefix? "--features=" flag)
(map string->symbol
(string-split (substring flag 11) ","))
(list)))
(features (list))
(platform #t))
(define (add names rest)
(loop rest
(append features (map string->symbol (string-split names ",")))
platform))
(cond
((null? flags) (values features platform))
;; past `--' the flags are the C compiler's
((string=? (car flags) "--") (values features platform))
((string=? (car flags) "--no-platform-features")
(loop (cdr flags) features #f))
((and (member (car flags) '("-f" "--features")) (pair? (cdr flags)))
(add (cadr flags) (cddr flags)))
((string-prefix? "--features=" (car flags))
(add (substring (car flags) 11) (cdr flags)))
((string-prefix? "-f" (car flags))
(add (substring (car flags) 2) (cdr flags)))
(else (loop (cdr flags) features platform))))))
(string-split (cadr compilation)))
(list))))
(define (process-file target-path)
(let ((first-pass (split-settings (read-raw-forms target-path))))
(let-values (((features platform?) (compilation-features (car first-pass))))
(if (and (null? features) platform?)
first-pass
(parameterize ((current-features
(append (if platform? (platform-features) (list))
features)))
(split-settings (read-raw-forms target-path)))))))
(let* ((first-pass (split-settings (read-raw-forms target-path)))
(features (compilation-features (car first-pass))))
(if (null? features)
first-pass
(parameterize ((current-features (append (platform-features) features)))
(split-settings (read-raw-forms target-path))))))
(define (compile src compilation sexc)
(let ((compiler (or

View File

@@ -2,8 +2,6 @@
(get-env-var
set-working-directory
to-absolute-pathname
comment-form?
strip-header-comments
list-split
list-join
recons

View File

@@ -9,7 +9,6 @@
map-fields
get-type-info
get-tag-info
get-fields
get-underlying-type)
"types.scm")

View File

@@ -15,11 +15,7 @@
srfi-1
srfi-69)
;;; Two namespaces: `struct point' and a `point' typedef are separate
;;; declarations. Tags -- struct, union and enum alike -- share the
;;; second table between them
(define +type-db+ (make-hash-table)) ; typedefs and defines
(define +tag-db+ (make-hash-table)) ; struct, union and enums
(define +type-db+ (make-hash-table))
(define (strip-pub form)
(if (eq? (car form) 'pub) (cdr form) form))
@@ -48,17 +44,17 @@
(list))))
(define (add-struct name form)
(hash-table-set! +tag-db+ name
(hash-table-set! +type-db+ name
(list 'struct name (normalize-fields (aggregate-fields form)))))
(define (add-union name form)
(hash-table-set! +tag-db+ name
(hash-table-set! +type-db+ name
(list 'union name (normalize-fields (aggregate-fields form)))))
;;; ([pub] enum name (value ...))
(define (add-enum name form)
(let ((f (strip-pub form)))
(hash-table-set! +tag-db+ name
(hash-table-set! +type-db+ name
(list 'enum name (if (and (pair? (cddr f)) (list? (caddr f)))
(caddr f)
(list))))))
@@ -74,14 +70,8 @@
(hash-table-set! +type-db+ name
(list 'define name (cddr (strip-pub form)))))
(define (get-tag-info name)
(hash-table-ref/default +tag-db+ name #f))
;;; First look up the ordinary identifier, then tag of that id when
;;; no ordinary one was declared, as C does
(define (get-type-info name)
(or (hash-table-ref/default +type-db+ name #f)
(get-tag-info name)))
(hash-table-ref/default +type-db+ name #f))
;;; ((name type) ...) for a struct or union, #f for anything else --
;;; including a name that was never declared. Callers give the better
@@ -110,31 +100,19 @@
;;; The declaration NAME ultimately names. For a typedef that is the
;;; entry of whatever it stands for, and for anything else it is
;;; NAME's own info
(define (target-tag target)
(cond
((symbol? target) target)
((and (pair? target)
(memq (car target) '(struct union enum))
(pair? (cdr target))
(symbol? (cadr target)))
(cadr target))
(else #f)))
;;; NAME's own info.
(define (resolve-type-info name)
(let ((info (resolve-ordinary-type-info name)))
(if (and info (memq (car info) '(struct union enum)))
info
(or (get-tag-info name) info))))
(define (resolve-ordinary-type-info name)
(let ((info (get-type-info name)))
(and info
(if (eq? (car info) 'typedef)
(let ((tag (target-tag (get-underlying-type name))))
;; A typedef target is written in type position, where
;; `struct point' means the tag
(and tag (or (get-tag-info tag) (get-type-info tag))))
(let* ((target (get-underlying-type name))
(tag (cond ((symbol? target) target)
((and (pair? target)
(pair? (cdr target))
(symbol? (cadr target)))
(cadr target))
(else #f))))
(and tag (get-type-info tag)))
info))))
;;; Type matcher macro

View File

@@ -2,8 +2,6 @@
(get-env-var
set-working-directory
to-absolute-pathname
comment-form?
strip-header-comments
list-split
list-join
recons

View File

@@ -42,24 +42,6 @@
(current-directory)
pathname)))
(define (comment-form? form)
(and (pair? form) (eq? (car form) 'comment)))
;;; Remove the comment forms from the first COUNT elements of FORM --
;;; its header -- so that the positional accessors reading it are not
;;; shifted by one
(define (strip-header-comments form count)
;; This always rebuilds the list, so the location has to be carried
;; over explicitly -- otherwise every form loses it
(copy-form-source!
form
(let loop ((rest form) (kept 0) (acc (list)))
(cond
((null? rest) (reverse acc))
((= kept count) (append (reverse acc) rest))
((comment-form? (car rest)) (loop (cdr rest) kept acc))
(else (loop (cdr rest) (+ kept 1) (cons (car rest) acc)))))))
(define (list-split src-list split-elt)
;; split '(1 2 / 3 4 / 5 6) by '/ -> '((1 2) (3 4) (5 6))
(fold (lambda (elt acc)