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.

No Parameters
NEW OPERATION greet
OPEN
  EMIT "Hello World"
CLOSE
With One Parameter
NEW OPERATION greet_user WITH ?name
OPEN
  EMIT "Hello, " & ?name
CLOSE
With Multiple Parameters
NEW OPERATION greet_full WITH ?first AND ?last
OPEN
  EMIT "Hello, " & ?first & " " & ?last
CLOSE
With RETURN
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.

Locals Do Not Escape the Operation
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.

Escaping the Frame with !GLOBAL
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.

REQUIRED Parameter
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
DEFAULT Parameter — Called with a Literal or a Variable
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
REQUIRED with a Different Type
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.

RUN Without SET — No Parameters
NEW OPERATION greet
OPEN
  EMIT "Hello World"
CLOSE
AFTER RUN greet
(* Output: "Hello World" — EMIT fires, no RETURN to capture *)
RUN Without SET — With Argument
NEW OPERATION greet_user WITH ?name
OPEN
  EMIT "Hello, " & ?name
CLOSE
AFTER RUN greet_user WITH "Alice"
(* Output: "Hello, Alice" *)
RUN With SET — Captures RETURN
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 *)
RUN with Mixed Literals and Variables
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 Only — Fires Regardless of SET
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 *)
EMIT Only — Not Capturable by SET
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 *)
RETURN Only — Lost Without SET
NEW OPERATION silent
OPEN
  RETURN "hello"
CLOSE
AFTER RUN silent
(* No output — RETURN is lost without SET *)
RETURN Only — Captured with 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 *)
Both EMIT and RETURN — Without SET
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 *)
Both EMIT and RETURN — With SET
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.

Unreachable Code After RETURN
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 *)
Early RETURN — No Value, Condition True
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 *)
Early RETURN — No Value, Condition False
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.

Top-Level RETURN
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.

Capturing Top-Level RETURN via INCLUDE — The Included File

Illustrative only — this is the content of /root/math.oql, referenced by the runnable example below.

CALCULATE 10 + 5 SET ?result
AFTER RETURN ?result
Capturing Top-Level RETURN via INCLUDE — With SET
INCLUDE "/root/math.oql" SET ?sum
AFTER EMIT ?sum
(* Output: 15 — top-level RETURN from included script captured *)
Capturing Top-Level RETURN via INCLUDE — Without SET
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.

Discarding a CALCULATE Result
CALCULATE 1 + 1
(* No output, no capture — valid, result discarded *)
EMIT Needs No SET
EMIT "Hello World"
(* Output fires — no SET needed on EMIT *)
Discarding a RUN Result
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 is to the response what RETURN is to the caller. They are not interchangeable. An operation can use both — EMIT for immediate output and RETURN for a capturable value — or either one alone.