1 Commits

Author SHA1 Message Date
Pavel Kulyov
78862cfe4f TMP
All checks were successful
Sex CI / build-linux (pull_request) Successful in 4m45s
2026-09-18 18:42:52 +03:00
22 changed files with 172 additions and 396 deletions

View File

@@ -57,9 +57,7 @@ jobs:
hash -r hash -r
csc -version csc -version
- name: Install dependencies # Eggs are pinned in eggs.lock and installed by the Makefile into .eggs/.
run: make deps
- name: Build sexc - name: Build sexc
run: make && ./sexc --help run: make && ./sexc --help

View File

@@ -34,27 +34,24 @@ DEPSLOCK = eggs.lock
EGGS_DIR := $(abspath .eggs) EGGS_DIR := $(abspath .eggs)
# Compiler's own repository (types.db, chicken.*), ignoring ambient venv vars. # Compiler's own repository (types.db, chicken.*), ignoring ambient venv vars.
SYSTEM_CHICKEN_REPO := $(shell env \ SYSTEM_CHICKEN_REPO := $(shell env -u CHICKEN_INSTALL_REPOSITORY -u CHICKEN_REPOSITORY_PATH -u CHICKEN_EGG_CACHE -u CHICKEN_INSTALL_PREFIX $(CHICKEN_INSTALL) -repository 2>/dev/null)
-u CHICKEN_INSTALL_REPOSITORY -u CHICKEN_REPOSITORY_PATH \
-u CHICKEN_EGG_CACHE -u CHICKEN_INSTALL_PREFIX \
$(CHICKEN_INSTALL) -repository 2>/dev/null)
CHICKEN_ABI := $(notdir $(SYSTEM_CHICKEN_REPO)) CHICKEN_ABI := $(notdir $(SYSTEM_CHICKEN_REPO))
EGGS_STAMP := $(EGGS_DIR)/.installed-$(CHICKEN_ABI) EGGS_STAMP := $(EGGS_DIR)/.installed-$(CHICKEN_ABI)
# Try project-local chicken repository first. In case it doesn't exist (packaging for distros), export CHICKEN_EGG_CACHE := $(EGGS_DIR)/cache
# system-wide repository will be used. export CHICKEN_INSTALL_REPOSITORY := $(EGGS_DIR)
unexport CHICKEN_INSTALL_REPOSITORY export CHICKEN_INSTALL_PREFIX := $(EGGS_DIR)
unexport CHICKEN_EGG_CACHE
unexport CHICKEN_INSTALL_PREFIX
export CHICKEN_REPOSITORY_PATH := $(EGGS_DIR):$(SYSTEM_CHICKEN_REPO) export CHICKEN_REPOSITORY_PATH := $(EGGS_DIR):$(SYSTEM_CHICKEN_REPO)
all: sexc all: sexc
sexc: $(OBJ) main.scm sexc: $(EGGS_STAMP) $(OBJ) main.scm
$(CHICKEN_C) $(CSC_FLAGS) main.scm -o sexc-tmp -link sexc $(CHICKEN_C) $(CSC_FLAGS) main.scm -o sexc-tmp -link sexc
# otherwise csc hangs, probably because it tries to compile to sexc.o first # otherwise csc hangs, probably because it tries to compile to sexc.o first
mv sexc-tmp sexc mv sexc-tmp sexc
$(OBJ): $(EGGS_STAMP)
#------------------------------------------------------------------ #------------------------------------------------------------------
utils.o: utils.module.scm utils.scm utils.o: utils.module.scm utils.scm
@@ -85,16 +82,15 @@ sexc.o: sexc.module.scm types.o sexc.scm fmt-c-writer.o sex-macros.o sex-modules
$(CHICKEN_C) $(CSC_FLAGS) $(MODULE_FLAGS) sexc.module.scm -o sexc.o -unit sexc -link fmt-c-writer,sex-macros,sex-modules,reader,semen,types,utils $(CHICKEN_C) $(CSC_FLAGS) $(MODULE_FLAGS) sexc.module.scm -o sexc.o -unit sexc -link fmt-c-writer,sex-macros,sex-modules,reader,semen,types,utils
# Unit testing # Unit testing
sex-tests: sex-tests: $(EGGS_STAMP)
$(MAKE) -C ./tests sex-tests $(MAKE) -C ./tests sex-tests
cp ./tests/sex-tests ./ cp ./tests/sex-tests ./
sextest: sextest: $(EGGS_STAMP)
$(MAKE) -C ./tools/sextest sextest $(MAKE) -C ./tools/sextest 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
# 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
@@ -119,11 +115,9 @@ installdirs:
uninstall: uninstall:
rm -f $(DESTDIR)$(bindir)/sexc rm -f $(DESTDIR)$(bindir)/sexc
$(EGGS_STAMP) deps-update: export CHICKEN_EGG_CACHE := $(EGGS_DIR)/cache # eggs.lock is the pin file (chicken-status -list). Install from it;
$(EGGS_STAMP) deps-update: export CHICKEN_INSTALL_REPOSITORY := $(EGGS_DIR) # do not float versions on a normal build. Regenerating the lock:
$(EGGS_STAMP) deps-update: export CHICKEN_INSTALL_PREFIX := $(EGGS_DIR) # make deps-update
$(EGGS_STAMP) deps-update: export CHICKEN_REPOSITORY_PATH := $(EGGS_DIR):$(SYSTEM_CHICKEN_REPO)
$(EGGS_STAMP): $(DEPSLOCK) $(EGGS_STAMP): $(DEPSLOCK)
mkdir -p $(CHICKEN_EGG_CACHE) mkdir -p $(CHICKEN_EGG_CACHE)
$(CHICKEN_INSTALL) -force -from-list $(DEPSLOCK) $(CHICKEN_INSTALL) -force -from-list $(DEPSLOCK)
@@ -131,10 +125,10 @@ $(EGGS_STAMP): $(DEPSLOCK)
deps: $(EGGS_STAMP) deps: $(EGGS_STAMP)
deps-update: $(DEPSFILE) deps-clean deps-update: $(DEPSFILE)
rm -rf $(EGGS_DIR)
mkdir -p $(CHICKEN_EGG_CACHE) mkdir -p $(CHICKEN_EGG_CACHE)
$(CHICKEN_INSTALL) -force $$(cat $(DEPSFILE)) $(CHICKEN_INSTALL) -force $$(cat $(DEPSFILE))
# Local repo only: a system path here would leak distro eggs into the lock.
CHICKEN_REPOSITORY_PATH=$(EGGS_DIR) $(CHICKEN_STATUS) -list | sort > $(DEPSLOCK).tmp CHICKEN_REPOSITORY_PATH=$(EGGS_DIR) $(CHICKEN_STATUS) -list | sort > $(DEPSLOCK).tmp
mv $(DEPSLOCK).tmp $(DEPSLOCK) mv $(DEPSLOCK).tmp $(DEPSLOCK)
touch $(EGGS_STAMP) touch $(EGGS_STAMP)

