This file only works in a JavaScript-enabled HTML5 browser
This is a programming language that is made to be fast, accessible and simple.
ctrl.DReset();
foreach( at, item : speech )
{
distfromlast = speech.size - at - 1;
a = 0.9 * pow( 0.7, distfromlast );
ctrl.DCol( 0.9, a );
ctrl.DText( item.text,
x + padding, y + padding + lineheight / 2,
HALIGN_LEFT, VALIGN_CENTER );
y += lineheight;
}
It is similar to many but the exact combination makes it unique in comparison to others. Thus, it might be best to describe it in comparison to others, especially the ones that heavily influenced the development of SGScript.
It adds dynamic typing and built-in serialization to C. Unlike Lua, it has C-like syntax and proper 0-based indexing and advanced object model, and much more versatile error handling. It has the consistency you'd want after working with PHP, and is considerably more lightweight. It untangles some of the mess JavaScript has introduced, and gives coroutines for a considerable boost in power of expression. And, unlike Python, SGScript won't care as much about formatting.
You can find more about the differences at the "Why SGScript?" page.
First-class functions and ability to attach variables to functions (creating closures)
Coroutines (with threading & sync primitives)
The standard library includes:
array, dict, map
math, string, I/O, O/S and formatting functions
eval, file import, DLL import, coroutines
coroutines/threads, events, sync/race
Native serialization
[+] SGScript, Python
[-] JavaScript, Lua, Squirrel
Without any extra work, most of the data can be converted to a byte buffer so that it could be stored in a file or sent over a network and later converted back again. Languages that don't have it will require extra effort to get it and make sure it works with all kinds of custom objects. How much extra effort? Well, try googling for "lua serialize userdata". I didn't find a single way. SGScript - easy: https://github.com/snake5/sgscript/blob/6e9349a5a1ef5210ee440301b889f9afd78291be/ext/sgsxgmath.c#L248
Reference counted memory management support
[+] SGScript, Python, Squirrel
[-] JavaScript, Lua
This is a preferred method to garbage collection since it releases resources as soon as possible, avoiding random stalls throughout the game and thus providing a smooth gameplay experience. There are algorithms that reduce these stalls (incremental/generational garbage collection) but, given enough objects, they will be noticeable again. Source: http://sealedabstract.com/rants/why-mobile-web-apps-are-slow/
Coroutines
[+] SGScript, Lua, Squirrel, Python
[-] JavaScript (only generators are supported)
True cooperative multitasking allows the direction of timed events to be greatly simplified. SGScript goes one step further and also provides helper constructs (thread, subthread, sync, race) to make things as simple as possible for the user.
These are objects that can be created in C/C++, with special interfaces that support operator overloading, serialization, debug printing, conversions, cloning, type name and iterator retrieval, index and property retrieval. If not used, this feature has no cost, however it helps greatly with defining fast and accessible interfaces by encapsulating native resource management and access. To see what SGScript objects are all about, check the previous GitHub link, there's quite a lot of them.
Native arrays (not to be confused with arrays that contain native data types, that is a subset of these) offer increased performance and memory-friendly storage over arrays made from hash tables. Array stores size as uint32, capacity as uint32 and values (16 bytes + extended data in SGScript) x size. A table would store all the same + keys (16 bytes + extended data) + hash array (size may differ but it's generally another array with size, capacity and a list of hash and index values). When arrays are considered, less (memory usage) is more (capacity).
Map support (all non-string/number keys)
[+] SGScript, Python, JavaScript (requires the support of an extension)
[-] Lua, Squirrel (some types are not supported in both)
The ability to map any variable to any other variable provides extended metadata storage possibilities - it can be stored without modifying the original variable.
m = map();
m[ sprite_obj ] = { high = 5 };
Game math library
[+] SGScript, Python, Lua, JavaScript
[-] Squirrel
A library with vector/matrix objects and functions for games. Not having to rewrite at least the bindings for it saves a lot of time.
Native debugging/profiling facilities
[+] SGScript, Python, JavaScript (support differs between JS engines)
[-] Lua, Squirrel
Introspective debugging and time/memory usage profiling can help resolve various issues found. SGScript supports call stack time, instruction time and call stack memory usage profilers out-of-the-box. At any point, all data can be dumped via the built-in output facilities that can be rerouted to any file or parser.
They are written in C to ensure a practically minimal performance impact while profiling. Significantly less than if the profiler was written in Lua, which is the main solution there.
There's also access to some stats in SGScript so it is easy to see, for example, how many new allocations were done each frame.
Advanced native function argument parsing facilities.
[+] SGScript, Python
[-] Lua, Squirrel, JavaScript
Every modern scripting engine should have a function that parses and validates function arguments according to a specification and puts the data in the specified locations. With bigger functions it saves you from writing a lot of boilerplate code.
SGScript:
SGSFN( "fmt_string_parser" );
if( !sgs_LoadArgs( C, "?m|ii", &off, &bufsize ) ) // in case of type mismatch, emits a warning
return 0; // ... and returns here to continue execution
float x =luaL_checknumber(L,1); // in case of type mismatch, emits a fatal error, cannot continue script execution after this function call
float y =luaL_checknumber(L,2); // same here
const char* str=luaL_checkstring(L,3); // same here
Non-fatal error messaging facilities without exceptions
[+] SGScript
[-] Python, Lua, Squirrel, JavaScript
This feature allows you to try and continue execution after a failed function call or intercept the error for debugging with the option of continuing later anyway. This is useful when code is published and there's a necessity to avoid going back to bug fixing immediately, before gathering more information about the state of the program.
Why exceptions don't fit the criteria: they force the code to break out of the original execution path, thus severely reducing the usefulness of error suppression with logging.
name = string_cut( other.name ); // warning: missing argument 2; after call, name = null
// name gets printed somewhere as 'null' or is invisible due to some other function not accepting null for a string
// everything else works
This is a very useful feature to have when you need to debug data (i.e. always). Simply passing a variable to some function (for example, printvar) prints some useful information about it - the type, contents, linked resources.
[-] Lua, Squirrel, JavaScript (no warnings about it at all)
This feature allows to specify, per-read, whether the property is expected to be there or not.
a = obj.prop; // expected, emits a warning if not found
b = @obj.prop; // might not be there, no error
This also works for many other actions, like function calls and assignments.
Custom native iterators
[+] SGScript, Python
[-] JavaScript, Lua, Squirrel
An extension for custom native objects, it allows to create objects that can be foreach'ed through.
foreach( entry : io_dir( "." ) ) println( entry ); // prints the contents of current directory
Dual access dictionaries
[+] SGScript, Lua, JavaScript, Squirrel
[-] Python
The ability to access simple dictionaries just like any other object visually and syntactically reduces code complexity.
a.b = x; // simple
a["b"] = x; // not so simple
Explicit closures
[+] SGScript
[-] Lua, JavaScript, Squirrel, Python
Explicit closures (specifying which local variables to pass over to the newly defined function) make it easier to read code using closures and prevents closure-related accidents, like having a variable changed unexpectedly.
Multi-index/property-set operation without temporary tables
[+] SGScript
[-] Lua, JavaScript, Squirrel, Python
Simplifying code further without the introduction of sub-optimal memory access patterns.
SGScript:
obj.{ // object name written only once, accessed only once
a = 1, // property write
b = 2, // property write
c = 3, // property write
d = 4, // property write
};
Lua, method 1:
obj.a = 1 // property write
obj.b = 2 // property write
obj.c = 3 // property write
obj.d = 4 // property write
// object name written four times, accessed possibly four times (depending on compiler)
Lua, method 2:
for k, v in pairs({ a = 1, b = 2, c = 3, d = 4 }) do // create table, property write x4, function call, create closure
obj[ k ] = v // object access x4, property write x4
end
Even though it is argued that 1-based indexing may help someone understand code better (which is actually hard to prove), I have a few arguments to make against it:
Array index is generally the distance (or as programmers would rather say, offset) from first element. 1 - 1 = 0. Not a distance from 0th element that doesn't even exist. Not the first element itself because elements have no keys in arrays.
Programming languages don't exist in a vacuum. Almost every other programming language treats indices as distances (offsets). Going against the grain makes it hard to interpret both languages at once for comparison or interface design. Whoever has to write that binding, has to keep in mind this difference for every array access made in the binding area. This is brain power not spent well.
Similarly to previous argument, this difference makes porting code from other languages harder.
Statements
print "Text";
println( "!" );
b = rand();
Comments
// this is a comment that ends with the line
/* this type of comment
can be more than
one line long
*/
Basic calculations
a = 1, b = 2, c = 5;
d = a + b * c - b / a;
a += 3.14;
c = "eye" $ "sight" // stitch (concatenate) strings together!
d = "Ocean's " $ 11;
Comparison
if( c > 5 )
print "as expected";
y = x <= 8.8; // result of comparison can be assigned!
Useful shortcuts
a += 1; // short for a = a + 1
a++; // short for a += 1
a--;
a = if( b > 5, 10, 20 ); // short for if( b > 5 ){ a = 10; } else { a = 20; }
Control flow
if( a > b )
{
print "something happens only if a is greater than b";
}
else
print "...and here the opposite is true";
while( a > b )
{
print "something happens as long as a is greater than b";
a--; // but not for too long, as we see
}
// or maybe ...
do
{ // first we do
print a;
}
while( a++ < 10 ); // then we check
More useful shortcuts
for( i = 0; i < 10; i++ )
print i;
// .. is the short form for ..
i = 0;
while( i < 10 )
{
print i;
i++;
}
for(;;) // this is not going to stop...
print "x";
More control flow
x = 5;
for(;;)
{
print x;
x--;
if( x < 0 )
break; // this says that we don't want to continue staying in the loop
}
// << this is where "break" leads us, right after the loop
// this goes through a list
foreach( fruit : [ "apple", "orange", "banana" ] )
println( fruit );
// .. of 2 items ..
list = { "1" = "apple", "2" = "orange", "3" = "banana" };
foreach( number, fruit : list )
println( number $ ". " $ fruit );
Functions
// cornerstore of reusable code
function square( x )
{
return x * x;
}
print square( 5 );
function print_quoted( text )
{
print '"' $ text $ '"';
}
print_quoted( "It works!" );
Objects
a = [ 1, 2, "three" ];
a[2] = 3;
a[1]++;
d = {
name = "test",
text = "hello",
};
d.name = "test, edited";
d.text $= ", world!";
println( d.text ); // hello, world!
Interesting stuff
printvar( x ); // tells you all you want to know about "x"
// emits an error with the given message if the first argument is false
assert( x > 5, "x must be greater than 5" );
// returns the type name
typeof( x );
// universal external code loader
include "math", "string", "something";
Primary operations
// create the context
sgs_Context* C = sgs_CreateEngine();
// load the built-in math library
sgs_LoadLib_Math( C );
// load a file
sgs_ExecFile( C, "script.sgs" );
// call a global function with 0 arguments, expecting 0 values returned
sgs_GlobalCall( C, "myFunction", 0, 0 );
// destroy the context
sgs_DestroyEngine( C );
Data in, data out
sgs_Context* C = sgs_CreateEngine();
sgs_LoadLib_Math( C );
// push a variable on the stack
sgs_PushReal( C, 3.14 );
// call a global function with 1 argument, expecting 1 value returned
sgs_GlobalCall( C, "sin", 1, 1 );
// check the returned value
printf( "returned value: %f\n", sgs_GetReal( C, -1 ) );
// clean the stack
sgs_SetStackSize( C, 0 );
SGScript has 10 primary variable types:
null: the 'lack of a better value' type
bool: boolean, true/false
int: signed integer
real: floating point number
string: byte buffer
(SGS) function: function that was defined in and compiled from SGScript code
C function: function defined in C code
object: the extension type and container for complex built-in types
ptr: the pointer type
thread: the context/thread/coroutine type
Extended variable types (built on object):
array: a dynamically allocated variable array
dict: a map/table structure where variables are mapped to string keys
map: a map/table structure where variables are mapped to variable keys
class: object that uses two other objects to build its interface
closure: a callable object consisting of another callable and several variables
event: an object that allows to stop a thread
from \ to
bool
int
real
string
ptr
null
false
0
0.0
"null"
NULL
bool
-
0/1
0.0/1.0
"false"/"true"
NULL/0x1
int
0 => false, otherwise true
-
-
-
cast
real
0.0 => false, otherwise true
round to nearest
-
sprintf %g
cast
string
empty => false, otherwise true
*1
*1
-
char*
func
true
0
0.0
"function"
NULL
cfunc
true
0
0.0
"C function"
NULL
object *2
true
0
0.0
"object"
data ptr.
ptr
NULL => false, otherwise true
cast
cast
sprintf ptr(%p)
-
thread
true
cast
cast
sprintf thread(%p)
cast
"-" means the conversion is not necessary, does not affect data or the effect of conversion should be immediately obvious
*2) these are the default values, overrides can be provided in CONVERT function and type name for string
Code can contain constants, identifiers, expressions and statements. Statements usually include expressions but expressions can also include statements, specifically - function expressions.
Available constant formats:
type
subtype
examples
null
-
null
bool
-
true, false
int
decimal
1413, -583, 0
int
binary
0b0101, 0b11
int
octal
0o644, 0o1, 0o77
int
hexadecimal
0x1f, 0xCA5
real
basic
0.14, -25.48
real
scientific
1.5e-5, 1e+10
string
normal
"text", '1\n2'
string
unescaped*
"""text""", '''1\2'''
*) unescaped strings don't parse their contents at all, and the ending markers (""" or ''') cannot be escaped
Identifiers can contain letters (a-z, A-Z), numbers (0-9) or underscores ("_") but they cannot begin with a number.
Special keywords and hardcoded constants:
name
type
usage
this
special identifier, read-only
method context retrieval
_G
special identifier, read/write
global environment access
_R
special identifier, read-only
global registry access
_F
special identifier, read-only
current function retrieval
_T
special identifier, read-only
current thread retrieval
null
constant
constant
true
constant
constant
false
constant
constant
var
restricted keyword
variable declaration
global
restricted keyword
variable declaration
class
special identifier
class declaration
new
restricted keyword
class instantiation
thread
restricted keyword
thread control
subthread
restricted keyword
thread control
sync
restricted keyword
thread control
race
restricted keyword
thread control
defer
restricted keyword
destruction statement helper
function
restricted keyword
function definition
use
restricted keyword
function definition
if
restricted keyword
"if" statement
else
restricted keyword
"if/else" statement
do
restricted keyword
"do/while" statement
while
restricted keyword
"while", "do/while" statements
for
restricted keyword
"for" statement
foreach
restricted keyword
"foreach" statement
break
restricted keyword
"break" statement
continue
restricted keyword
"continue" statement
return
restricted keyword
"return" statement
print
function command
"print" function
println
function command
"println" function
yield
function command
"yield" function
include
function command
"include" function
Expressions can be categorized in many different ways: type of action, appearance, whether it also assigns the value somewhere or if its purpose has a special meaning.
There are 5 types of action for expressions in SGScript: arithmetic, bitwise, logical, comparison and special.
expression
appearance
type of action
assign
# in.
add
A + B
arithmetic
no
2
subtract
A - B
arithmetic
no
2
multiply
A * B
arithmetic
no
2
divide
A / B
arithmetic
no
2
modulo
A % B
arithmetic
no
2
pre-increment
++ A
arithmetic
self
1
pre-decrement
-- A
arithmetic
self
1
post-increment
A ++
arithmetic
self
1
post-decrement
A --
arithmetic
self
1
add-assign
A += B
arithmetic
yes
2
subtract-assign
A -= B
arithmetic
yes
2
multiply-assign
A *= B
arithmetic
yes
2
divide-assign
A /= B
arithmetic
yes
2
modulo-assign
A %= B
arithmetic
yes
2
bitwise AND
A & B
bitwise
no
2
bitwise OR
A | B
bitwise
no
2
bitwise XOR
A ^ B
bitwise
no
2
left shift
A << B
bitwise
no
2
right shift
A >> B
bitwise
no
2
bitwise AND-assign
A &= B
bitwise
yes
2
bitwise OR-assign
A |= B
bitwise
yes
2
bitwise XOR-assign
A ^= B
bitwise
yes
2
left shift-assign
A <<= B
bitwise
yes
2
right shift-assign
A >>= B
bitwise
yes
2
bitwise invert
~ A
bitwise
no
1
logical AND
A && B
logical
no
2
logical OR
A || B
logical
no
2
first-not-null
A ?? B
logical
no
2
logical AND-assign
A &&= B
logical
yes
2
logical OR-assign
A ||= B
logical
yes
2
first-not-null-assign
A ??= B
logical
yes
2
logical invert
! A
logical
no
1
less than
A < B
comparison
no
2
less than or equal
A <= B
comparison
no
2
greater than
A > B
comparison
no
2
greater than or equal
A >= B
comparison
no
2
equal
A == B
comparison
no
2
not equal
A != B
comparison
no
2
strict equality
A === B
comparison
no
2
strict inequality
A !== B
comparison
no
2
raw comparison
A <=> B
comparison
no
2
error suppression
@ A
special
no
1
declare-local
var A
special
maybe
any
declare-global
global A
special
maybe
any
array literal
[ A, B, .. ]
special
no
any
dict. literal
{A=1,B=2,..}
special
no
any
map literal
map{[A]=B}
special
no
any
assign
A = B
special
yes
1
concatenate
A $ B
special
no
2
concatenate-assign
A $= B
special
yes
2
concatenate
A .. B
special
no
2
concatenate-assign
A ..= B
special
yes
2
property
A . B
special
maybe
2
index
A [ B ]
special
maybe
2
multi-index-assign
A[]<dict.lit>
special
yes
any
multi-property-assign
A.<dict.lit>
special
yes
any
function call
A ([B,..])
special
no
<=256
comp. function call
O!A ([B,..])
special
no
<=256
function definition
function A..
special
maybe
0
inline if
if(A,B,C)
special
no
3(2)
subexpression
( A[, ..] )
special
maybe
any
thread-call
thread f()
special
maybe
<=256
subthread-call
subthread f()
special
maybe
<=256
new-call
new f()
special
maybe
<=256
Some notes on the special cases:
[increment,decrement] pre-increment and pre-decrement operators return the modified value, post- operators - the original one
[logical] all logical operators except 'logical invert' (which returns bool) set/return one of the two operands passed
[equality] strict (in)equality operators are the same as their non-strict counterparts with one difference: they do type checking, for example 5 == 5.0 would return 'true' and 5 === 5.0 would return 'false'
[declare] declarations only actually set the data if they include the assignment expression, otherwise only the type is internally noted for other references
[declare] variables can be redeclared as long as they maintain their access level
[property,index] whether property or index expressions set data depends if they're on the receiving (left) end of an assignment expression
[inline if] 3 inputs are required, 2 are actually used (as if if(A){return B;}else{return C;} was used)
[subexpression] subexpressions can set data if they are set up like this: <subexpression> = <function-call> - this is the way to read more than one return value from a function
[error suppression] the @ operator disables warnings, errors and any other messages for as long as the subexpression is executed, this is mostly useful for things like reading a property or calling a function that may not exist, in cases where that isn't an error
[dict./map literal] 3 types of keys are supported: string, identifier (interpreted as string), variable ("[ <expression> ] = <value>")
[function] the full function definition expression syntax: function <name> ( <args> ) [ use ( <use-list> ) ], followed by either { ... } or = ... ;. <name>, <args> and the 'use' block are all optional.
[multi-set] operator returns A, that is, the object itself
[thread] thread/subthread commands support all kinds of calls (f(), o.f(), o!f())
[new] new supports only the basic calls (f(), o.f()), and this is not passed (as it would refer to the newly created instance)
compatible function call / inheritance call / global method call ( O!A ([B,..]) )
CFC is created to encourage users to reduce memory usage without sacrificing clarity of code. Object and its processing function can reside in different locations (like class instance / inherited class) and still be easily accessible.
Properties:
uses the same symbol as logical inversion operator
usage: simplified & optimized calling of non-integrated compatible functions
instead of comp_func.call( object, arg1 ) (removed since 1.4) write object!comp_func( arg1 )
instead of putting functions with data / class interfaces, requiring additional memory usage, allows to easily keep them separate
simplify inheritance-like code models to the extent permitted by a dynamic language
Example WITHOUT:
function create_object()
{
object = { x = 0, y = 0 };
function object.move( x, y ){ this.x = x; this.y = y; @this.move_callback(); }
function object.tick( delta ){ this.move( this.x + delta, this.y ); }
return object;
}
Example WITH:
function Object_Move( x, y ){ this.x = x; this.y = y; @this.move_callback(); }
function create_object()
{
object = { x = 0, y = 0 };
function object.tick( delta ){ this!Object_Move( this.x + delta, this.y ); }
return object;
}
Now, by itself it may not mean much, however let's see what happens if the following code is used:
for( i = 0; i < 100; ++i )
objects.push( create_object() );
Without the usage of a global method, there's a key 'move' in each object that points to the function. In real software there may be no less than 10 such keys in many instances of many objects. And if there are no overrides planned for it, there's no need for it to be there but we can't really move it. Classes are an option that involves a few more allocations and it requires more memory to use them. Using .call/sys_call or replacing this with an argument would sacrifice readability and the option to relocate functions, as well as performance, this being a double call. This is where CFC comes in - syntax allows to clearly state the object and function used, and the required interface for the object to be compatible with said function.
There are 4 statement types in SGScript:
Container statements
There are two types of container statements: expression statements and block statements.
Expression statements contain zero or more expressions, separated by commas (",") and ending with semicolons (";").
These statements decide which code gets executed when. They include loops (while, for, foreach, do/while), the if/else construct, break/continue/return.
if( a )
{
print( "a is true" );
}
else
print( "a is not true" );
while( b > 0 ) b--;
defer is a special case since it moves the given statement to the end of the block (however that is reached). This can be useful for resource acquisitions:
rsrc = acquire_resource();
defer release_resource( rsrc );
if( work_with( rsrc ) )
return; // release_resource is called here
more_work_with( rsrc );
// .. and here
Declaration statements
Statements that begin with "var", "global", "function" or "class" declare and set variables.
Examples:
var x = 5, y = 6, z;
global World = null;
function fn(){ INFO( "'fn' was called" ); }
class C
{
var s = 1;
function f(){ INFO( "'C.f' was called" ); }
}
Function call statements
These statements are converted directly to function calls. Currently, 'include' and 'print' functions are available as statements.
Formats:
include: include [<file1>[, <file2>...]]; - for each entry, a function call is generated
print: print [<string>[, <string>...]]; - one function call is generated for all items
Examples:
include "io", "string";
print "Count: ", count;
There are several built-in accessors for the variable types that are available.
name
variable type
accessor type
description
length
string
property
returns the length of the string, in bytes
char. at
string
index
returns the character from the specified position
apply
SGS/C function
property
function that allows to specify "this" & arg. array
was_aborted
thread
property
returns if thread was aborted
not_started
thread
property
returns if coroutine was not started yet
running
thread
property
returns if thread is currently running
can_resume
thread
property
returns if thread is not finished (can be resumed)
end_on
thread
property
function that can specify an event to end the thread
resume
thread
property
function that can resume the thread (see co_resume)
class SampleClass // create a class
{
// static member variable (can be read from both class definition and instance)
global StaticVariable = 5;
// the default construction function
function __construct( x )
{
// instance member variable (can be read only from class instance, not definition)
this.c = x;
}
// static construction function
function Create( x )
{
return new SampleClass( x );
}
function SampleMethod( a, b ) // "SampleClass.SampleMethod" is the name that will show up in debug output
{
return a + b + this.c; // usage of "this" turns the function into a method
}
function SampleFunction( a, b )
{
return a + b;
}
}
// instances of class
inst1 = new SampleClass( 123 );
inst2 = SampleClass.Create( 456 );
// inspect instance 1, instance 2 and the underlying interface of instance 1
printvar( inst1, inst2, metaobj_get( inst1 ) );
Inheritance
// class A - base class
class A
{
function func()
{
println( "base" );
}
}
// class B - inherits from A
class B : A
{
function func()
{
this!A.func();
println( "override" );
}
}
instA = new A();
instB = new B();
instA.func(); // prints "base"
instB.func(); // prints "base", then "override"
">>>" means "until any other symbol has been found" (the text is too dense for exact repetition)
The string is not a number if no numeric characters (0-9) could be parsed. (for example, "+" isn't a number but "0x" is)
if string begins with 0b and has at least 3 characters, try to read a binary integer
>>>, 0 or 1 adds the specified bit to the integer
if string begins with 0o and has at least 3 characters, try to read an octal integer
>>>, characters 0-7 add the specified 3 bits to the integer
if string begins with 0x and has at least 3 characters, try to read a hexadecimal integer
>>>, characters 0-9, a-f and A-F add the specified 4 bits to the integer
otherwise, try to read a decimal number
do a test to see if the number is not an integer
skip the sign character (+ or -)
skip the 0-9 characters
if e, E or . is found, try to read a real value
read the sign character (+ or -)
>>>, characters 0-9 add the specified decimal number to the whole part
if the dot character (.) is found, read the fractional part
>>>, characters 0-9 add the specified decimal number to the fractional part
if at least 3 more characters can be found and the first is "e" or "E", read the exponent part
read the sign character (+ or -)
>>>, characters 0-9 add the specified decimal number to the exponent part
otherwise, try to read the integer
read the sign character (+ or -)
>>>, characters 0-9 add the specified decimal number to the integer
The interface that it defines is one of the driving forces of SGScript. It is heavily influenced by Lua, while also adding some features of PHP (Zend Engine) and Python.
The most important difference is in error handling and triggers: errors detected in C can easily be non-fatal. Just like with PHP, minor errors are emitted as warnings that also propagate invalid (null) values throughout the system to effectively disable the subsequent calls and notify whoever is interested of the exact trail of errors. Furthermore, this behavior can be modified by using protected calls (pcall function) in SGScript and custom messaging callbacks in C.
The virtual machine supports:
Operator overloading
On-demand garbage collection
Full introspection of machine state
Type registration for simplified cross-library data reuse
This part of the documentation describes the various types and definitions used in the C API.
sgs_MsgFunc [void ( void* userdata, sgs_Context* C, int code, const char* text )]: messaging function
sgs_HookFunc [void ( void*, sgs_Context*, int )]: debug hook function
sgs_ScriptFSFunc [SGSRESULT ( void*, sgs_Context*, int, sgs_ScriptFSData* )]: virtual file system function
These are the default meanings for error codes that are honored throughout the API.
Generated by the C API
SGS_SUCCESS: operation was successful
SGS_ENOTFND: item was not found
SGS_ECOMP: compile error
SGS_ENOTSUP: operation is not supported
SGS_EINVAL: invalid value was passed
SGS_EINPROC: process was interrupted
Generated by SGScript VM
SGS_INFO: information about potential issues and state of the system
SGS_WARNING: non-fatal issues
SGS_ERROR: fatal issues
SGS_APIERR: API usage errors
SGS_INTERR: internal errors
any other integer may be defined and returned by other interfaces
SGS_VT_NULL: null
SGS_VT_BOOL: boolean
SGS_VT_INT: integer
SGS_VT_REAL: real value
SGS_VT_STRING: string
SGS_VT_FUNC: SGS function
SGS_VT_CFUNC: C function
SGS_VT_OBJECT: object
SGS_VT_PTR: pointer
SGS_VT_THREAD: thread/coroutine/context
Messages are combinations of importance code and a null-terminated string that holds the message itself. The messaging system's purpose generally is to notify about problems and allow handling them to some extent.
The sgs_Msg function basically generates the full string from the format string and calls the callback that is currently set. Due to the data association and accessibility, various systems can be implemented on top of it:
Logging
All messages can simply be logged. For the most simple approach, the sgs_StdOutputFunc callback can be used with any FILE* for the data pointer.
A limited amount of exception handling
This system can be used to break out of any point in SGS code up until the last C function. For this, pcall can be used together with a callback that calls abort when necessary.
Debugging
On each warning or error, debugging code can be triggered. It can be an interactive console debugger (as implemented in ext/sgs_idbg), it could also be code that dumps the relevant state and sends it to the developer.
Hidden data collection
Similarly to exception handling, pcall can be used also for gathering messages otherwise skipped by the system. The sub-zero importance range is generally hidden - there's 2^31 values to use to mark special messages - however, sys_replevel or sgs_Cntl with SGS_CNTL_(SET_)MINLEV must be used to enable that range.
Iterators are objects that enable sequential, ordered traversal of object contents, allowing to read up to 2 variables at each position: the key and the value.
Iterator starts at the pre-first position (-1 in terms of indices), so nothing can be read initially, before the first advance. This allows to implement iterator handling with rather simple code:
// assuming iterable object is at the top of the stack
sgs_PushIterator( C, sgs_StackItem( C, -1 ) );
while( sgs_IterAdvance( C, sgs_StackItem( C, -1 ) ) > 0 )
{
sgs_StackIdx ssz = sgs_StackSize( C );
sgs_IterPushData( C, sgs_StackItem( C, -1 ), 1, 1 ); // push both key and value
sgs_SetStackSize( C, ssz ); // restore stack to have iterator at -1 again
}
sgs_Pop( C, 1 ); // pop the iterator
Another example using pointers:
sgs_Variable iterator, iterable, key, value;
// .. assuming iterable is initalized here ..
sgs_GetIterator( C, iterable, &iterator );
while( sgs_IterAdvance( C, iterator ) > 0 )
{
sgs_IterGetData( C, iterator, NULL, &value );
// .. use value ..
sgs_Release( C, &value );
sgs_IterGetData( C, iterator, &key, &value );
// .. use key and value ..
sgs_Release( C, &key );
sgs_Release( C, &value );
}
sgs_Release( C, &iterator );
// .. release the iterable or not, depending on how it was initialized ..
The garbage collector currently implemented is a basic stop-the-world mark-and-sweep algorithm that goes over all objects and recursively marks them as available.
The marking is done on the variable named redblue, which contains 0 or 1 and in all avaiable objects is synced with the same variable for the context. On each pass, the bit is flipped for all available objects, so all objects that didn't have the bit flipped can be safely removed.
The garbage collector is invoked with sgs_GCExecute in the C API and gc_collect in SGScript.
Return value: the item to return as the cloned object
__tobool
When called: on bool value retrieval
Arguments: none
Return value: bool
__tostring
When called: on string value retrieval
Arguments: none
Return value: string
__negate
When called: on negation (-obj)
Arguments: none
Return value: the negated object
__add
When called: on addition (var1 + var2) when one of the variables has this interface defined
Arguments: operands A and B (types are not restricted)
Return value: the product of addition
__sub
When called: on subtraction (var1 - var2) when one of the variables has this interface defined
Arguments: operands A and B (types are not restricted)
Return value: the product of subtraction
__mul
When called: on multiplication (var1 * var2) when one of the variables has this interface defined
Arguments: operands A and B (types are not restricted)
Return value: the product of multiplication
__div
When called: on division (var1 / var2) when one of the variables has this interface defined
Arguments: operands A and B (types are not restricted)
Return value: the product of division
__mod
When called: on modulo (var1 % var2) when one of the variables has this interface defined
Arguments: operands A and B (types are not restricted)
Return value: the product of modulo
__compare
When called: on comparison (var1 + var2) when one of the variables has this interface defined
Arguments: operands A and B (types are not restricted)
Return value: the result of comparison (real value indicating < 0 if A<B, > 0 if A>B, = 0 if A=B)
__call
When called: on function call
Arguments: arguments from the original call
Return value: any
Closures are a combination of one callable and some variables. They are meant to simplify interfaces and code around places where it was important for functions to have access to call-invariant (same on every call) data.
Closures can be created from both SGScript and the C API, though both options have their limitations.
In SGScript, closures can be created by letting functions "use" a variable, e.g. by writing function func( args ) use( closures ){ ..body.. }
Leaves the stack the same as it was before the call.
Alias to sgs_ExecBuffer( C, str, SGS_STRINGLENGTHFUNC( str ) ).
Theoretically bytecode is supported but it will most probably be trimmed by the string length function, unless a different string termination mark is used, and a different string length function to detect it is set.
Push the variable specified by starting point var and traversal path path, return if successful.
The safety of this function is similar to that of the printf family of functions. Be explicit in what types you pass to the variable argument list to avoid errors.
The syntax of path: a list of letters, specifying a sub-variable to request (o,p,s,i,k,n).
Table of accepted letters
letter
property?
variable arguments
virtual machine access mode / description
o
yes
SizeVal
integer property
p
yes
C string
string property
s
yes
SizeVal, buffer
special string property
i
no
SizeVal
integer index
k
no
C string
string index
n
no
SizeVal, buffer
special string index
Legend:
SizeVal: sgs_SizeVal / int32_t
C string: null-terminated char* string
buffer: char* byte array, size is specified in the previous argument
property?: if yes, property access is used (object.property), otherwise index access is used (object[index])
Set value val to variable specified by starting point var and traversal path path.
The safety of this function is similar to that of the printf family of functions. Be explicit in what types you pass to the variable argument list to avoid errors.
The syntax of path is the same as with sgs_PushPath.
The last item on the path (or in the case of an empty path, the starting point) is accessed with a store operation, the rest - with a push operation.
void sgs_StoreFuncConsts( sgs_Context* C, sgs_Variable var, const sgs_RegFuncConst* list, int size )
void sgs_StoreIntConsts( sgs_Context* C, sgs_Variable var, const sgs_RegIntConst* list, int size )
void sgs_StoreRealConsts( sgs_Context* C, sgs_Variable var, const sgs_RegRealConst* list, int size )
Loads the specified list of constants in the specified variable.
The end of list can be specified in two ways:
set the size to sizeof(list)/sizeof(list[0]);
set the size to -1 and end the list with SGS_RC_END();
using both at the same time should be reserved to special cases only.
int sgs_ArgErrorExt( sgs_Context* C, int argid, int method, const char* expect, const char* expfx )
Prints the argument type mismatch error.
expfx - prefix of expect, put exactly before this for things like 'strict '.
Always returns 0 so it can be used to return and print an error in the same statement.
int sgs_ArgError( sgs_Context* C, int argid, int expect, int is_strict )
Prints the argument type mismatch error.
Always returns 0 so it can be used to return and print an error in the same statement.
int sgs_FuncArgError( sgs_Context* C, int argid, int expect, int is_strict )
int sgs_MethodArgError( sgs_Context* C, int argid, int expect, int is_strict )
Prints the argument type mismatch error.
Always returns 0 so it can be used to return and print an error in the same statement.
Alias to sgs_ArgError( C, argid, Func/Method => 0/1, expect, is_strict ).
If object of the right type is first in the stack, its data pointer is received.
Function also registers its name to "objname.methodname".
SGSBOOL sgs_Method( sgs_Context* C )
Unlock the 'this' variable, return if the function was called as a method (and thus variable was unlocked).
Method calls look like this: "object.method(...)".
SGSBOOL sgs_HideThis( sgs_Context* C )
SGSBOOL sgs_ForceHideThis( sgs_Context* C )
Hide the 'this' variable, return if successful.
sgs_ForceHideThis attempts to hide the first item on stack even if it didn't belong to the call, sgs_HideThis only does it if it belongs to the call and was hidden before.
The hiding method is stack offset pointer shift.
int sgs_ArgCheck_Object( sgs_Context* C, int argid, va_list* args, int flags )
Argument parsing function for parsing any objects.
To be used with sgs_LoadArgs and related functions.
Strictness controls whether null is accepted.
Returned value: sgs_VarObj* (expects sgs_VarObj** argument for it).
int sgs_ObjectArg( sgs_Context* C )
Returns the additional integer argument to an object interface function call.
For getindex and setindex: if argument is nonzero, property is requested, zero - index.
int sgs_XFCall( sgs_Context* C, int args, int gotthis )
Call the function with the arguments on stack, returning variables on stack and their count.
Call the function with the arguments on stack, returning variables on stack and their count.
Aliases to sgs_XFCall, for the first function gotthis = 0, for the second gotthis = 1.
int sgs_FCall( sgs_Context* C, int args, int expect, int gotthis )
Call the function with the arguments on stack, returning the expected number of variables on stack.
args: the primary variables to be passed to the function, in the same order they are in the stack.
expect: the number of variables to be left after a successful call.
gotthis: whether the function is a method and an additional argument needs to be passed as the 'this' value before others.
Return value of function is the original number of returned values on stack from this call.
After a successful call, all arguments will be popped off the stack and the expected number of variables will appear in their place. If the underlying callable does not return enough values, 'null' variables will be pushed instead. If the number of returned values is bigger than expected, only the first expected return values will stay on the stack.
Expected stack structure:
[function]
(if gotthis != 0) [this]
[argument] x args
A callable is one of the following:
SGS function
C function
object with CALL interface function defined
To check if function call was aborted, see sgs_Cntl / SGS_CNTL_GET_ABORT.
To find out if execution state was suspended in this call, see sgs_Cntl / SGS_CNTL_GET_PAUSED.
void sgs_Call( sgs_Context* C, int args, int expect )
void sgs_ThisCall( sgs_Context* C, int args, int expect )
Call the function with the arguments on stack, returning the expected number of variables on stack.
Aliases to sgs_FCall, for the first function gotthis = 0, for the second gotthis = 1.
SGSBOOL sgs_GlobalCall( sgs_Context* C, const char* name, int args, int expect )
Call the global variable name as a function.
Combination of global function retrieval and function call; see sgs_PushGlobalByName and sgs_FCall for more info on behavior.
void sgs_TypeOf( sgs_Context* C, sgs_Variable var )
Return the type name string of the specified variable.
Object type sources are processed in the following order:
Metamethod __typeof;
sgs_ObjInterface::name;
If both methods failed, just return 'object'.
void sgs_DumpVar( sgs_Context* C, sgs_Variable var, int maxdepth )
Convert the variable to a highly informative string that should display its contents, up to maxdepth depth.
If object does not have the DUMP interface function implemented, only its pointer in memory and reference count are displayed.
void sgs_GCExecute( sgs_Context* C )
Call the garbage collector on the VM.
This procedure may take some time if the object graph is big.
During the procedure, the GCMARK interface functions of objects are invoked for the objects visible from entry points (like stack and global tables).
const char* sgs_DebugDumpVarExt( sgs_Context* C, sgs_Variable var, int maxdepth )
Push a string containing variable data and return const char* to it.
If maxdepth is negative, variable is converted to a normal string (sgs_ToString) instead of dump string (sgs_DumpVar).
const char* sgs_DebugDumpVar( sgs_Context* C, sgs_Variable* var )
Push a variable dump string and return const char* to it.
Replace the topmost variable of current stack frame with a string variable where two spaces are appended after every newline.
void sgs_ToPrintSafeString( sgs_Context* C )
Replace the topmost variable of current stack frame with a string where all non-printable (except space), non-ASCII characters converted to a character hex code.
void sgs_StringConcat( sgs_Context* C, int args )
Concatenate the args number of topmost string variables in the current stack frame.
If args = 0, empty string is pushed.
void sgs_CloneItem( sgs_Context* C, sgs_Variable var )
Push a copy of the specified stack item.
Even though operations with functions and strings succeed, functions and strings are not actually cloned since they are immutable.
Function will fail if any object attempted to be cloned does not support cloning.
Return an item from the current stack frame, converted in-place to string.
char* sgs_ToStringBufP( sgs_Context* C, sgs_Variable* var, sgs_SizeVal* outsize )
char* sgs_ToStringBufFastP( sgs_Context* C, sgs_Variable* var, sgs_SizeVal* outsize )
Return the value of the specified variable, converted in-place to string.
In ***P functions previous variable is properly released before conversion takes place and reacquired afterwards.
If item is out of bounds or conversion fails, NULL is returned.
The 'fast' version uses type name conversion on object, instead of string conversion - this is to avoid recursion while generating a short description about object contents.
Length of string is returned to the value that outsize points to.
Attempt to convert a negative stack index into a positive one.
This function can still return negative values - make sure that valid values are passed to it or check if the returned value is in the range [ 0; stack_frame_size ).
Avoid using this function with functions that can alter the stack and remove the source item.
This function does not acquire the variable and thus can't be used in functions that modify it without previously taking ownership of it, such as sgs_Assign or sgs_To***; to acquire variable on retrieval, see sgs_GetStackItem or sgs_Acquire.
This function can be used to pass arguments from stack in most simple operations.
SGSBOOL sgs_GetStackItem( sgs_Context* C, sgs_StkIdx item, sgs_Variable* out )
Write the data of the specified stack variable and increments its reference count if the index is valid.
Function returns whether the stack index was valid.
void sgs_SetStackItem( sgs_Context* C, sgs_StkIdx item, sgs_Variable val )
Copy the specified variable to the specified position in stack.
This function is to be used in cases where objects are owned by a single entity but have handles all over the place - calling this function would not delete the object but would free its resources, thus leaving handles intact, with their states updated.
Sets the function used in 'print'/'errprint' function family.
Default output function can be set by passing SGSOUTPUTFN_DEFAULT to func with userdata as FILE*, allocated using the same C runtime that SGScript uses.
Default implementation - sgs_StdOutputFunc - copies data to FILE* stream, specified in userdata.
To use this function with non-default (stderr) stream, pass it explicity due to the possibility of having different runtime libraries linked to different binaries.
Helps to retrieve file name, function name and line number of the specified stack frame.
Each non-NULL pointer is initialized with valid data; even if it may not be very helpful (for example, all anonymous functions get the same name - "<anonymous function>").
sgs_StackFrame* sgs_GetFramePtr( sgs_Context* C, sgs_StackFrame* from, int bwd )
Returns a call stack frame pointer.
If bwd is 0, request a pointer for forward iteration:
if from is NULL, return pointer to first frame, otherwise return the frame after from (or NULL if it doesn't exist).
If bwd is not 0, request a pointer for reverse iteration:
if from is NULL, return pointer to last frame, otherwise return the frame before from (or NULL if it doesn't exist).
int sgs_Errno( sgs_Context* C, int clear )
Copies errno to internal errno value if clear is not 0, otherwise internal errno value is set to 0, returns clear.
Used with a boolean expression and chaining to set errno if the expression returned false, like this:
sgs_PushBool( C, sgs_Errno( C, rename( a, b ) == 0 ) )
int sgs_SetErrno( sgs_Context* C, int err )
Sets a specific value err to the internal errno variable.
Alias to sgs_Cntl( C, SGS_CNTL_SET_ERRNO, err ).
int sgs_GetLastErrno( sgs_Context* C )
Returns the currently set internal errno variable.
Push an uninitialized string buffer, returns pointer to buffer.
Created string is a valid resource in terms of acquisition/release but not hashed and internalized yet, thus it cannot be used in indexing/comparison functions until it's finalized, see sgs_FinalizeStringAlloc(P).
It is safe to write string data (to pointer retrieved from sgs_GetStringPtr(P) function) up until finalization.
char* sgs_InitStringAlloc( sgs_Context* C, sgs_Variable* var, sgs_SizeVal size )
Set an uninitialized string buffer to a variable, returns pointer to buffer.
Created string is a valid resource in terms of acquisition/release but not hashed and internalized yet, thus it cannot be used in indexing/comparison functions until it's finalized, see sgs_FinalizeStringAlloc(P).
It is safe to write string data (to pointer retrieved from sgs_GetStringPtr(P) function) up until finalization.
void sgs_FinalizeStringAllocP( sgs_Context* C, sgs_Variable* var )
Finalize (prepare for usage) an uninitialized string buffer.
Preparations include string hashing and internalization (release of owned copy in favor of exact same string in string table).
After using these functions, string data cannot be modified.
A C function has the type int CFunc( sgs_Context* ). It receives the context that called it and must return the number of return values pushed on stack.
Conventions
There are no forced courses of action beyond this point. However, to simplify development, some conventions were established and are used throughout the standard library and are suggested to follow.
General structure
the usage of SGS_CTX in argument list is suggested to enable the use of certain macros that assume the availability of sgs_Context* C
there are no restrictions on function names, however it helps to mark the scripting engine functions with a common prefix and if they wrap a native function, including the name of the wrapped function is suggested
Argument loading
prefer using sgs_LoadArgs, followed by sgs_LoadArgsExt, followed by the sgs_Parse***/sgs_Is***/sgs_ItemType(Ext)/... functions together with ArgError function family for error handling
Error handling
it is preferred to do most of it at the beginning, before custom allocations (VM stack doesn't count here), where it is possible to just return sgs_Msg( C, SGS_WARNING, "Error message" )
SGS_WARNING is for non-fatal errors, SGS_ERROR is for errors that make it impossible to continue (severe, undoubted logical errors fall into this category)
on error, functions should return nothing or null
A typical function
int sgsfunc_sample( SGS_CTX )
{
sgs_Int num;
char* str;
char* buf;
sgs_SizeVal bufsize;
if( !sgs_LoadArgs( C, "ism", &num, &str, &buf, &bufsize ) )
return 0;
if( bufsize == 0 )
return sgs_Msg( C, SGS_WARNING, "buffer cannot be empty" );
// sgs_Msg always returns 0
// .. do something with the passed arguments ..
// anything can be returned ..
// .. but in this case, we want to say ..
// .. that the action was successful
sgs_PushBool( C, 1 );
return 1;
}
Every interface function has the type int ObjCallback ( sgs_Context* C, sgs_VarObj* data, ... ). Not every of them has to be implemented (none of them are required) but for all non-pointer objects it helps to have at least one of them.
Interface is a structure that contains of an array and 10 function pointers in the following order: destruct, gcmark, getindex, setindex, convert, serialize, dump, getnext, call, expr. This is how interfaces are usually defined in code:
Non-negative value if successful, negative on error.
Additional notes:
It is important to minimize the possibility of failure here. The system cannot help in any way if memory or ownership states are corrupted.
GETINDEX - index/property retrieval callback
When called:
On A[B] (index) and A.B (property) reads in SGScript.
Index/Property/Path function families in the C API.
Additional arguments:
object argument (see sgs_ObjectArg) -- (0/1) whether this is a property read (1) or index read (0)
Stack frame:
Item 0 - key variable to be used to find a sub-item.
Expected to have at least one item on stack after a successful index/property read. The topmost one is used.
Return values:
Non-negative value if successful, negative on error.
Use SGS_ENOTFND if the specified index/property was not found.
Additional notes:
It is safe to use conversion functions on the key variable.
SETINDEX - index/property setting callback
When called:
On A[B] (index) and A.B (property) writes in SGScript.
Index/Property/Path function families in the C API.
Additional arguments:
sgs_Variable* key -- key variable to be used to find a sub-item
sgs_Variable* value -- value variable to be used in setting the value of the sub-item
object argument (see sgs_ObjectArg) -- (0/1) whether this is a property read (1) or index read (0)
Stack frame:
Item 0 - key variable to be used to find a sub-item.
Item 1 - value variable to be used in setting the value of the sub-item.
Return values:
Non-negative value if successful, negative on error.
Use SGS_ENOTFND if the specified index/property was not found.
Use SGS_EINVAL if the given value could not be used.
Additional notes:
It is safe to use conversion functions on both variables.
CONVERT - conversion callback
When called:
Depending on the argument, it is called by different sources:
type conversion: to*** function families and other conversion triggers (like operators) in SGScript and the Get/To/Parse function families in the C API.
Non-negative value if successful, negative on error.
Additional notes:
Callback requires no data but expects that sgs_Serialize / sgs_SerializeObject is successfully called once. In the case of sgs_SerializeObject, the necessary number (equal to argument count, passed to the function) of sgs_Serialize calls must be made before it.
int act -- specifies the actions that should be taken in this call.
if argument == 0, iterator's position must be increased by one step and the return value must contain whether iterator has not reached end (positive value if so, otherwise - 0) or an error if callback has failed
otherwise, callback must push the data required (if SGS_GETNEXT_KEY is set, the key, and if SGS_GETNEXT_VALUE is set - the value, in exactly that order)
Stack frame:
Empty at beginning. Expects at least one (if either flag is set, but not both) or two (if both flags are set) variables on success. Key first, value second.
Return values:
Non-negative value if successful, negative on error.
if argument == 0, must return whether iterator has not reached end (positive value if so, otherwise - 0)
Additional notes:
None.
CALL - the "function call" callback
When called:
when an object is used like a function
object_variable( .. ) in SGScript
Call function family in the C API
Additional arguments:
None.
Stack frame:
Contains all the same things as any C function call would: optional this variable and optional argument list. Expects at least the returned number of items after the call to be used as return values.
Return values:
Non-negative value if successful, negative on error.
On success, the number returned is the number of variables returned by the function.
Additional notes:
None.
EXPR - expression callback
When called:
On arithmetic operations with the object as one or both operands in SGScript.
sgs_Variable* A - the first (left side) operand or the only one in case of SGS_EOP_NEGATE
sgs_Variable* B - the second (right side) operand or NULL in case of SGS_EOP_NEGATE
int op - operation type, one of SGS_EOP_[ADD|SUB|MUL|DIV|MOD|COMPARE|NEGATE]
Stack frame:
Empty at beginning. Expects at least one variable on success (the topmost one will be used).
Return values:
Non-negative value if successful, negative on error.
Additional notes:
SGS_EOP_COMPARE expects int/real value: >0 if A > B, <0 if A < B, =0 if A = B
The full list of operators triggering the operations:
SGS_EOP_ADD: binary +, +=
SGS_EOP_SUB: binary -, -=
SGS_EOP_MUL: *, *=
SGS_EOP_DIV: /, /=
SGS_EOP_MOD: %, %=
SGS_EOP_COMPARE: <, <=, >, >=, ==, !=, ===, !==
SGS_EOP_NEGATE: unary -
Most operations are directed towards the scripting engine, that is - environment makes a function call and scripting engine handles it. For everything else, there are callbacks.
Memory allocation
sgs_MemFunc must be specified in creation of the scripting engine context
sgs_MemFunc (re)allocates and frees memory
unlike realloc, if size = 0, this function must return NULL
The OLD way, version 2: Method + function handling (function that can be called as a method too)
int method_call = sgs_Method( C );
sgs_FuncName( C, method_call ? "<object>.<method>" : "<object_function>" );
if( !sgs_IsObject( C, 0, Object_interface_pointer ) )
return sgs_ArgErrorExt( C, 0, method_call, "<object>", "" );
ObjectDataStruct* data = (ObjectDataStruct*) sgs_GetObjectData( C, 0 );
sgs_ForceHideThis( C );
The reason why the first method is preferred to others is: it's shorter, does pretty much the same thing and resolves to sgs_ParseMethod, which allows sharing more code and rebuilding the SGS_PARSE_METHOD macro abstraction, if necessary.
This library is loaded on initialization and contains the functions and constants necessary to do primary actions:
execution of additional scripts
creating and using array, class, dict, map, closure
converting between variables
serialization / deserialization
operation control
error handling and throwing
global environment modification
standard input / output
data debugging
Functions:
containers
array - returns an array containing the specified variables
dict - returns a key-value map object (string keys) containing the specified variables
map - returns a key-value map object (many key types) containing the specified variables
class - returns an index fallback / overloading object, linking both specified variables
array_filter - returns an array that is filtered with a truth test or the specified function
array_process - convert all array elements by a function
dict_filter - returns a dict object that is filtered with a truth test or the specified function
map_filter - returns a map object that is filtered with a truth test or the specified function
map_process - convert all dictionary elements by a function
returns a 'dict' (dictionary/hash table) object, containing the even arguments mapped to respective previous arguments
if no arguments are passed, an empty dictionary is returned
if an even number of arguments is passed, function returns null and emits a warning
all even arguments must be strings or convertible to strings
dict( "name", "John", "phone", 1234567890 ); // same as { name = "John", phone = 1234567890 }
map( [key, value, ...] )
returns a 'map' (map/hash table) object, containing the even arguments mapped to respective previous arguments
if no arguments are passed, an empty map is returned
if an even number of arguments is passed, function returns null and emits a warning
map( "John", "name", 1234567890, "phone" ); // useful for mapping additional data to variables or making sets
class( object obj, object metaobj )
sets metaobj as meta-object for obj and enables metamethods for obj, returns obj
refer to Classes for more information on how class objects work and how to use them
someCommonInterface = { printdata = function(){ print( this.data ); } };
c = class( { data = "5" }, someCommonInterface );
c.data = "6";
c.printdata(); // prints 6
array_filter( array[, callable] )
return an array with the items that convert to boolean 'true', optionally preprocessed by a callable
two arguments are passed to the callable: value, index
array_filter([ 0, 1, 2, 3 ]); // returns [1,2,3]
array_process( array, callable )
return an array with the items passed through a callable
two arguments are passed to the callable: value, index
dict_filter( dict[, callable] )
return a dict with the items that convert to boolean 'true', optionally preprocessed by a callable
two arguments are passed to the callable: value, key
dict_filter({ a = 0, b = 1 }); // returns {b=1}
map_filter( map[, callable] )
return a map with the items that convert to boolean 'true', optionally preprocessed by a callable
two arguments are passed to the callable: value, key
map_filter(map{ a = 0, b = 1 }); // returns [map]{b=1}
map_process( obj, callable )
pass all items through a callable, return same object
two arguments are passed to the callable: value, key
dict_size( dict )
return the number of entries in the dict object
x = { a = 5, b = 123 };
dict_size( x ); // returns 2
map_size( dict )
return the number of entries in the map object
x = map( 5, "a", 123, "b" );
map_size( x ); // returns 2
isset( var, key )
returns whether a property key is readable (exists) in variable var
x = { a = 5 };
isset( x, "a" ); // returns 'true'
isset( x, "b" ); // returns 'false'
isset( print, "call" ); // returns 'true' -- works with built-in special properties too
unset( dict|map var, (string) key )
removes an entry named key from the dictionary var
key is only required to be of string type if a dict is passed
clone( var )
creates a one-reference copy of the variable var or returns null and emits a warning on failure
variables that are passed by value (null, bool, int, real, cfunc) or strings/functions are returned as-is, since for value types all copies are same and strings/functions are immutable
x = { a = 5 };
y = clone( x );
z = x;
x.a = 6;
print( y.a ); // prints "5"
print( z.a ); // prints "6"
get_keys( iterable var )
returns an array of keys found in the iterable object var or returns null and emits a warning on failure
var must be an iterable object (has interface OP_CONVERT defined and successfully handles a CONVOP_TOITER request) - such objects are array, dict, map and io_dir
returns an array of values found in the iterable object var or returns null and emits a warning on failure
var must be an iterable object (has interface OP_CONVERT defined and successfully handles a CONVOP_TOITER request) - such objects are array, dict, map and io_dir
it = get_iterator( [ 5 ] ); // retrieves iterator from array [5]
iter_advance( it ); // returns true, indicating that the current position is still a valid one
it = get_iterator( [ 5 ] ); // retrieves iterator from array [5]
iter_advance( it ); // returns true, indicating that the current position is still a valid one
(key,value) = iter_getdata( it, true ); // returns 0, 5 -- key, value
tobool( var )
returns a boolean, generated from variable var using the Conversion rules
passes all arguments, converted to strings, to the (error) output callback
(err)print passes arguments without modifications
(err)println passes a newline character after all variables
(err)printlns passes a newline character after each variable
print( 5, "x" ); // prints "5x"
println( 5, "x" ); // prints "5x\n"
printlns( 5, "x" ); // prints "5\nx\n"
errprint( 5, "x" ); // prints "5x" to error output stream
errprintln( 5, "x" ); // prints "5x\n" to error output stream
errprintlns( 5, "x" ); // prints "5\nx\n" to error output stream
printvar_ext( var, int maxdepth = 5 )
passes a dump of the variable (informative string version) to the output callback, allowing to optionally specify the maximum depth of the dump (how deep it is allowed to look for sub-variables)
returns a random 'int' value in the range [0;RAND_MAX] - from 0, inclusive, to RAND_MAX, inclusive
randf()
returns a random 'real' value in the range [0;1] - from 0, inclusive, to 1, inclusive
srand( int seed )
specify a different seed value for the built-in pseudo-random number generator
srand( 5 ); // restart the generator with the value specified
srand( ftime() ); // restart the generator depending on the second the code was executed
hash_fnv( string buf, bool as_hexstring = false )
generate a hash, using the FNV-1a algorithm
if as_hexstring is true, an 8 characters long string containing the hexadecimal fixed-length hash value is returned, otherwise the value is returned as integer
generate a hash/checksum, using the CRC32 algorithm
if as_hexstring is true, an 8 characters long string containing the hexadecimal fixed-length hash/checksum value is returned, otherwise the value is returned as integer
va_get_args()
return an array containing all arguments passed to the calling function
va_get_arg()
returns one of the arguments passed to calling function
va_arg_count()
return the number of arguments passed to the calling function
metaobj_set( object obj, object metaobj )
sets the meta-object of an object
metaobj_get( object obj )
retrieves the meta-object of an object
metamethods_enable( object obj, bool enable )
enables or disables metamethod support for an object
metamethods_test( object obj )
retrieves metamethod support state
mm_getindex_router( key )
__getindex to __get_*** router
when applied to metaobject of a metamethod-enabled object's __getindex property, it routes every request to __get_<key>
This object can be used to stop a thread. See end_on for more info.
If the event is found in the object as a property by the given name, that event is returned instead, without modification.
Otherwise, a new event is created and registered to the specified object as a property with the given name.
Event is initialized to the state specified by signaled.
This object consists of one property - bool signaled. It can be modified to change the state of the event.
end_on( this thread, object event, bool enable = true )
Set or unset an event object that can stop a thread.
An "event" is any object that implements the "convert to bool" behavior. True means 'signaled', false - inactive.
When the event is signaled, the thread will be aborted as soon as possible.
Alternatively accessible via the "end_on" property of any thread.
function returns a coroutine object that can be started/resumed with co_resume
co_resume( coroutine[, args..] )
Starts or resumes the coroutine.
if function is called for the first time, the attached callable is called within the coroutine, passing given arguments to the callable
if function is suspended, arguments will be passed as return values from the yield call that suspended it
thread_create( fn, this[, args..] )
Starts a managed topmost coroutine (thread).
This function returns the created thread.
The function passed is immediately called once.
This thread is owned by the topmost owned thread (engine thread or coroutine created with co_create) - if it's destroyed, this thread will be aborted and destroyed as well.
subthread_create( fn, this[, args..] )
Starts a managed sub-coroutine (subthread).
This function returns the created subthread.
The function passed is immediately called once.
This thread is owned by the current context (thread, subthread, engine thread or coroutine) - if it's destroyed, this thread will be aborted and destroyed as well.
process_threads( dt[, ctx ] )
Advance the managed subthreads once, using the specified delta time.
If ctx is unspecified, it defaults to the current context.
This function should only be used on the engine thread and owned coroutines since it recursively advances all subthreads.
yield([ args.. ])
Suspends the current state, returning to caller.
Arguments are passed as return values to the caller.
State cannot be suspended if there are C functions or other unusual constructs in the stack.
abort([ contexts... ])
Stops execution and returns to C code as soon as possible.
If no contexts are specified, current context is aborted, otherwise only the specified contexts are aborted.
This function is equivalent to using sgs_Abort in the C API.
abort();
print( 5 ); // this line is not reached
sys_apply( func, this, args )
calls the specified function with the specified this value and argument array
If argument count is known at compile time, prefer using the compatible function call syntax (thisvar!func( arg1, ... )) to this function.
C functions, SGS functions and closure objects have this function under the "apply" property.
pcall( callable func[, callable errh ])
Calls the callable func, hiding internal errors from the caller or optionally passing them to callable errh.
errh error handler is called with two arguments - int error_type, string message - and expected to return 0-1 arguments ([int])
error handler must return 0/null/nothing if the error has been successfully processed or the new error type otherwise
errors thrown inside the handler will not be caught so any error can be transformed to any other
an empty function behaves the same way as pcall without function
loads the global variables of the specific library lib in the state, returns success, as bool, emits a warning on failure
lib must be one of 'fmt', 'io', 'math', 'os', 'string'
if override is specified, library will be reloaded even if it was already loaded before
this function is equivalent to the sgs_LoadLib_* functions in the C API
printvar( sin ); // warning, null
include_library( "math" );
printvar( sin ); // C function
include_file( string file[, bool override ] )
executes the file pointed to by file, returns success, as bool, emits a warning on failure
this function does NOT check include paths (SGS_PATH)
if override is specified, file will be reloaded even if it was already loaded before
this function is equivalent to sgs_ExecFile in the C API
include_file( "something.sgs" ); // loads something
include_file( "something.sgs" ); // does not load it again
include_file( "something.sgs", true ); // loads it again
include_shared( string file[, bool override ] )
runs the shared library pointed to by file, returns success, as bool, emits a warning on failure
file must be available according to the platform-specific rules of dynamic libraries (shared objects)
the library must contain a 'sgscript_main' function that will be called on initialization
if override is specified, file will be reloaded even if it was already loaded before
include_shared( "sgsjson.dll" ); // load the JSON library DLL on Windows
find_include_file( string file )
Find the include file according to SGS_PATH.
File is searched according to the semicolon-separated pattern list specified in the global variable SGS_PATH.
| - replaced with calling file's directory.
@ - replaced with process directory.
? - replaced with file.
A path to file is only returned if the file exists.
include( string file[, bool override ] )
tries to load a library or a file according to the include path
the order of actions:
first, a library load is attempted
on failure, a file is looked for by every entry of the sys_include_path variable (? - the file replacement symbol, | - file directory replacement symbol, ; - separator)
if file is not found, everything stops
if file is found, first - a library load is attempted, on failure - bytecode & source code loading
this function is also available as a replicating statement 'include' (ex. 'include "fmt", "io";' works the same way as include("fmt"); include("io");)
import_cfunc( string file, string funcname )
retrieves the funcname function of the shared library file, returns success, as bool, emits a warning on failure
Do not call 'sgscript_main' using this function! That function has different return value semantics so at best, something will work, a warning could be emitted and the worst case is that something will crash.
file must be available according to the platform-specific rules of dynamic libraries (shared objects)
returns the path of the file (as passed on load) that contains the currently executed code or null if the file cannot be determined (eval, C functions)
// a.sgs
print( sys_curfile() ); // prints "a.sgs"
// b.sgs
include "a.sgs";
sys_curfiledir()
returns the directory path of the file (as passed on load) that contains the currently executed code or null if the file cannot be determined (eval, C functions)
// ext/a.sgs
print( sys_curfiledir() ); // prints "ext"
// a.sgs
print( sys_curfiledir() ); // prints "."
// b.sgs
include "ext/a.sgs", "a.sgs";
sys_curprocfile()
returns the path of the executable file the script is executed in
in case of failure, function will set errno
sys_curprocdir()
returns the path to the directory of the executable file the script is executed in
combines prefixes (paths) and suffixes (extensions) through the joiner string to create an array of paths
default value of suffixes is platform-specific and equal to the last logical half of the initial SGS_PATH value
from code: suffixes = "?;?" SGS_MODULE_EXT ";?.sgc;?.sgs" (SGS_MODULE_EXT is either ".dll" or ".so")
prefixes and suffixes are expected to be in the SGS_PATH format - semicolon-separated (;) strings
sys_backtrace( bool as_string = false )
returns the call stack function/line/file list, either as array of dicts, or as a string, formatted exactly like in error messages (via sgs_PushErrorInfo)
in the dict, there are three keys:
func (string)
line (null/int)
file (string)
errprint( sys_backtrace( true ) ); // print backtrace to stderr
println( sys_backtrace().last.func ); // print name of current function
sys_msg( int code, string message )
passes a message to the internal messaging system (one that's commonly used to report errors and warnings)
Different codes can be handled differently by the system. By default, SGS_ERROR code will stop execution and return to C code as soon as possible.
code is the code to use to pass the message (ex. SGS_INFO, SGS_WARNING, SGS_ERROR)
INFO( string message )
passes a message to the internal messaging system
the name of this function is written in all caps to avoid shadowing by a common variable name
works exactly like sys_msg( SGS_INFO, message )
WARNING( string message )
passes a message to the internal messaging system
the name of this function is written in all caps for consistency with the other two
works exactly like sys_msg( SGS_INFO, message )
ERROR( string message )
passes a message to the internal messaging system
By default, SGS_ERROR code (the one this function uses internally) will stop execution and return to C code as soon as possible.
the name of this function is written in all caps to avoid shadowing by a common variable name
works exactly like sys_msg( SGS_INFO, message )
app_abort()
calls the abort() function of the C standard library (crashes the application)
app_exit( code = 0 )
calls the exit() function of the C standard library (exits the application)
sys_replevel([ level ])
returns the current reported error level and optionally sets a new one
The effects of this function are not reverted automatically at any moment (unless implemented manually with hooks).
old_level = sys_replevel( SGS_ERROR ); // report only errors or worse
magic = calculate_magic(); // any warnings are never processed at this point
sys_replevel( old_level ); // restore the old reported level
sys_stat( code )
prints info about virtual machine state, everything is implementation-defined
accepted codes are the same as for sgs_Stat, which this function is equivalent to
sys_stat( 11 ); // dumps all globals
errno( as_string = false )
returns the last relevant error number for the C standard library, as an integer or a string if as_string is set
data = io_file_read( "doesnotexist" ); // file that does not exist
print errno(); // prints 2
print errno(true); // prints "No such file or directory" on Windows
errno_string( int code )
returns the error string for the given code
errno_string(errno()) is equivalent to errno(true)
print errno_string(2); // prints "No such file or directory" on Windows
errno_value( string key )
returns the number for the error key (for "ENOENT" it would return 2)
this function might be a bit slow considering that it currently does not use any extended lookup systems, such as hash tables
data = io_file_read( "doesnotexist" );
if( errno() == error_string("ENOENT") )
println( "file does not exist" );
dumpvar_ext( var, int depth = 5 )
similar to printvar_ext() but returns the dump instead of printing it
this function is equivalent to sgs_DumpVar in the C API
similar to printvar() but returns the dumps, concatenated, instead of printing them
gc_collect()
Runs the garbage collector on the virtual machine, waiting until it has finished
returns the reduction in objects or false on failure
a = [];
a.push(a); // creates a circular dependency
a = null; // a is not actually freed
gc_collect(); // a is freed now
serialize( var, int mode = 2 )
Converts the variable to a byte buffer (string), containing the serialized data that can be recreated with unserialize() or returns null and emits a warning on failure
C functions and objects without OP_SERIALIZE implemented cannot be serialized
data = serialize({ name = "A", info = "B" });
print data; // prints random-looking, yet deterministic garbage
print unserialize(data); // prints {name=A,info=B}
unserialize( string data, int mode = 2 )
Recreates a variable from the buffer with serialized data or returns null and emits a warning on failure
this function will internally call global object creation functions specified in the data, so they must be defined and the state could change in the process
data = serialize({ name = "A", info = "B" });
print data; // prints random-looking, yet deterministic garbage
print unserialize(data); // prints {name=A,info=B}
sgson_encode( var[, string tab ])
Encodes the given variable in the SGSON format.
tab allows to specify the string used for tabs
if tab is null, the text output is tightly packed, without any extra spaces
sgson_decode( string data )
Decodes the SGSON text into a variable
SGS_INFO, SGS_WARNING, SGS_ERROR, SGS_APIERR
defined to the values of the C macros, respectively 100, 200, 300 and 330
these constants are defined to the values of the C macros (with the prefix "SGS_VT_" in C) and can be compared with the values returned in typeid()
RAND_MAX
The maximum number that can be returned by rand().
A hard-coded global value that points to the global dictionary. Can be used to access non-identifier globals and change the global dictionary.
_G["$diff"] = 5; // no way to access this via usual globals
_G = {}; // global value dictionary is changed, previous functions are lost unless stored somewhere
A hard-coded global value that points to the registry. Can be used to access the symbol table (_R["$sym"]) or include list (_R["$inc"]).
It is preferred to use sym_get and sym_register to deal with symbols, this table is mostly available for introspection and workarounds.
The currently executed function.
It can be used in a function to call itself recursively, especially when the function is defined in a position where it cannot reference itself without additional effort.
function f(){ printvar( _F ); }
f(); // prints info about function 'f'
The currently executed thread (context/coroutine).
function f(){ printvar( _T ); }
f(); // prints info about current context
thread f(); // prints info about this newly created thread
appends the variables passed to the end of array in the same order, returns the array for chaining
a = [ 5 ];
a.push( 6, 7 ).push( 8 ); // a = [5,6,7,8]
array.pop()
removes one item from the end of array or emits a warning if there are no items in the array, returns the removed item
a = [ 5 ];
a.pop(); // array is empty now
a.pop(); // warning: "array is empty, cannot pop"
array.shift()
removes one item from the beginning of array or emits a warning if there are no items in the array, returns the removed item
a = [ 5, 6 ];
a.shift(); // a = [6], returned 5
array.unshift( ... )
prepends the variables passed to the beginning of array in the same order, returns the array for chaining
a = [ 5 ];
a.unshift( 6, 7 ); // a = [6,7,5] now
array.insert( int pos, ... )
inserts the variables passed (all arguments after first) to the position specified in the array or emits a warning on failure (index out of bounds), returns the array for chaining
pos accepts both positive and negative values, the meaning is "which value to insert before"
negative values are converted to positive ones by adding (<size> + 1) to them
beginning of array can be inserted to using position 0 or (- <size> - 1)
end of array can be inserted to using position <size> or -1
a = [ 5, 7 ];
a.insert( 1, 6 ); // inserts 6 at position 1 (before item with index 1)
a.insert( -1, 8 ); // appends to the end of array, a = [5,6,7,8] now
array.erase( int[, int] )
erases item or a range of items from the array, depending on the arguments passed or emits a warning on failure, returns the array for chaining
both arguments have same index processing rules as array.insert(), but with one difference - if both arguments are passed, after resolving (converting negative indices to positive ones, if there are any), first must be smaller than second
a = [ 5, 6, 7, 8 ];
a.erase( 1, 2 ); // erases all items between position 1 and 2, including; gives a = [5,8]
a.erase( 0 ); // a = [8]
array.part( int from[, int max ] )
returns a new array starting from index from, with the max. size max
if from is negative, it is subtracted from the end of array
max cannot be negative, everything else is allowed
erases all items from the array, returns the array for chaining
a = [ 1, "asd", 8 ];
a.clear(); // a = []
array.reverse()
reverses the order of items in the original array, returns the original array for chaining
a = [ 1, 2, 3 ];
b = a;
a.reverse(); // a = [3,2,1]
print( b ); // prints [3,2,1]
array.resize( int size )
changes the size of the array, returns the array for chaining
size must be larger than or equal to 0
if previous size was less than passed to the method, null variables are appended
if previous size was more than passed to the method, items are popped from the end of the array
if size was not changed, nothing else will change
a = [ 5, 6, 7 ];
a.resize( 5 ); // a = [5,6,7,null,null]
a.resize( 2 ); // a = [5,6]
array.reserve( int capacity )
reserves the space for the requested number of elements in the array, returns the array for chaining
capacity must be larger than or equal to 0
if previous capacity was less than passed to the method, capacity will be increased to the requested amount
if previous capacity was more than or equal to what was passed to the method, nothing will change
a = [ 5, 6, 7 ];
a.capacity( 1 ); // nothing happens
a.capacity( 5 ); // a.capacity = 5 and two variable additions can now happen without reallocations
array.sort([ bool reverse ])
sorts the array using the sgs_Compare C API function for comparisons, returns the array for chaining
if reverse is true, array is sorted in the reverse order
a = [ 6, 8, 7, 5 ];
a.sort(); // a = [5,6,7,8]
array.sort_custom( callable[, bool reverse ] )
sorts the array using the callable for comparisons, returns the array for chaining
This function is considerably slower than array.sort or array.sort_mapped so prefer those if performance matters.
callable must return a number, specifying the relative order of the two passed arguments: less than 0 if first variable should be placed before second, greater than 0 if first variable should be placed after second, or 0 if it doesn't matter
if reverse is true, array is sorted in the reverse order
a = [ 6, 8, 7, 5 ];
// this code will sort numbers into odd/even ones and in ascending order
a.sort_custom( function(a,b){return a-b+(b%2-a%2)*1000; } ); // a = [5,7,6,8]
array.sort_mapped( array map[, bool reverse ] );
sorts the array by sorting the passed array and applying the index map to the first one, returns the array for chaining
both arrays must have the same size, otherwise a warning is emitted
all variables in the mapping array are interpreted as 'real' values
if reverse is true, array is sorted in the reverse order
a = [ 5, 6, 7, 8 ];
b = [ 3, 1, 4, 2 ];
a.sort_mapped( b ); // a = [6,8,5,7]
array.find( var item[, bool strict[, int from ]] )
attempts to find item in array, starting from 0 or the index passed with from, if it exists, using basic or strict equality comparisons (depending on strict), returning the index or 'null' if item was not found
if strict comparisons are enabled, variable types are also checked for equality
array.remove( var item[, bool strict[, bool all[, int from ]]] )
attepts to find and remove first or all item variables in array (depending on all), according to the rules specified in array.find(), returning the number of removed items
a = [ 5, 2, 6, 7, 8, 2 ];
a.remove( "7" ); // returns 1; a = [5,2,6,8,2]
a.remove( "6", true ); // returns 0; a remains unchanged
a.remove( 2, false, true ); // returns 2; a = [5,6,8]
array.unique( bool strconv = false )
returns an array without duplicates, where a duplicate is a strictly equal variable or a string conversion match
if strconv is true, items are converted to strings before comparison
unpacks the byte buffer data using the format fmt, returns unpacked items (possibly in an array)
if '#' can be found in the format string, all data is returned in, otherwise
unpacks c/w/l/q/p to integers, f/d to floats, s to strings
if signed numbers are expected (as set by the "-" modifier), the sign bit of the expected type is extended to the end of the native integer type, this makes the loaded integer signed
refer to fmt_pack() for more info about the format
fmt_custom_encode( string text, string class, string prefix, int format, int mindigits = 0, string postfix = "" )
encodes a custom format (selected character codes to numeric version)
Every character (byte) from text that matches class is converted to the following format:
prefix
number according to format, at least mindigits long
postfix
format must be one of:
FMT_NUMBER_BINARY - binary (base 2) number
FMT_NUMBER_OCTAL - octal (base 8) number
FMT_NUMBER_DECIMAL - decimal (base 10) number
FMT_NUMBER_HEX, FMT_NUMBER_HEX_LC - lowercase hexadecimal (base 16) number
FMT_NUMBER_HEX_UC - uppercase hexadecimal (base 16) number
Use case example - encode a string for C/C++ source code output:
// character class:
// - '^' - invert class (exclude the following characters/ranges)
// - <space>, '!' - characters to exclude (first printable characters, before quote)
// - '#-~' - character range to exclude (other printable characters, after quote)
// the extra set of quotes around hex code prevent hyperactive parsers from thinking ..
// .. that the following characters might be a part of the hex code
fmt_custom_encode( 'some text', "^ !#-~", '""\\x', FMT_NUMBER_HEX, 2, '""' )
fmt_text( [int prealloc,] string text, ... )
parses all format specifiers in text and returns the result
see string_format if you need position (argument index) specifications in the format string
prealloc specifies number of bytes to be preallocated on the buffer to avoid continuous reallocations during string generation
the general format of a format specifier is as follows: {fmt[size][.prec][r][p<char>]}
fmt: one-character output type (b-binary, o-octal, d-decimal, x/X-hexadecimal, f-floating-point, g/G-compact floating-point, e/E-scientific floating-point, s-valid string, c-always converted to string)
size: minimum number of characters to print
prec: precision of floating-point variables, string length limit
r: add "r" to right-pad (left-justify)
p<char>: add "p" and any character to set that character as the padding character (default: space/0x20)
if anything unexpected happens, this function will emit a warning and put "#error#" in the place of a format specifier
print fmt_text( "{d} -> {x}", 1337, 1337 ); // prints "1337 -> 539"
print fmt_text( "null: {d}, {s}, {c}", null, null, null ); // prints "null: #error#, #error#, null" and emits two warnings for item 1 and item 2
print fmt_text( "pi: {f10.10r} {g10r} {E10r}", M_PI, M_PI, M_PI ); // "pi: 3.1415926536 3.14159 3.141593E+000"
fmt_parser( callable[, buffersize ] )
creates a fmt_parser object, connected to the callable
the callable is a function that returns at most the number of bytes requested from the stream
if previous request reached end, subsequent requests must return 'null'
f = io_file( "test.txt", FILE_READ );
// usually it is easier to use @fmt_file_parser instead of the next line of code
p = fmt_parser( function( num ) use( file ){ if( file.eof() ) return null; return file.read( num ); } );
checks if the first character of string char is included in the character class class
character class is a regex-like list of specific symbols and ranges (its low and high symbols separated by "-"), optionally prepended by "^" that inverts the scope of the class
closes the previously open file if any, opens the file name for operation mode mode, returns bool/sets errno
mode must be one of FILE_READ, FILE_WRITE or FILE_READ|FILE_WRITE
file is always opened in binary mode
f = io_file();
f.open( "file" ); // returns true or false, sets errno accordingly
io_file.close()
closes the previously open file, if any, returns whether the file was open or not
f = io_file( "file.txt" );
f.close(); // returns true if the file existed
f.close(); // returns false
io_file.read( int num )
reads and returns at most num bytes from file, sets errno
io_file.write( string data )
writes the byte buffer data to the file, sets errno
io_file.seek( int off, int mode )
sets the offset in file, returns bool/sets errno
mode must be one of SEEK_SET (sets as-is), SEEK_CUR (sets relative to current), SEEK_END (sets relative to end)
io_file.flush()
flushes a buffered file, returns bool
io_file.setbuf( int size )
sets the size of file buffer (0 to disable buffering), returns bool
buffering allows to avoid committing each write immediately to disk, resulting in a performance gain, however it may introduce issues if application is expected to fail at any moment, resulting in some data not being written in such cases
io_file([ string name, int mode ])
creates and returns a file, optionally allowing to open it on creation
see io_file.open() for more info on opening the file
features:
tostring = "directory_iterator"
iterator interface (key = whether real item, value = item name)
real items are all except "." and ".."
returns self as iterator
GC-safe
type identification (returns the string "directory_iterator")
io_dir( string dir )
creates a directory iterator for the directory dir, sets errno, returns null and emits a warning on failure
abs( 2.2 ); // real (2.2)
abs( -3.1 ); // real (3.1)
floor( x )
returns the largest integer that is not bigger than x, as real
floor( 3.4 ); // real (3)
floor( 3.8 ); // real (3)
floor( 4.2 ); // real (4)
floor( -3.1 ); // real (-4)
ceil( x )
returns the smallest integer that is not smaller than x, as real
ceil( 3.4 ); // real (4)
ceil( 3.8 ); // real (4)
ceil( 4.2 ); // real (5)
ceil( -3.1 ); // real (-3)
round( x )
returns the closest integer to x, as real
round( 3.4 ); // real (3)
round( 3.8 ); // real (4)
round( 4.2 ); // real (4)
round( -3.1 ); // real (-3)
pow( x, y )
returns x raised to the power y, as real
If base (x) is negative and exponent (y) is not an integral value, or if base is zero and exponent is negative, function returns null and emits a warning message.
returns the arctangent of x (angle in radians), as real
atan( 0 ); // real (0)
atan( 1 ); // real (0.785398)
atan( 9999999 ); // real (1.5708)
atan2( y, x )
returns the extended arctangent of y/x (angle in radians), as real
Signs of x and y are used to determine the quadrant, thus y is expected to be the sine of the angle to be returned (the y coordinate of a point) and x - the cosine (the x coordinate).
Due to the common requirement to use this function to determine the angle between two somewhat random points (usually from a simulation), it will not emit a warning when both arguments are 0 - it will return 0 instead.
atan2( 0, 1 ); // real (0)
atan2( 1, 0 ); // real (1.5708)
atan2( -1, -1 ); // real (-2.35619)
atan2( 0, 0 ); // real (0)
deg2rad( x )
returns angle, converted from degrees to radians, as real
deg2rad( 0 ); // real (0)
deg2rad( 180 ); // real (3.14159)
deg2rad( -90 ); // real (-1.5708)
rad2deg( x )
returns angle, converted from radians to degrees, as real
rad2deg( 0 ); // real (0)
rad2deg( M_PI ); // real (180)
rad2deg( -M_PI / 2 ); // real (-90)
M_PI
the ratio of circumference of a circle to its diameter (pi)
the value of this constant is 3.14159265358979323846
M_E
the natural logarithmic base (e)
the value of this constant is 2.7182818284590452354
re_match( string str, string pattern, int flags = 0, int offset = 0 )
check for matches in string str for pattern, returning whether a match was found or details of the first match, as specified in flags
offset specifies the character to start matching from, negative offset is subtracted from the end
flags expect RE_RETURN_CAPTURED, RE_RETURN_OFFSETS or RE_RETURN_BOTH, which is a combination of the previous two
if flags is one of the special values, function returns an array of captured ranges
each entry is either a string (on RE_RETURN_CAPTURED) or an array of beginning and end offsets (at positions 0 and 1, respectively, on RE_RETURN_OFFSETS) or an array of a string and the beginning and end offsets (on RE_RETURN_BOTH)
the first character in the pattern specifies a delimiter that separates the pattern from modifiers
example pattern strings: "/^[a-z]+/mi", "#[a-z0-9_]+.*?#s"
more info about the supported features and modifiers at https://github.com/snake5/sgregex
re_match_all( string str, string pattern, int flags = 0, int offset = 0 )
check for matches in string str for pattern, returning the number of matches or details of all matches, as specified in flags
similar to re_match, only difference is in the return value:
if flags is one of the special values, function returns an array of matches where each match is an array of captured ranges
find and replace all occurrences of pattern in string str with the replacement string
replacement string can contain backreferences in the forms "$[0-9]" or "\[0-9]" (in a string constant, a double backslash is required due to rules in SGScript)
there can be up to 10 backreferences, 0-9, 0 being the whole match
These constants are used in re_match / re_match_all to specify the format of the return value.
If none of these are specified (flags = 0), only the success state is returned (true/false/match count).
RE_RETURN_CAPTURED - return the captured string
RE_RETURN_OFFSETS - return array of start/end offsets
RE_RETURN_BOTH - return both the captured string and the start/end offsets in an array
This library contains the string handling functions. In all functions, except the utf-8 ones, it is assumed that strings are byte buffers where each byte is one character. If correct indices are applied, many of them will work with multibyte strings.
string_format - format variables according to the specified format string
Constants:
STRING_NO_REV_INDEX - used by string_cut / string_part - disable handling of negative indices
STRING_STRICT_RANGES - used by string_cut / string_part - disable handling of out-of-bounds indices
STRING_TRIM_LEFT, STRING_TRIM_RIGHT - used by string_trim - specify sides to be trimmed
STRING_PAD_LEFT, STRING_PAD_RIGHT - used by string_pad - specify sides to be padded
read/write properties:
[int] offset - returns the point in string from which last character was retrieved, can set point in string from which next character will be retrieved
read-only properties:
[string] string - returns the string currently being iterated on
other features:
iterator (self)
cloning
serialization
GC-safe
string_cut( string str, int from[, int to[, int flags]] )
returns a part of string str, from and to being positions of the first and last character returned, respectively
if to is not specified, to is assumed to be the position of the last character in string str
if from or to are negative, they point to characters before the end of string (-1 being the last one)
available values for flags:
STRING_NO_REV_INDEX - emit warnings on negative indices, instead of handling them
STRING_STRICT_RANGES - emit warnings on out of bounds from/to values instead of silently ignoring the outside characters
Utility to combine a single script with the virtual machine. After compilation, the executable passes all input arguments as-is as argc and argv to the script.
Command line syntax
sgsexe <output-file> <script-file>
The application runs all test scripts in ./tests directory. File handling rules are as follows:
Ignore files with !_ prefix.
Require compilation success for tests with s_ prefix.
Require compilation failure for tests with f_ prefix.
If there is no prefix, require that the system does not crash.
If a file name as TF in it, use the advanced testing framework.
Files are sorted according to the logical order of testing: requirements grow with test file number:
first sort by whether testing framework (TF) is required (files that don't need it go first);
then sort by whether compilation success is required (files that do need it go first);
finally, sort by whether compilation state is expected (files that do need it go first);
The sort order broken down:
1. s_ tests without TF
2. f_ tests without TF
3. tests without prefix and TF
4. s_ tests with TF
5. f_ tests with TF (none such tests exist because it would make no sense to have them)
6. tests without prefix but with TF
The application sgstest must be run from root, like so: bin/sgstest. It generates two log files: ./tests-errors.log and ./tests-output.log.
This application tests the core programming interface. It generates two log files: ./apitests-errors.log and ./apitests-output.log.
This application tests the C++ programming interface that utilizes the binding compiler.
This application tests the SGScript engine's ability to work with more than one thread (different instances for each) at the same time.
This is an engine profiler hook extension. It supports three processing modes.
Modes
Function timing profiler: use SGS_PROF_FUNCTIME to initialize
Instruction timing profiler: use SGS_PROF_OPTIME to initialize
Memory usage profiler: use SGS_PROF_MEMUSAGE to initialize
Functions
void sgs_ProfInit( sgs_Context* C, sgs_Prof* P, int mode ) - initialize the profiler
void sgs_ProfClose( sgs_Prof* P ) - free the profiler
void sgs_ProfDump( sgs_Prof* P ) - dump the data
Usage example
sgs_Prof profiler; // keep this allocated while it is linked to engine
// initialize and attach the profiler
sgs_ProfInit( contextPtr, &profiler, [SGS_PROF_FUNCTIME|SGS_PROF_OPTIME|SGS_PROF_MEMUSAGE] );
// ...do things with the engine...
sgs_ProfDump( &profiler ); // dump the data (done through @sgs_Write* functions)
// free the profiler - must be done before freeing the engine
sgs_ProfClose( &profiler );
This is an engine debugging hook extension. It provides the opportunity to interactively introspect the engine state on each message (info/warning/error).
Functions
int sgs_InitIDbg( sgs_Context* C, SGS_IDBG ) - initialize the debugger
int sgs_CloseIDbg( sgs_Context* C, SGS_IDBG ) - free the debugger
Usage example
sgs_IDbg debugger; // keep this allocated while it is linked to engine
// initialize and attach the debugger
sgs_InitIDbg( contextPtr, &debugger );
// ...do things with the engine...
// free the debugger
sgs_CloseIDbg( contextPtr, &debugger );
In SGScript, serialization is conversion to a binary stream of a specific format. Serialization in the language API is done by calling the "serialize" function and deserialization is done with "unserialize". The C API has similar functions: sgs_Serialize(Ext) / sgs_SerializeObject and sgs_Unserialize, respectively.
check the "Possible gotchas" part of this page if it is intended to trust the end user with access to serialized data
The format (modes 1 and 2)
a list of operations
each operation consists of a 'type' byte and additional data
a "push" operation has the byte 'P', the data consists of the type to push (a byte consisting of one base flag from SGS_VT_*) and the binary data of the type
a "call" operation has the byte 'C', the following data differs between formats:
MODE 1: number of arguments (4 bytes), function name length (1 byte) and the null-terminated function name itself (<length>+1 bytes)
MODE 2: number of arguments (4 bytes), function name length (1 byte), argument indices (4 bytes for each, count as previously specified) and the null-terminated function name itself (<length>+1 bytes)
The format (mode 3)
SGScript Object Notation (SGSON) - a minimal version of the script syntax for defining data
Supported features:
keywords: null, false, true
integers and real values
strings ("..." or '...')
containers:
array: [...]
dict: { a = 1, b = "x" }
map: map{ [false] = 5, [{}] = "test" }
function calls: <identifier>(<arg1>, <arg2>, ... <argN>)
if object is a 'dict' or a 'map', all keys and values in it will be serialized using sgs_Serialize and sgs_SerializeObject will generate a call to 'dict'/'map'
"push", "call", "symbol" operations are executed, thus regenerating the data
"push" operation pushes a variable on the stack
"call" operation calls the specified function
"symbol" operation resolves the given string to a variable from the symbol table
Possible gotchas
unserialization is not by default safe in the sense that any function can be executed with a carefully crafted byte buffer; this can be prevented by temporarily replacing the global environment (_G)
if environment overrides are used on deserialization, remember to add "array", "dict" and "map" to the list if you use them, they are not always supported automatically
mode 1/3 serialization will not preserve any variable reuse relationships so after deserialization the structure could use more memory than before; if more efficient behavior is desired, it is suggested to use mode 2 (default) instead
To ensure safety of the underlying environment, there are functions that need to be protected or disabled.
Critical (access to file system, possibility of taking control of the machine):
core functions:
include_library - access to file system, can load other libraries
include_shared - access to file system, can run some library code
include_file - access to file system, can run script files
include - access to file system, can load other libraries, can run script files or some library code
import_cfunc - access to file system, can run some library code
eval_file - access to file system, can run script files