Index

Ufy Language Specification

Introduction

Ufy is the outcome of a mixture of thoughts about javascript, C, perl and java. It follows their Algol-like structure, and it copies their curly braces outlook. It simulates a meta-language through '#'-directives, like C. It supports object-orientation and is inherently multi-threaded. This document attempts to explain the structure of the language and its syntax, or how ufy's lexer and parser look at ufy-code.

About this document

Conventions

For clarity's sake, several different styles are used in this document. This is a link, for example.

Example ufy code is denoted in this font:

printf("hello world\n");

Usage of constructs, directives, keywords and operators is denoted in the following style:

'#require' S <module>

Lastly, examples sometimes yield output in your terminal. In that case, the following style is used:

'Foobar'

Usage Notation

Definitions

<statements> ::= ( <statement> )*

<text> ::= ( <character> )*

<modulename> ::= ( <modulecharacter> )+

<modulecharacter> ::= 'a'-'z', 'A'-'Z'

Work In Progress

Language Constructs

Back to Top

Commentary

Since you can leave arbitrary, but explanatory text inside comments, commentary makes your code easier to understand for other programmers, including yourself. It is also stripped ruthlessly from your code by the interpreter at the first opportunity because it does nothing with it. Commentary can be denoted in two ways in ufy; as double slash ('//') which creates a comment until the end of the line, and as delimited by '/*' and '*/', which can have any character in between. For documentation which is captured by the parser, please refer to the '#doc' directive.

Example:


// This is a comment.

/*
   So is this.
*/

// /* This is a comment, but not because of the '/*'.

/* // This is also a comment, but not because of the '//'. */

Back to Top | Back to Constructs | The '#doc' directive

Directives

Directives are special within ufy-code. They have the appearance of the precompiler-directives in C-code, of being prefixed with a hash-symbol ('#'), but that is only appearance; the parser passes over ufy-code just once. Ufy directives do however, have special meaning to the interpreter; the interpreter does in fact pass over the parsetree twice, first to interpret the directives and store the functions, secondly to execute the code.
/*
** This directive tells the interpreter to use the 'std' module,
** which is, in fact, an alias for the
** 'stdlib', 'stdio', 'string', 'net' and 'time' modules.
** The 'printf' function is in the 'stdio' module, and the '#use'
** directive below makes sure we can actually use it.
*/
#use std
printf("Hello world\n");
Hello world

Back to Top | Back to Constructs | More on Directives

Blocks

In ufy, as like in all Algol-like languages, one can group pieces of code together by concentrating them in blocks. These blocks form the body of control structures, function- or class-declarations, they can be used sometimes in expressions, or they might just be used as blocks, simply to concentrate code. Blocks are delimited by curly braces, one opening and one closing curly brace at the beginning and ending of a block, respectively. This should pose no problem to programmers who are aware of the workings of perl, java or C; there it's done in exactly the same way.

Example:

#use std
while (1) {
  // here we are inside the block body.
  printf("'Round we go.\n");
}
'Round we go.
'Round we go.
'Round we go.
...

Variables declared within the body of a block, will not survive the end of a block; at that time they are removed from the stack. This is why it can be elegant to sometimes create blocks out of the blue; it will leave your variable namespace outside the block intact.

Example:

#use std
var a = 3;
{
  var a = 5 * a;
  printf("Inside the block, a = %a\n", a);
}
printf("Outside the block, a  = %a\n", a);
Inside the block, a = 15
Outside the block, a  = 3

Back to Top | Back to Constructs

Expressions, statements

Ufy code itself consists of control structures (conditions and loops), expressions and statements. These are listed inside ufy in a simple iterative manner, separated by semi-colons (';'). However, code inside blocks (as part of control structures, for example) doesn't need a terminating semi-colon before the next statement.

Example:

#use std
var a = 5;
var b = 6 * a;
printf("a = $(a)\n");
printf("b = $(b)\n");
// and so on..
a = 5
b = 30

Back to Top | Back to Constructs

Declaration of functions

In order to be able to repeat certain parts of code under possibly slightly different circumstances, ufy has the concept of functions. These functions have to be declared within ufy code, and will not be executed unless they are called. The code is included in their function-body and is parameterized by the parameter-list in the declaration.

Example:

function foo(a, int b) {
  return a + b;
}
foo("b = ", 3);

Declaration of classes

