Basics

First program

Enter the following into a text editor, and save the file as hello_world.cpp.

 
#include <cppscript>
 
var script_main(var args)
{
    writeln("Hello world!");
    return 0;
}

Next run the program at a command prompt using the cppscript utility

$ cppscript hello_world.cpp
 

The program should output "Hello world!"

hello_world.cpp explained

The first line,

 
#include <cppscript>
 

is required for all C++Script programs.  This tells the C++ compiler to include the C++Script functionality.

 
var script_main(var args)
 

defines a function called script_main, that takes one argument (of type var, with name args), and returns a value of type var.  The next line

 
{
 

starts a block of code, and is required when defining a function.  The next line

 
    writeln("Hello world!")
 

Calls the function writeln(), with the string "Hello world!".  This writes the string to the output.

 
    return 0;
 

returns the value 0 from the script_main function.  This is the value which the program returns to the shell.

 
}
 

Ends the function definition of script_main.

 
$ cppscript hello_world.cpp
 

Runs the cppscript program, passing it the file name hello_world.cpp.  The cppscript program calls the C++ compiler (e.g. g++) to compile hello.cpp and produces the hello executable (or hello.exe).  Finally cppscript executes the program, which outputs "Hello world!".

main() and script_main()

C and C++ programs require a function called main() to mark the start of the program. 

This is less suitable for C++Script because it uses native types (e.g. char**), and it doesn't catch exceptions.  Therefore, C++Script provides a main() function that catches exceptions, and calls a script_main() function instead.  C++Script can use either main() or script_main().

Variables

C++Script uses a single variable type, called var.  A var can take on any type of value, such as:

Whilst in a normal C++ program you need to declare the type of the variable, in C++Script you just use the var type which holds whatever you assign to it.

Like most scripting languages, var can hold values of any type.

e.g. (basic_types.cpp)

#include <cppscript>
 
var script_main(var)
{
    var a=12, b=3.14, c="Hi";
    a=true;
    writeln(a); 
    return 0;
}

Obviously vars do have types at run-time, but they are just not declared in the program at compile-time.

Null

Uninitialized variables (that have not had a value assigned to them) take on the null value.

var x; 
writeln(x);      
// Output: null

The null value is a perfectly legitimate value.  The null value converts to the string "null", the integer 0, or the Boolean false.

var x; 
if(!x) writeln(“x is false”); 

The constant null has the null value.  e.g.

 
x="hello";
writeln(x);
x=null;
writeln(x);    // "null"

Operators

Like normal C++ variables, var values support the entire set of operators.  Thus you can perform arithmetic on var values, and use var values just like any other built in type in C++.

var performs automatic conversion.  Usually the second operand is converted to the type of the first operand.  For example if you add an integer to a string, then the second operand (the integer) gets converted to a string.  On the other hand, if you add a string to an integer, then the second operand (the string) gets converted to an integer.  The following example (operators.cpp) illustrates this.

#include <cppscript>
 
var script_main(var) 
{ 
    var x=12, y=4, z="10"; 
    writeln(x-y);     // 8
    writeln(x+"8");   // 20
    writeln(z+20);    // 1020
    return 0; 
}

Some operations are not supported.  For example attempting to subtract two strings fails (subtract_strings.cpp), throws an exception:

#include <cppscript> 
 
var script_main(var) 
{ 
     writeln("We are about to throw an exception"); 
     var("abc") - var("def"); 
     return 0; 
}

Converting between types

It is straightforward to convert between C++ values and var.

Usually C++ values are implicitly converted to var as needed.  e.g.

var x = 12;
var y = x + 15;
var z = var(“12”)+21; // “1221”

The var class has the following methods to convert the var into a C++ type:

e.g.

 
int i = x.as_int();
double j = y.as_double();

These functions will do their best to perform the conversion, for example var(“1.3e2”).as_int() will return 1, since it tries to parse the string as an integer.

Conversion can also convert between different types of var.  e.g.

var a = "1234"; 
var b = a.as_int();

var has an implicit conversion to bool.  That means that you can put var into conditions, such as if statements and while loops.

Comparisons

The C++ comparison operators (<, >, ==, !=, <=, >=) work on var.

All different var types can be compared.  vars are first compared on type, then by value.  Conversions are never performed for comparison (since comparison should be symmetrical).

Comparison may not work quite as expected, e.g. integers are always less than doubles, no matter what their value.  Also all narrow strings are less than wide strings, no matter what the contents of the string.  Essentially, each var has a built-in type, which is compared prior to comparing the contents of the var.  This is because sometimes a comparison makes no sense (e.g. a list an an integer), or could be ambiguous (e.g a double and an int - does this mean integer comparison or double comparison), or a string and an int (does this mean parse the string as an int, or convert the int to a string?).

