Vaquero is a Lisp variant with single-dispatch message passing, multiple dispatch via generic procedures, first-class delimited lexical environments, first-class delimited continuations, restartable exceptions, object-capability security on operating system interfaces, and syntactic abstraction via procedural macros.
The Vaquero bootstrap interpreter is written in Chicken Scheme.
You'll need this to compile and run it.
cd Vaquero
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 Vaquero. This should give the user a feel for whether the compilation worked.
Or you can build an image from the Dockerfile, if you're into that sort of thing. It will boot into a repl with docker run -it lang/vaquero
.
home> ./vaquero
Usage:
vaquero repl
vaquero eval "<code string>"
vaquero run <filename>
vaquero check <filename>
vaquero expand <filename>
vaquero compile <filename>
vaquero clean
vaquero <filename> is shorthand for vaquero run <filename>
The Vaquero executable responds to the seven messages above.
- repl starts a Read-Eval-Print Loop.
- eval parses a string into code and evaluates it.
- run takes a filename and expands, links, compiles, caches, and executes it.
- check tests the syntax of a file.
- expand pretty-prints the named file with all operators expanded
- compile expands, links, compiles, and caches a file without executing it.
- clean clears the Vaquero cache in ~/.vaquero.
# you don't have to use rlwrap to run the REPL,
# but I can't recommend it enough
home> rlwrap ./vaquero repl
vaquero> (say "Hello, I'm Johnny Cash.")
Hello, I'm Johnny Cash.
null
vaquero>
The executable examples below show various facets of the language.
Traditional single-dispatch OOP
An entity system using generic procedures
Vaquero code is composed of S-expressions. Structures in parentheses are assumed to be lists unless they contain a period, in which case they are a pair. Other structures begin with #( ...).
(1 . 2) ; pair
(1 2) ; list of two elements
#(cell 5) ; cell containing the number 5
#(vector 2 3 5) ; vector of three cells
#(object foo 2 bar 3) ; user-defined type
Unquoted lists are evaluated 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.
The Vaquero 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.
Vaquero is lexically-scoped by default. However, the wall operator allows one to delimit the extent to which subforms can capture the enclosing environment.
(let (x 1 y 2)
(wall (z (+ x y))
z))
; 3
(let (x 1 y 2)
(wall (z (+ x y))
x))
; (runtime-error (undefined-symbol x "Name not defined."))
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 control structure without the sorrows of full continuation capture.
gate and capture correspond to reset and shift in the academic literature on delimited continuations.
(gate
(+ 1
(capture kont
(+ 7
(kont (kont (kont 2)))))))
; 12
The guard operator adds a handler to the error continuation, which is separate from the user continuation. When an error is signaled (via fail or 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.
(proc handler (err kontinue)
(if (= err 'resume)
(kontinue 69)
(if (= err 'default)
42
(fail 'aww-hell))))
(guard handler
(+ 3 2))
; 5
(guard handler
(+ 3 (fail 'default)))
; 42
(guard handler
(+ 3 (fail 'resume)))
; 72
(guard handler
(+ 3 (fail 'crap)))
; (runtime-error aww-hell)
; most system procs throw a more sophisticated error than a symbol
(proc show-it (e k)
(list e.name e.form e.message))
(guard show-it
(error 'wrong-way '(go left) "Left was a poor choice."))
; (wrong-way (go left) "Left was a poor choice.")
Modules are imported via use. The pathname can be a local filesystem path or an HTTP[S] uri.
Modules export a set of names. These names are packaged into a module object in the environment of the use statement.
The import operator allows the programmer to import names into the current environment from a module previously defined by a use statement.
; dk.vaq
(export
shred
pentuple)
(proc shred (x)
(* x x 100))
(op pentuple (x)
%(list $x $x $x $x $x))
The pentuple operator uses quasiquotation.
#!/usr/local/bin/vaquero run
; prog.vaq
(use dk "dk.vaq")
(def n sys.rest.head.to-number)
(say (dk.shred n))
(say (dk.pentuple '(foo bar)))
(import dk shred pentuple)
(say (shred (+ n 2)))
(say (pentuple 'foonballardy))
home> ./prog.vaq 7
4900
((foo bar) (foo bar) (foo bar) (foo bar) (foo bar))
8100
(foonballardy foonballardy foonballardy foonballardy foonballardy)
Two modules may have mutually recursive procedures. If modules import one another, neither can rely on the other to finish evaluation; procedures may be mutually recursive, but not operators. Such leads to an infinite loop.
The only global interfaces to the operating system are stdout, stdin, stderr, and the global procedures which use them: read, write, print, say, and log. All parts of a program, including modules, have access to these. All other operating system services are contained in the sys object, which is available only to the top-level program. Libraries that wish to read command-line arguments, fork processes, or open files must be passed this capability from the top-level.
gen and spec allow the programmer to create generic procedures that dispatch based on the types of an arbitrary list of arguments. It's one answer to the expression problem.
Vaquero operators use ordinary Vaquero code to transform source code at compile time. This allows user-level creation of custom syntax. An example of this power: the only primitive form of looping is tail recursion; all others (loop, for, while) are defined in terms of recursion via operators. They can be found in the prelude.
Vaquero has a few convenient reader literals inspired by Lisp and Perl.
The wiki contains a more detailed reference of all core data types, operators, procedures, and objects.
Most of the interpreter was written by Felix Winkelmann and the Chicken Scheme team, only they don't know it. The bootstrap interpreter snarfs a lot of functionality from Chicken, so implementation details sometimes leak through.
The REPL is a bit fragile. It doesn't handle reader errors gracefully.
Unix domain sockets are broken at the moment because the egg I used hasn't yet been updated to Chicken 5.
The interpreter works well. The metacircular compiler is a work-in-progress; it ain't ready for prime-time.