* The Sex language #+NAME: the Sex logo #+ATTR_HTML: :width 300px [[sex.png][file:./sex.png]] Sex is a S-expressions language. Sex is written in Chicken, which is an [[https://call-cc.org][R7RS Scheme]]. Sex is statically typed, compiled general purpose language. * Compilation First, get yourself a Chicken, then, some Chicken deps. You also will need a C compiler. ** Development #+begin_src sh make deps make #+end_src ~make deps~ installs pinned eggs from ~eggs.lock~ into a project-local ~.eggs/~ repository. ~dependencies.txt~ is the unpinned request list. To refresh ~eggs.lock~ after changing it: #+begin_src sh make deps-update #+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 The default ~CSC_FLAGS~ include ~-static~, so ~sexc~ does not need ~.eggs~ at runtime and ~make install~ stays relocatable. Chicken must provide ~libchicken.a~ (E.g. for gentoo: ~dev-scheme/chicken~ with ~static-libs~ use flag). To link dynamically instead (the binary will look for eggs under this tree's ~.eggs~ path): #+begin_src sh make CSC_FLAGS='-K prefix' #+end_src ** Installation GNU directory variables: ~prefix~, ~exec_prefix~, ~bindir~, ~DESTDIR~. #+begin_src sh # default installation (/usr/local/bin/) make install # customize the prefix (installs to ~/.local/bin) make prefix=$(HOME)/.local install # staged install for packaging make DESTDIR=/tmp/stage prefix=/usr install #+end_src * Usage ** Summary #+begin_src Usage: sexc [options] filename [-- options-for-c-compiler] Options: --c-compiler=ARG Select C compiler. Defaults to value of SEX_CC environment variable, or if it is empty, to cc -c, --compile-object Compile object file instead of executable program -f, --features=ARG Comma-separated feature names, added to the host's own for #+ and #- feature expressions. May be given more than once --no-platform-features Leave out the host's own features. With --features, this reads a file the way another platform would -C, --emit-c Emit C code --public-interface Get module's public interface -h, --help Show this help -m, --macro-expand Emit macro-expanded semantically processed Sex code -o, --output=ARG Write output to file. Default file name is a.out. If -E or -m options are provided, defaults to stdout --line-directives=ARG How much #line information to emit: statement (default), toplevel, or none. `statement' is what makes a debugger land on the right source line; `none' is for reading -C output by eye #+end_src ** Compiling Hello World #+begin_src shell sexc ./examples/hello-world.sex -o hello #+end_src That's it. Now you should have executable named ~hello~ in your directory. Sex uses C under the hood, the default C compiler is ~cc~, but you can pass any using ~--c-compiler~ option, or by setting ~SEX_CC~ environment variable. Everything after ~--~ is handed to the C compiler exactly as written: #+begin_src shell sexc example/sdl3-triangle.sex -o triangle -- `pkg-config --cflags --libs sdl3` -framework OpenGL #+end_src ** Example An example of Sex source: #+begin_src scheme (include stdio.h) (pub fn main ((argc int) (argv [* const char])) int (puts "Hello from Sex!") (var name [char 512]) (puts "What is your name?") (scanf "%s" (cast (& name) (* char))) (printf "Hello, %s!\n" name) (return 0)) #+end_src Compile and run: #+begin_src shell ~/dev/sex $ ./sexc ./example/hello-world.sex -o hello-world ~/dev/sex $ ./hello-world Hello from Sex! What is your name? Alex Hello, Alex! #+end_src * Features ** Full C interoperability Just ~(include "Your/Favourite/Library.h")~ and use it as you would have is C. *** Auto kebabification For hardcore fans of traditional Lisp naming convention, Sex offers automatic kebabification of all symbols, i.e. no more ugly ~GL_ARRAY_BUFFER~ s in your code, they may be written in their proper form: ~GL-ARRAY-BUFFER~. ** Modules Each source file is a module. Module can provide public interface and be imported by using ~(import path/to/module)~ expression. Module search path consists of two parts: first is relative to the source being compiled location, and the second is ~SEX_MODULE_PATH~ environment variable. Module's public interface consists of everything declared ~pub~. Structures, function, macros, types, variables can be public. ** Read-time feature expressions Sex is able to use ~#+~ and ~#-~ for conditional compilation: the form that follows is kept only when the feature expression is true, and otherwise is read and thrown away. #+begin_src scheme #+macosx (include OpenGL/gl3.h) #-macosx (include GL/gl.h) #+(and unix (not macosx)) (define HAVE-EPOLL 1) #+end_src An expression is a feature name, or ~and~, ~or~ and ~not~ of them. This is read time, not compile time. What does not apply never reaches macro expansion, the type database or the generated C. The features are the host's ~(software-version)~, ~(software-type)~ and ~(machine-type)~, e.g. ~macosx unix arm64~ or ~linux unix x86-64~. ~--features~ adds to them: #+begin_src shell sexc prog.sex -f debug,with-sdl sexc prog.sex --features=debug --features=with-sdl #+end_src A feature is never taken away. The host's features can be disabled, e.g. for checking output for other platform: #+begin_src shell sexc example/sdl3-triangle.sex -C --no-platform-features --features=linux,unix,x86-64 #+end_src ** Aggregate initializers and compound literals ~#(...)~ is a brace initializer. On its own it has no type and takes one from where it is written: #+begin_src scheme (var p (struct point) #(1 2)) #+end_src A ~:~ inside one ends a type and makes the whole thing a compound literal --- an unnamed object of that type, usable anywhere an expression is: #+begin_src scheme (var q (struct point) #(struct point : 3 4)) (var a (* int) #([int 3] : 10 20 30)) (draw-line ui #(struct point : 0 0) end) (var p (* struct point) (& #(struct point : 9 9))) ; an lvalue, so `&' works #+end_src The type is written as bare words, the way it is everywhere else in the language; ~:~ is what ends it. A leading ~.~ names a field, so initializers may be designated, given in any order, and mixed with positional ones: #+begin_src scheme (var r (struct named) #(struct named : .n 7 .first-name "zoe")) #+end_src A compound literal written inside a block lives until the end of that block and no longer, so returning its address is a dangling pointer. ** Syntactic macros Sex has support for syntactic macros. Macro definitions look like functions: they have a name, an argument list and a body. Macro should return Sex code. A macro returns *one* form. To return several --- a function beside the struct it works on, say --- return them under =$=, which splices them in where the macro was written: #+begin_src scheme (defmacro (pair-of-fns a b) `($ (fn ,a () int (return 1)) (fn ,b () int (return 2)))) #+end_src =($)= expands to nothing. Everything else is a single form, including one whose head is itself a form: =`((make-adder 10) 5)= calls what =make-adder= returned, and is not two forms. *** Examples: **** Structure with templated value type #+begin_src scheme (pub defmacro (list-T type) (let ((list-type (cat 'list- type))) `(struct ,list-type ((value ,type) (next (* ,list-type)))))) (list-T int) #+end_src -> #+begin_src scheme (struct list_int ((value int) (next (* list_int)))) #+end_src **** Wrapper for checking return codes #+begin_src scheme (pub defmacro (check-sdl-return call message ret-code) `(if (< 0 ,call) (do (puts ,message) (return ,ret-code)))) (pub fn init () int (check-sdl-return (SDL-Init SDL-INIT-VIDEO) "Failed to initialize SDL" 1) ...) #+end_src -> #+begin_src scheme (pub fn init () int (if (< 0 (SDL_Init SDL_INIT_VIDEO)) (do (puts "Failed to initialize SDL") (return 1))) ...) #+end_src ** Compile-time type information Sex has a number of type reflection features, aiming to help with macro writing. During the compilation, all type info is collected, and is accessible during macro expansion. This allows us to write things like providing auto serialization, adding meta information, and so on. ** Use an established environment for development As Sex is S-expressions, you always have Emacs with paredit as your best option. *** sex-mode.el To harness the power of sex-mode, add the following lines to your ~$HOME/.config/emacs/init.el~: #+begin_src emacs-lisp (use-package sex-mode :load-path "/path/to/sex" :mode ("\\.sex\\'")) #+end_src