- The Sex language
- Compilation
- Usage
- Features
The Sex language
Sex is a S-expressions language. Sex is written in Chicken, which is an 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
make deps
make
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:
make deps-update
Packaging for system package managers
- Depend
sexpackage on Chicken-6 (withlibchicken.a) and all eggs fromdependencies.txt. - Compile and install with
make(usually no other arguments required). - Move resulting
sexcbinary 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):
make CSC_FLAGS='-K prefix'
Installation
GNU directory variables: prefix, exec_prefix, bindir, DESTDIR.
# 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
Usage
Summary
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
Compiling Hello World
sexc ./examples/hello-world.sex -o hello
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:
sexc example/sdl3-triangle.sex -o triangle -- `pkg-config --cflags --libs sdl3` -framework OpenGL
Example
An example of Sex source:
(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))
Compile and run:
~/dev/sex $ ./sexc ./example/hello-world.sex -o hello-world
~/dev/sex $ ./hello-world
Hello from Sex!
What is your name?
Alex
Hello, Alex!
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.
#+macosx (include OpenGL/gl3.h)
#-macosx (include GL/gl.h)
#+(and unix (not macosx)) (define HAVE-EPOLL 1)
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:
sexc prog.sex -f debug,with-sdl
sexc prog.sex --features=debug --features=with-sdl
A feature is never taken away. The host's features can be disabled, e.g. for checking output for other platform:
sexc example/sdl3-triangle.sex -C --no-platform-features --features=linux,unix,x86-64
Aggregate initializers and compound literals
#(...) is a brace initializer. On its own it has no type and takes one
from where it is written:
(var p (struct point) #(1 2))
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:
(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
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:
(var r (struct named) #(struct named : .n 7 .first-name "zoe"))
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:
(defmacro (pair-of-fns a b)
`($ (fn ,a () int (return 1))
(fn ,b () int (return 2))))
($) 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
(pub defmacro (list-T type)
(let ((list-type (cat 'list- type)))
`(struct ,list-type
((value ,type)
(next (* ,list-type))))))
(list-T int)
->
(struct list_int
((value int)
(next (* list_int))))
Wrapper for checking return codes
(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)
...)
->
(pub fn init () int
(if (< 0 (SDL_Init SDL_INIT_VIDEO))
(do (puts "Failed to initialize SDL") (return 1)))
...)
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:
(use-package sex-mode
:load-path "/path/to/sex"
:mode ("\\.sex\\'"))