Models

A "model" is a complete source file which the Vesta evaluator can execute.

A Vesta model is made up of three parts in sequence:

  1. An optional files clause
  2. An optional import clause
  3. A block of statements, ending with a return/value statement

The files and import clauses bring in external data. The block of statements is the body of the model. Every model is really a program, or more precisely a function, which is why they end with a return statement. The return statement specifies the result value which will be produced when the model is evaluated.

files Clause

A files clause turns files and directories into variables in your model.  For example, in a model in the same directory with a file named "foo.c" this files clause:

files
  foo = foo.c;

Would put the contents of foo.c into a variable named foo as a text value.  There's a shortcut for naming the variable created after the file or directory being referenced, which works just like the self-assignment shortcut for bindings.  A shorter way to write the above would be:

files
  foo.c;

With the difference being that the variable introduced is named "foo.c" rather than just "foo".  (The SDL identifier syntax rules allow periods in variable names.)

Entire directories can also be turned into variables.  If there was a sub-directory named "sources" in the same directory as a model then this:

files
  sources;

Would introduce a variable named sources with the contents of the directory represented as a binding as its value.  Each file or directory within sources would become a name/value pair of the binding.  Files would get text values with their contents, and directories would get nested binding values.

This brings up a key feature of the Vesta SDL:

As is the first example, you can use any variable name you want to store the contents of the directory:

files
  c_files = sources;

It's also possible to create a binding in-line in a files clause, like this:

files
  c_files = [ utils.c, main.c ];

This creates a variable named c_files whose value is a two-element binding.  Within it, the names "utils.c" and "main.c" are bound the to contents of those files as text values.

You can even create a binding with files from sub-directories:

files
  c_files = [ sources/utils.c, sources/main.c ];

The files and directories referenced in a files clause are almost always named by relative paths, which are interpreted relative to the location of the model containing the files clause.  Absolute paths are permitted, but they are considered poor form and only immutable files and directories within the Vesta repository can be referenced.  (So, for example, you couldn't turn "/vesta" into a variable, because it's appendable not immutable, and you couldn't turn "/usr/lib/libc.a" into a variable because it's outside the repository.)

Note that you can't use "." or ".." in a files clause.

files
  foo = ./file1.txt;      // evaluation stops with "Not found" error

  bar = ../file2.c;       // evaluation stops with "Not found" error

If you reference a nested file/directory without specifying a variable name, the first arc of the path will be used as the variable name.

files
  dir1/dir2/file.txt;               // creates variable named "dir1"

  file.txt = dir1/dir2/file.txt;    // probably more useful

Note that the "=" in a files clause isn't really an assignment statement. You can't use operators on the right-hand side.

files
  files = sources ++ headers;    // syntax error

If you need to reference a file or directory which has a name that is neither a legal identifier nor an integer constant or is a reserved word, you can quote it (just like a text string):

files
  files = [ "file-with-dashes.c", "sub-dir"/foo.txt ];
  dir = "sub-dir"/another_dir;

Take care when referencing files/directories not to try assign them to variables which aren't proper identifiers.

files
  "file-with-dashes.c";    // "not an identifier" error

  1234/foo.txt;            // "not an identifier" error

import Clause

The import clause allows one Vesta SDL model to use another.  For example, this is how std_env references a version of the C++ compiler bridge, how a model to build an executable references the libraries the program is linked against, and how a release model references particular versions of each sub-component.  In other words, imports are used to make modular builds.

Imported models become functions with no formal arguments.  Just as the the files clause assigns files and directories to variables, the import clause creates variables which hold models.  They can be called in the body of the model, just like a function defined within the model.

The simplest way to import another model is with a relative path.

import
  model1 = tests.ves;     // creates the variable "model1" from tests.ves

  model2 = src/lib.ves;   // creates the variable "model2" from src/lib.ves
{
  foo = model1();         // calls tests.ves, storing its result in "foo"

  bar = model2();         // calls src/lib.ves, storing its result in "bar"

  // ...
}

This is often used to separate different components of a build in the same package.  For example, separating the instructions for building a library from the instructions for building associated test programs, or separating the description of how to build a program from the specification of which target platform it should be built for.

