SGScript tutorial


Table of Contents


The language

println( "Hello, world!" );

SGScript is a dynamic, procedural language, similar in syntax to many other programming languages, including C and JavaScript. It consists of various operations and structures that can be combined to specify operations and data that they use.


The basics

function printText()
{
	println( "text" );
}
printText();
y = 15.51;
x = 3 * y + 10;
x * y;
include "math";
function sinc( x )
{
	xpi = x * M_PI;
	return sin( xpi ) / xpi;
}
sinc3 = sinc( 3 );
x = 1; y = 2;
x += y;
++x;
--y;
x = 5; y = 3;
x *= y * ( y + x );
y -= x + x * y;
y += 5 * y += y;
printvar( y );
println( dumpvar( x ) );
x = 5;
global y = 6;
function z()
{
	a = 7;
	global b = 8;
	function c(){}
}
// testing the assignment operator
a = 5;
/* this ----------
-- should print --
------------- 5 */
println( a ); ////

Data types

a = null;
b = true;
c = 123;
d = 123.456;
e = "text";
f = function(){ return 4; };
g = println;
h = io_file(); // returns a file object
i = toptr(100);
arr = [ 1, 3, 5, 7 ];
println( arr[2] ); // prints 5
println( arr[4] ); // warning: index out of bounds
println( arr.size ); // prints 4
arr = [];
arr.push( 5 );
x = arr.pop();
arr.unshift( 4 );
y = arr.shift();
dct = { x = 5 };
dct[ "key" ] = "value";
println( dct ); // {x=5,key=value}
fnmap = map();
fnmap[ print ] = "print";
myObj = { x = 5, y = 7 };
function myObj.moveLeft(){ --this.x; }
myObj.moveLeft(); // method call
function myObj_moveRight(){ ++this.x; }
myObj!myObj_moveRight(); // compatible call

Flow control

if( a > 5 )
{
	println( "'a' is greater than 5" );
	if( a < 10 )
		println( "...but less than 10" );
}
else
	println( "'a' is not greater than 5" );
while( a > 5 )
{
	println( a );
	--a;
}
for( i = 0; i < 5; ++i )
	println( i );
foreach( name : [ "one", "two", "three" ] )
	println( name ); // prints one, two, three
foreach( key , : _G )
	println( key ); // prints names of all global variables
foreach( key, value : myObject )
{
	if( string_part( key, 0, 2 ) == "!_" )
		println( value ); // print all values for keys beginning with "!_"
}
foreach( value : data )
{
	if( value === false )
		continue;
	if( value )
		break;
}

Advanced concepts

Bitwise operations

hex = 0x12400;
bin = 0b10011010;
b_or = hex | bin ^ hex & bin;

The main difference from other languages is that the integer type by default is 64 bits long. Left/right shift rules are equal to those of the C language. Thus, it is safe to use only shift distances between 0 and 63.

Truth tables for AND/OR/XOR operations

AND01 OR01 XOR01
000 001 001
101 111 110


Building with SGScript

This section describes all supported ways to compile SGScript and integrate it into your project.


Downloading SGScript

There are two kinds of downloads - source and binaries. All downloads are available in the download page. Source files are hosted on Github.

Even though master branch is supposed to be the 'stable' branch, it is highly suggested that apidev branch is tried first since it is the most up-to-date branch, it is expected that generally less bugs are there. master branch is more thoroughly tested at the time of release and apidev may contain recent changes that subtly break the build.


Building with GNU Make

Required software

Building

@ECHO OFF
mingw32-make %*

Build options

Targets:

Options:

Additional build options & platforms


Building with IDEs

All IDE-related build data is under the build directory.

Supported IDEs

IDEs with necessary files but without support


Building with other tools

CMake

There is a file CMakeLists.txt in the root directory of the project. It only builds the dynamic library at the moment.

File integration

The code is made to support various compilers and compile under C and C++ so it is quite safe to just drag & drop it into your project.


Including SGScript into your project

There are generally two ways to include SGScript: link the library or include the files directly.

Note: Including the files isn't an option if SGScript modules are to be used.

For simplified inclusion of files, add src/ and ext/ to the include paths.


Using the C API

This section describes various use cases and how to apply the SGScript C API to achieve the expected results.


Starting up and running code

SGS_CTX = sgs_CreateEngine(); // SGS_CTX is alias to `sgs_Context* C`
sgs_Include( C, "something" );
sgs_ExecFile( C, "myscript.sgs" );
sgs_DestroyEngine( C );
sgs_GlobalCall( C, "main", 0, 0 ); // may fail but it's not important in this case
sgs_PushBool( C, 1 );
sgs_PushInt( C, 42 );
sgs_PushReal( C, 3.14159 );
sgs_PushString( C, "some C string" );
sgs_PushStringBuf( C, "not\0really\0C\0string", 19 );
sgs_PushPtr( C, C );
sgs_PushGlobalByName( C, "main" ); // assuming this does not fail
sgs_Call( C, SGS_FSTKTOP, 6, 1 ); // calls function from global 'main' with 6 arguments and expects 1 value in return
// again, it's assumed that sgs_Call does not fail
// ...but it may, so checking return value is a very good idea
if( SGS_CALL_FAILED( sgs_GlobalCall( C, "func", 0, 3 ) ) )
    /* handle the error */; // it is important this time since stack state matters
