42 Commits

Author SHA1 Message Date
37a252f2b1 Make Sex templates more pleasant syntactically
No more unquotes to instantiate templates.
Also no need to pass quoted substitution lists, just use them as
regular lisp macros.
2025-07-11 18:27:50 +03:00
162d4f5322 add info about Emacs sex-mode to Readme.org 2025-06-19 12:31:43 +03:00
7115f61538 update Readme.org
a bit of spellchecking, add some examples for templates
2025-06-16 15:18:21 +03:00
alex-eg
a1e0fbc4f9 support unkebabification in #()-forms
which are vectors in Chicken, {}-initializers in C
2025-06-14 19:02:45 +03:00
alex-eg
e1c386ebcd support cpp define 2025-06-14 18:49:01 +03:00
alex-eg
27822cee2a prefix define and define-syntax with chicken-
as we have define in cpp
2025-06-14 18:09:50 +03:00
alex-eg
af6b7a37ee don't unkebabify -> in symbols (it's -> from C) 2025-06-14 17:48:41 +03:00
alex-eg
1229f89153 fix pub function prototypes 2025-06-14 17:21:40 +03:00
alex-eg
bb3d7c1d3e fix non-symbolic template replacements causing error on expansion
symbol->string doesn't work for anything besides synmols. And not
any (not (list? ...)) is a symbol. String are not, for example.
2025-06-14 17:08:18 +03:00
alex-eg
df9c62ffa4 add define and define-syntax syntax coloring in sex-mode.el 2025-06-14 14:10:19 +03:00
alex-eg
3da85c3e2e change load to chicken-load in sex sources
to explicitly denote that it is chiken
2025-06-14 14:09:39 +03:00
alex-eg
8b1ade4f24 add chicken-import support to sex
also fix unquoting to single atom processing wrongly in walk-sex-tree
2025-06-14 14:07:41 +03:00
alex-eg
750d8f8256 rename dir lib -> example
because it is not quite lib yet, but an example dump definitely
2025-06-14 12:40:15 +03:00
alex-eg
db8ff39171 hanlde pub for global vars correctly 2025-06-14 12:39:13 +03:00
alex-eg
0ebdef2792 add union support to sex-mode.el 2025-06-13 18:21:06 +03:00
alex-eg
2295b5ad17 add extern support 2025-06-13 18:20:51 +03:00
alex-eg
4efa730b21 sex headers are now seh, not hsex 2025-06-13 14:47:55 +03:00
alex-eg
7f2463823a update Readme.org 2025-06-13 14:31:10 +03:00
alex-eg
e9c0323e6e add field access support (also unfuck tree walker a bit) 2025-06-12 23:40:35 +03:00
alex-eg
497603c111 remove useless empty line 2025-06-12 23:40:15 +03:00
alex-eg
35c7539d1e fix indent 2025-06-12 23:40:08 +03:00
alex-eg
88b441b58c sex-mode inherits scheme mode 2025-06-11 20:24:12 +03:00
alex-eg
ad983a0d9c unshittify templates 2025-06-11 20:23:54 +03:00
2ba9ad37b7 implement templates (kinda)
uh oh
2025-06-09 00:30:14 +03:00
fea7e58dd1 make some exceptions for unkebabification 2025-06-09 00:29:43 +03:00
dead339e8c add clean target 2025-06-09 00:28:50 +03:00
f46e087958 [] isn't gonna work with Sheme reader 2025-06-09 00:27:36 +03:00
e195ba8553 don't need map here, for-each is enough 2025-06-09 00:27:21 +03:00
9a1757dda8 fix reading from files not in current dir 2025-06-09 00:27:03 +03:00
2641e9f4fb add auto typedefs for structs 2025-06-09 00:23:49 +03:00
3d080b2bb0 typo 2025-06-05 17:17:58 +03:00
3224e8cf6a rewrite tree walk with tree module
also add pub support
2025-06-05 17:12:16 +03:00
0452f442b1 filter out '()s from the tree 2025-06-05 12:15:06 +03:00
4389392123 add cmdline options
-h help
-o write to file
-m expand macros in Sex code and print result
2025-06-05 07:32:05 +03:00
alex-eg
555bd836a9 add sex-mode.el 2025-05-30 12:38:18 +03:00
alex-eg
c4dd5786a8 maybe rewrite map-filter-tree with tree.scm's tree inversions?
They don't seem intuitive, need to mess around a bit to understand
them.
2025-05-30 12:37:28 +03:00
alex-eg
24bcd93a36 add template list class and test 2025-05-30 12:37:07 +03:00
alex-eg
80b69cc8ad add link to Chicken web site 2025-05-29 22:19:23 +03:00
alex-eg
794e97c473 fix format in Readme 2025-05-29 22:15:12 +03:00
alex-eg
61ddabf85f implement comma in Sex sources 2025-05-29 22:08:14 +03:00
alex-eg
861b862a13 add info about Chicken deps to Readme.org 2025-05-27 14:50:10 +03:00
alex-eg
7007623e94 update Readme 2025-05-27 12:02:54 +03:00
9 changed files with 765 additions and 38 deletions