Note that unlike in a files clause, you must provide an explicit variable name for models imported with relative paths.

import
  tests.ves;     // parse error

  src/lib.ves;   // also a parse error

As in a files clause, you can import several models into a binding.

import
  b = [ tests = tests.ves,
        lib = src/lib.ves ];
{
  foo = b/tests();    // calls tests.ves, storing its result in "foo"

  bar = b/lib();      // calls src/lib.ves, storing its result in "bar"

  // ...
}

Unlike in a files clause, importing a directory will not create a binding.  Instead, it is equivalent to importing the model named "build.ves" within that directory.

import
  foo = src;    // If "src" is a directory this is equivalent 
                // to "foo = src/build.ves".

As in a files clause, any path component that is neither a legal identifier nor an integer constant or is a reserved word must be quoted:

import
  foo = "subdir-with-dashes"/foo.ves;
  bar = "binding"/bar.ves;

Also note that models must end in ".ves".  If you try to import something which is not a directory and doesn't end in ".ves", the evaluator automatically adds the ".ves" for you.  (It does this even if the file without ".ves" exists, which can be a little counter-intuitive.)

import
  foo = src;    // If "src" is not a directory (even if it is a file)
                // this is equivalent to "foo = src.ves"!

The from keyword can be used to import models relative to a different directory.  This is used to import components from specific versions of other packages.

from /vesta/vestasys.org/vesta import
  progs = eval/4/src/progs.ves;

  // progs = /vesta/vestasys.org/vesta/eval/4/src/progs.ves

The shortcut for "build.ves" and the requirement that model files end in ".ves" also apply within the scope of a from.

from /vesta/vestasys.org/vesta import
  eval = eval/4;
  progs = eval/4/src/progs;

  // eval  = /vesta/vestasys.org/vesta/eval/4/build.ves
  // progs = /vesta/vestasys.org/vesta/eval/4/src/progs.ves

Unlike with local imports, within a from you can omit the variable name and "=" and have the first path component be used as the variable name.

from /vesta/vestasys.org/vesta import
  eval/4;
  weeder/1/src/progs.ves;

  // eval   = /vesta/vestasys.org/vesta/eval/4/build.ves
  // weeder = /vesta/vestasys.org/vesta/weeder/1/src/progs.ves

As with local imports, a binding can be created with several imports.

from /vesta/vestasys.org/vesta import
  b = [ eval/4,
        weeder/1,
        cache/5 ];

  // b/eval   = /vesta/vestasys.org/vesta/eval/4/build.ves
  // b/weeder = /vesta/vestasys.org/vesta/weeder/1/build.ves
  // b/cache  = /vesta/vestasys.org/vesta/cache/5/build.ves

Note that a from changes the path of all imports up to the next from.  Therefore, you must place all local imports before the first from.

from /vesta/vestasys.org/vesta import
  eval/4;

import
  local = local.ves;  // error, tries to import:
                      //     /vesta/vestasys.org/vesta/local.ves

Use of symbolic links (such as the latest link in packages) is not allowed in an import clause. (To do so would allow the possibility of a model's imports changing without the model's contents changing, which would lead to irreproducible builds.)

from /vesta/vestasys.org/vesta import
  eval/latest/build.ves;    // "Not a directory" error

The vimports command parses the import clause of a model and displays the models imported.  The vupdate command provides a method of automatically editing the import clause of a model.

There is a special variable named "_self" which is essentially an implicit import of the model in which it is used.

Model Body

A block of statements, ending with a return/value statement. The return statement specifies the result value which will be produced when the model is evaluated.

{
  return [ foo/bar = 2 ];
}

{
  ovs = [ Cxx/switches/program = [ shared_libs = "-static" ]] ++
        [ Cxx/switches/compile = [ optimize = "-O2", debug = "-g3" ]];

  libs = <./libs/c/clib_umb>;

  return ./Cxx/program("foo", foo_c, foo_h, libs, ovs) ++
         ./Cxx/program("bar", bar_c, bar_h, libs, ovs);
}

Kenneth C. Schalk <ken@xorian.net>   / Vesta SDL Programmer's Reference