All containers (with the exception of strings), compare on object address.  This is because a lexographic comparison might result in an infinite loop.  std::lexographic_compare() is available if that is indeed the intended comparison method.

Variable scope

In C++, the end of the block (a closing curly brace) marks the end of the lexical scope of a variable, meaning that variable names are only local to the block in which they are declared.  In standard C++, variables are destroyed at the end of the block, but in C++Script, vars are destroyed by the garbage collector.

In C++Script, vars usually share the underlying data (with the exception of integers and doubles).  Copying a var does not copy the underlying data.

e.g. (var_copy.cpp)

 

#include <cppscript>
 
var script_main(var)
{
    var a = "abc", b=a;
    b += "def";
    writeln(a);    // abcdef
    return 0;
}

Cloning

A var can be cloned, meaning that it creates a copy of the value.  The unary + operator creates a clone of a var.

This means that the two values (the original and the copy) can be modified independently.  e.g. (var_clone.cpp)

#include <cppscript>
 
var script_main(var)
{
    var a = "abc", b=+a;
    b += "def";   
    writeln(a);    // abc
    return 0;
}

Cloning is supported on all var types.  Some types (such as ints) are not shared, so cloning has no effect, just like unary +.  Cloning of containers (arrays etc) performs a shallow copy, meaning that whilst the array itself is different, the copied contents are still shared.

Comments

