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:

  1. A tilde ~ as first character will be replaced by user home directory,
  2. At sign @ as first character will be replaced by application installation prefix (that is the value for --prefix option given to configure script),
  3. A dot . as first character is considered as current directory, and
  4. 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:

  1. $ for blob (aka. structure) datatype
  2. * for pointer datatype
  3. @ for handle datatype
  4. # for color datatype
  5. % for image datatype
  6. & 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:

  1. red, get or set the red color component
  2. green, get or set the green color component
  3. blue, get or set the blue color component
  4. alpha, get or set the alpha value
  5. 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:

  1. width, get image width in pixels
  2. 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:

  1. x, get or set the X coordinate
  2. y, get or set the Y coordinate
  3. z, get or set the Z coordinate
  4. angle_xy, calculate vector angle in degrees by its position, or manipulate XY coordinates by given angle
  5. mag_xy, calculate magnitude of 2 dimensional point, or manipulate XY coordinates by given magnitude
  6. 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:

  1. rc for global return value, when external function, subroutine or whatever returns something, returned data is stored here
  2. NULL for empty string, works just like its equivalent in C language
  3. 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:

  1. Logical instruction set,
  2. Math instruction set,
  3. Gaussian instruction set, and
  4. 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:

  1. alarm (see timer snippet),
  2. callback (see menu and widget resource guides),
  3. sighandler,
  4. thread (see thread creation snippet), and
  5. 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

print

Evaluates and prints given parameter string to standard output. Some special characters can be used with the string. Those include:

  1. \0 for string termination,
  2. \a bell,
  3. \b backspace,
  4. \t horizontal tab,
  5. \n line feed,
  6. \v vertical tab,
  7. \f form feed, and
  8. \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.

  1. A is set if first operand is greater, clear if its not.
  2. B is set if second operand is greater, clear if its not.
  3. E is set if operands are equal, clear if they are not.
  4. S is set if operand is signed, clear if its unsigned.
  5. 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:

  1. 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:

  1. 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.
  2. 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:

  1. stacksize sets the stack size in bytes for the thread.
  2. 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.

  1. break jumps directly at the start of finally block.
  2. 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.

  1. break stops the loop immediately. Its like an unconditional jump out from the loop.
  2. 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:

  1. 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
    
    Above condition test can be written in plain Ano script like this:
    	cmp	rc (NULL)
    	je	"label_to_jump"
    
  2. unless_null, test if rc variable is not NULL and jump to label.
  3. if_invalid, test if rc variable is INVALID and jump to label.
  4. unless_invalid, test if rc variable is not INVALID and jump to label.
  5. if_zero, test if rc variable is 0 and jump to label.
  6. unless_zero, test if rc variable is not 0 and jump to label.

Conditional tests for calling subroutine instead of jumping to label:

  1. 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
    
    Above condition test can be written in plain Ano script like this:
    	cmp	rc (NULL)
    	jne	"rc_is_not_null"
    	call	"function_to_call"
    : "rc_is_not_null"
    
  2. call_unless_null, test if rc variable is not NULL and call subroutine.
  3. call_if_invalid, test if rc variable is INVALID and call subroutine.
  4. call_unless_invalid, test if rc variable is not INVALID and call subroutine.
  5. call_if_zero, test if rc variable is 0 and call subroutine.
  6. 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.

  1. 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
    
    Above condition test can be written in plain Ano script like this:
    	cmp	rc (NULL)
    	jne	"rc_is_not_null"
    	ret
    : "rc_is_not_null"
    
  2. ret_unless_null, test if rc variable is not NULL and return from function.
  3. ret_if_invalid, test if rc variable is INVALID and return from function.
  4. ret_unless_invalid, test if rc variable is not INVALID and return from function.
  5. ret_if_zero, test if rc variable is 0 and return from function.
  6. 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.

  1. 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
    
    Above condition test can be written in plain Ano script like this:
    	cmp	rc (NULL)
    	jne	"rc_is_not_null"
    	end
    : "rc_is_not_null"
    
  2. end_unless_null, test if rc variable is not NULL and end callback.
  3. end_if_invalid, test if rc variable is INVALID and end callback.
  4. end_unless_invalid, test if rc variable is not INVALID and end callback.
  5. end_if_zero, test if rc variable is 0 and end callback.
  6. 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.

  1. 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
    
    Above condition test can be written in plain Ano script like this:
    	cmp	rc (NULL)
    	jne	"rc_is_not_null"
    	exit
    : "rc_is_not_null"
    
  2. exit_unless_null, test if rc variable is not NULL and exit program.
  3. exit_if_invalid, test if rc variable is INVALID and exit program.
  4. exit_unless_invalid, test if rc variable is not INVALID and exit program.
  5. exit_if_zero, test if rc variable is 0 and exit program.
  6. 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:

  1. hup for catching HUP (hangup) signal,
  2. int for catching INT (interrupt) signal,
  3. term for catching TERM (software termination from kill) signal,
  4. quit for catching QUIT signal, and
  5. 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:

  1. mode, sets the trigger running mode. Defaults to blocking and can be one of:
    1. blocking, trigger blocks the calling thread until it completes,
    2. concurrent, trigger runs concurrent with calling thread, and
    3. threaded is an alias for concurrent.
  2. 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:

  1. deliver alias put, and
  2. 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:

  1. entry attribute parameter is the name of the hook to call before function instructions are executed,
  2. 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:

  1. alarms aka. timers,
  2. callbacks,
  3. subroutines (aka. functions), and
  4. 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.