Vision Tutorial

Installation

  • Download the Windows binary or build a copy for your platform.
  • Place the compiler somewhere on your server. I recommend /usr/bin/vision, but something like /home/username/www/vision will work just as well, as long as CGI scripts are allowed to access the directory.
  • Run Vision scripts using any of these methods:
    • Specify #!/path/to/vision in an executable file containing Vision code.
    • Configure your server to pass files with some extension on to vision. If you do this, I recommend using .vis as the extension.
    • Run vision filename from a CGI script.
    • Run vision -c filename > output.html from the command line for offline testing.

Note that server configuration rules about what constitutes a CGI script (e.g., cgi-bin path or .cgi extension restrictions) still apply to Vision scripts. If you want pretty URIs for your Vision scripts, you'll have to configure your server accordingly or talk with someone who can.

Command-line Options

-c
Generate HTML output only, without any HTTP headers.
-h, -?
Write some help output and exit.
-v
Write the current version number and exit.

Basic Usage

Comments

Comments begin with a hash character (#) and continue to the end of the line.

Text

Literal text output is given in double quotes (""). SGML markup-significant characters are escaped, and newlines are converted to <br/>. Double quotes and backslashes can be escaped with backslashes; the sequence \" translates to &quot;.

"This is some output. \"These quotes\" and this backslash: \\ are literal."

For finer-grained control over output, use a single quotes (''), which do not automatically escape their contents:

'This text can contain <tt>tags</tt> &amp; HTML escapes
as well as newlines, and they will be copied literally to the output.'

When a large quantity of XML output needs to be quoted, use angle brackets (<>), which don't auto-escape and don't require escaping of quotes, but do require that angle brackets within them be balanced, i.e., there can be no partial tags.

<
<h1>Header</h1>
<p style="text-indent: 1em;">Content containing <tt>more XML</tt>.</p>
>

Tags

To produce an empty XML tag, simply give the tag name, terminated by a semicolon:

# Yields <br/>
br;

To give content to the tag, supply it in curly brackets:

# Yields <p>Content</p>
p { "Content" }

A semicolon is optional after a block, but the preferred style is to omit it. Further, semicolons are optional at the end of a block or file. Blocks can be nested just like XML tags.

Very often, a tag will only have one element of content. In that case, you can introduce the content with a colon:

h1 : "Heading";
p : b : "Paragraph";

Note: a colon is a valid character in the middle of an identifier, but not at the beginning or end. Therefore, h1:"Heading" works as expected, but p:b does not: it yields a <p:b> tag. In light of this, you should get into the habit of surrounding the colon character with whitespace, as in the above example.

Note that a semicolon is required to terminate "Heading", but is not required to terminate the p tag, because of the presence of the block.

To give parameters to a tag, supply them in parentheses (()) and separate them with semicolons or commas; commas are considered syntactic sugar for semicolons.

# Yields <img src="..." alt="..."/>
img (src "gross.jpg", alt "Some hilarious meme image");

Note the syntax src "...": no colon is necessary. Parameters can, however, accept a colon-introduced element or a brace-delimited block of content, and this will be evaluated and output in the same manner, e.g., src : "..." or src { "..." }.

Of course, a tag can have both parameters and content:

p (style "text-indent: 2em") { "Lorem ipsum dolor sit amet..." }

Files

To expand the contents of a file, simply specify the literal filename. Literal filenames and identifiers have the same restrictions: the name must contain only alphanumeric characters and any of the punctuation $+-./:_~; it must begin with any of these characters that is neither a digit nor a colon.

# Yields contents of lorem.txt, e.g., "Lorem ipsum..."
../files/text_data/lorem.txt

Templates

Now here comes the really interesting bit: defining templates. In Vision, commands are called directives, and their identifiers begin with an at-sign (@). To define a template, use the @define directive and supply a block:

@define MyTemplate {
    p : "Paragraph";
}

To expand the template, simply say its name:

# Expands to <p>Paragraph</p><p>Paragraph</p>
MyTemplate; MyTemplate;

Templates aren't very interesting without the ability to plug stuff into them. To specify named parameters when defining the template, give them in parentheses following the template identifier:

@define MyTemplate (TITLE, TEXT) {
    html {
        head : title : TITLE;
        body : p : TEXT;
    }
}

Writing template parameter identifiers in all caps is a handy convention that sets them apart from other identifiers, but you can name them however you want, subject to the usual rules. To expand a template with parameters, give the parameters in a content block when you invoke the template:

# Generates a nice simple HTML document containing filler text.
MyTemplate { "Title"; lorem.txt }

If you don't specify a template parameter, its value is empty, and it expands to an empty string. For example:

MyTemplate;

Expands to:

<html>
<head>
<title></title>
</head>
<body>
<p></p>
</body>
</html>

A template Foo can make use of another template Bar at will. Bar need not even exist when Foo is defined: Vision only searches for the definiton of Bar when Foo is invoked:

@define BasicPage (TITLE, CONTENT) {
    html {
        Head : TITLE;
        Body : CONTENT;
    }
}

@define Head (TITLE) {
    head {
        title : TITLE;
    }
}

@define Body (CONTENT) {
    body {
        p : CONTENT;
    }
}

BasicPage { "Title"; lorem.txt }

Templates are just like tags in that they accept either a block or a colon-introduced single element.

Templates can even be defined within other templates. A template defined inside another template is local to the enclosing template, and does not exist outside it. Local templates are usually used for repeated elements:

@define LinksList {

    @define Link (TEXT; URI) :
        li : a (href URI) : TEXT;

    ul {
        Link { 'Home';   'home.htm' }
        Link { 'Abroad'; 'abroad.htm' }
        # ...
    }

}

Variadic Templates

Often it is useful for a template to take a variable number of arguments, or, for instance, to take a few named arguments followed by some content. This is accomplished with the special variable ..., which contains the result of evaluating all extra parameters beyond the named parameters to a template. If no extra parameters are given, ... is undefined. If a template parameter is named ..., its value is immediately overwritten if extra values are supplied.

@define Section (TITLE) {
	h1 : TITLE;
	...
}

Section {
	'Important Stuff';
	p { 'This stuff is important because blah blah blah...' }
	p { 'This other stuff is important because yak yak yak...' }
	p { 'Etc.' }
}

This results in:

<h1>Important Stuff</h1>
<p>This stuff is important because blah blah blah...</p>
<p>This other stuff is important because yak yak yak...</p>
<p>Etc.</p>

Advanced Usage

ID Parameters

In many cases it is useful to specify an id attribute for an element, but not necessarily any other parameters. In that case, rather than write:

div (id "main") { "..." }

You can write:

div main { "..." }

I highly recommend this, as it improves legibility by doing away with inordinate punctuation.

@expand Directive

To expand a file whose name contains characters that are illegal in identifiers, use the @expand directive followed by the quoted filename:

@expand "my#file.txt";

@expand can also be used to expand templates and template parameters:

@define Template (FOO) : @expand FOO;
@expand Template : @expand 'foo.txt';

# Equivalent to:

@define Template (FOO) : FOO;
Template : foo.txt;

@system Directive

In much the same way that using CSS allows separation of markup from styling, using Vision allows the separation of page templates from dynamic content generation. In order to generate dynamic content, you can use the @system directive to execute a command in a shell and capture its output:

@system './news.pl';

If a command requires parameters that you would like to specify in the Vision script, supply a list of parameters in the body of the directive. They will automatically be expanded and joined with spaces between them.

@system './news.pl' { '-u', STUFF }

If a certain command is available on Unix-like systems but not Windows, or vice versa, then you can use the @unix or @windows directives to perform platform-specific processing. These directives expand to an empty string on unsupported platforms.

@chdir Directive

When a dynamic content generator is invoked using @system or its platform-specific cousins, the current working directory of the script is the same as that of the Vision script. To change the current working directory, use the @chdir directive:

@chdir 'pl';
@system './news.pl';

Form Processing

Vision allows you to process forms submitted using either GET, or POST using the default enctype of application/x-www-urlencoded. Variables sent to a Vision script in this manner are defined as templates, prefixed with the request method and a colon. For example, invoking page.vis?foo=bar&boo=far is exactly equivalent to:

@define GET:foo : 'bar';
@define GET:bar : 'far';

# Remainder of page.vis

This works in a similar manner when using POST.

If you intend to use form results with @system, you must read the following section!

@quote Directive

The @quote directive is used to escape shell-significant characters for passing to a shell using @system. Assuming POST:username and POST:password are form variables, you should absolutely never do this:

# DO NOT DO THIS EVER

@system './login.pl' { POST:username, POST:password }

If POST:username or POST:password contain any characters that are significant to the shell, then at best the invocation will not work, and at worst the security of the executing system will be compromised. To combat this, simply use the @quote directive to prevent injection attacks:

# DO THIS

@system './login.pl' { @quote : USERNAME; @quote : PASSWORD }

This guarantees that all potentially sensitive characters are properly escaped for passing via the shell.

Conditions

Sometimes, you may want a template to give different output based on whether a particular template parameter is specified. The @ifdef directive evaluates a block if an identifier is defined; similarly, @ifndef evaluates its block if the identifier is not defined:

@define Count (FIRST; SECOND) {
    @ifdef FIRST {
        FIRST;
        @ifdef SECOND { " and "; SECOND }
    }
}

Count { "Spam"; "Eggs" }    # Expands to "Spam and Eggs".
Count { "Spam" }            # Expands to "Spam".
Count;                      # Expands to nothing.

To make a template parameter undefined while still specifying later parameters in the list, supply an empty string ('', "", or <>) for the parameter:

Count { ''; 'Eggs' }    # Expands to nothing because FIRST is not defined.

Inclusion

To import the contents of a Vision file, use the @include directive. Templates defined in the included file will be available after it is included, and output that the file produces will be produced at the point of inclusion:

# IncludeFile
@define Cow (FOO, BAR) : p { FOO; br; BAR }
# MainFile
@include IncludeFile;    # Or quoted: @include 'IncludeFile';
Cow { "This"; "That" }

A common idiom to prevent multiple inclusion of a header is an inclusion guard, a concept directly borrowed from C++, which has the following form:

@ifndef guard:myheader.vis {
@define guard:myheader.vis : '1';


# Header contents.


} # guard:myheader.vis

Once the file has been included, the template guard:myheader.vis is defined, and subsequent inclusions of the file will therefore skip the contents of the @ifndef, preventing multiple inclusion. The variable used is called a guard token, and is conventionally the header filename prefixed with guard:. This decreases the likelihood that the guard token will be inadvertently used elsewhere as an identifier.

Scope

Like most programming languages, Vision has a notion of variable scope; templates defined within another template are local to the enclosing template, and cannot be used outside the scope in which they were defined. The @define directive is unlike other directives in that it creates a new variable scope; if you define a template within, say, an @ifdef block, that template will continue to be available past the end of the @ifdef.

There are two additional directives available for managing scopes: @local and @export. The @local directive is used to create a new local variable scope. Inside @local or @define, a new template can be created with the same name as another template in another scope, and the pre-existing variable will become temporarily unavailable. After the local scope completes, any templates that are defined within it will cease to exist, and any templates that have been shadowed by local templates will become available again.

@define Salutation : 'Hello. ';
Salutation;
@local {
    @define Salutation : 'Goodbye. ';
    Salutation;
}
Salutation;

This expands to "Hello. Goodbye. Hello. ", as expected.

The @export directive is the opposite of @local: if you create a template in a local scope and want that template to be available after the scope completes, you can use @export to push the definition of that template into the enclosing scope.

The most common usage of this is for conditional exporting. You might write a header that exports certain symbols only if a template named EVERYTHING is defined:

@ifndef guard:myheader.vis {
@define guard:myheader.vis : '1';


@local {

    @define NormalThing { ... }
    @define OtherThing { ... }

    @export NormalThing;
    @ifdef EVERYTHING : @export OtherThing;

} # local


} # guard:myheader.vis

Note: at present the @export directive does not cause the local copy of the template to be undefined, which means that local modifications to the exported template after it has been exported will affect only the local copy. Therefore, any changes made to an already exported template will need to be re-exported in order to persist. This behaviour may change in the future, and may be replaced by the use of an @import directive.

@eval Directive

In order to support certain types of metaprogramming and code generation, the @eval directive allows the result of an expansion to be evaluated as Vision code. As with @system, using @eval on unauthenticated content poses a security risk. The scope of @eval is the same as that of an included file: all externally available identifiers continue to be available, and all identifiers created within the @eval are automatically exported.

@eval : <@define Foo : 'bar'>;
Foo;

@eval is used to great effect in the standard library.

Date and Time

The following commands expand to numeric representations of portions of the server date and time:

@year
Four-digit year, e.g., 2010
@month
Two-digit month, 01–12
@day
Two-digit day, 01–31
@hour
Two-digit hour, 00–23
@minute
Two-digit minute, 00–59
@second
Two-digit second, 00–59