Operations & Return
NEW OPERATION defines a reusable block of OcaltQL. RUN executes it. RETURN passes a value back to the caller silently. EMIT outputs immediately and is never captured. Understanding the difference is fundamental.
NEW OPERATION
Operations are defined with NEW OPERATION followed by a name. Parameters are declared with WITH ?param AND ?param. The body is wrapped in OPEN and CLOSE. Operations can have no parameters, one parameter, or many.
NEW OPERATION greet
OPEN
EMIT "Hello World"
CLOSE
NEW OPERATION greet_user WITH ?name
OPEN
EMIT "Hello, " & ?name
CLOSE
NEW OPERATION greet_full WITH ?first AND ?last
OPEN
EMIT "Hello, " & ?first & " " & ?last
CLOSE
NEW OPERATION make_greeting WITH ?name
OPEN
STRING "Hello, " & ?name SET ?greeting
AFTER RETURN ?greeting
CLOSE
Operation Scope Is Isolated
A NEW OPERATION body runs in its own frame, separate from the script that calls it. Parameters and any variable set with SET inside the operation are local to that call — they do not exist outside it, and disappear once the operation ends. Only a RETURN value crosses that boundary.
NEW OPERATION set_local
OPEN
STRING "only visible in here" SET ?local
CLOSE
AFTER RUN set_local
AFTER EMIT ?local
(* Output: null — ?local was set inside set_local's own frame and never existed out here *)
To write a value from inside an operation out to the wider execution, use !GLOBAL('key') — a writable, execution-wide store, distinct from an ordinary ?var. See Header & Globals.
NEW OPERATION set_global
OPEN
SET !GLOBAL('shared') AS "visible everywhere"
CLOSE
AFTER RUN set_global
AFTER EMIT !GLOBAL('shared')
(* Output: visible everywhere — !GLOBAL is not frame-scoped *)
Typed Parameters
Operation parameters can carry a type and an optional modifier — AS STRING, AS NUMBER, AS BOOL, or AS DATE, followed by REQUIRED or DEFAULT value. This is purely additive — untyped parameters (WITH ?param) still work exactly as before. RUN stays positional either way; typing only adds validation at the declaration.
NEW OPERATION calculate_total WITH ?price AS NUMBER REQUIRED
OPEN
CALCULATE ?price * 1.15 SET ?total
AFTER RETURN ?total
CLOSE
AFTER RUN calculate_total WITH 100 SET ?result
AFTER EMIT ?result
NEW OPERATION apply_discount WITH ?price AS NUMBER REQUIRED
AND ?discount AS NUMBER DEFAULT 99
OPEN
CALCULATE ?price - ?discount SET ?final
AFTER RETURN ?final
CLOSE
AFTER NUMBER 50 SET ?myDiscount
AFTER RUN apply_discount WITH 500 AND 99 SET ?resultLiteral
AFTER EMIT ?resultLiteral
AFTER RUN apply_discount WITH 500 AND ?myDiscount SET ?resultVariable
AFTER EMIT ?resultVariable
NEW OPERATION greet_user WITH ?name AS STRING REQUIRED
OPEN
EMIT "Hello, " & ?name
CLOSE
AFTER RUN greet_user WITH "Alice"
RUN
RUN executes an operation. Arguments are passed with WITH val AND val positionally matching the parameters declared in NEW OPERATION. SET ?var captures the RETURN value — not the EMIT output.
NEW OPERATION greet
OPEN
EMIT "Hello World"
CLOSE
AFTER RUN greet
(* Output: "Hello World" — EMIT fires, no RETURN to capture *)
NEW OPERATION greet_user WITH ?name
OPEN
EMIT "Hello, " & ?name
CLOSE
AFTER RUN greet_user WITH "Alice"
(* Output: "Hello, Alice" *)
NEW OPERATION make_greeting WITH ?name
OPEN
STRING "Hello, " & ?name SET ?greeting
AFTER RETURN ?greeting
CLOSE
AFTER RUN make_greeting WITH "Alice" SET ?msg
AFTER EMIT ?msg
(* ?msg = "Hello, Alice" — RETURN captured, no EMIT in make_greeting so no separate output *)
NEW OPERATION calculate_tax WITH ?amount AND ?rate
OPEN
CALCULATE ?amount * ?rate / 100 SET ?tax
AFTER RETURN ?tax
CLOSE
AFTER NUMBER 100 SET ?price
AFTER NUMBER 15 SET ?vat
AFTER RUN calculate_tax WITH ?price AND ?vat SET ?tax
AFTER EMIT ?tax
(* Output: 15 *)
EMIT vs RETURN
This is the most important distinction in OcaltQL operations:
EMIT— outputs immediately to the response. Always fires when reached. Cannot be captured bySET.RETURN— passes a value back to the caller silently. No output. Captured bySETonRUN. If there is noSET, the value is discarded.
NEW OPERATION noisy_calc WITH ?a AND ?b
OPEN
CALCULATE ?a + ?b SET ?sum
AFTER EMIT ?sum
CLOSE
AFTER RUN noisy_calc WITH 5 AND 3
(* Output: 8 — EMIT fires *)
NEW OPERATION noisy_calc WITH ?a AND ?b
OPEN
CALCULATE ?a + ?b SET ?sum
AFTER EMIT ?sum
CLOSE
AFTER RUN noisy_calc WITH 5 AND 3 SET ?answer
(* Output: 8 — EMIT fires *)
(* ?answer is EMPTY — no RETURN, EMIT is not captured *)
NEW OPERATION silent
OPEN
RETURN "hello"
CLOSE
AFTER RUN silent
(* No output — RETURN is lost without SET *)
NEW OPERATION silent
OPEN
RETURN "hello"
CLOSE
AFTER RUN silent SET ?x
AFTER EMIT ?x
(* ?x = "hello" — RETURN captured, still no separate output from silent itself *)
NEW OPERATION smart WITH ?a AND ?b
OPEN
CALCULATE ?a + ?b SET ?sum
AFTER EMIT "Sum is: " & ?sum
AFTER RETURN ?sum
CLOSE
AFTER RUN smart WITH 5 AND 3
(* Output: "Sum is: 8" — EMIT fires, RETURN discarded *)
NEW OPERATION smart WITH ?a AND ?b
OPEN
CALCULATE ?a + ?b SET ?sum
AFTER EMIT "Sum is: " & ?sum
AFTER RETURN ?sum
CLOSE
AFTER RUN smart WITH 5 AND 3 SET ?answer
(* Output: "Sum is: 8" — EMIT still fires *)
(* ?answer = 8 — RETURN also captured *)
RETURN Ends Execution
RETURN terminates the operation at the point it is reached. Any statements after it are unreachable. With no value, RETURN exits the operation early without returning anything.
NEW OPERATION add WITH ?a AND ?b
OPEN
CALCULATE ?a + ?b SET ?result
AFTER RETURN ?result
AFTER EMIT "never reached"
CLOSE
AFTER RUN add WITH 5 AND 3 SET ?sum
AFTER EMIT ?sum
(* Output: 8 *)
NEW OPERATION early_end WITH ?a
OPEN
IF ?a IS EQUAL TO 0
OPEN
RETURN
(* Ends operation early, no value returned *)
CLOSE
AFTER EMIT "continuing"
CLOSE
AFTER RUN early_end WITH 0
(* No output — returned early *)
NEW OPERATION early_end WITH ?a
OPEN
IF ?a IS EQUAL TO 0
OPEN
RETURN
(* Ends operation early, no value returned *)
CLOSE
AFTER EMIT "continuing"
CLOSE
AFTER RUN early_end WITH 5
(* Output: "continuing" *)
RETURN at Top Level
RETURN at the top level of a script — outside any operation — ends the entire script execution at that point.
EMIT "step 1"
AFTER RETURN "final"
AFTER EMIT "never reached"
(* Script ends at RETURN — everything after is unreachable *)
A top-level RETURN value is also capturable when the script is loaded via INCLUDE SET.
Illustrative only — this is the content of /root/math.oql, referenced by the runnable example below.
CALCULATE 10 + 5 SET ?result
AFTER RETURN ?result
INCLUDE "/root/math.oql" SET ?sum
AFTER EMIT ?sum
(* Output: 15 — top-level RETURN from included script captured *)
INCLUDE "/root/math.oql"
(* No output, no capture — RETURN value is lost without SET *)
Discarding Results
SET is always optional. Calling a verb or operation without SET simply discards whatever would have been returned. This is valid in all cases.
CALCULATE 1 + 1
(* No output, no capture — valid, result discarded *)
EMIT "Hello World"
(* Output fires — no SET needed on EMIT *)
NEW OPERATION log_message WITH ?msg
OPEN
EMIT "LOG: " & ?msg
CLOSE
AFTER RUN log_message WITH "System started"
(* Side effect (EMIT inside the operation) executes; no RETURN to capture *)
EMIT for immediate output and RETURN for a capturable value — or either one alone.