View File

@@ -1,4 +1,19 @@
CHICKEN_C = csc
sexc: sexc.scm
$(CHICKEN_C) $< -o $@
MODULES = sexc templates main
OBJ = $(MODULES:%=%.o)
# chicken flags
CFLAGS = -compile-syntax
sexc: $(OBJ)
$(CHICKEN_C) $^ -o $@
main.o: main.scm
$(CHICKEN_C) $< -c -o $@
%.o: %.scm
$(CHICKEN_C) $< -e -c -o $@ $(CFLAGS)
clean:
rm -f $(OBJ) sexc

View File

@@ -1,9 +1,20 @@
* The Sex language
Sex is a S-expressions language, which transpiles to C.
Sex is also Chicken, since all source processing and compile-time
computations are written in Chicken.
And Chicken is [[https://call-cc.org][R5RS Scheme]].
* Compilation and usage
Sex is written in Chicken Scheme, so first you'll need to get yourself
a Chicken.
First, get yourself a Chicken. Second, some Chicken deps.
** Install Chicken Eggs
By the way, there's a way to make Chicken install eggs non-globally. Refer to
the documentation for more info:
https://wiki.call-cc.org/man/5/Extension%20tools#changing-the-repository-location
~chicken-install fmt getopt-long brev-separate~
** Compilation
~make~
@@ -15,16 +26,199 @@ cat hello-world.sex | sexc > hello_world.c
cc hello_world.c -o hello_world
#+end_src
* Example
Here is an example, demonstrating what Sex source looks like, and what
it compiles too. An avid reader also shall notice how we call Chicken
procedures in Sex source.
The Sex source:
#+begin_src
(include stdio.h)
(define (foo)
"Hello from Chicken code!\n")
(pub fn int main ((int argc) (char **argv))
(puts "Hello from Sex!")
(var (array char 512) name)
(puts "What is your name?")
(scanf "%s" &name)
(printf "Hello, %s!\n" name)
(printf ,(foo))
0)
#+end_src
The resulting C source:
#+begin_src
#include <stdio.h>
int main (int argc, char **argv) {
puts("Hello from Sex!");
char name[512];
puts("What is your name?");
scanf("%s", &name);
printf("Hello, %s!\n", name);
printf("Hello from Chicken code!\n");
return 0;
}
#+end_src
* Features
** Full C interoperability
Just ~(include "Your/Favourite/Library.h")~ and use it as you would
have is C. For hardcore fans of traditional Lisp naming convention,
have is C.
For hardcore fans of traditional Lisp naming convention,
Sex offers automatic unkebabification of all symbols, i.e. no more
ugly ~GL_ARRAY_BUFFER~'s in your code, they may be writted in their
ugly ~GL_ARRAY_BUFFER~ s in your code, they may be written in their
proper form: ~GL-ARRAY-BUFFER~.
** Auto typedef for structs
Probably harmless idk. Example:
#+begin_src
(struct foo
((float a)
(int b)))
#+end_src
expands to
#+begin_src
typedef struct foo foo;
struct foo {
float a;
int b;
};
#+end_src
** Syntactic templates
Sex has support for template substitutions. Any piece of code can be
templated. Template declarations look like functions: they have a
name, an argument list and a body. When declared template is
encountered during reading of Sex code, its body will udergo syntactic
rewriting using the provided values by the following rules:
1. If the value is a symbol, all arguments in a body are replaced with
the value, and also all /parts/ of any other symbol equal to the
value also get replaced.
2. If the value is a non-symbolic form, all arguments in a body are
replaced with it, but no symbolic substitution is performed.
Formally, template declaration has the following syntax:
#+begin_src
(template (name . substitute-args) . body)
#+end_src
*** Examples:
**** Structure with templated value type
#+begin_src
(template (foo ?T)
(struct foo-?T
((?T value))))
(foo float)
#+end_src
->
#+begin_src
typedef struct foo_float foo_float;
struct foo_float {
float value;
};
#+end_src
Note that ~?~ at the start of template argument is not syntax, just
convention.
**** Wrapper for checking return codes
#+begin_src
(template (check-sdl-return call message ret-code)
(if (< 0 call)
(begin
(puts message)
(return ret-code))))
(fn int init ()
(check-sdl-return
(SDL-Init SDL-INIT-VIDEO) "Failed to initialize SDL" 1)
...)
#+end_src
->
#+begin_src
static int init () {
if (0 < SDL_Init(SDL_INIT_VIDEO)) {
puts("Failed to initialize SDL");
return 1;
}
return 0;
}
#+end_src
**** A bit of everything
#+begin_src
(template (list-T ?T)
(struct list-?T
((?T value)
((* list-?T) next))))
(template (list-for-each type list-var elt-var body)
(var type elt-var (-> list-var value))
(while (!= (-> list-var next) NULL)
body
(= list-var (-> list-var next))
(= elt-var (-> list-var value))))
; ... somewhere later
(list-T int)
(pub fn void print-list (((const list-int) *l))
(list-for-each int l v (printf "%d " v))
(printf "\n"))
#+end_src
Then will be expanded in the following code:
#+begin_src
(typedef struct list_int list_int)
(struct list_int ((int value) ((* list_int) next)))
(%fun void
print_list
(((const list_int) *l))
(%var int v (-> l value))
(while (!= (-> l next) NULL)
(printf "%d " v)
(= l (-> l next))
(= v (-> l value)))
(printf "\n"))
#+end_src
And then translated to:
#+begin_src
typedef struct list_int list_int;
struct list_int {
int value;
list_int *next;
};
void print_list (const list_int *l) {
int v = l->value;
while (l->next != NULL) {
printf("%d ", v);
l = l->next;
v = l->value;
}
printf("\n");
}
#+end_src
** Use an established environment for development
As Sex is S-expressions, you always have Emacs with paredit as your
best option.
** COMING SOON: Polymorhpism
*** sex-mode.el
To harness the power of sex-mode, add the following lines to your
~$HOME/.config/emacs/init.el~:
#+begin_src
(use-package sex-mode
:load-path "/path/to/sex"
:mode ("\\.sex\\'" "\\.seh\\'"))
#+end_src
** COMING SOON?: Polymorphism

36
example/list.seh Normal file
View File

@@ -0,0 +1,36 @@
(template (list-T ?T)
(struct list-?T
((?T value)
((* list-?T) next))))
(template (make-list-T ?T is-public?)
(,@(if 'is-public? '(pub) '()) fn (* list-?T) make-list-?T ()
(var (* list-?T) list (cast (* list-?T) (malloc (sizeof list-?T))))
(= (-> list next) NULL)
list))
(template (add-value-list-?T ?T is-public?)
(,@(if 'is-public? '(pub) '()) fn void add-value-list-?T ((list-?T *list) (T value))
(while (!= (-> list next) NULL)
(= list (-> list next)))
(= (-> list next) (make-list-?T))
(= (-> list value) value)))
(template (length-list-?T ?T is-public?)
(,@(if 'is-public? '(pub) '()) fn size-t length-list-?T ((list-?T *list))
(var size-t n 0)
(while (!= (-> list next) NULL)
(= list (-> list next))
(++ n))
n))
(template (is-empty-list-?T ?T is-public?)
(,@(if 'is-public? '(pub) '()) fn bool is-empty-list-?T ((list-?T *list))
(== (-> list next) NULL)))
(template (list-for-each list-var elt-var what-do)
(var int elt-var (-> list-var value))
(while (!= (-> list-var next) NULL)
what-do
(= list-var (-> list-var next))
(= elt-var (-> list-var value))))

69
example/test-list.sex Normal file
View File

@@ -0,0 +1,69 @@
(include stdlib.h)
(include stddef.h)
(include stdbool.h)
(include stdio.h)
(chicken-import srfi-1 brev-separate) ; list routines, e.g. fold
(chicken-load "list.seh")
(chicken-define (imports-test a b c)
(fold + 0 (list 1 2 3 a b c)))
(struct foo
((float a-field)
(int b)
((const char *) c)
((fn bool ((bool val))) not)))
(var foo f #((= .a-field 1.2)))
(list-T int)
(make-list-T int)
(add-value-list-T int)
(length-list-T int)
(is-empty-list-T int)
(extern fn void puk ((int a) (float b)))
(fn int bar () ,(imports-test 10 20 30))
(pub fn void baz () true)
(extern var int i)
(var int j)
(pub var int k)
(pub fn int main ()
(var (* list-int) l (make-list-int))
(printf "Size of the list: %zu\n" (length-list-int l))
(add-value-list-int l 3)
(add-value-list-int l 4)
(printf "Size of the list: %zu\n" (length-list-int l))
(list-for-each l v
(printf "%d " v))
(printf "\n")
(printf "%zu\n" l->next)
0)
(pub fn segs-renderer* create-renderer ())
(pub fn void clear-command-buffer ((segs-renderer *r)))
(pub fn void add-render-command ((segs-renderer *r) ((fn void ()) command)))
(pub fn void commit-command-buffer ((segs-renderer *r)))
(template (list-T ?T)
(struct list-?T
((?T value)
((* list-?T) next))))
(template (list-for-each type list-var elt-var body)
(var type elt-var (-> list-var value))
(while (!= (-> list-var next) NULL)
body
(= list-var (-> list-var next))
(= elt-var (-> list-var value))))
; ... somewhere later
(list-T int)
(pub fn void print-list (((const list-int) *l))
(list-for-each int l v (printf "%d " v))
(printf "\n"))

View File

@@ -1,10 +1,18 @@
(include stdio.h)
(include unistd.h)
(fn int main ((int argc) (char **argv))
(define (foo)
"Hello from Chicken code!\n")
(struct foo
((int a)
(float b)))
(pub fn int main ((int argc) (char **argv))
(var int a 10)
(puts "Hello from Sex!")
(var (array char 513) name)
(var (array char 512) name)
(puts "What is your name?")
(scanf "%s" &name)
(printf "Hello, %s!\n" name)
(printf ,(foo))
0)

6
main.scm Normal file
View File

@@ -0,0 +1,6 @@
;;; The purpose of this file is to compile it to the only
;;; .o that has main entry point.
(declare (uses sexc))
(main)

98
sex-mode.el Normal file
View File

@@ -0,0 +1,98 @@
(require 'scheme)
(defgroup sex-mode nil
"Major mode for Sex code."
:prefix 'sex-
:group 'languages)
(define-abbrev-table 'sex-mode-abbrev-table ()
"Abbrev table for Sex mode.
It has `scheme-mode-abbrev-table' as its parent."
:parents (list scheme-mode-abbrev-table))
(defvar sex-mode-syntax-table
(let ((table (make-syntax-table lisp-data-mode-syntax-table)))
table))
(defvar sex-mode-map
(let ((map (make-sparse-keymap)))
(set-keymap-parent map lisp-mode-shared-map)
map)
"Keymap for Sex mode.
All commands in `lisp-mode-shared-map' are inherited by this map.")
(defvar sex-mode-line-process "")
(defconst sex-font-lock-keywords
(eval-when-compile
(list
;; Declarations
(list (concat "("
(regexp-opt '("chicken-define"
"chicken-define-syntax"
"chicken-import"
"chicken-load"
"define"
"extern"
"include"
"fn"
"pub"
"struct"
"template"
"var"
"union")
'word)
"\\>"
"[[:space:]]*"
"\\([[:word:]]*\\)")
'(1 font-lock-keyword-face)
'(2 font-lock-function-name-face))
;; Keywords
(list (concat "("
(regexp-opt '(
"case"
"do"
"if"
"for"
"goto"
"return"
"switch"
"var"
"while")
'word)
"\\>")
'(1 'font-lock-builtin-face)))))
(defun sex-mode-set-variables ()
(set-syntax-table sex-mode-syntax-table)
(setq local-abbrev-table sex-mode-abbrev-table)
(setq mode-line-process '("" sex-mode-line-process))
(setq font-lock-defaults
'((sex-font-lock-keywords)
nil nil
(("+-*/.<>=!?$%_&:" . "w"))
nil
(font-lock-mark-block-function . mark-defun)))
(setq-local prettify-symbols-alist lisp-prettify-symbols-alist))
(put 'fn 'lisp-indent-function 'defun)
(put 'template 'lisp-indent-function 'defun)
(put 'struct 'lisp-indent-function 'defun)
(put 'var 'lisp-indent-function 0)
;;;###autoload
(define-derived-mode sex-mode lisp-data-mode "Sex"
"Major mode for editing Sex code.
Editing commands are similar to those of `lisp-mode'.
Commands:
Delete converts tabs to spaces as it moves back.
Blank lines separate paragraphs. Semicolons start comments.
\\{sex-mode-map}"
:group 'sex-mode
(sex-mode-set-variables))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.\\(sex\\|hsex\\)\\'" . sex-mode))
(provide 'sex-mode)

275
sexc.scm
View File

@@ -1,33 +1,268 @@
(import brev-separate fmt fmt-c tree (chicken string))
(declare (unit sexc)
(uses templates))
(import brev-separate
(chicken pathname)
(chicken plist)
(chicken pretty-print)
(chicken process-context)
(chicken string)
fmt
fmt-c
getopt-long
regex
srfi-1 ; list routines
tree)
(define +debug+ #f)
(define (unkebabify sym)
(case sym
((-) sym)
((--) sym)
((->) sym)
((-=) sym)
(else
(string->symbol
(string-translate (symbol->string sym) #\- #\_)))
(string-substitute "-(?!>)" "_"
(symbol->string sym) #t)))))
(define (loop)
(let ((r (read)))
(unless (eof-object? r)
(cond ((eqv? (car r) 'define) (eval r))
(#t
(fmt #t
(c-expr
(tree-map
(fn
(case x
(define (atom-to-fmt-c atom)
(case atom
((fn) '%fun)
((prototype) '%prototype)
((var) '%var)
((begin) '%begin)
((define) '%define)
((pointer) '%pointer)
((array) '%array)
(([]) 'vector-ref)
((@) 'vector-ref)
((include) '%include)
((cast) '%cast)
(else
(if (symbol? x)
(unkebabify x)
x))))
r)))
(fmt #t "\n")))
(loop))))
(if (symbol? atom)
(unkebabify atom)
atom))))
(loop)
(define (tree-finder symbol)
(lambda (node)
(or (and (tree? node)
(eq? (car node) symbol))
#f)))
(define (make-field-access form)
(assert (= 2 (length form)) "Wrong field access format")
(unkebabify
(string->symbol
(fmt #f (cadr form) (car form)))))
(define (walk-generic form acc)
(cond
((null? form) (cons '() acc))
;; vector, e.g. {}-initializer
((vector? form)
(cons
(list->vector
(car (walk-sex-tree (vector->list form) (list))))
acc))
;; atom (hopefully)
((not (list? form)) (cons (atom-to-fmt-c form) acc))
;; special case - replace unquote with its expansion
((eq? (car form) 'unquote)
(fold
cons
acc
(car ; bc walk-sex-tree always
; wraps its result
(walk-sex-tree (eval (cadr form)) (list)))))
;; another special case - field access
((and (symbol? (car form))
(char=? #\. (string-ref (symbol->string (car form)) 0)))
(cons (make-field-access form) acc))
;; another special case - template
((template? form)
(append (fold
walk-generic
(list)
(eval form))
acc))
;; toplevel, or a start of a regular list form
(else
(let ((new-acc (list)))
(cons (reverse
(fold
walk-generic
new-acc
form))
acc)))))
(define (normalize-fn-form form)
;; (fn ret-type name arglist body) -> normal function
;; (fn ret-type name arglist) -> prototype
(if (>= (length form) 5)
form
(cons 'prototype (cdr form))))
(define (walk-function form static acc)
(if static
(append (walk-generic (list 'static (normalize-fn-form form))
(list))
acc)
(append (walk-generic (normalize-fn-form (cdr form))
(list))
acc)))
(define (walk-struct form acc)
(let ((name (unkebabify (cadr form))))
(append (walk-generic form (list))
(cons `(typedef struct ,name ,name) acc))))
(define (walk-extern form acc)
(case (cadr form)
((fn)
(append
(list (cons 'extern (walk-function form #f (list))))
acc))
((var)
(append
(list (cons 'extern (walk-generic (cdr form) (list))))
acc))
(else (error "Extern what?"))))
(define (walk-public form acc)
(case (cadr form)
((fn)
(walk-function form #f acc))
((var)
(append (walk-generic (list 'static (cdr form)) (list)) acc))
(else (error "Pub what?"))))
(define (template? form)
(and (list? form)
(symbol? (car form))
(get (car form) 'sex-template)))
(define (walk-sex-tree form acc)
(if (list? form)
(if (template? form)
(fold (fn (walk-sex-tree x y))
acc
(eval form))
(case (car form)
((fn) (walk-function form #t acc))
((extern) (walk-extern form acc))
((pub) (walk-public form acc))
((struct union) (walk-struct form acc))
((unquote) (fold (fn (walk-sex-tree x y))
acc
(eval (cadr form))))
(else (append (walk-generic form (list)) acc))))
;; only for unquote support
(list (list (atom-to-fmt-c form)))))
(define (process-form form acc)
(case (car form)
((chicken-define) (eval (cons 'define (cdr form))) acc)
((template) (eval form) acc)
((chicken-load)
(load (cadr form)) acc)
((chicken-import)
(eval (cons 'import (cdr form))) acc)
(else
(walk-sex-tree form acc))))
(define (process-raw-forms raw-forms acc)
(if (null? raw-forms)
(reverse acc)
(process-raw-forms (cdr raw-forms)
(process-form (car raw-forms) acc))))
(define (read-forms acc)
(let ((r (read)))
(if (eof-object? r) (reverse acc)
(read-forms (cons r acc)))))
(define (emit-c forms)
(for-each (lambda (form)
(fmt #t (c-expr form))
(fmt #t "\n"))
forms))
;;; Main function facilities
(define opts-grammar
`((output "Write output to file"
(required #f)
(value #t)
(single-char #\o))
(help "Show this help"
(required #f)
(value #f)
(single-char #\h))
(macro-expand "Emit macro-expanded Sex code instead of C"
(required #f)
(value #f)
(single-char #\m))))
(define (print-help)
(fmt #t "Usage: sexc [OPTIONS] [FILE]\n")
(fmt #t "Options: -o, --output <file> Write output to file. If omitted, write to stdout\n")
(fmt #t " -h, --help Show this help\n")
(fmt #t " -m, --macro-expand Emit macro-expanded Sex code instead of C\n"))
(define (help-arg? args)
(assoc 'help args))
(define (get-arg args arg-name default)
(let ((arg (assoc arg-name args)))
(if arg (cdr arg)
default)))
(define (get-input-file args)
(let ((rest-args (assoc '@ args)))
(if (= 1 (length rest-args))
'stdin
(cadr rest-args))))
(define (read-from-file file)
(with-input-from-file (pathname-strip-directory file)
(fn (read-forms (list)))))
(define (set-working-directory file)
(change-directory
(normalize-pathname
(make-absolute-pathname
(current-directory)
(pathname-directory file)))))
(define (main)
(let* ((raw-args (command-line-arguments))
(current-dir (current-directory))
(args (getopt-long raw-args
opts-grammar))
(output (get-arg args 'output 'stdout))
(help (help-arg? args))
(input (get-input-file args)))
(if help (print-help)
(let* ((raw-forms
(if (eq? input 'stdin)
(read-forms (list))
(begin
(set-working-directory input)
(read-from-file input))))
(sex-forms (process-raw-forms raw-forms (list))))
(change-directory current-dir)
(if (get-arg args 'macro-expand #f)
(map pp sex-forms)
(if (not (eq? output 'stdout))
(with-output-to-file output
(lambda ()
(emit-c sex-forms)))
(emit-c sex-forms)))))))

66
templates.scm Normal file
View File

@@ -0,0 +1,66 @@
(declare (unit templates))
(import
(chicken plist)
brev-separate
fmt
regex
srfi-1 ; list routines
tree)
(define (register-template name)
(put! name 'sex-template #t))
(define-syntax template
(syntax-rules ()
((template (name . args) . body)
(begin
(register-template 'name)
(define-syntax name
(syntax-rules ()
((name . applied-args)
(let* ((subst-alist (map cons 'args 'applied-args))
(replaced-body (apply-substitution `body subst-alist)))
replaced-body))))))))
(define (apply-symbol-substitution sym subst-alist)
;; All non-symbol substitutions will be filtered.
;; E.g. if the subst-alist is ((T + 1 2) (U . w) (W . e)),
;; only ((U . w) (W . e)) will be applied to symbols.
(let ((str (symbol->string sym))
(subst-map (map (fn
(if (symbol? (car x))
(cons
(fmt #f "([^\\-]?)"
(regexp-escape (symbol->string (car x)))
"([\\-$]?)")
(fmt #f "\\1" (symbol->string (cdr x)) "\\2"))
x))
(filter (fn (symbol? (cdr x))) subst-alist))))
(string->symbol
(string-substitute* str subst-map))))
(define (maybe-replace-symbol sym subst-alist)
(call/cc
(lambda (return)
(for-each (fn (when (eq? (car x) sym)
(return (cdr x))))
subst-alist)
(return sym))))
(define (apply-substitution target subst-alist)
;; Subsitute free symbols and -/$/^ separated parts
;; of symbols with provided forms.
;; E.g. with substitution (T int):
;; list-T -> list-int ; by apply-symbol-substitution
;; (var T data) -> (var int data) ; by maybe-replace-symbol
;; see respective functions for further details.
(tree-map
(fn
(if (symbol? x)
(let ((st (maybe-replace-symbol x subst-alist)))
(if (eq? st x)
(apply-symbol-substitution x subst-alist)
st))
x))
target))