Cinquecento Libraries

Cinquecento provides a syntax-based mechanism for modularizing code. The primary goals of this system are both to allow the creation of libraries that do not assign global variables, and to allow easy swapping of different implementations for the same set of symbols into a given piece of code.

In its most basic instantiation, a library is a file containing definitions for various functions and variables. There is currently no mechanism for putting macros (i.e. @defstx definitions) in a library -- any macros appearing in a library currently gets bound at the top level. Future version of the library system will remedy the macro limitations.

An example library file might look this file, named lib1.cqct:

@local helper_func2;
@export( func1, func2 );

@define func1() { ... }
@define helper_func2() { ... }
@define func2() { ... }

That code defines and exports two symbols: func1 and func2. It also defines a local variable helper_func2 that is visible only within the library. Assuming lib1.cqct is in the load path, Another bit of code can then run using those symbols as follows:

@with_imports( lib1 ) {
  ...
  func1();
  ...
  func2();
  ...
}
The @with_imports macro loads the library lib1.cqct, and binds all of that library's exported symbols in the following scope. Since lib1.cqct exports both func1 and func2 both symbols will be available. If one already has a binding for lib1_func1 and wants to bind it to a different symbol, one can do the following:
@define func1() { ... }
@with_imports( (lib1_func1, lib1.func1) ) {
  ...
  func1(); //calls above function 
  ...
  lib1_func1(); //calls func1 from lib1.cqct
}
In this case, the function func1 from lib1 is bound to the symbol lib1_func1 while the original binding for func1 remains unchanged. This sort of rebinding allows a programmer to avoid symbol colision for symbols defined by multiple libraries.

When writing a library one can use @import instead of @with_imports. Consider for example lib2.cqct


@export( something, somethingelse );
@import( lib1 );

@define something() { 
  ... 
  func1();
  ...
}
@define somethingelse() { ... }
The @import macro only works inside a library file -- at the top level @with_imports must be used instead.

@import and @with_imports are both variable argument macros for which all of the following are legal:


@import( lib1, lib2 );

@with_imports( (f1, lib1.func1), lib2 ) { ... }

@import( (func1, lib1.func1), (lib1_func2, lib1.func2), lib2 );

If one wants to import a file that is in a directory dir1 that is in the load path, one may use the following constructions:


@import( dir1/lib, (f1, dir1/lib.func1));

@with_imports( dir1/lib, (f1, dir1/lib.func1) ) { ... }

Imported libraries must be present in the load path. That is, when importing from lib1, there must be a lib1.cqct present in the load path at compile time. Recursive or mutually recursive libraries are not allowed. Any @defstx statements in any library are bound globally during compilation.

Dynamically determined imports

The above macros allow one to specify at compile time which library to import and what function renaming to do. If one wants to decide on the library that will be imported at runtime, one can use the dynamic_imports macro. This macro is just like the with_imports macro, except that it evaluates its arguments at runtime and imports libraries specified by the resulting strings. For instance:


lib = "dir1"; // dir1/somthing.cqct defines fn1
lib2 = "dir2"; // dir2/something.cqct also defines fn1
foreach(@lambda(dir) {
  @dynamic_imports( sprintfa("(f, %s/something.fn1)",dir) ) {
    f();
  }
}, [lib,lib2]);

Cinquecento Library Macros

@add_loadpath(path))
path: string
nothing
This function adds the given path to the load path at compile time. Note that if path is computed using an expression, the expression will be evaluated at compile time before any compiled code has been run. Because of this, one can only use simple, self-contained expressions to compute path. For instance:
@loadpath("/"); //legal
@loadpath(loadpath[0]+"../something"); //legal
path = compute_path(); //this statement does not execute until run time
@loadpath(path); //error: path is unbound until runtime
@loadpath(compute_path()); //error: compute_path is also unbound until runtime
      
@dynamic_imports( expression [,expression...] ) { body }
expression: cqct expressions
Macro that allows one to decide at runtime which libraries are to be imported. The expressions will be evaluated at runtime and should produce string representations of the import_spec statements described in @import. The macro body will be run with symbols from libraries bound just like an @with_imports macro.
@export( sym1 [, sym2 [,...]] )
sym1: symbol name
nothing
Can only be used inside a library file. Exports the named symbols. Only symbols named in this way will be exported from the library.
@import( import_spec [, import_spec [,...]] )
import_spec: (dir/)*libname or (sym, (dir/)*libname.sym) or
nothing
Can only be used inside a library file. Imports the named libraries and symbols. For each import_spec, one of the following happens:
  • If the import_spec is (dir/)*libname, then the library libname is loaded from the loadpath concatenated with the specified directory sequence. All symbols exported from that library are bound to locals in the current library. For example:
    @import( dir1/lib1 );
    
    Will import all symbols exported by the library found in the first dir1/lib1.cqct in the load path (the load path is searched in order).
  • If the import_spec is (sym, (dir/)*libname.esym), then the library libname is loaded and the symbol esym from that library is bound to the symbol sym in the current library. For instance
    @import( (myfn1, dir1/lib1.fn1) );
    
    Will load the first file dir1/lib1.cqct found in the load path as a library and set the local variable myfn1 to the value of fn1 in that library.
@library(name) { body }
name: library name
body: cqct code
This macro parses its containing body as library code with the given library name. In the body, the @import and @export macros work as though they were in the file associated with the named library. This macro can be used to create new libraries or to add to existing libraries.
@with_exports(name, sym [, sym [,...]] ) { body }
name: library name
sym1: symbol names
body: cqct code
Mainly for internal use. This macro exports the given symbols that are defined in its body as though they were associated with the given library name.
@with_imports( import_spec [, import_spec [,...]] ) { body }
import_spec: import_spec
body: cqct code
Used to import code from libraries. See @import for a description of import_spec. All imported symbols will only be visible inside the provided body.