sgs_Int i = sgs_GetInt( C, -3 ); // first return value
const char* string_ptr = NULL;
sgs_SizeVal string_size = 0;
if( sgs_ParseString( C, -2, &string_ptr, &string_length ) )
    /* successfully parsed a string, deal with it */;
sgs_Bool b = sgs_GetBool( C, -1 );
sgs_Pop( C, 3 ); // remove all 3 returned values from top of the stack
sgs_PushBool( C, 1 );
sgs_PushInt( C, 42 );
sgs_PushReal( C, 3.14159 );
sgs_PushArray( C, 3 ); // pushes an array with 3 items, made from 3 topmost stack items

sgs_PushString( C, "some C string" );
sgs_PushStringBuf( C, "not\0really\0C\0string", 19 );
sgs_CreateDict( C, NULL, 2 ); // pushes a dictionary with one entry, made from 2 topmost stack items

Making your own native functions

int sample_func( SGS_CTX )
{
    char* str;
    float q = 1;
    SGSFN( "sample_func" );
    if( sgs_LoadArgs( "s|f", &str, &q ) )
        return 0;
    // ... some more code here ...
    sgs_PushBool( C, 1 );
    return 1; // < number of return values or a negative number on failure
}
// method #1
sgs_SetGlobalByName( C, "sample_func", sgs_MakeCFunc( sample_func ) );
// method #2
sgs_RegFuncConst funcs[] = { { "sample_func", sample_func } };
sgs_RegFuncConsts( C, funcs, sizeof(funcs) / sizeof(funcs[0]) );
// method #3
sgs_RegFuncConst funcs2[] = { { "sample_func", sample_func }, SGS_RC_END() };
sgs_RegFuncConsts( C, funcs2, -1 );

Sub-item (property/index) data access

// push the 'print' function
// method 1
sgs_PushGlobalByName( C, "print" );
// method 2
sgs_PushEnv( C );
sgs_PushProperty( C, -1, "print" );
sgs_PopSkip( C, 1, 1 );
// method 3
sgs_PushEnv( C );
sgs_PushString( C, "print" );
sgs_PushIndex( C, sgs_StackItem( C, -2 ), sgs_StackItem( C, -1 ), 1 );
sgs_CreateDict( C, NULL, 0 );
sgs_PushString( C, "test" );
sgs_SetIndex( C, sgs_StackItem( C, -2 ), sgs_StackItem( C, -1 ), sgs_StackItem( C, -1 ), 0 );
// result: {test=test}

Prefer index access (isprop=0) to property access since the general convention is that indices provide raw access but property access can be augmented with function calls.

sgs_Variable dict, key;
sgs_CreateDict( C, &dict, 0 );
sgs_InitString( C, &key, "test" );
sgs_PushInt( C, 5 ):
sgs_SetIndex( C, dict, key, sgs_StackItem( C, -1 ), 0 );
// result: {test=5}
sgs_PushVariable( C, dict );
sgs_Release( C, &dict );
sgs_Release( C, &key );
sgs_PushInt( C, 3 );
sgs_PushInt( C, 4 );
sgs_PushInt( C, 5 );
sgs_CreateArray( C, NULL, 3 );
sgs_PushNumIndex( C, sgs_StackItem( C, -1 ), 1 ); // pushes [int 4]
sgs_PushPath( C, sgs_StackItem( C, -1 ), "i", (sgs_SizeVal) 1 ); // pushes [int 4] too

Native object interfaces

sgs_ObjInterface object_iface[1] =
{{
    "object_name",          // type name
    NULL, NULL,             // destruct, gcmark
    NULL, NULL,             // getindex, setindex
    NULL, NULL, NULL, NULL, // convert, serialize, dump, getnext
    NULL, NULL              // call, expr
}};
sgs_CreateObject( C, NULL, malloc( sizeof( mystruct ) ), object_iface );
sgs_CreateObjectIPA( C, NULL, sizeof( mystruct ), object_iface ); // preferred method

It is very important that all memory operations on in-place allocated blocks do not, at any time, operate beyond the boundaries of those blocks. Be especially wary of putting arrays at the beginning of a structure since accidentally applying negative indices to such arrays could create issues that are extremely hard to debug.

int object_destruct( SGS_CTX, sgs_VarObj* obj )
{
    free( obj->data );
    return SGS_SUCCESS;
}
int object_getindex( SGS_CTX, sgs_VarObj* obj )
{
    char* str;
    if( sgs_ParseString( C, 0, &str, NULL ) )
    {
        if( strcmp( str, "data_pointer" ) == 0 ) return sgs_PushPtr( C, obj->data );
        
        if( strcmp( str, "do_something" ) == 0 ) return sgs_PushCFunc( C, object_do_something );
        if( strcmp( str, "do_smth_else" ) == 0 ) return sgs_PushCFunc( C, object_do_smth_else );
    }
    return SGS_ENOTFND; // return that the specified key was not found in object
}
// a slightly cleaner but less hands-on, the macro-based version:
int object_getindex( SGS_ARGS_GETINDEXFUNC )
{
    SGS_BEGIN_INDEXFUNC
        SGS_CASE( "data_pointer" ) return sgs_PushPtr( C, obj->data );
        
        SGS_CASE( "do_something" ) return sgs_PushCFunc( C, object_do_something );
        SGS_CASE( "do_smth_else" ) return sgs_PushCFunc( C, object_do_smth_else );
    SGS_END_INDEXFUNC
}