7. Expressions
EBNF
expression = assignment_expression | arithmetic_expression | logical_expression | inscription | inspection | unary_expression .unary_expression = [ negation ] "(" expression ")" | [ negation ] simple_unary_expression .simple_unary_expression = object | list | custom_function_call | reference | chance_expression | literal | trope_fit | trope_fit_sugared | action_search | sifting .negation = "!" .An expression is a syntactic form that evaluates to a value.
Expressions appear throughout Viv—in conditions, effects, casting-pool directives, and many other contexts. This chapter describes each expression form.
Operator precedence
Section titled “Operator precedence”The following table lists operators from lowest to highest precedence. Operators within the same row share the same precedence level. To override the default grouping for a complex expression, parenthesize one or more of the component expressions.
| Precedence | Operators | Associativity |
|---|---|---|
| 1 (lowest) | =, +=, -=, *=, /=, append, remove | None |
| 2 | inscribe, inspect | None |
| 3 | ` | |
| 4 | && | Left |
| 5 | ==, !=, <, <=, >, >=, in, knows, caused, triggered, preceded | None |
| 6 | +, - | Left |
| 7 | *, / | Left |
| 8 (highest) | ! | Unary (prefix) |
Assignments, inscriptions, and inspections are tried before the precedence chain via ordered alternatives in the grammar. As such, they do not compose freely with other operators. For example, an expression like @a.mood + 5 inscribe @b yields a parse error.
As the table notes, relational operators are non-associative: A < B < C is a parse error. Use A < B && B < C instead.
Here are some examples showing how valid complex expressions are grouped according to operator precedence:
A + B * C→A + (B * C)A * B + C > D→((A * B) + C) > DA > B && C > D || E→((A > B) && (C > D)) || EA + B > C && !D || E == F→(((A + B) > C) && (!D)) || (E == F)
Parenthesized expressions
Section titled “Parenthesized expressions”Any expression may be wrapped in parentheses to override the default precedence grouping:
(@person.mood + @friend.mood) / 2Negation
Section titled “Negation”The negation operator ! inverts the truthiness of an expression:
!@person.dead!(@person.best_friend == @target)Literals
Section titled “Literals”The literal forms are valid expressions whose syntaxes are specified in their respective sections: booleans, null, numbers, strings, template strings, enums, lists, and objects.
References
Section titled “References”EBNF
reference = [ scratch_variable_sigil | local_variable_sigil ] ( entity_sigil | symbol_sigil ) identifier [ group_role_decorator ] [ eval_fail_safe_marker ] [ reference_path ] .A reference is the primary way to read data from the simulated storyworld.
It’s anchored in the name of a role or variable, and may include an optional reference path.
Bare references
Section titled “Bare references”A bare reference to an entity—meaning a bare entity name with no reference path—always evaluates to its entity ID, which is ultimately a string. (Note that as soon as any reference path is attached to such a reference, the entity ID is automatically dereferenced—i.e., you can write @person.friends in lieu of @person->friends.) This is called dehydration.
Meanwhile, a bare reference to a symbol evaluates to the bound symbol value itself.
Reference paths
Section titled “Reference paths”EBNF
reference_path = ( property_access | pointer_access | lookup_access )+ .Property access (.)
Section titled “Property access (.)”EBNF
property_access = "." property_name [ eval_fail_safe_marker ] .property_name = ( letter | digit | "_" )+ .eval_fail_safe_marker = "?" .A property access uses dot notation to evaluate the specified property of the evaluation of a given reference.
Note that, unless the fail-safe marker is used, the specified property MUST exist.
Here’s a few examples:
@person.name- Evaluates to the value stored in the
nameproperty of the entity cast as@person.
- Evaluates to the value stored in the
@person.personality.shy- Evaluates to the value stored in the
shyfield of thepersonalityproperty on the entity cast as@person.
- Evaluates to the value stored in the
_@wand.power- Evaluates to the value stored in the
powerproperty of the entity saved to the local variable_@wand.
- Evaluates to the value stored in the
Lookup access ([])
Section titled “Lookup access ([])”EBNF
lookup_access = "[" expression "]" [ eval_fail_safe_marker ] .eval_fail_safe_marker = "?" .A lookup access uses bracket notation to access by key (or index) a value in the evaluation of a given reference, where the key is an arbitrary expression.
Here are some important constraints (which can be suppressed via the fail-safe marker):
- If the property being accessed is an array, the key expression MUST evaluate to a non-negative integer and MUST fall in bounds (given the array length).
- If the property being accessed is an object, the key expression MUST evaluate to a string and MUST be defined in the object.
And here are some examples:
@person.friends[0]- Evaluates to the 0th element stored in the
friendsarray property of the entity cast as@person.
- Evaluates to the 0th element stored in the
@person.affinities[@other]- Evaluates to the value stored a) in the
affinitiesobject property of the entity cast as@person, b) under the key corresponding to the evaluation of@other. As noted above, a bare reference like@otherevaluates to an entity ID, which is ultimately a string (and thus a valid key).
- Evaluates to the value stored a) in the
Pointer access (->)
Section titled “Pointer access (->)”EBNF
pointer_access = "->" property_name [ eval_fail_safe_marker ] .property_name = ( letter | digit | "_" )+ .eval_fail_safe_marker = "?" .A pointer access uses arrow notation of the form <reference>-><property_name> to access a property on another entity.
The reference preceding the arrow evaluates to an entity ID, which is dereferenced, and then the specified property on that entity is accessed.
Here are some important constraints (which can be suppressed via the fail-safe marker, except where noted):
- A pointer access MUST NOT be the first segment in a reference path. While a syntax like
@person->bossmight seem required—because@personevaluates to an entity ID—Viv enables authors to directly access the properties of the anchor entity, as in@person.boss. To reduce confusion, Viv enforces that this pattern be used exclusively. Under the hood, this is supported by the interpreter hydrating the anchor entity ID prior to walking a reference path. This constraint cannot be suppressed by the fail-safe marker. - The reference preceding the arrow MUST evaluate to an entity ID.
- The specified property MUST exist on the dereferenced entity.
Finally, some examples:
@person.boss->boss- Evaluates to the person’s boss’s boss—that is, the value stored in the
bossproperty of the entity whose ID is stored in thebossproperty of the entity cast in the@personrole.
- Evaluates to the person’s boss’s boss—that is, the value stored in the
@item.location->address- Evaluates to the address of the location of the item: the value stored in the
addressproperty of the entity whose ID is stored in thelocationproperty of the entity cast in the@itemrole.
- Evaluates to the address of the location of the item: the value stored in the
Chaining
Section titled “Chaining”Path segments may be chained in any combination, with the components of the reference path being evaluated left to right, with each segment operating on the result of the previous one.
Here’s some examples of complex reference paths:
@person.friends[0]->name@person.kids[0]->best_friend->father->name.middle@person.spouse->spouse->spouse->spouseFail-safe marker
Section titled “Fail-safe marker”The fail-safe marker ? instructs the interpreter to treat a missing or null value as a soft failure rather than an error.
It may appear after a reference or after any segment of a reference path, including the final segment. If the value at the marked position is null or undefined, evaluation of the reference short-circuits and yields null instead of raising an error:
// If @person has a null 'partner' property, yields null instead of an error@person.partner?->name
// Fail-safe on the reference itself@person?.name
// Multiple fail-safe markers in a chain@person?.partner?->friends?[0]?The fail-safe marker also applies in casting-pool directives, where a null result causes the pool to be treated as empty rather than producing an error.
Assignments
Section titled “Assignments”EBNF
assignment = reference assignment_operator expression .assignment_operator = "=" | "+=" | "-=" | "*=" | "/=" | "append" | "remove" .An assignment expression modifies a value in the host application via the adapter. The left-hand side MUST be a reference. The available operators are:
| Operator | Meaning |
|---|---|
= | Set the value. |
+= | Add to the value. |
-= | Subtract from the value. |
*= | Multiply the value. |
/= | Divide the value. |
append | Append to a list value. |
remove | Remove from a list value. |
@person.mood = 50@person.mood += 10@person.friends append @new_friend@person.enemies remove @former_enemyAssignment expressions always evaluate to true.
Arithmetic expressions
Section titled “Arithmetic expressions”EBNF
arithmetic_expression = unary_expression arithmetic_operator expression .arithmetic_operator = "+" | "-" | "*" | "/" .An arithmetic expression applies a binary arithmetic operator to two operands:
@person.strength + 10@person.gold * 2(@buyer.budget + @seller.asking) / 2Logical expressions
Section titled “Logical expressions”EBNF
logical_expression = disjunction .disjunction = conjunction { "||" conjunction } .conjunction = relational_expression { "&&" relational_expression } .relational_expression = unary_expression relational_operator unary_expression | unary_expression .Logical expressions combine relational tests using the conjunction operator && (logical AND) and the disjunction operator || (logical OR). Standard short-circuit evaluation applies.
@person.mood > 50 && @person.boldness > 30@person.hungry || @person.tiredRelational operators
Section titled “Relational operators”EBNF
relational_operator = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" | "knows" | "caused" | "triggered" | "preceded" .A relational expression compares two operands using one of the following operators:
Standard comparison
Section titled “Standard comparison”| Operator | Meaning |
|---|---|
== | Equal to |
!= | Not equal to |
< | Less than |
<= | Less than or equal to |
> | Greater than |
>= | Greater than or equal to |
Special comparison
Section titled “Special comparison”The additional relational operators each merit a bit more detail.
The in operator tests whether the left operand is contained in the right operand (typically a list or collection):
@person in @group.membersThe knows operator tests whether a character (left operand) has a memory of a given action (right operand). It returns true if the character’s memories include the action, and false otherwise:
@person knows @betrayal_actioncaused
Section titled “caused”The caused operator tests whether an action (left operand) is a causal ancestor of another action (right operand)—that is, whether the left action directly or transitively led to the right action:
@insult caused @retaliationtriggered
Section titled “triggered”The triggered operator tests whether an action (left operand) is a direct cause of another action (right operand)—that is, whether the right action’s immediate causes include the left action:
@insult triggered @retaliationpreceded
Section titled “preceded”The preceded operator tests whether an action (left operand) occurred before another action (right operand). If both actions have the same timestamp, the left is deemed to have preceded the right if and only if the right is not a causal ancestor of the left:
@first_meeting preceded @betrayalAction-relation operand constraints
Section titled “Action-relation operand constraints”The caused, triggered, and preceded operators MUST receive single-action operands. A group action role reference (e.g., @setup*) is not permitted as an operand. To test a relation for each member of a group, use a loop:
loop @setup* as _@s: _@s preceded @climaxendChance expressions
Section titled “Chance expressions”EBNF
chance_expression = number "%" .A chance expression evaluates to true with the specified probability (expressed as a percentage) and false otherwise:
50% // true half the time10% // true 10% of the timeChance expressions are useful in conditions and effects to introduce randomness.
Custom function calls
Section titled “Custom function calls”EBNF
custom_function_call = "~" identifier "(" [ expression { "," expression } ] ")" [ "?" ] .A custom function call invokes a function exposed by the host application in its adapter. The call is prefixed with the ~ operator, followed by the function name, parenthesized arguments, and an optional fail-safe marker:
~getRandomInsult()~calculateDamage(@attacker.strength, @defender.armor)~findNearbyCharacters(@location)?Arguments are evaluated and dehydrated (entity data is converted to entity IDs) before being passed to the adapter function. If the ? marker is present and the function returns null or undefined, evaluation short-circuits gracefully rather than raising an error.
Trope fits
Section titled “Trope fits”EBNF
trope_fit = "fit" "trope" identifier ":" bindings .trope_fit_sugared = bindings_sugared "fits" "trope" identifier .A trope fit expression tests whether a trope matches for a given set of bindings. It evaluates to a boolean:
// Standard formfit trope rivalry: with: @hero: @person1 @villain: @person2
// Sugared form<@person1, @person2> fits trope rivalryAction searches
Section titled “Action searches”EBNF
action_search = action_search_header ":" action_search_body | action_search_bare_header ":" action_search_bare_body .action_search_header = "search" "query" identifier .action_search_bare_header = "search" .action_search_body = (* unordered, each field optional *) [ search_domain ] [ bindings ] .action_search_bare_body = search_domain .An action search expression executes a query over a search domain and returns the list of matching action instances. There are two forms:
Named query search
Section titled “Named query search”A named query search references a defined query by name, with optional bindings and a search domain:
search query recent-betrayals: over: chronicle with: @perpetrator: @villainBare search
Section titled “Bare search”A bare search specifies only a search domain, without referencing a query:
search: over: @personSearch domains
Section titled “Search domains”EBNF
search_domain = "over" ":" ( "inherit" | "chronicle" | expression ) .A search domain specifies the collection of action instances to search over. There are three forms:
| Domain | Meaning |
|---|---|
inherit | The search domain inherited from an enclosing context (e.g., a sifting pattern that is itself searching over a domain). |
chronicle | The global chronicle—the complete record of all actions performed in the simulation. |
| expression | An expression that evaluates to a collection of action instances, or a character reference whose memories will be searched. |
search query recent-acts: over: chronicle
search query personal-history: over: @person
search query subset: over: inheritSifting expressions
Section titled “Sifting expressions”EBNF
sifting = sifting_header sifting_body .sifting_header = "sift" "pattern" identifier ":" .sifting_body = (* unordered, each field optional *) [ search_domain ] [ bindings ] .A sifting expression invokes a sifting pattern over a search domain, with optional bindings. It returns the list of matches—each match being a set of role and action bindings that satisfy the pattern:
sift pattern betrayal-arc: over: chronicle with partial: @betrayer: @villainInscriptions
Section titled “Inscriptions”EBNF
inscription = unary_expression "inscribe" expression .An inscription expression records knowledge of an action onto an item. The left operand MUST evaluate to an item, and the right operand MUST evaluate to an action. After evaluation, the item carries a record of the action, which can later be revealed to a character via inspection:
@letter inscribe @secret_meetingInscription always evaluates to true.
Inspections
Section titled “Inspections”EBNF
inspection = unary_expression "inspect" expression .An inspection expression causes a character to learn about all actions inscribed on an item. The left operand MUST evaluate to a character, and the right operand MUST evaluate to an item. After evaluation, the character gains memories of all actions previously inscribed on the item:
@reader inspect @letterInspection always evaluates to true.