View File

@@ -12,25 +12,22 @@ Sex is statically typed, compiled general purpose language.
First, get yourself a Chicken, then, some Chicken deps. You also will First, get yourself a Chicken, then, some Chicken deps. You also will
need a C compiler. need a C compiler.
** Development Eggs are installed into a project-local ~.eggs/~ repository; they do not
touch the Chicken system repository.
** Compilation
#+begin_src sh #+begin_src sh
make deps
make make
#+end_src #+end_src
~make deps~ installs pinned eggs from ~eggs.lock~ into a project-local That installs pinned eggs from ~eggs.lock~ into ~.eggs/~ if needed, then
~.eggs/~ repository. ~dependencies.txt~ is the unpinned request list. builds ~sexc~. ~dependencies.txt~ is the unpinned request list. To
To refresh ~eggs.lock~ after changing it: refresh ~eggs.lock~ after changing it:
#+begin_src sh #+begin_src sh
make deps-update make deps-update
#+end_src #+end_src
** Packaging for system package managers
- Depend ~sex~ package on Chicken-6 (with ~libchicken.a~) and all eggs from ~dependencies.txt~.
- Compile and install with ~make~ (usually no other arguments required).
- Move resulting ~sexc~ binary to the appropriate place.
** Static compilation ** Static compilation
The default ~CSC_FLAGS~ include ~-static~, so ~sexc~ does not need The default ~CSC_FLAGS~ include ~-static~, so ~sexc~ does not need
~.eggs~ at runtime and ~make install~ stays relocatable. Chicken must ~.eggs~ at runtime and ~make install~ stays relocatable. Chicken must

