Language

Sytes implements a Scheme-like mini language to be used in templates. It
has most features you'd expect from a Lisp—lexical scope, closures,
macros—but it does not expose much of the host Lisp system. Templates
should be kept simple.

In this section there are some notes about the language; to get familiar
with the syntax you might want to read the syntax
pages.

Sytes, template contexts

Sytes is designed to be able to run multiple websites on the same Lisp
image. A Hunchentoot dispatcher checks the HOST from the HTTP request and
selects the appropriate syte instance.

Each syte instance contains an execution context (let's name it *syte-context*) where one can define functions to be seen only by this
syte. This context inherits from *toplevel-context* which
is where most of the core language functions are defined. In turn, for
each template that is compiled a new execution context is created, having
*syte-context* as parent.

Compiled templates are cached in memory and will not be recompiled if the
file wasn't modified since the time it was cached. A classic problem to
be wary about is macros—if you modify a file containing macros, it will
not automatically force a recompilation of all templates using those
macros.

Variables

There are two ways to define variables in Sytes, and they're different
even as top-level expressions:

(defglobal foo 10)
(define foo 10)

The first creates a global variable, while the second creates a lexically
scoped one. As top-level definitions, both of them make the variable
usable as if it were global, but the difference becomes important when you
want to export functions from a template to use them in another. I'll get
to this later.

define will create lexical variables in the most nested
enclosing scope. Functions delimit scope. For example:

Defining functions

As you can see above you can use define for defining
functions, however, most of the times you will want your functions to be
global. You can use defun for that:

{[defun say-hi (dude) {<p>Ciao {dude}!</p>}]}

Internally, defun uses defglobal to do its
job.

Functions support Scheme-like variable argument lists.

Modules

Because each template runs in its own execution context, they can't share
anything—not even global variables. Even if you have
{(defglobal foo "the foo")},
that doesn't make “foo” available in other templates.

Globals however are treated as “exported” functionality when you include
another template via require:

require looks up “foo.syt” (relative to the current
template), compiles it if needed and executes it, but instead of returning
its text output (which would be a bunch of newlines for the above
example), it returns its context. Special magic wired into the parser
allows us to use the dot notation to access global variables from a
context.

Based on require we also have an import
macro which makes selected symbols available as variables in the current
context:

{[import "foo.syt" 'callme]}
{(callme "guess why")}

Compilation and evaluation

There are 3 distinct phases that combine to execute a template:

parsing

compilation

evaluation

Unlike Common Lisp, you can't customize the Sytes reader (except for the
.SYNTAX directive), but you have more control over the last
two items: compilation and evaluation.

Because we support macros, the compiler needs to be able to execute macros
at compilation time. Not much else is executed at this phase, so if
macros use a function previously defined, for example, it won't be able to
“see” it because the function begins to exist effectively after its
definition is evaluated, which happens at the last phase.

A single special form allows you to control this: eval-when-compile.
What follows might seem a little tricky (it took me quite some time to
figure out Common Lisp's evaluation model), here's an example and
following it you'll see the template output.

{[eval-when-compile
(define seen 0)]}
{(set! seen (1+ seen))}

Output:
64

When you read this the number might not be 1. It'll actually increase at
each page request. Try it, refresh this page! Also note that this number
doesn't count your views, but all page views (all other visitors too).
This is a little useless, but it serves to explain the function of
eval-when-compile: its body is evaluated at the time the
template is compiled (therefore, the variable is defined at phase
“compilation” and its value set to zero). The following set! expression increases the current value of the variable and
returns the new value; this is executed in phase “evaluation”, since it's
outside the eval-when-compile body. Because the template is
not compiled at each request, but only when it's changed, the value keeps
increasing until the page is modified or the server restarted.

The use case for eval-when-compile is therefore to define
stuff that is required at compile time, such as functions used by macros,
but also it can be used as a means to optimize code. However, I would
advise against using it if it's not really necessary.