System overview Using the editor The Exotool language HTML templates The diagram The element catalogue
The Exotool Language — Reference Manual
This is a reference for the Exotool language: the small language you write inside activities to give your app its behavior. It covers the core language, the macro system, and the explicit JavaScript/PHP code blocks.
Scope. This manual documents the core activity language, macros, and explicit blocks. The Handlebars-style HTML templates are a separate syntax documented in the templates reference (
exotool_docs("templates")).Exotool itself is a visual RMAD platform; this manual covers only the activity-code language you write inside it. For the big picture — how the visual editor, diagrams, and the compiler fit together — start with the System Overview (
exotool_docs("overview")), and for building apps in the editor see Using the Exotool Editor (exotool_docs("editor")).
Contents
- What the language is
- How a program is structured
- Lexical structure
- Expressions
- Statements
- Subroutines
alwaysstatements- Declarations
- Client/server conditional statements
- Macros
- Explicit JS and PHP blocks
- Debug instrumentation
- Built-in functions
- Quick reference
1. What the language is
The Exotool language is the code you write inside activities — the behavior layer of an Exotool app. You do not run it directly. When you build your app, the Exotool compiler translates it into the program that actually runs: a mix of client-side JavaScript (on the device) and server-side PHP (on the server).
The defining feature of the language is location transparency: you write one body of logic, and the compiler decides which parts run on the device and which run on the server, generating the communication that moves data between them for you. Constructs like declarations, client/server conditionals, and explicit JS/PHP blocks exist to influence or override that decision when you need to.
During the build, the compiler also expands any macros you have defined and inserts debugging markers (see Debug instrumentation) so the visual debugger can follow execution.
2. How a program is structured
An activity is either a sequence of statements or, in a few places, a single expression. Statements are the usual case.
A program is one or more statements separated by whitespace. There is no
statement terminator — statements are separated by spaces and newlines, not by
;.
count = 3
price = 10
total = count * price
If a program cannot be parsed, the editor reports the line and position where it stopped — usually at or just after the mistake.
3. Lexical structure
Whitespace
Whitespace — spaces, tabs, and newlines — separates tokens and statements. Comments (below) also count as whitespace. In most places a newline is interchangeable with a space.
Comments
Two comment forms, both treated as whitespace:
// line comment — runs to end of line
/* block comment —
may span multiple lines */
Block comments do not nest.
Identifiers
An identifier — a name for a variable, function, subroutine, macro, or macro parameter — starts with a letter or underscore, followed by any number of letters, digits, or underscores. The first character may not be a digit.
x total customer_name _tmp item2
Reserved names
Three kinds of name are taken, and the compiler does not warn when you reuse one:
- Keywords and constants —
always,if,else,declare_location,declare_type,allocate,deallocate,if_on_client,if_on_server,subroutine_def,defmacro,null,true,false, and the block openersJS<,JS<P,PHP<. - Built-in function names — every function of §13
and of the generated Function Reference, and the compiler's own
(
DBpos,DBsource,_DBpos,_DBsource,_send_signal_to_other,_send_signal_toself,Padd5,ss_id). A subroutine you define with one of these names replaces the built-in wherever it is called. - Variable names — on the client every variable becomes a member of the app's
Envobject, so these names collide with the runtime's own:action,async_foreach,cloudname,delayshow,forms,last_opened_page,logged_in,login_error,new_field_value,position_error,sessionid,upload_error,user_billing_info,user_email,user_id,user_is_admin,user_name,user_organization,user_type; anything beginning__(the compiler's flags and lists); the key of any element of the diagram (an element's own variables live under its key, see@elementvarin the templates reference); and<key>_errorfor any element key (its error variable).
Numbers
A numeric literal is a sequence of digits 0–9. Integers are written this
way; a leading minus sign is the prefix operator -, not
part of the number.
Strings
String literals come in double- and single-quoted forms:
"a double-quoted string with \"escapes\" and \n \t \r"
'a single-quoted string with \'escapes\''
Recognized escapes are \n, \t, \r, the escaped quote, and \ itself.
Special constants
null true false always
These four may appear anywhere a constant is expected. (Note always is also
a statement keyword; which meaning applies depends on
context.)
4. Expressions
An expression produces a value. Exotool expressions are:
- constants — numbers, strings, and the special constants
null/true/false/always - variable references —
x,user.email,items[0] - function calls —
add(1, 2) - parenthesized expressions —
(a + b) - prefix-operator expressions —
-x,!done - binary-operator expressions —
a + b,x < limit - macro expressions and explicit JS/PHP block expressions (see §10 and §11)
Constants
A constant is one of the special constants, a number, or a string (see §3).
Variable references
A variable reference names a value. It is a plain name, or a name followed by one or more modifiers:
.name— a dotted field access[expression]— an indexed access
So all of these are valid variable references:
alpha
alpha.beta
alpha.beta.gamma
items[0]
matrix[row][col]
record.fields[i].name
Function calls
A function call is a name followed by a parenthesized, comma-separated list of argument expressions (zero or more arguments):
doThing()
add(1, 2)
format(name, makeTitle(x), 42)
A function call is valid both as an expression and as a statement.
Parenthesized expressions
Use parentheses to group sub-expressions:
(a + b)
Binary-operator expressions
A binary-operator expression joins two expressions with an operator:
a + b
count * price
x >= 0 && x < limit
The available binary operators are:
+ - * / && || <= < >= > == != & |
Precedence and associativity. There are no precedence levels — all binary operators are treated alike, and a chain associates to the right (
a + b + cis read asa + (b + c)). Always use explicit parentheses when grouping matters; do not rely on conventional arithmetic precedence.
Prefix operators
A prefix operator applies to the expression that follows it:
-x
!done
-(a + b)
The prefix operators are - (negation) and ! (logical not).
5. Statements
A statement is one of the following:
| Statement | Form |
|---|---|
always block |
always <exp-or-assignment> { ... } |
always statement |
always <exp-or-assignment> |
| client/server conditional | if_on_client <stmt> / if_on_server <stmt> |
| explicit JS / server block | JS< ... >JS, PHP< ... >PHP |
| declaration | declare_location / declare_type / allocate / deallocate |
| assignment | lhs = expression |
| block | { ... } |
| if / if-else | if (cond) stmt [else stmt] |
| subroutine definition | subroutine_def name() { ... } |
| macro definition | defmacro name(...) { ... } |
| function call | name(args) |
| macro statement | a macro call used in place of statements |
| bare variable reference | name |
Assignment
The left-hand side is any variable reference, including modified ones; the right-hand side is any expression.
x = 5
total = count * price
record.name = "Ada"
items[i] = compute(i)
Blocks
A block groups statements inside braces. It is itself a statement, so blocks may
be nested and used anywhere a statement is expected (notably as the body of an
if).
{
a = 1
b = 2
doSomething(a, b)
}
if and if/else
if (condition) statement
if (condition) statement else statement
The condition must be parenthesized. The body is a single statement — use a block for more than one statement.
if (total > 20) {
show(total)
}
if (ready)
go()
else
wait()
An
elseclause always binds to the nearestif, so it attaches when present.
Bare variable reference as a statement
A lone variable name is a valid statement. This mainly exists so that a macro parameter can stand in for a statement inside a macro body (see §10).
6. Subroutines
subroutine_def <name> ( <args> ) { <statements> }
subroutine_def greet() {
show("hello")
}
greet()
Subroutines are defined with the subroutine_def keyword and called like an
ordinary function.
Important notes:
- Arguments are not yet implemented. The argument list must be present but is ignored.
- A subroutine's code is only emitted if it is actually called somewhere.
- Location is inferred from the body:
- If the body contains any client-side specificity, the subroutine becomes client-callable only. It lives on the device and may call out to the server as needed for its server-side sections.
- If the body has only server-side (or location-agnostic) specificity, a single server-side function is generated, and a client-side entry point is also generated if it is called from the device. Such a subroutine is callable from both the device and the server.
7. always statements
always declares a reactive binding: the associated logic re-runs whenever
any variable in its source expression changes.
always <exp-or-assignment>
always <exp-or-assignment> { ... }
where <exp-or-assignment> is either an assignment or a plain expression.
Reactive assignment
always beta = alpha + 5
beta is kept equal to alpha + 5: the assignment re-executes whenever alpha
(any variable referenced on the right-hand side) changes.
Reactive block
An optional block runs on every update as well:
always beta = alpha + 5 {
log("beta updated to", beta)
}
If the part after always is a bare expression (not an assignment), the
block is mandatory — the expression only names the variables to watch, and
the block is what runs.
always rules and caveats
- A reactive assignment must target a simple named variable (not
a.bora[i]). - The assignment/block runs at least once when the
alwaysstatement is first executed. - Change detection and the update logic run on the device side only. This
means watched logic is effectively client-side, and the values it depends on
are made available on the device — so do not feed sensitive data (such as
passwords) into an
always. - Recursion is not detected, there is no debounce for rapid updates, and
the debugger does not currently step into
alwaysbodies. Use with care.
8. Declarations
Declarations give the compiler information it cannot infer, primarily about where a variable lives and what type it holds.
Location declaration
declare_location <name> <location>
where <location> is client or server.
declare_location currentUser server
declare_location scrollPos client
Pins a variable to the device or the server, constraining the compiler's location decisions.
Type declaration
declare_type <name> <type>
where a <type> is one of:
array of <type>
object with { <members> }
cloudobject with { <members> }
<type-name>
and a member list is one or more name : type pairs separated by whitespace:
declare_type ids array of number
declare_type point object with { x : number y : number }
declare_type user cloudobject with { name : string age : number }
declare_type score number
cloudobject types denote persistent, server-stored objects; object denotes a
plain structured value.
Allocation declarations
allocate <name>
deallocate <name>
allocate buffer
deallocate buffer
Declare explicit allocation / deallocation of a named resource.
9. Client/server conditional statements
These run a statement only on a specific tier:
if_on_client <statement>
if_on_server <statement>
if_on_client show("loading…")
if_on_server logAccess(currentUser)
if_on_client forces its statement onto the device; if_on_server forces its
statement onto the server. Use these only when you need to override the
compiler's automatic placement.
10. Macros
Macros are textual substitution, expanded before the rest of compilation — much like the C preprocessor.
Definition
defmacro <name> ( <param>, <param>, ... ) { <body> }
The body must parse as either a sequence of statements or a single expression:
// expression macro
defmacro myexp() { 4 + 5 }
// statement macro
defmacro myops() { alert("a") alert("b") }
// with parameters
defmacro twice(x) { x x }
Definitions may appear anywhere in the source — all definitions are collected
before any are expanded. A defmacro produces no output in place; it only
registers the definition.
Expansion
A macro is called like a function, and the call may stand in for either an expression or a set of statements:
- As an expression:
myexp()expands to4 + 5. - As statements:
myops()expands toalert("a") alert("b")— without wrapping braces.
Argument substitution is literal (text) substitution: each parameter name in the body is replaced by the source text of the corresponding argument.
defmacro addone(n) { n + 1 }
x = addone(5) // expands to: x = 5 + 1
y = addone(a * b) // expands to: y = a * b + 1
Macro rules and caveats
- The expansion must be syntactically valid at the point of use. A statement macro used where an expression is required (or vice versa) will produce invalid code.
- One special allowance: a single identifier (a macro parameter) may stand in place of a statement and still parse — this is why a bare variable reference is a valid statement.
- Argument count should match. Extra arguments beyond the macro's parameter list are not substituted — such a parameter name is simply left as-is.
- Expansion is recursive but capped at 100 levels, after which a call is emitted verbatim without expansion — a guard against runaway recursive macros.
11. Explicit JS and PHP blocks
When you need to drop directly into the target language, use explicit blocks. Everything between the delimiters is passed through as raw target-language code.
JavaScript blocks (client side)
JS< ... raw JavaScript ... >JS
A JS block embeds client-side JavaScript. It may be used as an expression or as a statement.
result = JS< Math.floor(x / 2) >JS
JS<
console.log("hello from the client");
document.title = "ready";
>JS
An asynchronous variant uses the JS<P opener (the P denotes a
promise/async block):
data = JS<P fetch("/api").then(r => r.json()) >JS
PHP blocks (server side)
PHP< ... raw PHP ... >PHP
A PHP block embeds server-side PHP. It may be used as an expression or a statement.
cmodified = PHP<
$cmodified = array();
foreach($args->modset as $id => $obj) {
$satisfied = !$obj->was_deleted && $args->inquery[$id] && true;
if ($args->session->tracking[$id] != $satisfied) {
$args->session->tracking[$id] = $satisfied;
$cmodified[$id] = $satisfied ? $obj : 'deleted';
}
}
return $cmodified;
>PHP
Values from the surrounding Exotool scope are made available inside server blocks via an
$argsobject, and the block'sreturnvalue flows back out. Exotool handles the conversion of data between the device and the server (for example, JSON values to PHP arrays and back, and the marshaling of cloud objects).
12. Debug instrumentation
This is not something you write, but you will see it in compiled output and should not be surprised by it. During the build, the compiler injects debugging markers so the visual debugger can follow execution:
- a
DBsource(...)marker at the start of every fragment, subroutine body, and statement-macro body — it tells the debugger which source text is executing. - a
DBpos(...)marker before each ordinary statement — it tells the debugger which line is executing. - a trailing
DBpos(-1, 0)at the end of a fragment or subroutine to refresh the variable display.
Declarations, blocks, subroutine and macro definitions, and client/server
conditionals are not prefixed with position markers, and a bare (non-block)
if/else body is wrapped in braces so the marker and body stay together.
13. Built-in functions
The activity language ships with a library of built-in functions you can call from any activity. They are organized below by category. Each is shown in the form you call it, with a one-line description.
These summaries are condensed from Exotool's generated online Function Reference, which is the authoritative, always-current source — it gives the full call format, every parameter (with types and which are optional), the return value, and worked examples for each function. Use this appendix as a map; consult the online reference for detail. The language constructs (
defmacro,subroutine_def,always,JS<…>JS/PHP<…>PHP, declarations, comments, constants) also appear there, but are documented in the body of this manual above.
Access & Security
| Function | Description |
|---|---|
create_user(...) |
Creates a new user. |
register_user(...) |
Registers a new user. |
change_password(...) |
Changes a user's password. |
dologin(...) |
Logs the current session in from submitted login-form values; sets variables reflecting success/failure. |
login_session(...) |
Logs a session in; returns a structure describing the result. |
logout() |
Logs the current session out. |
logout_session(...) |
Logs a specific session out (normally used internally by the login-form element). |
get_current_session() |
Returns the current session, creating one if none exists. |
is_admin_user() |
Whether the logged-in user is an administrator — for extra checks before admin-only operations. |
send_tfa_code(...) |
Sends a two-factor authentication code by email (given an email address or a clouduser). |
verify_tfa_code(...) |
Verifies a two-factor code against the one most recently sent. |
Cloud Data
| Function | Description |
|---|---|
create_cloudobject(type [, ...]) |
Creates a new cloud object of the given type. |
expect_cloudobject(...) |
Finds a cloud object by identifier, creating one if none is found. |
locate_cloudobject(type [, identifier]) |
Finds a cloud object by type (and optional identifier); returns it, or false if none. |
delete(object_or_set) |
Deletes a cloud object, or every object in a cloud set. |
Sets
| Function | Description |
|---|---|
lookup(set) |
Returns an array of the cloud objects in a cloud set. |
count(set_or_array) |
Returns the number of items in a set or array. |
first(set) |
Returns the first object in a set (most useful with ordered sets). |
one(set) |
Returns a single object from a set (useful for locating one item). |
ordered(set, ...) |
Returns an ordered (sorted) version of a set. |
limited(set, ...) |
Returns a subset limited to some of the items (e.g. for paging large results). |
explicit_set(...) |
Returns a set containing specific objects you supply (zero or more). |
superset(...) |
Returns a set with more added — each argument may be a set, an array, or a single object. |
cloudobjects_of_type(type) |
Returns the set of all cloud objects of a given type. |
possible_values(set, key) |
Returns all possible values of a parameter across the objects in a set. |
subset(set, key, value [, ...]) |
Subset whose objects match the given key/value pairs (equality). |
subset_neq(set, key, value) |
Subset where a value is not equal to the given value. |
subset_gt(set, key, value) |
Subset where a value is greater than the given value. |
subset_ge(set, key, value) |
Subset where a value is greater than or equal to the given value. |
subset_lt(set, key, value) |
Subset where a value is less than the given value. |
subset_le(set, key, value) |
Subset where a value is less than or equal to the given value. |
subset_like(set, key, pattern) |
Subset where a value matches a pattern (text match). |
User Input
| Function | Description |
|---|---|
prompt(...) |
Asks the user for a text value via a modal. |
confirm(...) |
Asks the user a yes/no question via a modal (confirm or cancel). |
imageprompt() |
Prompts the user to upload an image; returns a cloud object (auto-processed into original, mid-size, and thumbnail). |
fileprompt() |
Prompts the user to upload a file; returns a storedCloudFile cloud object (with a url, etc.). |
get_form_values(...) |
Reads the values the user entered in a form in the app. |
set_form_values(...) |
Sets (changes) the values displayed in a form in the app. |
Modals and Alerts
| Function | Description |
|---|---|
alert(message [, ...]) |
Displays a modal alert until the user clicks OK (extra arguments are concatenated into the message). |
show_notification(...) |
Displays a notification in the app. |
UI Control
| Function | Description |
|---|---|
sync() |
Refreshes the app's data lists with the most current data from the server. |
page_back() |
Navigates the current page back (as a Back link would). |
close_popup([popup]) |
Closes the indicated popup, or all open popups. |
close_panel([panel]) |
Closes the indicated panel, or all open panels. |
sleep(seconds) |
Pauses for a number of seconds (fractional amounts allowed). |
abort([message]) |
Stops execution of the current thread, optionally showing a modal message first. |
run_subprocess(...) |
Runs a sub-process defined by a subview. |
Messaging
| Function | Description |
|---|---|
send_signal(...) |
Sends a push-style signal to active "Listen" elements, in this app or another. |
| Function | Description |
|---|---|
send_mail(...) |
Sends an email. |
Time and Date
| Function | Description |
|---|---|
now() |
Returns the current time as a UNIX timestamp (seconds since 1 Jan 1970 GMT). |
datestring(format, timestamp) |
Formats a date/time from a format string and a UNIX timestamp (similar to PHP's date()). |
Math
| Function | Description |
|---|---|
floor(number) |
Returns the integer value of a number, dropping any fractional part. |
String Manipulation
| Function | Description |
|---|---|
concat(...) |
Concatenates strings together (arguments that are not strings are converted first). |
E-Commerce
| Function | Description |
|---|---|
usd(number) |
Truncates a number to dollars and cents (e.g. 23.13000001 → 23.13). |
14. Quick reference
Keyword summary
Reserved word-like tokens used by the core language, macros, and blocks:
if else
subroutine_def
defmacro
always
declare_location client server
declare_type array of object with cloudobject
allocate deallocate
if_on_client if_on_server
null true false (constants; `always` is also one)
Delimiters for explicit blocks:
JS< JS<P >JS PHP< >PHP
Operators
binary: + - * / && || <= < >= > == != & |
prefix: - !
Remember: binary operators have no precedence levels and associate to the right — parenthesize when grouping matters (see §4).