Comments can be written in a C style (between /* and */) or C++ style (after //).

var x = f(f(g));    // Ensure that the sprogit is fused

Control flow

C++ (and therefore C++Script) programs are organised into functions, and the statements in each function are executed sequentially.  You can change the sequence (the control flow) using if, for, while, goto (not recommended), exceptions and the new foreach macro.

If-statements evaluate a condition, and execute either the “then” part or the “else” part.  If there is more than one statement in the “then” or “else” parts, then the statements must be grouped using curly braces.  e.g.

if( args.size() != 3 )
{
    writeln( "Please supply 3 arguments" );
    return 10;
}
else
{
    writeln( "Thank you" );
}

The curly brackets are optional if they contain a single statement.

While-statements execute the body of the loop until the condition is false.  e.g. (while.cpp).

#include <cppscript>
 
var script_main(var args)
{
    var i=0;
    while(i++<10) writeln(i);
    return 0;
}

Do-while-statements execute the body of the loop until the condition is false, but execute the loop at least once.

writeln("Enter a number"); 
var m;
do 
{ 
    m = readln().as_int();
}
while( m==0 );

For-statements are a lot like while loops, except that they specify an initializer (the initial state of the loop), the condition (what must be true to continue looping), and an increment (move on to the next item in the loop).  The general structure is as follows:

for(initializer; condition; increment)
    body

e.g.

for(var i=0; i<10; ++i) 
{ 
    writeln(i);
}

foreach

C++Script provides a foreach macro to match the facilities in other scripting languages.  This makes it much simpler to loop through containers.

The first parameter to the foreach macro is the loop variable, and the second parameter is the container.  This example (foreach1.cpp) loops through all of the arguments to the program, and displays a message.

#include <cppscript> 
 
var script_main(var args) 
{ 
    foreach(i, args) writeln("Hello "+i); 
    return 0; 
} 

The range() function creates a range which can then be iterated using foreach.  This example (range1.cpp) displays multiplication tables.

#include <cppscript> 
 
var script_main(var) 
{ 
    foreach( i, range(1,12) ) 
    {
        foreach( j, range(1,12) ) 
             writeln( "" + i + " * " + j + " = " + i*j ); 
    }
    return 0; 
} 

Functions

Functions are named blocks of code.  One of the reasons for splitting a program up into functions is to make the whole thing more manageable.  Functions shouldn't contain more than 20 or so lines of code - obviously rules can be broken, but splitting functionality up into small individually-testable units is a good thing.

Functions have arguments (inputs), and a return value (output).

main() and script_main()

These functions are called when the C++Script program starts.

Library functions

We’ve already met the library function writeln() – it displays the value followed by a new line.  There are many more functions described throughout the tutorial and in the reference section.

C and C++ functions

C++Script can call C and C++ functions.  Like normal C++, you must #include the appropriate header file at the beginning of the .cpp file, and link with the appropriate library.

User-defined functions

The general form of a function definition is

var fn(var arg1, var arg2, var arg3 ...) 
{ 
    // Function body 
    return ... ; 
} 

where fn is the function name, and arg1, arg2 etc are the arguments to the function.  A function can have no arguments, in which case the function is written

var fn() 
{ 
    // ...
} 

It is not possible to define functions inside functions ("nested functions") in C++.

C++ functions are "overloaded". This means that several functions can have the same name but can have different arguments.  The C++ compiler selects the most appropriate function.

Return value

In C++Script, the value returned from functions is a var.  If a function does not return anything at all, the return type can be void, and the function is written

void fn( /* ... */ ) 
{ 
} 

A non-void function returns a value using the return statement.  This is written

return X; 

where X is the value to be returned.  The script_main() function returns an integer to the shell.

Calling functions

We have already seen how to call writeln().  A function is called by supplying the arguments to the function in round brackets after the function name, separated by commas. The number of arguments must match the number of arguments in the function definition, or the compiler will give an error.

This example shows a function being declared and then called.  (function1.cpp)

#include <cppscript> 
 
var add(var a, var b) { return a+b; } 
 
var script_main(var) 
{ 
    writeln( add(2,3) ); // 5
    return 0;
}

Function declarations

C++ has a rule whereby functions must be declared or defined before they are called.  In a single C++ file this is not a problem because you can almost always organise the functions in such a way as to satisfy this rule.  (The exception to this is mutually recursive functions).  With multiple .cpp files, or when you call functions in a library, you must forward-declare the functions, which is the same as a function definition but with a semicolon instead of the function body.  Normally you put the forward-declarations in a header file (.h or .hpp file) which you #include into all of the .cpp files that need it.

This example shows a declaration of a function. (function_declaration.cpp)

#include <cppscript>
 
var f(); 
 
var script_main(var) 
{ 
    return f();
} 
 
var f() 
{ 
    return 2; 
}

Functors

A functor is a var that acts as a function.  Any function of the form

var fn(var, var, var ...) 
void fn(var, var, var, ...) 

can be stored in a var.  The var can then be called as a function, as follows: (functor.cpp)

#include <cppscript> 
 
var sum(var a, var b) { return a+b; } 
 
var script_main(var) 
{ 
    var sum_fn = sum; 
    writeln( sum_fn(2,3) ); // 5
    return 0;
}

See OOP for further explanation of functors

Variable number of arguments

C++ does not have very good support for variable numbers of arguments.  Things are a little bit better in C++Script.  You can have variable arguments either by passing an array to the function, or by using the varargs() function which automatically creates the array from variable numbers of arguments.  e.g. (varargs.cpp)

#include <cppscript>
 
void greet_list(var greeting, var people) 
{ 
    foreach(i, people) writeln(greeting + " " + i); 
}
 
var script_main(var args)
{
    var hello = varargs( bind(greet_list, "Hello") );
    hello("cat", "dog", "horse");
    return 0;
}

Exceptions

An exception is an event indicating an unexpected failure.  Types of failure that might cause exceptions are:

Catching exceptions

Exceptions thrown from C++Script have type var, and can be caught using try-catch statement:

try 
{ 
    // Code which might throw the exception 
} 
catch(var ex) 
{ 
    // Handle exception 
} 

Naturally you can combine this with other C++ exception handling.  Unlike C++, the main() function implemented by C++Script catches and reports exceptions.

Throwing exceptions

You can throw an exception at any time by writing throw X; where X is the value you want to throw.  In C++Script, there is one type of exception (var).  The helper class exception constructs an exception var.  e.g. (exception.cpp)

#include <cppscript> 
 
var compute() 
{ 
    throw exception("compute_failure", "Not implemented");
}
 
var script_main(var) 
{ 
    try 
    { 
        compute(); 
    } 
    catch(var ex) 
    { 
        if( ex.class_name() == "compute_failure" )
            writeln("The computation failed because " + ex["text"]);
        else 
            throw; // Re-throws the exception - caught in main()
    }
    return 0;
}

C++Script's exception handling is limited because you can't switch on type, so you have to check var::class_name().

Finally

It is important that your program is able to recover cleanly whenever exceptions are thrown.  Recovering from exceptions is a topic in itself, however the approach that C++ often uses is to use RAII, have cleanup code in its destructor, or use scopeguard.  Other languages have finally and using statements.  If these words don't mean very much to you then don't worry.  The important concept is the idea that some code is executed  that cleans up whatever needs to be cleaned up (e.g. closing a file, unlocking a lock, terminating a network connection).

C++Script has a garbage collector, which means that you don't need to clean up memory, but there are other resources which may need cleaning up.  The garbage collector will clean up certain resources like files, but much later than you intended if your code fails.  For example

var f = write_file("data.txt");
write_data_to_file(f);    // May throw an exception
f["close"]();

What you want is for f["close"]() to be called even if write_data_to_file() throws an exception.  You can wrap write_data_to_file() in a try-catch block, but it is neater to use a finally macro to execute f["close"]() at the end of the block, even if write_data_to_file() throws an exception.  e.g.

var f = write_file("data.txt");
finally(f["close"]);
write_data_to_file(f);

The finally macro takes a functor and executes it at the end of the block.  See e.g. finally.cpp.