-
Notifications
You must be signed in to change notification settings - Fork 4
tldr
The Sexy bootstrap interpreter is written in Chicken Scheme.
You'll need this to compile and run it.
cd Sexy_github_repo_directory
sh ./bin/get_eggs.sh # may need to run this part as root
sh ./bin/compile.sh
sh ./bin/run_tests.sh
Compilation should take a minute or two. run_tests.sh will execute a few hundred tests written in Sexy. This should give the user a feel for whether the compilation worked.
./sexy
Usage:
sexy repl
sexy exec "<code string>"
sexy run <filename>
sexy check <filename>
sexy expand <filename>
sexy compile <filename>
sexy clean
The Sexy executable responds to the seven messages above.
rlwrap ./sexy repl # you don't have to use rlwrap to run the REPL,
# but I can't recommend it enough
(sexy) (sys.say "Hello, I'm Johnny Cash.")
Hello, I'm Johnny Cash.
null
(sexy)
null lacks value. It's not equal to anything but itself. It answers almost every message with null.
true and false are the boolean values. null, 0, "", and empty lists, vectors, records, and objects evaluate to false in a boolean context. All other values evaluate to true.
Sexy, like most programming languages, has numbers.
Single characters are called runes in Sexy and are preceded by a whack: \A, \x, \λ, \א, \象. There are special rune literals for whitespace: \space, \tab, \lf, and \cr.
A symbol names a value. They make the best record keys and object messages.
A string of runes is called a text. They have built-in messages for dealing with regexen. They are normally delimited by quotes ("Hi guys!\n"), but there are also reader literals for unescaped quoting (text: ...) and string interpolation (template: ...).
The humble pair is the simplest compound data structure. Pairs form into lists and trees, and Sexy code is made from these.
A vector is like a heterogeneous array.
A record is an unordered set of pairs (hashtables, under the hood).
Every Sexy procedure is first class, is variadic, and can accept arbitrary optional arguments.
Sexy operators are procedures that run at compile time. They alter source code to create new syntax.
Sexy environments are first-class as well. They can be accessed with the env operator.
A stream is a sequence of runes, possibly from outside the program. Input streams have a number of useful parsing primitives. Output streams can write, print, or say things to the outside world.
Sexy supports both tcp and unix sockets.
Programmers can create their own data types with the object procedure. The auto: option allows the creation of thunks that auto-execute on message reception. The resend: keyword allows an object to easily delegate messages to another object. The default: keyword allows an object to answer arbitrary messages it doesNotUnderstand.
(def foo 1)
(sys.say foo) -> 1
(set! foo 2)
(sys.say foo) -> 2
Execute the given expressions in sequence, returning the value of the last one. Procs and lets have an implied seq, so this form is mostly useful in conditionals.
(seq
(sys.say "Here it comes!")
(sys.say 42)
true)
(if true 1 2) -> 1
(if false 1 2) -> 2
(when (= x 0)
(sys.say "Zero!"))
(cond
(= x 0) 'zero
(= x 1) 'one
default:
(seq
(sys.say "Fell through!")
'infinity))
(case x
(0) 'zero
(1 2 3 5 7) 'small-prime-number
(4 6 8 9) 'small-composite-number
default: 'way-too-big)
(def square
(proc (x)
(* x x)))
(proc square-2 (x)
(* x x))
(def square-3
(_ (* _ _)))
Sexy has tail-call optimization, so a program written in tail-recursive form can loop forever without blowing the stack. let is useful for anonymous recursion. The while form has next and last operators. for has next, last, and redo.
(let loop (x items.head xs items.tail acc ())
(if items.tail
(send (pair x acc) 'reverse)
(loop xs.head xs.tail (pair x acc))))
(let (i 0 total 0)
(while (< i 20)
(set! total (+ total i))
(set! i i.inc)
(list i total)))
-> (20 190)
(for (i 0 total 0) (<= i 20) (set! i i.inc)
(set! total (+ total i))
total)
-> 210
(def foo 17)
(def bar (list 1 2 3))
foo -> 17
(quote foo) -> foo
'foo -> foo
bar -> (1 2 3)
'bar -> bar
(qq (foo bar)) -> (foo bar)
(qq ((unq foo) (unq bar))) -> (17 (1 2 3))
(qq ((unq foo) (unqs bar))) -> (17 1 2 3)
%($foo $bar) -> (qq ((unq foo) (unq bar))) -> (17 (1 2 3))
`(,foo ,bar) -> (qq ((unq foo) (unq bar))) -> (17 (1 2 3)) ; included for old Lisp grognards
%($foo @bar) -> (qq ((unq foo) (unqs bar))) -> (17 1 2 3)
`(,foo @bar) -> (qq ((unq foo) (unqs bar))) -> (17 1 2 3) ; ditto. No , before the @ though.
Sexy has a number of fun features.
Sexy code is composed of S-expressions, except that it does not recognized dotted pairs. Structures in parentheses are assumed to be lists unless they begin with a keyword (that is, a symbol ending in a colon, like foo: or bar:), indicating a reader literal. There are special literals for quasiquotation and such as well.
(1 2 3) -> list
(pair: x y) -> pair
(vector: 1 2 3) -> vector
(record: x 1 y 2) -> record
There is a single namespace for data and procedures, as in Scheme.
Lists that begin with something other than a keyword are interpreted as code. The head of the list (the first item) should be either an operator, a procedure, or another object that responds to the apply message.
As mentioned, a tail-recursive Sexy program can loop forever without devouring all the RAM.
The Sexy global environment is sacred. Its names cannot be reassigned or shadowed. This helps eliminate a certain class of symbol capture problems with macros. So long as your operators are pure - that is, relying only on global operators and procedures or other pure operators - you should have little to fear. proc will always mean proc.
The Sexy object system resembles Self more than Smalltalk or Java. There are no built-in classes or prototypes, per se. An object is a single entity, created to respond to certain messages; a "class" is nothing more than a factory procedure that takes some arguments and returns a particular kind of object. The auto: keyword tells an object to execute the thunk named as an auto, rather than returning it. The resend: keyword allows for an object to delegate messages to as many other objects as it likes. default: allows objects to answer any arbitrary message.
Like Scheme, Sexy is lexically-scoped by default. However, the wall operator allows one to delimit the extent to which subforms can capture the enclosing environment.
The env operator grants access to the local environment object.
First class sub-continuation capture gives the programmer the ability to build coroutines, generators, backtracking, or any other fancy control structure without some of the sorrows of full continuation capture.
gate and capture correspond to reset and shift in the academic literature on delimited continuations.
The guard operator adds a handler to the error continuation, which is separate from user-level delimited continuations. When an error is signaled (via error), a handler can do one of three things: return a default value to the enclosing scope, retry the computation from the sub-form where it errored by providing another value, or throw an error itself, at which point the next handler in the error continuation is called.
Modules are imported via load or use. The pathname given can be a local filesystem path, an HTTP or HTTPS uri, or a symbol with a corresponding procedure in ~/.sexy/symbols.sxy.
Modules accept arguments and return a value. This value must normally be assigned to a name in the local environment. Modules do not affect the importing environment or continuations at run time; the syntax operator, if present, will affect the importing environment at compile time.
The same module may be imported at different points in the same program, with different arguments, and return a different value in each case.
Because modules accept arguments and return a value, they can be recursive. A module with a termination clause in its lib procedure can import itself, and separate modules can be mutually recursive. The dependencies don't have to form a DAG.
All operating system services are contained in the sys object, which is available only to the top level program. Libraries that wish to print, fork processes, or open files must be passed this capability from the top level. Requesting certain items from sys should be part of the module's lib signature, if necessary.
gen and spec allow the programmer to create generic procedures that dispatch based on arbitrary predicate expressions. It's one crazy answer to the expression problem.
Macros are one area in which Sexy takes after Common Lisp more than Scheme. Sexy operators use ordinary Sexy code to transform source code at compile time. This allows user-level creation of custom syntax.
Sexy has a few convenient reader literals inspired by Lisp and Perl.