Syntax & Grammar
- 1. File and function structure
- 2. Function declaration
- 3. Statements and the
;terminator - 4. Comments
- 5. Identifiers and reserved keywords
- 6. Variable declaration & assignment
- 7. Literals
- 8. The
logstatement - 9. The
returnstatement - 10. Conditionals —
if / else if / else - 11. Loops —
for eachis the only form - 12. Error handling —
try / catch - 13. The
new NameSpace(...)form - 14.
Module(...)andReading(...)atoms - 15.
Calender.XandClock.Xatoms - 16. Operators
- 17. List literals
- 18. Map literals
- 19. Criteria literals
- 20. The
db_paramblock - 21. Method chaining
- Common syntax errors
Canonical language reference for Facilio Script. Every rule below maps to a production in the parser grammar (workflow.g4). Use this page to debug syntax errors and to know exactly what the parser accepts.
If you only read one section, read Common syntax errors at the end.
1. File and function structure#
A Facilio Script file contains exactly one function. Nothing executable lives outside the function body.
void run() { // all logic goes here}Grammar: parse : function_block EOF.
2. Function declaration#
<returnType> <name>(<paramType> <paramName>, ...) { <statements> }Allowed return types and parameter types:
| Type | Notes |
|---|---|
void | Return type only — function returns nothing |
String | Text value |
Number | Integer or decimal |
Boolean | true / false |
Map | Key-value collection |
List | Ordered collection |
Criteria | Filter expression |
Object | Generic / dynamic |
// Void with no parametersvoid run() { log "hello";}
// Typed parameters + return typeString greet(String name, Number age) { return "Hi " + name;}Types appear in the signature only
Never repeat the type prefix on a variable inside the function body. The parser auto-detects the type from the assigned value.
Platform-invoked scripts use a fixed signature
When Facilio Script runs from a workflow, notification, custom button, approval, state flow, or formula field, the function MUST be void execute_script(Map <moduleLinkName>) { ... }. Schedulers use the no-parameter form void execute_script() { ... }. See Execution Contexts for the full contract and per-module examples.
3. Statements and the ; terminator#
Each of these statements must end with ;:
- Assignment —
x = 5; log—log "ready";return—return x;- Stand-alone expression —
obj.method();
Block-introducing statements (if, for each, try/catch) do not take a trailing ;. Whitespace and newlines are insignificant.
4. Comments#
// Single-line comment
/* Block comment. May span multiple lines.*/Both forms are stripped before parsing.
5. Identifiers and reserved keywords#
Identifier rule: [a-zA-Z_][a-zA-Z_0-9]* — start with a letter or underscore, followed by letters, digits, or underscores.
The following words are reserved and cannot be used as variable names:
| Category | Reserved words |
|---|---|
| Types | void, String, Number, Boolean, Map, List, Criteria, Object |
| Control flow | if, else, for each, in, return, try, catch |
| Literals | true, false, null |
| Operators | new |
| Module atoms | Module, Reading |
| Statement keywords | log |
| DB-param keys | criteria, fieldCriteria, field, aggregation, limit, range, to, groupBy, orderBy, asc, desc |
Calender. and Clock. are lexer literals that include the trailing dot, so the bare names Calender / Clock would lex as plain identifiers — but using them as variable names is confusing and not recommended.
6. Variable declaration & assignment#
Three LHS (left-hand-side) forms are supported:
// 1. Simple variablename = "Asset";
// 2. Bracket access (key is any expression)data["title"] = "Inspection";items[0] = "first";
// 3. Dotted access (multi-level)record.priority.name = "High";No type prefix in the body. This is the single most common source of LLM syntax errors.
// WRONGString name = "Asset";Number count = 10;
// RIGHTname = "Asset";count = 10;7. Literals#
Strings#
Double quotes only. The only escape sequence is \" for an embedded double quote. Raw newlines (\n, \r) inside the string are a parse error — build multi-line text with + concatenation.
title = "Quarterly Report";quoted = "He said \"go\".";multi = "line one" + " " + "line two";
// WRONGoneLine = 'single quotes'; // single quotes not allowedbroken = "firstsecond"; // raw newline not allowedNumbers#
INT (123) or FLOAT (123.45 or leading-dot .45).
a = 10;b = 0.5;c = .5; // valid — leading dotBooleans#
Lowercase only: true or false. True / TRUE is invalid.
Null#
result = null;8. The log statement#
Form: log <expr>; — no parentheses.
// RIGHTlog "starting";log workorder.subject;log "count = " + items.size();
// WRONG — parsed as a function call to a reserved word, not the log statementlog("starting");9. The return statement#
Form: return <expr>;. Required for any non-void function.
Number area(Number w, Number h) { return w * h;}10. Conditionals — if / else if / else#
Grammar: if (boolean_expr) statement_block (else if (boolean_expr) statement_block)* (else statement_block)?.
- Parens around the condition are mandatory.
- The body may be
{ block }or a single statement. - To combine sub-conditions logically, use
&&/||. Single&/|between two comparisons is parsed as a bitwise/boolean expression — usually not what you want for control flow.
if (priority > 3 && status == "Open") { log "high priority open";} else if (status == "Closed") { log "closed";} else { log "default";}See Conditions & Loops for more examples.
11. Loops — for each is the only form#
There is no for, no while, no do/while. The only loop is for each.
Grammar: for each VAR (, VAR)* in expr statement_block. No parens around the iterable.
// Single iterator over a listfor each value in items { log value;}
// Index + value over a listfor each i, value in items { log i; log value;}
// Key + value over a mapfor each key, value in data { log key; log value;}See Conditions & Loops for more.
12. Error handling — try / catch#
try { response = new NameSpace("http").get(url); log response;} catch { log "request failed";}No exception variable, no finally
The grammar accepts only try { ... } catch { ... }. Writing catch (e) { ... } or adding a finally { ... } block is a syntax error. To inspect the failure you must rely on logging from inside the try block.
13. The new NameSpace(...) form#
new VAR(args) is the only construct that uses the new keyword. It is used to instantiate a namespace, then chain a method.
fileId = new NameSpace("connectedApp").exportAsPDF("widget.pdf", "myApp", "report", {}, ctx);
new NameSpace("wms").sendMessageToUser(message);The lexer literal is 'new ' with a trailing space — newNameSpace(...) (no space) is a syntax error.
14. Module(...) and Reading(...) atoms#
Module and Reading are first-class atoms in the grammar — both names are reserved keywords. Write them exactly as shown, no new.
// Fetch a single record by criteriawo = Module("workorder").fetchFirst([id > 0]);
// Sensor / measurement readingr = Reading("ahu", assetId);Module takes one argument; Reading takes exactly two (module name and resource id).
15. Calender.X and Clock.X atoms#
First-class atom forms for date/time references. Use them inline as expression operands.
startOfToday = Calender.today;tomorrow = Calender.tomorrow;rightNow = Clock.now;nineThirty = Clock.9:30;See Date-time functions and System Variables for the available variants.
16. Operators#
Precedence — highest to lowest (derived from the expr production):
| Tier | Operators | Notes |
|---|---|---|
| Unary | -x, !x | Unary minus, logical NOT |
| Multiplicative | * / % | |
| Additive | + - | + also concatenates strings |
| Relational | < <= > >= == != | Returns Boolean |
| Bit / boolean | & \| | **Single character — the only &/` |
| Logical (combinator) | && \|\| | Used only to combine sub-conditions inside if (...) and criteria [...] |
Single & / \| vs double && / \|\| — by context
The parser puts these operators in different rules. Use the correct one for your context:
- Inside an expression (assignment RHS, arithmetic, function arguments): only single
&/\|are valid.&&/\|\|are not in theexprrule, soc = a && b;is a parse error. - Combining sub-conditions in
if (...)or in criteria[...]: use&&/\|\|. Inside criteria,&&/\|\|are the only combinator allowed between condition atoms —[a == 1 & b == 2]is a parse error.
// Expression context — must be singlecombined = a & b; // RIGHTcombined = a && b; // WRONG (&& not in expr)
// Combining conditions in if — use doubleif (a > 5 && b < 10) { ... } // RIGHTif (a > 5 & b < 10) { ... } // PARSES, but treated as a bit-op of two comparisons — rarely what you want for control flow
// Combining conditions in criteria — must be doublec = [status == "Open" && priority > 3]; // RIGHTc = [status == "Open" & priority > 3]; // WRONG (parse error inside criteria)^ (POW) is defined but NOT parsed
^ exists as a lexer token (POW=45) but no parser rule references it, so 2 ^ 3 is a syntax error. For exponentiation use the math namespace:
result = new NameSpace("math").pow(2, 3);17. List literals#
empty = [];colors = ["Red", "Yellow", "Green"];nums = [1, 2, 3];Restriction: elements must be atoms (literals or variables), not arbitrary expressions. Build computed lists imperatively.
// WRONG — computed expression inside literalnums = [a + 1, b * 2];
// RIGHT — build with .add()nums = [];nums.add(a + 1);nums.add(b * 2);There is also no nested literal indexing — assign to a variable first.
list1 = ["hello"];list2 = [list1];
// WRONGlog list2[0][0];
// RIGHTinner = list2[0];log inner[0];18. Map literals#
Only the empty form {} is allowed. Populate via dot or bracket assignment.
// WRONG — populated literalconfig = { name: "test", count: 1 };
// RIGHTconfig = {};config.name = "test";config.count = 1;// equivalent:config["name"] = "test";Nested dot assignment is supported:
m = {};m.a.b.c = "deep";log m.a.b.c;19. Criteria literals#
A criteria value is [ condition ]. Inside the brackets:
condition_atomisVAR <op> <expr>where<op>is one of< <= > >= == !=.- Conditions combine with
&&/||(not single&/|). - Use field names only — no module prefix.
openHigh = [status == "Open" && priority > 3];recent = [createdTime > Clock.now - 86400000];
// Empty / match-all criteriaallRecords = [id > 0];See Criteria function for combining criteria programmatically.
20. The db_param block#
Used inside Module(...).fetch({ ... }). The block opens with {, contains one criteria: entry followed by any number of optional groups, and closes with }. Commas between entries are accepted but not required by the grammar. All keys are reserved words.
| Key | Form | Notes |
|---|---|---|
criteria | criteria: <expr> | Required, must be the first entry |
fieldCriteria | fieldCriteria: <criteria> | Takes a criteria literal [ ... ] |
field | field: <expr> | List of fields to fetch |
aggregation | aggregation: <expr> | |
limit | limit: <expr> | |
range | range: <expr> to <expr> | Use to, not a comma |
groupBy | groupBy: <expr> | |
orderBy | orderBy: <expr> asc or orderBy: <expr> desc | Trailing asc / desc is required |
list = Module("workorder").fetch({ criteria: [status == "Open" && priority > 3], field: ["id", "subject", "priority"], orderBy: "createdTime" desc, range: 0 to 50});21. Method chaining#
After any atom you can chain .method(args) calls, .field accesses, or [atom] index accesses.
total = items.size();first = items.get(0).name;key = data["title"];Bracket index access takes a literal or variable only (atom), not an arbitrary expression — assign to a variable first:
// WRONGv = items[i + 1];
// RIGHTidx = i + 1;v = items[idx];Common syntax errors#
These are the failure modes the parser will reject. Each pair is WRONG → RIGHT.
Missing
;x = 5 // WRONGx = 5; // RIGHTType prefix in body
String name = "x"; // WRONGname = "x"; // RIGHTlogcalled as a functionlog("hi"); // WRONGlog "hi"; // RIGHTSingle-quoted strings
s = 'hello'; // WRONGs = "hello"; // RIGHTUnescaped quote inside string
s = "He said "hi""; // WRONGs = "He said \"hi\""; // RIGHTRaw newline inside string — split with
+.Traditional
forloopfor (i = 0; i < n; i++) { ... } // WRONGfor each i, v in list { ... } // RIGHT&&/||inexprcontext — not in the expression grammar.combined = a && b; // WRONG (parse error)combined = a & b; // RIGHTSingle
&/|between condition atoms inside criteria[...]— only&&/||combine atoms there.c = [status == "Open" & priority > 3]; // WRONG (parse error)c = [status == "Open" && priority > 3]; // RIGHTTrue/False(capitalized) — onlytrue/falsework.2 ^ 3—^is not a parser operator. Usenew NameSpace("math").pow(2, 3).catch (e) { ... }— no exception variable.try { ... } catch (e) { ... } // WRONGtry { ... } catch { ... } // RIGHTMissing
nullcheck afterfetchFirstwo = Module("workorder").fetchFirst([id == 1]);log wo.subject; // WRONG — wo may be nullif (wo != null) { log wo.subject; } // RIGHTReserved word as variable name —
log,Module,Reading,criteria,limit, etc.ifwithout parensif x > 5 { ... } // WRONGif (x > 5) { ... } // RIGHTParens around
for eachiterablefor each v in (list) { ... } // WRONGfor each v in list { ... } // RIGHTMissing space after
new— the lexer literal is'new '.newNameSpace("math").pow(2, 3); // WRONGnew NameSpace("math").pow(2, 3); // RIGHTComputed expression in list literal
nums = [a + 1, b * 2]; // WRONGnums = [];nums.add(a + 1);nums.add(b * 2); // RIGHTPopulated map literal
m = { name: "x", count: 1 }; // WRONGm = {};m.name = "x";m.count = 1; // RIGHT