Statements
Table of Contents
Const Statements
Constants may be defined and assigned to a symbolic name with the syntax
const <id> = <expr>
where <id>
is an identifier and <expr>
is a constant expression
that must evaluate to a constant at compile time and not reference any
runtime state such as this
, e.g.,
echo '{r:5}{r:10}' | super -z -c "const PI=3.14159 2*PI*r" -
produces
One or more const
statements may appear only at the beginning of a scope
(i.e., the main scope at the start of a query,
the start of the body of a user-defined operator,
or a lateral scope
defined by an over
operator)
and binds the identifier to the value in the scope in which it appears in addition
to any contained scopes.
A const
statement cannot redefine an identifier that was previously defined in the same
scope but can override identifiers defined in ancestor scopes.
const
statements may appear intermixed with func
and type
statements.
Func Statements
User-defined functions may be created with the syntax
func <id> ( [<param> [, <param> ...]] ) : ( <expr> )
where <id>
and <param>
are identifiers and <expr>
is an
expression that may refer to parameters but not to runtime
state such as this
.
For example,
func add1(n): (n+1) add1(this)
true
Loading...
produces
One or more func
statements may appear at the beginning of a scope
(i.e., the main scope at the start of a query,
the start of the body of a user-defined operator,
or a lateral scope
defined by an over
operator)
and binds the identifier to the expression in the scope in which it appears in addition
to any contained scopes.
A func
statement cannot redefine an identifier that was previously defined in the same
scope but can override identifiers defined in ancestor scopes.
func
statements may appear intermixed with const
and type
statements.
Operator Statements
User-defined operators may be created with the syntax
op <id> ( [<param> [, <param> ...]] ) : (
<sequence>
)
where <id>
is the operator identifier, <param>
are the parameters for the
operator, and <sequence>
is the chain of operators (e.g., operator |> ...
)
where the operator does its work.
A user-defined operator can then be called with using the familiar call syntax
<id> ( [<expr> [, <expr> ...]] )
where <id>
is the identifier of the user-defined operator and <expr>
is a list
of expressions matching the number of <param>
s defined in
the operator’s signature.
One or more op
statements may appear only at the beginning of a scope
(i.e., the main scope at the start of a query,
the start of the body of a user-defined operator,
or a lateral scope
defined by an over
operator)
and binds the identifier to the value in the scope in which it appears in addition
to any contained scopes.
Sequence this
Value
The this
value of a user-defined operator’s sequence is provided by the
calling sequence.
For instance the program in myop.spq
op myop(): (
yield this
)
myop()
run via
Error:echo {x:1} | super -z -I myop.spq -
produces
Arguments
The arguments to a user-defined operator must be either constant values (e.g., a literal or reference to a defined constant), or a reference to a path in the data stream (e.g., a field reference). Any other expression will result in a compile-time error.
Because both constant values and path references evaluate in
expression contexts, a <param>
may often be used inside of
a user-defined operator without regard to the argument’s origin. For instance,
with the program params.spq
op AddMessage(field_for_message, msg): (
field_for_message:=msg
)
the msg
parameter may be used flexibly
AddMessage(message, "hello")
{greeting:
"hi"}
Loading...
to produce the respective outputs
However, you may find it beneficial to use descriptive names for parameters where only a certain category of argument is expected. For instance, having explicitly mentioned “field” in the name of our first parameter’s name may help us avoid making mistakes when passing arguments, such as
AddMessage("message", "hello")
{greeting:
"hi"}
Loading...
which produces
A constant value must be used to pass a parameter that will be referenced as
the data source of a from
operator. For example, we
quote the pool name in our program count-pool.spq
op CountPool(pool_name): (
from eval(pool_name) |> count()
)
CountPool("example")
so that when we prepare and query the pool via
{greeting: "hello"}
{greeting:
"hello"}
Loading...
it produces the output
Nested Calls
User-defined operators can make calls to other user-defined operators that
are declared within the same scope or in a parent’s scope. To illustrate, a program in nested.spq
op add1(x): (
x := x + 1
)
op add2(x): (
add1(x) |> add1(x)
)
op add4(x): (
add2(x) |> add2(x)
)
add4(a.b)
run via
Error:echo '{a:{b:1}}' | super -z -I nested.spq -
produces
One caveat with nested calls is that calls to other user-defined operators must not produce a cycle, i.e., recursive and mutually recursive operators are not allowed and will produce an error.
Type Statements
Named types may be created with the syntax
type <id> = <type>
where <id>
is an identifier and <type>
is a type.
This creates a new type with the given name in the type system, e.g.,
type port=uint16 cast(this, <port>)
true
Loading...
produces
One or more type
statements may appear at the beginning of a scope
(i.e., the main scope at the start of a query,
the start of the body of a user-defined operator,
or a lateral scope
defined by an over
operator)
and binds the identifier to the type in the scope in which it appears in addition
to any contained scopes.
A type
statement cannot redefine an identifier that was previously defined in the same
scope but can override identifiers defined in ancestor scopes.
type
statements may appear intermixed with const
and func
statements.