Object orientation is a theory of programming which attempts to bring programming languages closer to modelling the real world. This is done by moving both functions and data under the umbrella of a named entity called a 'class', and then allow the code to create possibly endless instances of those classes, so called 'objects'. Classes can 'inherit' other classes, in fact copying their data and functions, and they can protect certain parts of their interfaces, so that they can show a comprehensive and adequate face to the rest of the code that uses them.

Work In Progress

Back to Top | Back to Constructs

Modularization and namespace protection

Ufy likes modularization. That is, it likes code placement inside properly placed files. It wants native functionality to be easily programmed, compiled and loaded. It tries to be intelligent about finding modules, and it supports modules in many shapes and forms.

Modularization tries to raise an extra question to the programmer. Not only how to divide code into functions and classes, but also where to put them and how to reach them when needed. This can be a great boon when coding is done in teams, or when parts of the code are so good, that they will be needed for time to come or by other people unbeknownst to the original programmer.

In ufy, there are the following types of modules:

And the following possibilities to compound modules in a single namespace:

Source code modules

Source code modules are written in ufy itself, and are commonly used for three things; as an easy (objectified) wrapper around a native module (also ensuring that obscure configurations to the ufy interpreter only get processed when a certain library is used), to provide source code implementations of certain functionality where speed is not an issue but clarity is, and to provide access to new and specific functionality.

Native modules

Native modules have been compiled from C. They usually provide access to functionality which can only be addressed from C. Since C libraries have been written for almost anything, native modules are ufy's portal to the world.

Remote modules

Remote modules can be very useful when functionality has to be distributed. Even though in some set-ups front-end CPU's can be purposely under-powered, or maybe when scalability is a necessity, one must never forget that the CPU cycles won because of distribution must weigh against the CPU cycles lost in forging network requests and parsing network responses.

Aliasing module namespace

Please refer to the 'alias' directive.

Directories as modules

Archives as modules

Work In Progress

Back to Top | Back to Constructs

Directives

Back to Top

Directive '#alias'

Usage:

'#alias' S <name> ( S <module> )+

Creates an alias for one or more modules. Consequent usage of the alias will instead load all of the modules that it's an alias for. The <name> and <module> identifiers are subject to the module-name-constraints.

Back to Top | Back to Directives

Directive '#doc'

Usage:

'#doc' <any amount of text> '}'

Provides a way to document ufy code at the closing of any block. It is equivalent to the normal closing of a block with a curly brace. For example, the following code:

try {
  something();
#doc
Here we try something
} fail {
  telltheworld();
#doc
But we might fail.
}
Inserts documentation into the parsetree-node at the place of this try-fail statement. That documentation can then be retrieved again by inspecting the parsetree. A tool, written in ufy, ufydoc does just that, and it sports a parser for extracting meta-data and fancy formatting from the documentation text, according to the ufydoc specification.

Back to Top | Back to Directives | Tools: ufydoc | Ufydoc Specification

Directive '#include'

Usage:

'#include' S <path>

Includes a file, which it may have found at its exact location, or at a location relative to the searchpath-list kept by the interpreter. When the interpreter passes over the place where the directive is specified, it will go into the included file's body instead and, on emerging from there, it will continue with the next statement.

Seemingly, this may make code specified in the included file completely out of reach. For example:

somefunction();
exit();
#include somefile.ufy
This code would seem to make the inclusion of 'somefile.ufy' to be pointless, after all, the code exits (that is, quits the interpreter) before it can even get there. This is not quite true, as the interpreter will have scanned the code before executing it and, having found the inclusion, will have loaded the code. Any functions defined in 'somefile.ufy' will be known to the interpreter. The code above is, of course, crappy code, but in fact, if 'somefunction' is defined in 'somefile.ufy', then this code will still succeed.

Back to Top | Back to Directives

Directive '#module'

Usage:

'#module' S <name> ( S <key> S '=' S <value> )+

Defines a module to the interpreter. A few key-value-pairs are of specific importance;

Work In Progress

Back to Top | Back to Directives

Directive '#path'

Usage:

'#path' S <path> ( ':' <path> )*

Add one or more paths to the list of paths that the interpreter uses to find modules or files to include. Its use is primarily in the ufy configuration file; so that installed modules in exotic places can have their paths readily available to any using code.

Back to Top | Back to Directives | ufyconf

Directive '#require'

Usage:

'#require' S <module>

Require this module to be used for each and every sourcecode module or include file. It's really a hack to ensure that every piece of ufy source uses the '$lang' module, which has a few functions that you can't do without, as well as the standard input, output and error stream globals. Its use is primarily in the ufy configuration file.

