-
Notifications
You must be signed in to change notification settings - Fork 19
Expressions
Expressions in PawnPlus enable runtime manipulation with dynamically typed objects. An expression tree can be constructed from individual operations and executed to obtain the value of the expression.
An expression is a recursive tree-like structure consisting of various operations and operands. Similarly to iterators, it is an abstract data type and its behaviour depends on the manner it was created in. Expressions are immutable (with one exception), so they can't be modified after their creation. They are also linked by reference with each other, so constructing an expression will not duplicate its arguments.
There are many types of differently behaving expressions. Please see here for detailed listing.
An expression can be created from an initial value or another expression. There are nullary expressions (not depending on any operand), unary expressions (producing a value based on a single operand), binary expressions (two operands), ternary expressions (three operands), and variadic expressions. PawnPlus also comes with a powerful expression parser supporting all types of expressions and special syntax.
new Expression:arg1 = expr_const(10); // creates a new expression with a constant value of 10
new Expression:arg2 = expr_const(5); // creates a new expression with a constant value of 5
new Expression:add = expr_add(arg1, arg2); // creates a new expression that sums the two expressions
print_s(str_val(expr_get_var(add))); // computes the result of the expression and prints it
Since an expression tree cannot be modified once it is created, combining expressions will not duplicate their instances. Neither expr_delete
nor garbage collection can delete an expression when it is used by other expressions.
new Expression:arg = expr_const(10);
new Expression:expr = expr_neg(arg); // negation expression
expr_delete(arg); // the expression cannot be truly deleted if it is used
assert !expr_valid(arg); // but the expression reference is not valid anymore
assert expr_get(expr) == -10;
Expressions can be used to represent abstract or complex operations via a single object, in which case a parameter must be provided to an expression. For example, there can be a generic function that operates on list elements and the operation is provided externally via an expression:
stock DoSomething(List:l, Expression:e)
{
for_list(it : l)
{
new Variant:v = iter_get_var(it);
v = expr_get_var(expr_bind(e, expr_const_var(v)));
iter_set_var(it, v);
}
}
Calling DoSomething
with expr_neg(expr_arg(0))
will negate all values in a list (no matter their types). expr_arg(0)
represents an external argument given to the expression by code that executes it. Here, expr_bind
is used to bind an expression to a constant value (obtained from the iterator). Without binding, the value of expr_arg(0)
would be unresolved. The result of the expression is stored back in the list.
expr_bind
only fills the initial sublist of arguments used by the expression with the given arguments. The rest is shifted so it can be filled by subsequent calls to expr_bind
. For example, expr_bind(expr_add(expr_arg(0), expr_arg(1)), expr_const(10))
binds the value of expr_arg(0)
to 10
, but the second argument is left unbound. Next call to expr_bind
starts at the second argument.
When an expression is executed, it performs its operation using the operands that were provided to the expression (if any). The operands receive a message corresponding to the type of the executed expression. For example, the call expression, when executed, sends the "evaluate" message to each of the arguments, and finally the "call" message to the function. The function expression then performs the actual call.
This behaviour indicates that expressions do not necessarily behave in a consistent way in all contexts. For example, the comma expression can return values from both operands at the same time, the last value, or no value at all, depending on the usage. When used as a function argument, the comma expression returns all values. When used as a single operand of an operator (+
etc.) only the last value is used. When used in expr_void
, no values will be produced.
For easier debugging and representing more complex expressions in a more readable syntax, expr_parse
can be used to convert a string to an expression tree it represents. The syntax is similar to Pawn, but there are some differences:
- There are no statements; only expressions are supported.
- Newline is not a special character.
- Comments are not allowed.
-
,
and;
are semantically equivalent, but;
doesn't have to be followed by an expression. -
rankof
,addressof
, andnameof
are introduced to assist with manipulating dynamic values and passing them to functions. -
tagof
,sizeof
, andrankof
may evaluate their argument if the result cannot be determined otherwise. - Strings and characters are tagged with
char:
. - Expressions may return multiple values in some contexts. Empty expression (
()
) is syntactically valid and results inexpr_empty
. - Function calls, array construction, and indexing accept a multi-valued expression. Commas or parentheses do not affect the number of values.
-
[a,b]
is the same as[a][b]
. -
$argn
can be used to representexpr_arg(n)
. -
$argsa_b
can be used to representexpr_arg_pack(a, b)
. -
$env
representsexpr_env()
. -
try[main]catch[fallback]
corresponds toexpr_try(main, fallback)
. -
(list)select[func]
corresponds toexpr_select(list, func)
. -
(list)where[func]
corresponds toexpr_where(list, func)
. -
void(expr)
corresponds toexpr_void(expr)
. -
[expr](args)
corresponds toexpr_bind(expr, args)
. -
<expr>
corresponds toexpr_quote(expr)
, unary^
results inexpr_dequote
. - Operations don't follow standard Pawn rules; instead, variant operations semantics are in effect.
- Single cells can be used for reference arguments, but they must be wrapped in
addressof
. - Unary operators
*
,&
,@
corresponding toexpr_extract
,expr_variant
, andexpr_string
. - Unary operator
+
corresponds toexpr_nested
. - Binary operator
..
corresponds toexpr_range
. - There is no
>>>
operator, and the bit operators only work on objects with a weak tag. - All combinations of syntax allowed by the grammar are syntactically valid; only at execution it is decided whether they are allowed.
- Operations may be performed on expressions instead of on values when executed in some cases, i.e. calling a conditional (
?:
) expression calls the expression that is chosen when the condition is evaluated. - Calling a single
Expression:
value calls the expression with the provided arguments. - Indexing a single
Expression:
value binds it to the arguments.
If a symbol (according to the Pawn rules) is encountered, it is first matched against the known keywords if it represents a special operation. If the symbol is not a keyword (or doesn't produce valid syntax that way), the internal table of literal values and intrinsic functions is searched for the symbol.
Name | Type | Meaning | Example |
---|---|---|---|
null |
value | The null variant. | |
true |
value | true |
|
false |
value | false |
|
cellmin |
value | -2147483648 | |
cellmax |
value | 2147483647 | |
cellbits |
value | 32 | |
concat |
function | Produces a string by concatenating all arguments. | concat("a", "b", "c") |
eval |
function | Parses the expression specified by the first argument and executes it on the remaining arguments. | eval("$arg0 + $arg1", 5, 6) |
unpack |
function | Returns all elements of an array sequentially. | unpack({1, 2, 3}) |
cast |
function | Changes tags of all arguments to the tag specified by the first argument. | cast(tagof(Float:), 1, 2, 3) |
When an unknown symbol is encountered, the current scope where the call to expr_parse
happens is searched first, similarly to debug_symbol
. If no symbol is found, the public function and public variable tables are searched for the name, and lastly the registered native functions in the AMX instance. If the symbol is still not found, the expression resolves to expr_global
indexing the expression environment.
The parser can be configured to disallow specific pieces of syntax. These are the corresponding options:
Option | Meaning | Example |
---|---|---|
parser_bare |
No other pieces of syntax are allowed. | |
parser_allow_unknown_symbols |
Accessing unresolved (i.e. global) symbols. | |
parser_allow_debug_symbols |
Accessing debug symbols. | |
parser_allow_natives |
Accessing native functions. | |
parser_allow_publics |
Accessing public functions. | |
parser_allow_pubvars |
Accessing public variables. | |
parser_allow_constructions |
Creating new instances. |
&1 , @"str" , <1>
|
parser_allow_ranges |
Creating ranges. | 1..1000 |
parser_allow_arrays |
Creating arrays. | {1, 2, 3} |
parser_allow_queries |
Performing queries. | (1, 2, 3) select [$arg0, $arg0] |
parser_allow_arguments |
Accessing external arguments. | $arg0 |
parser_allow_environment |
Accessing the global environment. | $env["index"] |
parser_allow_casts |
Performing tag casts. | Float:0 |
parser_allow_literals |
Accessing literal expressions. | null |
parser_allow_intrinsics |
Accessing intrinsic functions. | concat("a", "b") |
parser_allow_strings |
Producing strings or characters. | "str" |
parser_all |
The whole syntax is allowed. |
The parser flags are also passed to invoked intrinsic functions, i.e. calling eval
on a string will parse it with the same set of flags as specified for the parser of the original string expression.
Expressions are garbage-collected like strings, variants, and iterators, since most of the references are used as temporary arguments to other expression constructors. In case an expression should remain existing for a longer time, expr_acquire
and expr_release
should be used. See this for more information about garbage collection.
Expression objects that are kept alive by other expressions will be removed from the pool of active expressions when they are "deleted", but the actual objects will survive until they are no longer used by other expressions. An expression in this state is no longer "valid" and cannot be revived.