Description
Ano is not by any mean a general purpose programming language. Although applications can be written in pure Ano, the goal of Ano is to offer domain specific language for application kernel and event callback mechanism for own C-functions, and also hide the complexity of X windows and unix audio systems, plus offer one simple mechanism for applications to use them. See bottom of this page how to bind own functions useable in Ano scripts.
Ano script compiles to C, and is further compiled and linked as static part of the application. That means that compiled executable does not include Ano script anymore nor compile anything when it runs, everything is already there.
Provided Ano compiler is written in perl, and handles script syntax which looks a bit like asm. Ano script is line-based, which means that only one instruction or keyword is permitted per line:
; This is right and works okay mov cat (0) mov dog (1) loop (mov i (0); i < 10; inc i) { dump i } ; This is wrong and won't work mov cat (0) ; mov dog (1) loop (mov i (0); i < 10; inc i) { dump i }
Ano script compiler expects input file to be UTF-8 encoded, and supports include-directive, so script can be divided into several files:
include "the_other_script_file.ano"
Somewhere in Ano script, usually in the beginning, couple of tags can be specified telling some details about the script. Tags must be commented out and tags must start with @. Tags include:
@ANO_SCRIPT_NAME name of the Ano script, must be single word. @ANO_SCRIPT_VERSION version number of the script. @ANO_SCRIPT_DESCRIPTION couple of descriptive words about the script. @ANO_SCRIPT_COPYRIGHT copyright notice about the script. @ANO_FLAGS_USE_PROTOS enable support for function prototypes and named parameters with them in Ano script. @ANO_FLAGS_VAR_NAME_SUBS instruct Ano compiler to substitute variable names with very short aliases for faster processing. @ANO_FLAGS_VAR_WARN_UNUSED instruct Ano compiler to warn if some variable is declared but not used. @ANO_FN_NAMED_PARAMS list of named parameter declaration files for binded functions, see below for more information. @ANO_REMOTE_FUNCS list of procedures, for example, functions and callbacks, that are either allowed or denied to call remotely, see Remote control for more details. @ANO_UNVEIL_FILES list of files and dirs to pass to unveil(2) if unveil(2) is available and enabled, see below for more information.
There is also more detailed attributes available to describe the program along with @ANO_SCRIPT_ tags. Those include:
@TAG_ATTR_COMPANY program owner company name. @TAG_ATTR_CONTACT program contact person name. @TAG_ATTR_CREATED program creator name or date, or both. @TAG_ATTR_DEPARTMENT program owner department. @TAG_ATTR_DESCRIPTION general description about the program. @TAG_ATTR_HOMEDIRECTORY where the program is installed or should be. @TAG_ATTR_HOMEPAGE program home page or installation directory. @TAG_ATTR_MAIL program contact person mail addresses. @TAG_ATTR_NOTES notes about the program. @TAG_ATTR_OPTIONS preferred command line options for this program. @TAG_ATTR_ORGANIZATION program owner organization. @TAG_ATTR_ORIGINALNAME original distinguished program name. @TAG_ATTR_OWNER program owner name. @TAG_ATTR_REMOTECONTROLPORT program remote control port. @TAG_ATTR_REMOTECONTROLPROTO program remote control protocol. @TAG_ATTR_SCRIPTPATH where the Ano script and other files are. located which were used to produce this program. @TAG_ATTR_SEEALSO other notes or links. @TAG_ATTR_SERIALNUMBER program serial number.
For example, script header may look like:
; ; @ANO_SCRIPT_NAME Example ; @ANO_SCRIPT_VERSION 0.0.1 ; @ANO_SCRIPT_DESCRIPTION This is just an example script ; ; @TAG_ATTR_HOMEPAGE https://www.home.page ; @TAG_ATTR_MAIL mailto:me@home.page ; ; @ANO_FLAGS_VAR_NAME_SUBS yes ; @ANO_FLAGS_VAR_WARN_UNUSED no ;
Tick in a box -style is also supported for yes/no values:
; @ANO_FLAGS_VAR_NAME_SUBS [x] ; @ANO_FLAGS_VAR_WARN_UNUSED [ ]
In compiled program, content of @ANO_SCRIPT_ tags can be displayed along with some other info by using -V command line switch. Unveil list is not displayed. Output of -V switch is more detailed if --enable-debug was used with configure script.
Named parameters
Named parameters is a way to write more readable script, as every parameter given either internal engine functions, or own functions binded to Ano script, can be written as follows:
window_set_attrs (\ window_handle: hwnd, \ attribute: 1, \ attribute_value: 0)
Compared to above, the traditional way of calling functions will of course work too, even when the named parameter tag is present in Ano script header. As you can see, example above is more readable than this, as it documents itself:
window_set_attrs (hwnd, 1, 0)
Ano compiler has support for naming the parameters for every function provided by the engine. If named parameters are needed for own functions binded to Ano script, it needs a small file where those functions and their parameters are named. This is FOSS Mixer named parameter file, check the FOSS Mixer's Ano script and look for the functions listed here to get the idea:
; ; The purpose of this file is to allow named parameters in Ano script when ; calling functions listed below. This file is defined in Ano script by ; @ANO_FN_NAMED_PARAMS tag. See the Ano script for live use. ; ; Functions are declared as: ; ; function_name(name_of_parameter_1, name_of_parameter_2, ...) ; ; That allows functions to be called in Ano script like: ; ; function_name (\ ; name_of_parameter_1: value_1, \ ; name_of_parameter_2: value_2) ; bsd_prepare(window_handle) bsd_disperse() bsd_mixer_sel(widget_name, steps, step) bsd_mixer_rec(widget_name, steps, step) bsd_mixer_mix(widget_name, steps, step) bsd_mixer_ext(widget_name, steps, step) bsd_mixer_tst(widget_name, steps, step) bsd_mixer_tst_vol(widget_name, steps, step) bsd_mixer_tst_pan(widget_name, steps, step)
That file must be given as a parameter to @ANO_FN_NAMED_PARAMS tag in Ano script header, for example:
; ; @ANO_FN_NAMED_PARAMS "/path/to/file/my_named_funcs.txt" ;
Tag can have several files as a parameter, separated by comma.
In named parameter file, lines starting with semicolon (;) are treated as comments and ignored. Empty lines are skipped, spaces chopped, and function declarations can be spawned over multiple lines if needed. Parameter name can have optional type in front of it separated by colon (:). That type will override supplied parameter type if it differs. For example:
; This line is ignored function_name ( name_of_parameter_1, name_of_parameter_2) ; This declaration has also parameter type in it, ; first one is integer and another one is float: another_function_name ( int:name_of_parameter_1, f32:name_of_parameter_2)
Unveil
Unveil is a mechanism to unveil parts of a restricted filesystem access. If unveil is enabled, all access to filesystem is disabled by default. Whatever directories and/or files Ano script needs to access, they must be added to unveil list with desired permissions. This feature needs unveil(2) call to be available (at the time of writing, only OpenBSD supports it).
Each file or directory to unveil, the format is simply a directory or file name and its desired permissions separated by equals sign (=) with optional spaces around the delimiter. Each directory/file name and the permission pair must be double quoted. See unveil(2) for more information and permission characters available.
There is special characters with special meaning in directory and file paths:
- A tilde ~ as first character will be replaced by user home directory,
- At sign @ as first character will be replaced by application installation prefix (that is the value for --prefix option given to configure script),
- A dot . as first character is considered as current directory, and
- A dollar or peso sign $ as first character and a word after it is considered as environment variable.
Example:
; ; @ANO_UNVEIL_FILES "/tmp = rw", \ ; "/some/dir/somewhere = rx", \ ; "/some/dir/somewhere/and/some/file = r" ;
Function prototypes
⭐ New in 0.2.9.
Function prototype enables use of named parameters with ordinary functions and callbacks. Function caller must use parameter names declared in prototype, in that order. Prototype checking is enabled my using @ANO_FLAGS_USE_PROTOS tag somewhere, usually in the beginning, in Ano script. Prototype itself is declared with proto and callback or function keyword pair followed by function name and parameter declaration enclosed in parentheses. Here is simple example:
; ; Enable function prototype checking: ; ; @ANO_FLAGS_USE_PROTOS yes ; proto function my_function(first, second, third) main [exit: 0] { mov my_var (1) &my_function (\ first: my_var, \ second: "Second param", \ third: 123) } function my_function (_xxx, _yy, _z) { ; This function prints passed parameters print "This is the first param: " . _xxx . ",\n" print "this is second: " . _yy . ",\n" print "and this is third: " . _z . ".\n" }
Datatypes
Ano supports eight different datatypes in variables, where five is most commonly used, number, string, handle, color and point. The remaining three, blob, pointer and image are reserved for special cases.
Variable type can be set when variable is first used, by setting the type along with it content. Possible types are:
- $ for blob (aka. structure) datatype
- * for pointer datatype
- @ for handle datatype
- # for color datatype
- % for image datatype
- & for point datatype
See examples below.
Datatype can also be set when variable is declared:
var [number] x var [string] x var [blob] x ; Alternatively blob can be struct: ;var [struct] x var [pointer] x var [handle] x var [color] x var [image] x var [point] x
Number
Number datatype supports also RPN expressions. Expression result is cached when first evaulated if possible to avoid unnecessary evaluations in future. If expression is not cacheable, for example if it contains variables, expression is evaluated every time when instruction executes. Using math or other functions in RPN expressions is not supported. Number datatype has several subtypes:
; This defaults to double type: mov x (1.0) mov x ([int8] 1) mov x ([int16] 1) mov x ([int32] 1) mov x ([int64] 1) mov x ([int128] 1) mov x ([isize] 1) mov x ([uint8] 1) mov x ([uint16] 1) mov x ([uint32] 1) mov x ([uint64] 1) mov x ([uint128] 1) mov x ([usize] 1) ; These are float and double, respectively mov x ([f32] 1.0) mov x ([f64] 1.0) mov x ([byte] 'U') mov x ([byte] b'U') ; Byte must have b prefix when type is something else than u8: mov x ([uint] b'U') mov x ([dec] 85) mov x ([hex] 0x55) mov x ([oct] 0o125) mov x ([bin] 0b01010101) ; Numbers can contain underscore to improve readability: mov x ([bin] 0b0101_0101) ; Expression can have optional rpn in front of it: mov x ([rpn] (1.0 + 2.0) + 3.0 * 4.0 / y)) mov x ((1.0 + 2.0) + 3.0 * 4.0 / y) ; These are handy when passing data to external functions: mov x ([char] 1) mov x ([short] 1) mov x ([int] 1) mov x ([long] 1) mov x ([uchar] 1) mov x ([ushort] 1) mov x ([uint] 1) mov x ([ulong] 1) mov x ([float] 1.0) mov x ([double] 1.0)
String
String datatype has couple of subtypes:
mov x ("Hello, world!") mov x ([utf-8] "Hello, world!") mov x ([utf-32] "Ehtoota, Vorssa!")
Blob, or struct
Nested structures are not supported.
; These three, blob, struct and structure are identical definitions blob my_struct { number my_num = 0 } struct my_struct { number my_num = 0 } structure my_struct { string my_str_1 = "Hello again!" ; Like string above, but string type is explicitly set utf-32 my_str_2 number my_num_1 number my_num_2 = (1 + 2) * some_var / 3.4 ; Like numbers above, but type is set to ulong ulong my_num_3 = 123, my_num_4 = 234, my_num_5 = 345 ; Some window handles... handle my_window_hnd_1, my_window_hnd_2 ; ...bright red color... color my_rgb_red = (255, 0, 0) ; ...and here is point somewhere in space point my_xyz_pnt = (1.0, 2.0, -1.0) }
Structure items are accessible like shown in this example:
mov my_var (my_struct.my_str_1) mov my_struct.my_num_1 (123)
Pointer
For special use only.
mov x (*0)
Handle
Handle datatype is storage for function handles, for example, window_open() returns this kind of handle to pass forward to other functions, like window_close().
mov x (@0)
Color
Color datatype is storage for colors, for example, color mixing function color_mix() uses two of these to mix them together. Color datatype supports following methods:
- red, get or set the red color component
- green, get or set the green color component
- blue, get or set the blue color component
- alpha, get or set the alpha value
- brightness, get the pixel brightness
mov x ("indianred") mov x (# 0x11, 0x22, 0x33, 0x44) mov x.red (# 0x11) mov x.green (# 0x22) mov x.blue (# 0x33) mov x.alpha (# 0x44) mov b (x.brightness)
Image
For special use only. Image datatype supports following methods:
- width, get image width in pixels
- height, get image height in pixels
mov x (% "file_to_load") mov w (x.width) mov h (x.height)
Point
Point datatype is storage for three dimensional point, for example, one of the interpolation functions coords_intp_hermite_x() uses this datatype for its control points. Point datatype supports following methods:
- x, get or set the X coordinate
- y, get or set the Y coordinate
- z, get or set the Z coordinate
- angle_xy, calculate vector angle in degrees by its position, or manipulate XY coordinates by given angle
- mag_xy, calculate magnitude of 2 dimensional point, or manipulate XY coordinates by given magnitude
- mag_xyz, calculate magnitude of 3 dimensional point, or manipulate XYZ coordinates by given magnitude
mov x (& 1.1, 2.2, 3.3) mov x.x (& 1.1) mov x.y (& 2.2) mov x.z (& 3.3) mov a (x.angle_xy) mov d (x.mag_xy) mov d (x.mag_xyz) mov x.angle_xy (4.4) mov x.mag_xy (5.5) mov x.mag_xyz (6.6)
Variables
There is nothing special what comes to variables, they work just like variables in any other scripting language. Every variables is always casted to some datatype, which can be manipulated using conversion instruction set. Variable names starting with underscore (_) are treated as local variables. Local variables are visible and useable only in function where they are declared. Use of local variables is recommended where applicable.
Variables can be declared before use by using var command, or by moving data straight into it, when it is automatically declared and initialized. var alias is let, use whichever you wish.
There is following variables predefined in every Ano script:
- rc for global return value, when external function, subroutine or whatever returns something, returned data is stored here
- NULL for empty string, works just like its equivalent in C language
- INVALID for invalid handle value (equals to 0), for example, if some functions returns a handle and rc variable equals INVALID, that function returned zero
Example:
&subroutine ([float] 123) dump rc ; These are equivalents... mov string ("") dump string mov string (NULL) dump string ; ...and so are these mov handle (@0) dump handle mov handle (INVALID) dump handle end function subroutine (_value) { ; Return value is in rc variable after return ret _value * _value }
Variables supports triggers when their state or value changes. See variable triggers for further information.
Instruction set
Ano script is made from instructions. Each supported instruction useable in Ano script is explained below. Some of the subsets can be disabled to pass --disable-extended switch to configure script. If extended set is not needed, disabling it makes the exeutable bit smaller and probably saves memory a bit. Disabled instruction sets include:
- Logical instruction set,
- Math instruction set,
- Gaussian instruction set, and
- Checksum instruction set
External function call
To call external function, function name to call is divided into two by at sign (@). Left part tells the shared object where the function is located, and right part is the function name. Left part can be left empty, in that case the function is looked in calling process. There are many external functions in Detroit engine, please take a closer look of them in Engine function reference.
Flags affected: none
Example:
; Sleep for one second, this is engine's function, as there is no library name sleep (1, 0) ; ; This is abort() in libc. In order to abort(3) call to work properly, it ; return value type must be set in application's dynload_ret structure ; (dynload_ret_app.h), like this: ; ; static struct dynload_ret dynload_ret_t[] = { ; { "abort", RETURN_TYPE_VOID }, ; libc.so@abort exit
If you want to call external library functions, like the ones in libc, you need to create a wrapper function for each of them, as explained in Own functions binded to Ano section in bottom of this page. Example above will work though, as abort() does not need parameters, hence the wrapper function is not needed.
Basic instruction set
Basic set of instructions explained. Examples are for instruction demonstration only, they may or may not compile.
abs
Compute the absolute value of x. See fabs(3).
Flags affected: none
Example:
mov x (-1.0) abs x
add
Addition of two operands. The result of the addition is assigned to the first operand.
Flags affected: s, z
Example:
mov x (1.0) add x (1.0)
call
Call subroutine. If subroutine destination is variable, variable is evaluated when instruction is executed. See also callback & function section in this page.
Flags affected: none
Example:
mov x ("non_existent_subroutine") call [var] x ([float] 1.0, [float] 2.0) mov x ("subroutine") call [var] x ([float] 1.0, [float] 2.0) ; These three are equivalents call subroutine ([float] 1.0, [float] 2.0) call "subroutine" ([float] 1.0, [float] 2.0) &subroutine([float] 1.0, [float] 2.0) end ; This is traditional and now deprecated way to define a subroutine : "subroutine" (_param1, _param2) ret _param1 ; Subroutine should be written this way function subroutine (_param1, _param2) { ret _param1 }
cmp
Compare two operands.
Flags affected: a, b, e, s, z
Example:
mov x (1.0) cmp x (2.0)
dec
Decrement by 1. The result of the subtraction is assigned to the operand.
Flags affected: s, z
Example:
mov x (1.0) dec x
div
Divide of two operands. The result of the addition is assigned to the first operand.
Flags affected: none
Example:
mov x (1.0) div x (0.5)
end
End procedure, for example a callback function. It is possible to return code from procedure, ranging from 0 to 255, to remote caller. See Remote control for more details. Other inprocess return codes are ignored.
end ends following procedures:
- alarm (see timer snippet),
- callback (see menu and widget resource guides),
- sighandler,
- thread (see thread creation snippet), and
- main program, usually after control was passed to callback routines.
Flags affected: none
Example:
; Use default procedure return code end end (123) mov x ([int8] 123) end x
inc
Increment by 1. The result of the addition is assigned to the operand.
Flags affected: s, z
Example:
mov x (1.0) inc x
label
Define a label. Label is usually a jump destination, callable function, callback function, a timer or a thread.
Flags affected: none
Example:
; This is traditional way to define a label... label "the_end" end ; ...and this is shortcut to above... : "the_end" end ; ...but this is preferred way to define a label: the_end: end
mod
Compute the remainder x/y. See fmod(3).
Flags affected: none
Example:
mov x (1.0) mod x (1.0)
mov
Move data by copying the second operand to the first operand.
Flags affected: none
Example:
mov x (1.0)
deliver or put
Move data by copying the second operand to the first operand. If called from thread, first operand (destination) is variable in main thread. Acts like mov in single thread application.
Flags affected: none
Example:
See fetch
fetch or get
Move data by copying the second operand to the first operand. If called from thread, second operand (source) is fetched from main thread. Acts like mov in single thread application.
Flags affected: none
Example:
mov x (1) ; Create two threads for (mov i (0); i < 2; inc i) { thread_spawn ("My thread", "my_thread") } ; Wait for a while for threads to complete sleep (1, 0) ; This outputs 3 which comes from either thread dump result end thread my_thread { mov x ("invalidate") ; This outputs either 2 or 3, x is initialized in main thread, ; and i is the main thread loop counter fetch y (x + i) dump y ; Deliver y to main thread deliver result (y) }
mul
Multiplication of two operands. The result of the multiplication is assigned to the first operand.
Flags affected: none
Example:
mov x (1.0) mul x (1.0)
neg
Two's complement negation. The operand is subtracted from zero and result is assigned in the operand.
Flags affected: s, z
Example:
mov x (1.0) neg x
not
One's complement negation.
Flags affected: s, z
Example:
mov x (1.0) not x
pow
Compute x raised to the power y. See pow(3).
Flags affected: none
Example:
mov x (1.0) pow x (1.0)
rand
Generate random number. The result is assigned to the first operand.
Flags affected: none
Example:
rand x (10)
ret
Return from subroutine to next instruction after the call. ret can have optional return value, which is stored in internal variable rc after the return.
Flags affected: none
Example:
&subroutine1([float] 1.0, [float] 2.0) &subroutine2([float] 3.0, [float] 4.0) dump rc end function subroutine1 (_param1, _param2) { ret } function subroutine2 (_param1, _param2) { ret _param1 * _param2 }
sub
Subtraction of two operands. The result of the subtraction is assigned to the first operand.
Flags affected: s, z
Example:
mov x (1.0) sub x (1.0)
test
Logical compare.
Flags affected: s, z
Example:
mov x (1.0) test x (1.0)
Logical instruction set
Logical operations are done in 64 bit resolution and are useable with non-floating number formats. Examples are for instruction demonstration only, they may or may not compile.
and
Logical AND.
Flags affected: s, z
Example:
mov x (1.0) and x (1.0)
or
Logical inclusive OR.
Flags affected: s, z
Example:
mov x (1.0) or x (1.0)
xor
Logical exclusive OR.
Flags affected: s, z
Example:
mov x (1.0) xor x (1.0)
Branching instruction set
If jump destination label is variable, variable is evaluated when instruction is executed. Examples are for instruction demonstration only, they may or may not compile. In general, all branching instructions support following destination label types:
; Destination is quoted string: jmp "the_end" ; Destination is plain string: jmp the_end ; Destination is variable, note the [var] cast: mov x ("the_end") jmp [var] x
ja
Jump if condition flag a is set.
Example:
mov x (1.0) cmp x (2.0) ja "the_end" end : "the_end" end
jae
Jump if condition flag a or e is set.
Example:
mov x (1.0) cmp x (2.0) jae "the_end" end : "the_end" end
jb
Jump if condition flag b is set.
Example:
mov x (1.0) cmp x (2.0) jb "the_end" end : "the_end" end
jbe
Jump if condition flag b or e is set.
Example:
mov x (1.0) cmp x (2.0) jbe "the_end" end : "the_end" end
je
Jump if condition flag e is set.
Example:
mov x (1.0) cmp x (2.0) je "the_end" end : "the_end" end
jne
Jump if condition flag e is not set.
Example:
mov x (1.0) cmp x (2.0) jne "the_end" end : "the_end" end
js
Jump if condition flag s is set.
Example:
mov x (1.0) sub x (2.0) js "the_end" end : "the_end" end
jns
Jump if condition flag s is not set.
Example:
mov x (1.0) sub x (2.0) jns "the_end" end : "the_end" end
jz
Jump if condition flag z is set.
Example:
mov x (1.0) dec x jz "the_end" end : "the_end" end
jnz
Jump if condition flag z is not set.
Example:
mov x (1.0) dec x jnz "the_end" end : "the_end" end
jmp
Unconditional jump.
Example:
jmp "the_end" end : "the_end" end
Condition flag instruction set
All condition flags are automatically cleared at application startup. Examples are for instruction demonstration only, they may or may not compile.
clall
Clear condition flags a, b, e, s, z.
Example:
clall
cla
Clear condition flag a.
Example:
cla
clb
Clear condition flag b.
Example:
clb
cle
Clear condition flag e.
Example:
cle
cls
Clear condition flag s.
Example:
cls
clz
Clear condition flag z.
Example:
clz
stall
Set condition flags a, b, e, s, z.
Example:
stall
sta
Set condition flag a.
Example:
sta
stb
Set condition flag b.
Example:
stb
ste
Set condition flag e.
Example:
ste
sts
Set condition flag s.
Example:
sts
stz
Set condition flag z.
Example:
stz
Math instruction set
Math instructions explained. Examples are for instruction demonstration only, they may or may not compile.
acos
Computes the principle value of the arc cosine of x. See acos(3).
Flags affected: none
Example:
acos n (1.0)
acosh
Computes the principle value of the inverse hyperbolic cosine of x. See acosh(3).
Flags affected: none
Example:
acosh n (1.0)
asin
Computes the principal value of the arc sine of x. See asin(3).
Flags affected: none
Example:
asin n (1.0)
asinh
Computes the principle value of the inverse hyperbolic sine of x. See asinh(3).
Flags affected: none
Example:
asinh n (1.0)
atan
Computes the principle value of the arc tangent of x. See atan(3).
Flags affected: none
Example:
atan n (1.0)
atanh
Computes the principle value of the inverse hyperbolic tangent of x. See atanh(3).
Flags affected: none
Example:
atanh n (1.0)
atan2
Computes the principal value of the arc tangent of y/x. See atan2(3).
Flags affected: none
Example:
atan n (1.0, 2.0)
cbrt
Computes the cube root of x. See cbrt(3).
Flags affected: none
Example:
cbrt n (1.0)
ceil
Return the smallest integral value greater than or equal to x. See ceil(3).
Flags affected: none
Example:
ceil n (1.1)
copysign
Return x with its sign changed to y's. See copysign(3).
Flags affected: none
Example:
copysign n (1.0, -1.0)
cos
Computes the cosine of x. See cos(3).
Flags affected: none
Example:
cos n (1.0)
cosh
Computes the hyperbolic cosine of x. See cosh(3).
Flags affected: none
Example:
cosh n (1.0)
dim
Return the positive difference between their arguments: x - y if x > y, +0 if x is less than or equal to y. Might not be supported on every platform. See fdim(3).
Flags affected: none
Example:
dim n (1.0, 2.0)
erf
Calculates the complementary error function of x. See erf(3).
Flags affected: none
Example:
erf n (1.0)
erfc
Calculates the complementary error function of x. See erfc(3).
Flags affected: none
Example:
erfc n (1.0)
exp
Computes e**x, the base-e exponential of x. See exp(3).
Flags affected: none
Example:
exp n (1.0)
expm1
Computes e**x, the base-e exponential of x. See expm1(3).
Flags affected: none
Example:
expm1 n (1.0)
exp2
Computes 2**x, the base-2 exponential of x. Might not be supported on every platform. See exp2(3).
Flags affected: none
Example:
exp2 n (1.0)
floor
Return the largest integral value less than or equal to x. See floor(3).
Flags affected: none
Example:
floor n (1.1)
fma
Compute (x*y)+z, rounded as one ternary operation. Might not be supported on every platform. See fma(3).
Flags affected: none
Example:
fma n (1.0, 2.0, 3.0)
frexp
Break the floating-point number value into a normalized fraction and an integral power of 2. See frexp(3).
Flags affected: none
Example:
frexp f (1.2, i)
hypot
Computes the sqrt(x*x+y*y) without undue overflow or underflow. See hypot(3).
Flags affected: none
Example:
hypot n (1.0, 2.0)
ilogb
Return the exponent of x as a signed integer value. Might not be supported on every platform. See ilogb(3).
Flags affected: none
Example:
ilogb n (1.0)
isfinite
Return non-zero if and only if x is a finite number. Might not be supported on every platform. See isfinite(3).
Flags affected: s, z
Example:
isfinite n (1.0)
isinf
Return non-zero if and only if x is an infinity. Might not be supported on every platform. See isinf(3).
Flags affected: s, z
Example:
isinf n (1.0)
isnan
Return non-zero if and only if x is a NaN. See isnan(3).
Flags affected: s, z
Example:
isnan n (1.0)
isnormal
Return non-zero if and only if x is a non-zero normalized number. Might not be supported on every platform. See isnormal(3).
Flags affected: s, z
Example:
isnormal n (1.0)
jn
Computes the Bessel function of the first kind of the integer order n. See jn(3).
Flags affected: none
Example:
jn n (1.0, 2.0)
j0
Compute the Bessel function of the first kind of the order 0. See j0(3).
Flags affected: none
Example:
j0 n (1.0)
j1
Compute the Bessel function of the first kind of the order 1. See j1(3).
Flags affected: none
Example:
j1 n (1.0)
ldexp
Multiply x by 2 to the power n. See ldexp(3).
Flags affected: none
Example:
ldexp n (1.0, [int] 2)
lgamma
Calculates the natural logarithm of the absolute value of the gamma function of x. Might not be supported on every platform. See lgamma(3).
Flags affected: none
Example:
lgamma n (1.0)
log
Computes the value of the natural logarithm of argument x. See log(3).
Flags affected: none
Example:
log n (1.0)
logb
Return the exponent of x. See logb(3).
Flags affected: none
Example:
logb n (1.0)
log1p
Computes the value of the natural logarithm of argument x. See log1p(3).
Flags affected: none
Example:
log1p n (1.0)
log2
Computes the value of the logarithm of argument x to base 2. Might not be supported on every platform. See log2(3).
Flags affected: none
Example:
log2 n (1.0)
log10
Computes the value of the logarithm of argument x to base 10. See log10(3).
Flags affected: none
Example:
log10 n (1.0)
max
Return x or y, whichever is larger. See fmax(3).
Flags affected: none
Example:
max n (1.0, 2.0)
min
Return x or y, whichever is smaller. See fmin(3).
Flags affected: none
Example:
min n (1.0, 2.0)
modf
Break value into integral and fractional parts, each of which has the same sign as the argument. See modf(3).
Flags affected: none
Example:
modf f (1.2, i)
nan
Return a quiet NaN. Might not be supported on every platform. See nan(3).
Flags affected: none
Example:
nan n (0)
nearbyint
Return the integral value nearest to x according to the prevailing rounding mode. Might not be supported on every platform. See nearbyint(3).
Flags affected: none
Example:
nearbyint n (1.1)
nextafter
Return the next machine representable number from x in direction y. Might not be supported on every platform. See nextafter(3).
Flags affected: none
Example:
nextafter n (1.1, 2.0)
nexttoward
Return the next machine representable number from x in direction of y. Might not be supported on every platform. See nexttoward(3).
Flags affected: none
Example:
nexttoward n (1.1, 2.0)
remainder
Compute the value r such that r = x - n*y, where n is the integer nearest the exact value of x/y. Might not be supported on every platform. See remainder(3).
Flags affected: none
Example:
remainder n (1.0, 2.0)
rint
Return the integral value nearest to x. See rint(3).
Flags affected: none
Example:
rint n (1.1)
round
Return the integral value nearest to x rounding half-way cases away from zero, regardless of the current rounding direction. See round(3).
Flags affected: none
Example:
round n (1.1)
scalbn
Return x*(2**n) computed by exponent manipulation. Might not be supported on every platform. See scalbn(3).
Flags affected: none
Example:
scalbn n (1.0, [int] 2)
signbit
Returns non-zero if the value of the argument's sign is negative, otherwise 0. Might not be supported on every platform. See signbit(3).
Flags affected: s, z
Example:
signbit n (1.0)
sin
Computes the sine of x. See sin(3).
Flags affected: none
Example:
sin n (1.0)
sinh
Computes the hyperbolic sine of x. See sinh(3).
Flags affected: none
Example:
sinh n (1.0)
sqrt
Compute the non-negative square root of x. See sqrt(3).
Flags affected: none
Example:
sqrt n (1.0)
tan
Computes the tangent of x. See tan(3).
Flags affected: none
Example:
tan n (1.0)
tanh
Computes the hyperbolic tangent of x. See tanh(3).
Flags affected: none
Example:
tanh n (1.0)
tgamma
Calculates the gamma function of x. Might not be supported on every platform. See tgamma(3).
Flags affected: none
Example:
tgamma n (1.0)
trunc
Return the integral value nearest to but no larger in magnitude than x. Might not be supported on every platform. See trunc(3).
Flags affected: none
Example:
trunc n (1.1)
yn
Computes the Bessel function of the second kind for the integer order n for the positive integer value x. See yn(3).
Flags affected: none
Example:
yn n (1.0, 2.0)
y0
Compute the linearly independent Bessel function of the second kind of the order 0. See y0(3).
Flags affected: none
Example:
y0 n (1.0)
y1
Compute the linearly independent Bessel function of the second kind of the order 1. See y1(3).
Flags affected: none
Example:
y1 n (1.0)
Gaussian instruction set
Examples are for instruction demonstration only, they may or may not compile.
gdens
Compute Gaussian density of x.
Flags affected: none
Example:
gdens n (1.0)
gdist
Compute Gaussian distribution of x.
Flags affected: none
Example:
gdist n (1.0)
grand
Return Gaussian distributed random number of distribution x and sigma y.
Flags affected: none
Example:
grand n (1.0, 2.0)
Checksum instruction set
Examples are for instruction demonstration only, they may or may not compile.
crc32
Compute CRC32 checksum of x.
Flags affected: none
Example:
crc32 n ("Hello, world!")
crc64
Compute CRC64 checksum of x.
Flags affected: none
Example:
crc64 n ("Hello, world!")
Conversion instruction set
Examples are for instruction demonstration only, they may or may not compile.
ston
Convert string to number.
Flags affected: none
Example:
mov x ("1.0") ston x
pton
Convert pointer to number.
Flags affected: none
Example:
mov x (*0) pton x
hton
Convert handle to number.
Flags affected: none
Example:
mov x (@0) hton x
ntos
Convert number to string.
Flags affected: none
Example:
mov x (1.0) ntos x
ntop
Convert number to pointer.
Flags affected: none
Example:
mov x (1.0) ntop x
ntoh
Convert number to handle.
Flags affected: none
Example:
mov x (1.0) ntop x
toi8
Set number type to 8 bit integer.
Flags affected: none
Example:
mov x (1.0) toi8 x
toi16
Set number type to 16 bit integer.
Flags affected: none
Example:
mov x (1.0) toi16 x
toi32
Set number type to 32 bit integer.
Flags affected: none
Example:
mov x (1.0) toi32 x
toi64
Set number type to 64 bit integer.
Flags affected: none
Example:
mov x (1.0) toi64 x
toi128
Set number type to 128 bit integer. Might not be supported on every platform.
Flags affected: none
Example:
mov x (1.0) toi128 x
toisize
Set number type to ssize_t.
Flags affected: none
Example:
mov x (1.0) toisize x
tou8
Set number type to 8 bit unsigned integer.
Flags affected: none
Example:
mov x (1.0) tou8 x
tou16
Set number type to 16 bit unsigned integer.
Flags affected: none
Example:
mov x (1.0) tou16 x
tou32
Set number type to 32 bit unsigned integer.
Flags affected: none
Example:
mov x (1.0) tou32 x
tou64
Set number type to 64 bit unsigned integer.
Flags affected: none
Example:
mov x (1.0) tou64 x
tou128
Set number type to 128 bit unsigned integer. Might not be supported on every platform.
Flags affected: none
Example:
mov x (1.0) tou128 x
tousize
Set number type to unsigned size_t.
Flags affected: none
Example:
mov x (1.0) tousize x
tochar
Set number type to char.
Flags affected: none
Example:
mov x (1.0) tochar x
toshort
Set number type to short.
Flags affected: none
Example:
mov x (1.0) toshort x
toint
Set number type to int.
Flags affected: none
Example:
mov x (1.0) toint x
tolong
Set number type to long.
Flags affected: none
Example:
mov x (1.0) tolong x
touchar
Set number type to unsigned char.
Flags affected: none
Example:
mov x (1.0) touchar x
toushort
Set number type to unsigned short.
Flags affected: none
Example:
mov x (1.0) toushort x
touint
Set number type to unsigned int.
Flags affected: none
Example:
mov x (1.0) touint x
toulong
Set number type to unsigned long.
Flags affected: none
Example:
mov x (1.0) toulong x
tofloat, or tof32
Set number type to float.
Flags affected: none
Example:
mov x (1.0) tofloat x
todouble, or tof64
Set number type to double float.
Flags affected: none
Example:
mov x (1.0) todouble x
Supplemental instruction set
Examples are for instruction demonstration only, they may or may not compile.
dump
Print parameter type and content. For debug use only.
Flags affected: none
Example:
mov x (1.0) dump x
Evaluates and prints given parameter string to standard output. Some special characters can be used with the string. Those include:
- \0 for string termination,
- \a bell,
- \b backspace,
- \t horizontal tab,
- \n line feed,
- \v vertical tab,
- \f form feed, and
- \r for carriage return.
Flags affected: none
Example:
mov x (1.0) ; Print content of x with and without newline print x . "\n" print x ; Evaluate expression and print it with the string print (x - 0.5) . " is halfway between\n" print "The result is: " . (x + 0.5) . "\n" print "The value is: " . x . "\n" print 123 . " is very odd number." "\n" ; Print three separated strings with newline print "The first, " "the second, " "and the last." "\n" ; Print x three times without newline print x . "" . x .. x ; Print string with empty expression with newline print "This is the string" .. "\n" mov y (123) mov x ("y + 1") ; Print string without expression evaluation ; outputs: y + 1 print "x is considered as string here: " . x . "\n" mov x (1.0) mov y ("'Value is ' . (1.0 + x - 0.5) . ''") print "" . y . "\n" inc x print "" . y . "\n"
Print can also write its output to a variable. In that case the string must be enclosed in parentheses. Example:
print my_var ("value: " . x + 1 . "\n")
Condition flags
Almost all instructions changes condition flags according to instruction result. Condition flags affects the behaviour of branching instructions, and can be manipulated by using condition flag instruction set.
- A is set if first operand is greater, clear if its not.
- B is set if second operand is greater, clear if its not.
- E is set if operands are equal, clear if they are not.
- S is set if operand is signed, clear if its unsigned.
- Z is set if operand is zero, clear if its not.
High level statements
There is some high level statements and keywords available to write more tight or readable script. High level operands are exploded to smaller pieces by Ano compiler. In the end, they are a selection of Ano instructions, not real instructions by themselves.
Some of the statements has condition in it, for example for and while. Only single condition is supported at the moment.
; This does not work... while (i < 10 && j > 10) { } ; ...but this does while (i < 10) { }
High level statements supports nesting.
if
If statement is simple by design, there is no elseif or else, just if. Format of the statement is as follows:
if variable condition value : label
Simple example for simple statement:
; ; This snippet is shortened with if as shown below: ; ; cmp my_var (5) ; je "label_to_jump" ; ; This equals to snippet above if my_var == (5) : label_to_jump if my_var != (5) : &function_to_call if my_var < (5) : &another_function_to_call (1, 2, 3) end : "label_to_jump" ; Program jumps here if my_var equals 5 end function function_to_call { ; This function is called if my_var does not equal to 5 } function another_function_to_call (_a, _b, _c) { ; This function is also called with parameters }
for
A for statement is somewhat similar than equally named statement in C language. for syntax is:
for (init statement ; condition ; loop counter statement) { }
Init statement usually initializes the loop counter variable, and it runs always only once. Condition tests if the loop must be stopped or not, and loop counter statement usually increases or decreases the loop counter variable. Any of the three statements can be left empty. Init and loop counter parts can have multiple statements separated by comma. For example, this loop runs ten times:
for (mov i (0); i < 10; inc i) { dump i }
Above loop can be written in plain Ano script this way (and that's what Ano compiler actually does):
; Init statement mov i (0) : "loop" ; Loop counter statement inc i ; Condition cmp i (10) jb "loop_payload" jmp "loop_done" : "loop_payload" ; Loop payload dump i ; Loop back to begin jmp "loop" : "loop_done"
Unlike loop statement, with for it is possible to make endless loop, just like with its C equivalent. The following example makes a loop that never stops. Stopping the loop is still possible by using break keyword, or any other branching instruction to escape from the loop.
; This loop is endless for (;;) { }
loop
A loop statement looks a bit like for(), but works a little bit differently. loop always runs the content of the loop at least once. The check whether the loop must stop or not is done at the end of the loop, after running the loop payload. loop contains three statements, just like for():
loop (init statement ; condition ; loop counter statement) { }
Init statement usually initializes the loop counter variable, and it runs always only once. Condition tests if the loop must be stopped or not, and loop counter statement usually increases or decreases the loop counter variable. Any of the three statements can be left empty. Init and loop counter parts can have multiple statements separated by comma. For example, this loop runs ten times:
loop (mov i (0); i < 10; inc i) { dump i }
Above loop can be written in plain Ano script like this:
; Init statement mov i (0) : "loop" ; Loop payload dump i ; Loop counter statement inc i ; Condition cmp i (10) jb "loop"
If condition part is left empty, loop differs from for() statement; with loop, you cannot make endless loop like shown below, which will run the loop payload once. loop always needs condition statement in order it to roll.
; This loop is not endless, it runs once loop (;;) { }
while
A while loop keeps executing a block of instructions as long as condition is true. while contains one statement, the condition, which checks if the loop must be stopped:
while (condition) { }
For example, this loop runs ten times:
mov i (0) while (i < 10) { dump i inc i }
Above loop can be written in plain Ano script as here:
mov i (0) : "loop" ; Condition cmp i (10) jb "loop_payload" jmp "loop_done" : "loop_payload" ; Loop payload dump i inc i ; Loop back to begin jmp "loop" : "loop_done"
If condition is left empty, while creates an endless loop. Stopping the loop is still possible by using break keyword, or any other branching instruction to escape from the loop.
do
A do loop is similar to while loop, except it executes the loop payload at least once. The check whether the loop must stop or not is done at the end of the loop, after running the loop payload. do statement contains condition, which checks if the loop must be stopped:
do (condition) { }
For example, this loop runs ten times:
mov i (0) do (i < 10) { dump i inc i }
Above loop can be written in plain Ano script as shown in this example:
mov i (0) : "loop" ; Loop payload dump i inc i ; Condition cmp i (10) jb "loop"
If condition is left empty, do has no practical effect, as it just runs the loop payload once.
define
A define keyword defines a macro. Macros are like typeless source variables. When macro is used, it is evaluated and gets proper type. Macros can be redefined when needed, and undefined by using empty value.
define message ("Macro example") define expression (1 + (2 + 3) * multiplier) ; Just print message out, this macro is like constant string mov _msg (message) dump _msg ; Expression in macro is evaluated when macro is moved to _msg mov multiplier (1) mov _msg (expression) ; _msg has a value of 6 dump _msg ; mov multiplier (2) mov _msg (expression) ; _msg has a value of 11 dump _msg ; mov multiplier (4) mov _msg (expression) ; _msg has a value of 21 dump _msg ; Undefine macro define expression () ; This causes runtime error as macro is undefined now... ;mov _msg (expression) ; ...but macro can be redefined when needed define expression (_msg + 1) mov _msg (expression) ; _msg has a value of 22 dump _msg exit
Macros does not support recursion, this example won't work and causes an error:
define expression (1 + (2 + 3) * expression) mov _msg (expression)
synchronized
Synchronization is made for thread safe computing to prevent multiple threads accessing the same resource at the same time, which could produce unwanted results. Synchronization is achieved by using synchronized blocks. Only one thread can access and run instructions and functions in this block at a time.
There is attributes available to use with synchronized statement:
- oid, the object identifier, attribute sets the identifier for each synchronized block. oid type is always positive integer (cast to [int]). Keep the oid number as small as possible.
If oid attribute is left out, default value of 0 will be used.
Syncronization example snippets:
synchronized [oid: 1] { ; Do serialized work here } synchronized [oid: 1] { ; When some thread is working in synchronization block above, this ; block is also serialized as oid number is the same } synchronized { ; This block uses oid 0 by default synchronized [oid: 2] { ; This block is nested } }
As seen above, synchronized statements can be nested, if needed for some obscure reason.
More practical example, using synchronized statement with threads:
mov counter (1) thread_spawn ("First thread", "my_thread") thread_spawn ("Second thread", "my_thread") thread_spawn ("Third thread", "my_thread") mov _msg ("Press <ctrl-c> to exit") dump _msg end thread my_thread { ; Do parallel work here... synchronized { ; ...do some serialized work... rand _nap (3) sleep (_nap + 1, 0) dump counter inc counter } ; ...and return doing things in parallel here }
alarm & thread
Alarm, or timer, can be set by using timer function available in Detroit engine. Traditional and now deprecated way to set the timer is:
; Set alarm to trigger one second from now timer (1, 0, "my_timer) end : "my_timer" mov _msg ("Hello from timer!") dump _msg end
Timer should be set this way using alarm block:
; Set alarm to trigger one second from now timer (1, 0, "my_timer) end alarm my_timer { mov _msg ("Hello from timer!") dump _msg }
If alarm function needs to end abruptly, always use end instruction to end it, never use ret instruction with alarm. There is some attributes available for alarm block:
- respawn sets the new timeout for the timer. Immediate value or variable is supported. If variable or RPN expression is used, it must be placed inside parentheses.
- repeat sets the repeat count for the timer. Immediate value or variable is supported. If variable or RPN expression is used, it must be placed inside parentheses.
Example:
alarm my_timer [respawn: 10:20] { ; This timer respawnds continuously at 10 second and ; 20 nanosecond interval } alarm my_timer [respawn: 5, repeat: 10] { ; This timer spawns 10 times at 5 second interval } mov repeat_count (10) alarm my_timer [repeat: (repeat_count + 1)] { ; This timer respawns eleven times using original timer interval }
Timer can be stopped by calling timer function again with a time of 0.
Thread can be spawned by using thread_spawn function available in Detroit engine. Traditional and now deprecated way to spawn a thread is:
thread_spawn ("Ted the thread", "my_thread") end : "my_thread" mov _msg ("Hello from thread!") dump _msg end
Thread should be declared this way by using thread block:
thread_spawn ("Ted the thread", "my_thread") end thread my_thread { mov _msg ("Hello from thread!") dump _msg }
If thread needs to end abruptly, always use end instruction to end it, never use ret instruction with threads. There is some attributes available for thread block:
- stacksize sets the stack size in bytes for the thread.
- guardsize sets the guard size in bytes for thread's stack memory.
Example:
thread my_thread_a [stacksize: 4096] { ; This thread has stack size of 4096 bytes } thread my_thread_b [stacksize: 4096, guardsize: 1024] { ; This thread has stack size of 4096 bytes, and ; one kilobyte block guarding the stack }
callback & function
Callback functions are like subroutines, often plugged in menu items for example. When menu item is selected, that item callback function is called. See menu resources and widget resources for more information about their callback functions.
If callback function needs to return something, it must always use end instruction with return code instead of ret. Never use ret with callback functions.
; This is traditional and now deprecated way to define a callback function, ; note the end instruction which is mandatory here : "my_cb" (_item, _position, _tag, _flag) dump _item dump _position dump _tag dump _flag end ; Callback function should be written this way, ; end instruction is needed only if this callback returns something, ; or it ends abruptly callback my_cb (_item, _position, _tag, _flag) { dump _item dump _position dump _tag dump _flag }
Functions (also known as subroutines) are callable by using call instruction. They differ from callbacks that they use different return mechanism. Because of that, ret instruction is needed if subroutine needs to return something to caller. Never use end with subroutines.
; This is traditional and now deprecated way to define a subroutine : "my_subroutine" (_param1, _param2) dump _param1 dump _param2 ret ; Subroutine should be written this way function my_subroutine (_param1, _param2) { dump _param1 dump _param2 } ; This subroutine returns the sum of two params function my_subroutine (_param1, _param2) { ret _param1 + _param2 }
main & finalize
⭐ New in 0.2.9.
The entry point of a program where the execution starts can be wrapped in main block, just like in C programming language for example. Main block can have optional exit attribute with exit code in it. If exit attribute is not specified, main block just ends and passes control to callback functions.
; ; Execution starts in main block below, so any instruction ; written before main block are defunct. ; main { print "Execution starts here.\n" }
When there is no exit attribute, main block termination behaves like end instruction at the end of the block.
main { print "This is main block which does not exit, useful " \ "when control is passed to callback functions.\n" print "Press <ctrl-c> to exit.\n" }
This program exits with exit code of zero:
main [exit: 0] { print "This is main block which exits automatically with " \ "exit code of zero.\n" }
If finalize block is defined, it is called automatically on exit:
main [exit: 0] { print "This is main block which exits automatically and " \ "spawns finalize block as it is defined below.\n" } function my_function { ; This spawns finalize block too. exit } finalize { print "This block is started automatically when exit function " \ "is called. This block does not pass to control to " \ "main block anymore." ; Calling exit function here does not have any practical effect. ; exit }
Main block can also use variable to specify the exit code, which can of course to change in runtime:
main [exit: (_retval)] { print "This is main block which exits with exit code of 123.\n" mov _retval (123) }
switch/case/default
A switch/case/default statement is similar than in more popular languages, but it has some divergence, as shown in nonsense example below. The basic idea is that switch keyword allows a variable to be tested against a list of values or other variables, called cases. switch block is declared as follows:
switch (variable) { ; Colon after expression is optional case expression : ; What to do if this case is true ; break or next is optional break case expression : ; What to do if this case is true ; next or break is optional next default : ; What to do if none of the above cases was true ; break or next is optional break }
The nonsense but working example:
mov i (1) mov j (2) mov k (3) switch (i) { case "Hello, world!": mov j (2) ; This falls through to next case, as there is no break case 1: mov j (1) break case 2: mov j (3) ; This jumps back to first case and starts over next case k: mov j (4) break default: }
Unlike in C and other languages, default keyword is mandatory after case keywords. A next loop control keyword can be used in place of break, which will start the case roulette all over again. A break loop control keyword will break the switch statement as normal. A colon after case keyword can be left out, it is not mandatory.
try/catch/finally
A try/catch/finally statement is similar than in some other programming languages. The statement consists of try block with zero or more catch keywords in it, which specify handlers for different return codes from function(s) called in try block. Instructions in finally block always executes before control flow exits from try block. Unlike some other languages, finally block is mandatory after catch keywords, even when it is empty. A colon after catch and finally keywords can be left out, it is not mandatory.
try/catch/finally is declared as follows:
try { function_to_be_tested (...) ... &function_to_be_tested (...) ... catch : catch (optional_expression) : finally : }
For example:
try { ; Get the return value in rc variable &my_function([int] 123) ; There can be some other code as well catch (123): ; my_function failed once catch (rc <= 123): ; my_function failed twice finally: } exit function my_function (_value) { ret _value }
try block can have optional variable which value to check. If it is not specified, the default rc variable is used. If variable value is zero when function to be tested returns, control flow is passed directly to finally block, and if it is non-zero, control flow is passed to first catch block if there is any. If catch keyword does not contain an expression or condition in it, variable to be tested is tested against [int] 0 value.
An example of try statement with use of optional variable:
mov my_val ([int] 0) try my_val { &my_function ; These two catches are equal, test my_val instead of rc catch: catch (1): ; Function set my_val to one, which means error finally: } exit function my_function { mov my_val (1) }
As said, catch is an optional part of try block. catch can have optional expression or condition with it. Expression is just single value. When that value is compared, and if true, control flow is passed in that catch block. Condition is similar than for loop condition statement. For example:
try { catch: catch (1): catch (rc == 10): catch (rc != 10): catch (rc <= 10): catch (rc >= 10): catch (rc < 10): catch (rc > 10): finally: }
A finally block is mandatory even when it is not needed. Instructions in finally block will always be executed before control flow exits from try block, even when using break keyword.
It is possible to pass the control flow back to first catch block by using throw keyword. throw is like unconditional jump, and throw keyword can have optional parameter to pass to catches. For example:
try { mov rc (1) catch (1): ; rc is 1 by default, so this block is catched throw (2) catch (2): ; When rc is 2, this block is catched throw (3) finally: }
Loop control keywords, break and next, can be used in try/catch/finally block.
- break jumps directly at the start of finally block.
- next jumps directly at the start of try block, running functions to be tested again.
Loop control
To control loops when they run, there is two keywords available for that. Both of these keywords are only useable inside the high level statements discussed above.
- break stops the loop immediately. Its like an unconditional jump out from the loop.
- next jumps straight to next iteration of the loop, and if condition check does not stop the loop, it starts over.
To clarify the logic with small example:
; Init statement mov i (0) : "loop" ; The start of the loop ; Loop payload dump i ; Loop counter statement, next jumps here inc i ; Check statement cmp i (10) jb "loop" ; The end of the loop, break jumps here
It is safe to use any other branching instruction to achieve the same effect than using the loop control keywords listed above. They are just shortcuts to branching instructions for more convenient use.
Conditional tests
These oneliner shortcuts helps to write more tight Ano script. Simple tests with rc variable can be squeezed to one line as listed below.
Conditional tests for jumping to label:
- if_null, test if rc variable is NULL and jump to
label. Example:
; Jump to label_to_jump if rc is NULL if_null label_to_jump
cmp rc (NULL) je "label_to_jump"
- unless_null, test if rc variable is not NULL and jump to label.
- if_invalid, test if rc variable is INVALID and jump to label.
- unless_invalid, test if rc variable is not INVALID and jump to label.
- if_zero, test if rc variable is 0 and jump to label.
- unless_zero, test if rc variable is not 0 and jump to label.
Conditional tests for calling subroutine instead of jumping to label:
- call_if_null, test if rc variable is NULL and call
subroutine. Example:
; Call function_to_call subroutine if rc is NULL call_if_null function_to_call
cmp rc (NULL) jne "rc_is_not_null" call "function_to_call" : "rc_is_not_null"
- call_unless_null, test if rc variable is not NULL and call subroutine.
- call_if_invalid, test if rc variable is INVALID and call subroutine.
- call_unless_invalid, test if rc variable is not INVALID and call subroutine.
- call_if_zero, test if rc variable is 0 and call subroutine.
- call_unless_zero, test if rc variable is not 0 and call subroutine.
Conditional tests for returning from function. These shortcuts accepts optional return code as a parameter. Default return code is zero if not set.
- ret_if_null, test if rc variable is NULL and return
from function. Example:
; Return from this function if rc is NULL ret_if_null ; Optional return code can also be set: ret_if_null 123
cmp rc (NULL) jne "rc_is_not_null" ret : "rc_is_not_null"
- ret_unless_null, test if rc variable is not NULL and return from function.
- ret_if_invalid, test if rc variable is INVALID and return from function.
- ret_unless_invalid, test if rc variable is not INVALID and return from function.
- ret_if_zero, test if rc variable is 0 and return from function.
- ret_unless_zero, test if rc variable is not 0 and return from function.
Conditional tests for ending callback. These shortcuts accepts optional end code as a parameter. Default end code is zero if not set.
- end_if_null, test if rc variable is NULL and end
callback. Example:
; End this callback if rc is NULL end_if_null ; Optional end code can also be set: end_if_null 123
cmp rc (NULL) jne "rc_is_not_null" end : "rc_is_not_null"
- end_unless_null, test if rc variable is not NULL and end callback.
- end_if_invalid, test if rc variable is INVALID and end callback.
- end_unless_invalid, test if rc variable is not INVALID and end callback.
- end_if_zero, test if rc variable is 0 and end callback.
- end_unless_zero, test if rc variable is not 0 and end callback.
Conditional tests for exiting the program. These shortcuts accepts optional exit code as a parameter. Default exit code is zero if not set.
- exit_if_null, test if rc variable is NULL and exit
program. Example:
; Exit program if rc is NULL exit_if_null ; Optional exit code can also be set: exit_if_null 1
cmp rc (NULL) jne "rc_is_not_null" exit : "rc_is_not_null"
- exit_unless_null, test if rc variable is not NULL and exit program.
- exit_if_invalid, test if rc variable is INVALID and exit program.
- exit_unless_invalid, test if rc variable is not INVALID and exit program.
- exit_if_zero, test if rc variable is 0 and exit program.
- exit_unless_zero, test if rc variable is not 0 and exit program.
Subsystem tests
⭐ New in 0.2.9.
Configure script has switches to disable or enable certain features and subsystems with --disable and --enable switches. For example debugging can be enabled with --enable-debug switch, and GUI subsystem can be disabled with --disable-gui switch. In some cases it may be handy to test those flags in Ano script.
If debugging is enabled (that is, if engine/Makefile has -DPROG_HAS_DEBUG switch for C compiler) instructions inside debug_enabled block are executed, otherwise they are ignored:
debug_enabled { ; Do something if debugging is enabled with --enable-debug ; configure switch. }
debug_enabled block supports level attribute to check the debug level. For example, if engine/Makefile has -DPROG_HAS_DEBUG=5 switch for C compiler, following block of code would execute:
debug_enabled [level: 5] { ; Do something if debugging is enabled with --enable-debug ; configure switch and it has value of 5. }
Existence of each subsystem in Detroit engine can be checked this way:
audio_enabled { ; ...do something if audio subsystem is enabled... } bob_enabled { ; ...do something if bob subsystem is enabled... } draw_enabled { ; ...do something if drawing subsystem is enabled... } gui_enabled { ; ...do something if gui subsystem is enabled... } input_enabled { ; ...do something if input driver subsystem is enabled... } menu_enabled { ; ...do something if menu subsystem is enabled... } remote_enabled { ; ...do something if remote control subsystem is enabled... } widget_enabled { ; ...do something if widget subsystem is enabled... }
Signal handlers
A signal can report some more or less unexpected behavior to program. Each signal delivered can be catched by declaring a simple sighandler block. Block needs no further initialization. sighandler block syntax is:
sighandler signal { }
Where signal is one of:
- hup for catching HUP (hangup) signal,
- int for catching INT (interrupt) signal,
- term for catching TERM (software termination from kill) signal,
- quit for catching QUIT signal, and
- usr1 and usr2 for catching USR1 and USR2 signals.
Keep signal handlers as small as possible. A couple of sighandler examples:
; Do not declare new variables in signal handlers, declare needed ; ones in advance. mov msg (NULL) ; As there is no exit() call, program just sits here until <ctrl-c> ; is pressed. When done so, sighandler for int signal is ; automatically called. end sighandler hup { ; This handler is called when HUP (hangup) signal is received mov msg ("SIGHUP here!") dump msg ; exit() does not have any practical effect in signal handler, ; as program is already shutting down ;exit } sighandler int { ; This handler is called when INT (interrupt) signal is received, ; for example when pressing <ctrl-c> mov msg ("SIGINT here!") dump msg } sighandler term { ; This handler is called when TERM (software termination from kill) ; signal is received mov msg ("SIGTERM here!") dump msg } sighandler quit { ; This handler is called when QUIT signal is received mov msg ("SIGQUIT here!") dump msg }
Variable triggers
Variable triggers are like handlers that runs when variable state or value changes. Trigger mechanism needs no initialization, it is enough just to declare a trigger block which name is the global variable where to attach the trigger. There is no way to unset the trigger in runtime. Simple example may look like:
mov my_var (1) add my_var (2) end trigger my_var { fetch _tmp (my_var) print "This is trigger for my_var, localized as _tmp\n" print "_tmp value is " . _tmp . "\n" }
Above example outputs:
This is trigger for my_var, localized as _tmp _tmp value is 1 This is trigger for my_var, localized as _tmp _tmp value is 3
Trigger runs immediately after value change. These attributes are available to use with trigger block:
-
mode, sets the trigger running mode. Defaults to blocking and can
be one of:
- blocking, trigger blocks the calling thread until it completes,
- concurrent, trigger runs concurrent with calling thread, and
- threaded is an alias for concurrent.
- value, which causes trigger function to run if variable matches with value. See below for more info. Several value attributes can be defined.
Simple example above can be set to run concurrent with calling thread by using threaded attribute:
mov my_var (1) add my_var (2) end trigger my_var [mode: threaded] { fetch _tmp (my_var) print "This is trigger for my_var, localized as _tmp\n" print "_tmp value is " . _tmp . "\n" }
As said, trigger runs when its variable state or value changes. Commands listed below does not spawn the trigger even when they modify the destination variable:
- deliver alias put, and
- fetch alias get.
Trigger mechanism has optional trigger_eval block to decide if the actual trigger should be run or not. For example, trigger_eval may examine some condition, and if true, it exits with return value of zero. When that happens, calling thread starts the actual trigger immediately. If evaluation block returns some other value than zero, trigger block will not be started. Evaluation block runs always in blocking mode, so it blocks the calling thread until evaluation block ends. Evaluation block does not support mode or value attributes. Above example can be extended as:
mov my_var (1) add my_var (2) end trigger my_var [mode: threaded] { fetch _tmp (my_var) print "This is trigger for my_var, localized as _tmp\n" print "_tmp value is " . _tmp . "\n" } trigger_eval my_var { ; If trigger should not be run, end with nonzero value: end -1 ; If trigger should be run, end with zero value: end 0 ; ...or just end, as zero is the default value: end ; If there is no end at the end of the block, return value is ; zero by default, which causes actual trigger to start. }
As described above, trigger function can have optional value attribute to check if trigger should run or not. This more lightweight way to check whether to run the trigger. That value is checked against the variable where trigger is attached. There is several ways to set the value:
; Trigger runs if my_var value is 123: trigger my_var [value: 123] { } ; Trigger runs if my_var value is not 123: trigger my_var [value: !123] { } ; Trigger runs if my_var value is less than 123: trigger my_var [value: <123] { } ; Trigger runs if my_var value is greater than 123: trigger my_var [value: >123] { } ; Trigger runs if my_var value is less or equal to 123: trigger my_var [value: <=123] { } ; Trigger runs if my_var value is greater or equal to 123: trigger my_var [value: >=123] { } ; Trigger runs if my_var value equals to some_var: trigger my_var [value: some_var] { } ; Trigger runs if my_var value is not equal to expression: trigger my_var [value: ! some_var + other_var / 2] { } ; value can be numeral range, where values are separated with colon. ; Range is set as follows: ; ; low_value:high_value ; low_value:high_value:low_value ... ; ; Trigger runs if my_var value is between 10 and 20: trigger my_var [value: 10:20] { } ; Trigger runs if my_var value is between 10 and 20, or greater or equal to 30: trigger my_var [value: 10:20:30] { } ; Trigger runs if my_var value is between 10 and 20, or greater or equal to 30 ; but less or equal to some_var: trigger my_var [value: 10:20:30:some_var] { } ; Trigger runs if my_var value is not 123, but equals to some_var: trigger my_var [value: some_var, value: !123] { }
When using several value attributes, they are evaluated from right to left. If trigger mode is threaded and value attribute is set after mode attribute, it is evaluated before thread is spawned. If value attribute is before mode attribute, evaluation is done in trigger thread.
Function hooks
⭐ New in 0.2.9.
Each function can have hook subroutines defined by function attributes. Hooks are like constructors and destructors for functions called automatically. Hook can access and manipulate function variables directly. Also, variables defined by the hook are available to use with the function.
Function attributes for defining hooks include:
- entry attribute parameter is the name of the hook to call before function instructions are executed,
- return attribute parameter is the name of the hook to call just before control flow is passed back to caller of the function.
function my_function [entry: my_fnc_entry, return: my_fnc_ret] (_var1, _var2) { ; Entry hook is spawned here. print "This is subroutine with hooks.\n" ; This spawns the return hook and ends this function: ret ; Return hook is spawned at the end of the function too. } hook my_fnc_entry { ; This hook is called before my_function starts executing. ; ; my_function parameters _var1 and _var2 are accessible ; here. } hook my_fnc_ret { ; This hook is called just before control flow returns ; back to caller of my_function. ; ; my_function parameters _var1 and _var2 are accessible ; here along with other my_function variables. }
Hooks are available with following functions:
- alarms aka. timers,
- callbacks,
- subroutines (aka. functions), and
- threads.
Binding own functions to Ano script
There is couple of files to modify to get C functions useable by Ano script. Each file is explained below with simple examples. All of the files are located in package's root directory, same place where the configure script is. Example project Vesmir makes use of own functions binded to Ano script, check that for live example.
dynload_bind_app.c
Own function needs a wrapper function which is the function what the engine calls. That wrapper must check the sanity of function parameters supplied in Ano script and call the actual function. Wrapper function must have dynload_ at the start of its name.
/* This is the actual wrapper function called from Ano script */ void dynload_my_fn(struct a_funcs *c) { struct par_my_fn a; if(dynload_my_fn_pre(c, &a) != 0) { return; } my_fn(a.param1, a.param2); } /* Check function prerequisities, for example, sanity of the parameters can be checked here, its important to check that parameter count, c->c, is what it is expected to be */ int dynload_my_fn_pre(struct a_funcs *c, struct par_my_fn *a) { /* Two parameters is needed to fill param1 and param2 */ if(c->c != 2) { dynload_error_params(c, 2); return(-1); } /* Parameters are available in c->p array */ a->param1 = dynload_param_get_number(&c->p[0]); a->param2 = dynload_param_get_number(&c->p[1]); return(0); }
dynload_bind_app.h
This file is for defining structs and other stuff for above functions.
/* This is the structure to store function parameters provided by Ano script */ struct par_my_fn { double param1, param2; };
dynload_ret_app.h
Every function return value type must be specified here, as engine needs to know which kind of data each function returns, if any. Add each function to dynload_ret_t structure with proper return value type. See engine/dynload_str.h for possible types. For example, dynload_ret_t structure may look like this:
static struct dynload_ret dynload_ret_t[] = { { "my_fn", RETURN_TYPE_VOID }, /* This marks the end of the strucure, do not remove this line */ { NULL, RETURN_TYPE_NONE } };
symbols.dyn
Every function name must be added there for exporting them in executable's symbol table. Function name must be prefixed with dynload_. For example, file may look like this:
{ dynload_my_fn; # Dynamic symbols provided by engine # ... };
Makefile and Makefile.in
Place myfuncs.c in SRC array to get it compiled.
myfuncs.c
The file for own functions. Our example function may look like this:
/* This is the actual function to make the Mojo */ void my_fn(double param1, double param2) { printf("Two params are %f and %f.\n", param1, param2); }
declarations_app.h
If there is a need to declare your function, this file may be used for that. For example, file may look like:
#include "structures_app.h" void my_fn(double, double);
Keep the include statement there, it is needed for compiling the application.
Ano script
Example to call my_fn() function in Ano script:
my_fn (1.0, 2.0) end
If function returns a value of any kind, it is always stored in internal variable rc.
Copyright © 2024, Jani Salonen <salojan at goto10 piste co>. Piste is finnish word and means dot. All rights reserved.