However, you can tune your personal ufy installation in such a way, that you wouldn't ever need to '#use std', for example, by including in your configuration '#require std'. That would make all of your scripts more heavy, but probably most of your scripts one line shorter.

Back to Top | Back to Directives | ufyconf

Directive '#use'

Usage:

'#use' ( S 'private' )? S <module>

Loads a module, and executes it. The public function- and global namespace of the module are accessible to the using script.

Work In Progress

Back to Top | Back to Directives

Keywords

Back to Top

Keyword 'break'

Type: statement
Usage:

'break'

Breaks out of a loop or a switch. Example:

#use std
while (1) {
  break;
  exit();
}
printf("Exit was never called !\n");
Exit was never called !

Back to Top | Back to Keywords

Keyword 'case'

See switch.

Back to Top | Back to Keywords

Keyword 'class'

Type: declaration
Usage:

Declares a class, or creates an anonymous class within an expression.

Work In Progress

Back to Top | Back to Keywords

Keyword 'clone'

Type: operator
Usage:

'clone' S '{' S <statements> S '}'

Or:

'clone' S <functioncall>

Creates a child-thread of execution, in which it executes a set of statements or a functioncall. Returns a queue object which can be used in the yield / fetch pair of functioncalls.

Keyword 'const'

Back to Top | Back to Keywords

Keyword 'continue'

Type: statement
Usage:

Continue within a loop, that is, jump to the closing bracket of the loop-body.

#use std
var i=3;
while (i-- > 0) {
  printf("i is now %a, will we make it to the end of the loop ?\n", i);
  continue;
  exit();
}
printf("We made it to the end !\n");
i is now 2, will we make it to the end of the loop ?
i is now 1, will we make it to the end of the loop ?
i is now 0, will we make it to the end of the loop ?
We made it to the end !

Back to Top | Back to Keywords

Keyword 'default'

See switch.

Back to Top | Back to Keywords

Keyword 'destructor'

Type: declaration
Usage:

'destructor' S '{' S <statements> S '}'

Defines a destructor within a class. The destructor of a class is called when an object of the class is being destroyed. This happens when all references to the class have gone from instances on the stack, or through any instances on the stack.

Example:

#use std
class foo {
  destructor {
    printf(stderr, "Object of class 'foo' is being destroyed.\n");
  }
}
{
  var a = foo();
}
Object of class 'foo' is being destroyed.

Back to Top | Back to Keywords

Keyword 'else'

See if.

Back to Top | Back to Keywords

Keyword 'fail'

See try.

Back to Top | Back to Keywords

Keyword 'for'

Type: control structure
Usage:

'for' S '(' S (<expr>)? S ';' S (<expr>)? S ';' S (<expr>)? S ')' <statement-or-block>

Evaluates expression 1, determines whether the loop can continue based on the outcome of expression 2, and, up completion of the execution of the body of the loop, evaluates expression 3. Example:

#use std
for (i=0; i<4; i++) {
  printf("Your i is now $(i).\n");
}
Your i is now 0.
Your i is now 1.
Your i is now 2.
Your i is now 3.

Back to Top | Back to Keywords

Keyword 'foreach'

Type: control structure
Usage:

'foreach' S '(' S ( <ident> )? S ';' S ( <ident> )? S ';' S <expr> S ')' <statement-or-block>

Evaluates the expression, which must yield a list as a result, and iterates through each key-value-pair of the list, assigning them to the optional identifiers given. Example:

#use std
var animals = [ 'cat', 'dog', 'horse', 'last' -> 'canary' ];
foreach (n; animal; animals) {
  printf("Animal at number $(n) is '$(animal)'.\n");
}
Animal at number 0 is 'cat'.
Animal at number 1 is 'dog'.
Animal at number 2 is 'horse'.
Animal at number last is 'canary'.

Back to Top | Back to Keywords

Keyword 'function'

Type: declaration
Usage:

'function' S <ident> S '('

Work In Progress

Back to Top | Back to Keywords

Keyword 'if'

Type: control structure
Usage:

'if' S '(' S <expr> S ')' S <statement-or-block> ( S 'else' S <statement-or-block> )?

If evaluates the expression, and based on its outcome, it will execute the first body of code, or, when present, the second body of code. The first body of code (the 'if'-block) will be executed only when the expression evaluates to true, the second body of code (the 'else'-block) will be evaluated only in the opposite case. It's possible to chain 'if' and 'else' together to something resembling a switch statement:

if (foo == 'bar') {
  dosomething();
} else if (foo == 'foobar') {
  dosomethingelse();
} else if (foo == 'barfu') {
  dosomethingdifferent();
} else {
  dosomethingentirelydifferentstill();
}

Back to Top | Back to Keywords

Keyword 'isa'

Type: operator
Usage:

<object> S 'isa' S <class>

Or:

<object> S 'isa' S <object>

Returns true when the first object is indeed of the mentioned class or one of its subclasses, or, when an object is given as its second operand, whether the first object is of the class of the second object's class or one of its subclasses.

Example:

#use std
class foo {
}
class bar : foo {
}
var a = bar();
if (a isa foo) {
  printf("a is a foo !\n");
}
a is a foo !

Back to Top | Back to Keywords

Keyword 'lock'

Type: control structure
Usage:

'lock' S '{' <code> '}'

Or:

'lock' S '(' S <expr> S ')' S '{' <code> '}'

Work In Progress

Back to Top | Back to Keywords

Keyword 'member'

Type: declaration statement
Usage:

Work In Progress

Back to Top | Back to Keywords

Keyword 'method'

Type: declaration
Usage:

Work In Progress

Back to Top | Back to Keywords

Keyword 'operator'

Type: declaration
Usage:

Work In Progress

Back to Top | Back to Keywords

Keyword 'private'

Work In Progress

Back to Top | Back to Keywords

Keyword 'return'

Type: statement
Usage:

'return' ( S <expr> )?

Work In Progress

Back to Top | Back to Keywords

Keyword 'struct'

Work In Progress

Back to Top | Back to Keywords

Keyword 'succeed'

See try.

Back to Top | Back to Keywords

Keyword 'switch'

Type: control structure
Usage:

'switch' S '(' S <expr> S ')' S '{' ( S 'case' S <expr> S ':' ( <statement> )* )* ( S 'default' S ':' ( <statement> )* )? '}'

Work In Progress

Back to Top | Back to Keywords

Keyword 'try'

Type: control structure
Usage:

'try' S '{' S <statements> S '}'

Or:

'try' S '{' S <statements> S '}' S 'fail' S '{' S <statements> S '}'

Or:

'try' S '{' S <statements> S '}' S 'fail' S '{' S <statements> S '}' S 'succeed' S '{' S <statements> S '}'

Or:

'try' S '{' S <statements> S '}' S 'succeed' S '{' S <statements> S '}' S 'fail' S '{' S <statements> S '}'

Or:

'try' S '{' S <statements> S '}' S 'succeed' S '{' S <statements> S '}'

Try protects the thread execution of statements inside the first statement block against errors. When an error occurs, the code will break out of the first statement block, and start executing the code inside the 'fail' statement block, if present. If a 'succeed' statement block is present and no error occurs executing the first statement block, then that block will be executed after the thread breaks out of the first statement block naturally. Example:

#use std
try {
  throw("throw causes an error.");
} fail {
  printf("An error occurred: " + catch() + "\n");
} succeed {
  printf("You won't see this.\n");
}
An error occurred: throw causes an error.

Back to Top | Back to Keywords | Function throw | Function catch

Keyword 'var'

Type: declaration statement
Usage:

'var' S <ident> ( S '=' <expr> )? ( S ',' S <ident> ( S '=' <expr> )? )*

Declares one or more variables on the stack, and optionally assigns a value to them. Example:

var a = 'foo', b, c = 'bar';

Work In Progress

Back to Top | Back to Keywords

Keyword 'void'

Type: literal
Usage:

'void'

Is really a literal for the 'null'- or undefined value, and as such, can only be used in expressions. Example:

#use std
var a = void; // which is automatic, but this is an example
if (a == void) {
  printf("a is void.\n");
}
a is void.

Back to Top | Back to Keywords

Keyword 'while'

Type: control structure
Usage:

'while' S '(' S <expr> S ')' S <statement-or-block>

//.. text

#use std
var i=4;
while (--i >= 0) {
  printf("i=$(i), counting down..\n");
}
i=3, counting down..
i=2, counting down..
i=1, counting down..
i=0, counting down..

Back to Top | Back to Keywords

Operators

Work In Progress

Configuration

Please refer to the ufyconf documentation.

Libraries

Please refer to the libraries documentation.


Author: Kees Jan Hermans
Date: 2006-07-06