View File

@@ -135,6 +135,9 @@ forms, and what remains."
(car type) (car type)
type)) type))
(define (comment-form? f)
(and (pair? f) (eq? (car f) 'comment)))
;;; Strip ;s so ;;; Foo becomes /* Foo */ and not /* ;; Foo */ ;;; Strip ;s so ;;; Foo becomes /* Foo */ and not /* ;; Foo */
(define (strip-comment-marker text) (define (strip-comment-marker text)
(string-trim-both (string-trim text #\;))) (string-trim-both (string-trim text #\;)))
@@ -276,7 +279,7 @@ forms, and what remains."
;; it's a strong semantic cue ;; it's a strong semantic cue
`(%array ,(walk-type (maybe-unwrap-type array-type))))) `(%array ,(walk-type (maybe-unwrap-type array-type)))))
(('fn arglist ret-type) (('fn arglist ret-type)
`(%fun ,(walk-type ret-type) ,(walk-arg-types arglist))) `(%fun ,(walk-type ret-type) ,(walk-arglist arglist)))
(('fn . _) (('fn . _)
(sex-error form "malformed function type" form)) (sex-error form "malformed function type" form))
@@ -288,13 +291,8 @@ forms, and what remains."
(else (else
(type-convert-to-c form)))) (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) (define (has-pointer-star? form)
(and (pair? form) (and (pair? form)
(not (structured-type? form))
(or (memq '* form) (or (memq '* form)
(any has-pointer-star? (filter pair? form))))) (any has-pointer-star? (filter pair? form)))))
@@ -306,10 +304,6 @@ forms, and what remains."
(and (pair? type) (and (pair? type)
(any has-pointer-star? (filter 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) (define (type-convert-to-c type)
;; Our pointers to C pointers ;; Our pointers to C pointers
;; int -> int ;; int -> int
@@ -317,12 +311,6 @@ forms, and what remains."
;; const * const char -> const char * const ;; const * const char -> const char * const
(when (nested-pointer? type) (when (nested-pointer? type)
(sex-error type "pointer chains are written flat, as (* * T), not nested" 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) (if (atom? type) (atom-to-fmt-c type)
(flatten (flatten
(tree-map atom-to-fmt-c (tree-map atom-to-fmt-c
@@ -346,38 +334,25 @@ forms, and what remains."
((¤ * const volatile struct union) #t) ((¤ * const volatile struct union) #t)
(else #f))) (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) (define (walk-arglist form)
;; E.g.: ;; E.g.:
;; ((float) (int) (const char) (* const char) (¤ (* const struct res) 32)) ;; ((float) (int) (const char) (* const char) (¤ (* const struct res) 32))
;; ((f1 float) (f2 float) (f3 float) (res (¤ float 4))) ;; ((f1 float) (f2 float) (f3 float) (res (¤ float 4)))
(map (lambda (arg) (map (fn
(if (named-arg? arg) (match x
(list (arg-type arg) (walk-type (car arg))) (('¤ . _) (walk-type x))
(arg-type arg)))
(remove comment-form? form)))
(define (walk-arg-types form) ;; yeah shitty, but I don't know yet how to determine if the
(map arg-type (remove comment-form? form))) ;; 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) (define (walk-function form)
;; (fn ret-type name arglist body) -> normal function ;; (fn ret-type name arglist body) -> normal function

View File

@@ -115,21 +115,6 @@
((eq? tok dot-token) (error "Unexpected .")) ((eq? tok dot-token) (error "Unexpected ."))
(else tok)))) (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, ;;; Read list elements up to close-paren or close-bracket,
;;; honoring dotted-pair notation (a b . c) ;;; honoring dotted-pair notation (a b . c)
(define (read-list port closer) (define (read-list port closer)
@@ -223,24 +208,13 @@
(else (error "Malformed feature expression" test)))) (else (error "Malformed feature expression" test))))
;;; The #-/#+ preceded datum is always read -- there is no other way ;;; The #-/#+ preceded datum is always read -- there is no other way
;;; to know where it ends -- and then either returned or dropped. What ;;; to know where it ends -- and then either returned or dropped
;;; 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
(define (read-conditional port keep-when) (define (read-conditional port keep-when)
(let* ((test (next-code-token port)) (let ((keep (eq? keep-when (feature-true? (read-datum port)))))
(keep (begin (if keep
(unless (datum-token? test) (read-datum port)
(error "Unexpected end of input in feature expression")) (begin (read-datum port)
(eq? keep-when (feature-true? test)))) (next-token port)))))
(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))))
;;; #t / #true / #f / #false. `val' is the boolean; consume any ;;; #t / #true / #f / #false. `val' is the boolean; consume any
;;; trailing name characters and validate ;;; trailing name characters and validate

113
semen.scm
View File

@@ -72,7 +72,7 @@
((or ('var . _) ((or ('var . _)
('pub 'var . _) ('pub 'var . _)
('extern 'var . _)) (process-global-var sex-form acc)) ('extern 'var . _)) (process-global-var sex-form acc))
(('include . includes) (process-includes sex-form includes acc)) (('include _) (cons sex-form acc))
((or ('define name . _) ((or ('define name . _)
('pub 'define name . _)) ('pub 'define name . _))
(add-define name sex-form) (add-define name sex-form)
@@ -92,17 +92,6 @@
(else (sex-error sex-form "unknown top level form" sex-form)))) (else (sex-error sex-form "unknown top level form" sex-form))))
(define (process-includes sex-form includes acc)
;; consume (include ...) form and add to acc
;; (include <inc>) for each include
(let process ((includes includes)
(acc acc))
(if (null? includes)
acc
(process (cdr includes)
(cons (copy-form-source! sex-form `(include ,(car includes)))
acc)))))
(define (process-imports module-public-forms acc) (define (process-imports module-public-forms acc)
;; consume (import ...) form and process imports so ;; consume (import ...) form and process imports so
;; data types end up in types db ;; data types end up in types db
@@ -156,12 +145,15 @@
;;; C code it will be placed as a C commentary just before the function ;;; 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). ;;; definition (actually that works for all blocky things: enum, struct, union as well).
(define (comment-form? f)
(and (pair? f) (eq? (car f) 'comment)))
(define (fn-header-length fn-form) (define (fn-header-length fn-form)
(if (memq (first fn-form) '(pub extern)) 5 4)) (if (memq (car fn-form) '(pub extern)) 5 4))
(define (fn-core form) (define (fn-core form)
;; The (fn name args rettype . body) list, without pub/extern ;; The (fn name args rettype . body) list, without pub/extern
(if (memq (first form) '(pub extern)) (if (memq (car form) '(pub extern))
(cdr form) (cdr form)
form)) form))
@@ -169,42 +161,46 @@
;; If FORMS starts with a string, possibly after comment forms, return ;; If FORMS starts with a string, possibly after comment forms, return
;; that string and FORMS without it. Otherwise #f and FORMS unchanged ;; that string and FORMS without it. Otherwise #f and FORMS unchanged
(let loop ((fs forms) (prefix (list))) (let loop ((fs forms) (prefix (list)))
(match fs (cond
(() (values #f forms)) ((null? fs)
(((and cmt ('comment . _)) . rest) (values #f forms))
(loop rest (cons cmt prefix))) ((comment-form? (car fs))
(((? string? doc) . rest) (loop (cdr fs) (cons (car fs) prefix)))
(values doc (append (reverse prefix) rest))) ((string? (car fs))
(_ (values #f forms))))) (values (car fs) (append (reverse prefix) (cdr fs))))
(else
(values #f forms)))))
(define (extract-fn-docstring fn-form) (define (extract-fn-docstring fn-form)
(let ((lift (let ((n (fn-header-length fn-form)))
(lambda (proto body) (if (< (length fn-form) n)
(let-values (((doc rest) (take-leading-docstring body))) (values #f fn-form)
(let-values (((doc body) (take-leading-docstring (drop fn-form n))))
(if doc (if doc
(values doc (copy-form-source! fn-form (append proto rest))) (values doc
(copy-form-source! fn-form
(append (take fn-form n) body)))
(values #f fn-form)))))) (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) (define (extract-aggregate-docstring form)
;; ([pub] struct|union|enum name "doc" (fields ...) . attrs)
;; A string immediately after the name is the docstring; comments ;; A string immediately after the name is the docstring; comments
;; between name and fields are not skipped, they already confuse the ;; between name and fields are not skipped, they already confuse the
;; writer ;; writer
(match form (let* ((pub? (eq? (car form) 'pub))
(('pub (and kind (or 'struct 'union 'enum)) (core (if pub? (cdr form) form)))
(? symbol? name) (? string? doc) . rest) (if (and (pair? (cdr core))
(values doc (copy-form-source! form `(pub ,kind ,name ,@rest)))) (symbol? (cadr core))
(((and kind (or 'struct 'union 'enum)) (pair? (cddr core))
(? symbol? name) (? string? doc) . rest) (string? (caddr core)))
(values doc (copy-form-source! form `(,kind ,name ,@rest)))) (let ((new-core (cons (car core)
(_ (values #f form)))) (cons (cadr core) (cdddr core)))))
(values (caddr core)
(copy-form-source! form
(if pub?
(cons 'pub new-core)
new-core))))
(values #f form))))
(define (with-docstring doc form acc) (define (with-docstring doc form acc)
;; acc is newest-first; FORM is consed last so the final reverse ;; acc is newest-first; FORM is consed last so the final reverse
@@ -215,10 +211,21 @@
acc))) acc)))
(define (strip-fn-header-comments fn-form) (define (strip-fn-header-comments fn-form)
;; ([pub|extern] fn name arglist rettype). Comments in the body are ;; Remove comment forms from the function header
;; left in place as ordinary statements and preserved into the ;; ([pub|extern] fn name arglist rettype) so the positional accessors
;; generated C. ;; below are not shifted. Comments in the body are left in place as
(strip-header-comments fn-form (fn-header-length fn-form))) ;; ordinary statements and preserved into the generated C.
(let ((header-count (fn-header-length fn-form)))
;; 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) (define (process-fn sex-fn-raw acc)
(let-values (((doc sex-fn) (let-values (((doc sex-fn)
@@ -292,29 +299,27 @@
(define (sex-fn? form) (define (sex-fn? form)
"The `form` must be toplevel. "The `form` must be toplevel.
Returns #f if the form is not a function, returns the form otherwise" Returns #f if the form is not a function, returns the form otherwise"
(match form (and (non-empty-list? form)
((or ('fn . _) (let ((core (fn-core form)))
('pub 'fn . _) (and (pair? core) (eq? (car core) 'fn) form))))
('extern 'fn . _)) form)
(else #f)))
(define (sex-fn-public? fn-form) (define (sex-fn-public? fn-form)
(eq? (first fn-form) 'pub)) (eq? (car fn-form) 'pub))
(define (sex-fn-name fn-form) (define (sex-fn-name fn-form)
(assert (sex-fn? fn-form) (assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function")) (fmt #f "Form " fn-form " is not a function"))
(second (fn-core fn-form))) (cadr (fn-core fn-form)))
(define (sex-fn-arglist fn-form) (define (sex-fn-arglist fn-form)
(assert (sex-fn? fn-form) (assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function")) (fmt #f "Form " fn-form " is not a function"))
(third (fn-core fn-form))) (caddr (fn-core fn-form)))
(define (sex-fn-return-type fn-form) (define (sex-fn-return-type fn-form)
(assert (sex-fn? fn-form) (assert (sex-fn? fn-form)
(fmt #f "Form " fn-form " is not a function")) (fmt #f "Form " fn-form " is not a function"))
(fourth (fn-core fn-form))) (cadddr (fn-core fn-form)))
(define (sex-fn-prototype fn-form) (define (sex-fn-prototype fn-form)
"Returns all except body" "Returns all except body"

View File

@@ -737,11 +737,8 @@
(cat (c-type (cadr type) #f) (cat (c-type (cadr type) #f)
" (*" (or name "") ")(" " (*" (or name "") ")("
(fmt-join (lambda (x) (c-type x #f)) (caddr type) ", ") ")")) (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) ((%array)
(let ((name (cat (or name "") "[" (if (pair? (cddr type)) (let ((name (cat name "[" (if (pair? (cddr type))
(c-expr (caddr type)) (c-expr (caddr type))
"") "")
"]"))) "]")))
@@ -776,11 +773,8 @@
(cat (c-type (cadr type) #f) (cat (c-type (cadr type) #f)
" (*" (or name "") ")(" " (*" (or name "") ")("
(fmt-join (lambda (x) (c-type x #f)) (caddr type) ", ") ")")) (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) ((%array)
(let ((name (cat (or name "") "[" (if (pair? (cddr type)) (let ((name (cat name "[" (if (pair? (cddr type))
(c-expr (caddr type)) (c-expr (caddr type))
"") "")
"]"))) "]")))

View File

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

View File

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

View File

@@ -11,9 +11,6 @@
# It also checks what only a second translation unit can check: that an # It also checks what only a second translation unit can check: that an
# imported type reaches the type database, by expanding a macro that # imported type reaches the type database, by expanding a macro that
# reads the imported struct's fields. # 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 SEXC ?= ../../sexc

View File

@@ -7,11 +7,7 @@
(include stdio.h) (include stdio.h)
(pub var greet-count ;; a comment in the header of a public form is (pub var greet-count int 0)
;; 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 struct greeting (pub struct greeting
"A greeting to print." "A greeting to print."
@@ -29,8 +25,7 @@
(lambda (name field-type) (lambda (name field-type)
`(printf "%s " ,(symbol->string name)))))) `(printf "%s " ,(symbol->string name))))))
(pub fn greet ;; ...and here the prototype would lose its return type (pub fn greet ((name (* const char))) void
((name (* const char))) void
"Print a greeting for NAME." "Print a greeting for NAME."
(++ greet-count) (++ greet-count)
(printf "hello, %s\n" name)) (printf "hello, %s\n" name))

View File

@@ -80,22 +80,6 @@
;; guards nest ;; guards nest
(feature-test '((a)) (x y) "#+x #+y (a)") (feature-test '((a)) (x y) "#+x #+y (a)")
(feature-test '((b)) (x) "#+x #+y (a) (b)") (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 ;; a feature the program was not given is simply absent
(feature-test '() () "#+anything (a)") (feature-test '() () "#+anything (a)")

View File

@@ -115,17 +115,4 @@
(union t-doc-val ((i int) (f float)))) (union t-doc-val ((i int) (f float))))
(semen-process (semen-process
'((union t-doc-val "Either." ((i int) (f float)))))) '((union t-doc-val "Either." ((i int) (f float))))))
;; Includes
(test '((include stdio.h))
(semen-process
'((include stdio.h))))
(test '((include stdio.h)
(include stdlib.h))
(semen-process
'((include stdio.h
stdlib.h))))
(test '()
(semen-process
'((include))))
) )

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 map-fields
get-type-info get-type-info
get-tag-info
get-fields get-fields
get-underlying-type) get-underlying-type)
"../types.scm") "../types.scm")

View File

@@ -79,38 +79,6 @@
#f #f
(get-fields 't-u8)) (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 ;; A typedef can be written to lead back to itself. Resolving it must
;; stop rather than spin ;; stop rather than spin
(add-typedef 't-loop-a '(typedef t-loop-a t-loop-b)) (add-typedef 't-loop-a '(typedef t-loop-a t-loop-b))

View File

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

View File

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

View File

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

View File

@@ -15,11 +15,7 @@
srfi-1 srfi-1
srfi-69) srfi-69)
;;; Two namespaces: `struct point' and a `point' typedef are separate (define +type-db+ (make-hash-table))
;;; 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 (strip-pub form) (define (strip-pub form)
(if (eq? (car form) 'pub) (cdr form) form)) (if (eq? (car form) 'pub) (cdr form) form))
@@ -48,17 +44,17 @@
(list)))) (list))))
(define (add-struct name form) (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))))) (list 'struct name (normalize-fields (aggregate-fields form)))))
(define (add-union name 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))))) (list 'union name (normalize-fields (aggregate-fields form)))))
;;; ([pub] enum name (value ...)) ;;; ([pub] enum name (value ...))
(define (add-enum name form) (define (add-enum name form)
(let ((f (strip-pub 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))) (list 'enum name (if (and (pair? (cddr f)) (list? (caddr f)))
(caddr f) (caddr f)
(list)))))) (list))))))
@@ -74,14 +70,8 @@
(hash-table-set! +type-db+ name (hash-table-set! +type-db+ name
(list 'define name (cddr (strip-pub form))))) (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) (define (get-type-info name)
(or (hash-table-ref/default +type-db+ name #f) (hash-table-ref/default +type-db+ name #f))
(get-tag-info name)))
;;; ((name type) ...) for a struct or union, #f for anything else -- ;;; ((name type) ...) for a struct or union, #f for anything else --
;;; including a name that was never declared. Callers give the better ;;; 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 ;;; The declaration NAME ultimately names. For a typedef that is the
;;; entry of whatever it stands for, and for anything else it is ;;; entry of whatever it stands for, and for anything else it is
;;; NAME's own info ;;; 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)))
(define (resolve-type-info name) (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))) (let ((info (get-type-info name)))
(and info (and info
(if (eq? (car info) 'typedef) (if (eq? (car info) 'typedef)
(let ((tag (target-tag (get-underlying-type name)))) (let* ((target (get-underlying-type name))
;; A typedef target is written in type position, where (tag (cond ((symbol? target) target)
;; `struct point' means the tag ((and (pair? target)
(and tag (or (get-tag-info tag) (get-type-info tag)))) (pair? (cdr target))
(symbol? (cadr target)))
(cadr target))
(else #f))))
(and tag (get-type-info tag)))
info)))) info))))
;;; Type matcher macro ;;; Type matcher macro

View File

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

View File

@@ -42,24 +42,6 @@
(current-directory) (current-directory)
pathname))) 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) (define (list-split src-list split-elt)
;; split '(1 2 / 3 4 / 5 6) by '/ -> '((1 2) (3 4) (5 6)) ;; split '(1 2 / 3 4 / 5 6) by '/ -> '((1 2) (3 4) (5 6))
(fold (lambda (elt acc) (fold (lambda (elt acc)