SymOntoClay is game AI experimental open source engine.


  Warning logo Purely experimental and very unstable project developed by only one person
Please read the page before starting

Language Specification

Contens

Introduction

back to top

This is manual for SymOntoClay DSL.

This document contains all implemented grammar. You can use It.

SymOntoClay DSL is in development now. So new parts will be added here right after implementation and internal testing. I will try to do this as soon as possible.

Notation

back to top

The syntax is specified using Extended Backus-Naur Form (EBNF).

Source code representation

back to top

There are two views of source code file: as set of characters and as set of code entities.

Symbols

back to top

Characters

back to top

Source code is Unicode text encoded in UTF-8.

Newline = /* the Unicode code point U+000A */ .
UnicodeChar = /* an arbitrary Unicode code point except Newline */ .

Letters and digits

back to top

The underscore character _ (U+005F) is considered a letter.

Letter = "A" ... "Z" | "a" ... "z" | "_" .
DecimalDigit = "0" … "9" .

Kind of source code files

back to top

There are different kinds of source code files which can contain different code entities.

World source code file

back to top

The file contains World declaration. There can be only one World declaration in the file. Also this file can contain other content which is allowed in Class source code file.

App source code file

back to top

The file contains App declaration. There can be only one App declaration in the file. Also this file can contain other content which is allowed in Class source code file.

Lib source code file

back to top

The file contains Lib declaration. There can be only one Lib declaration in the file. Also this file can contain other content which is allowed in Class source code file.

Class source code file

back to top

The file contains all possible code entities, except World and App declarations. There exist special source code files for these code entities. But content of Class source code file can be included into World and App source code files.

Lexical elements

back to top

Comments

back to top

SymOntoClay supports both single and multiline comments. It is a programmer-readable explanation or annotation of source code like in many other programming languages. Also It helps to cut temporarily unused code fragments.

//@r = @b = 1;
MultiLineComment = "/*" [{ UnicodeChar }] "*/" .
/*@r = @b = 1;
@bx >> @>log;*/

You can see examples here.

Tokens

back to top

Tokens form the vocabulary of the SymOntoClay DSL. There are four classes: identifiers, keywords, operators and punctuation, and literals.

Identifiers

back to top

Identifiers name code entities.

Since SymOntoClay has many kinds of code entities, I have decided It will be better to recognize kind of code entity by identifier. So SymOntoClay has many kinds of identifiers with different prefixes. Each of these identifiers has Its own meaning in code.

Concept identifier

back to top

Names a concept (or type).

ConceptIdentifier = (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
a
_a
a2
aB
ab_1
Great_Britain
`Great Britain`
Predefined concept identifiers

back to top

Some concepts are predefined and reperesent code entities.

Instance identifier

back to top

Names an instance of concept.

InstanceIdentifier = "#" (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
#123

Fact identifier

back to top

Names a rule or fact.

FactIdentifier = "#^" (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
#^123

Variable identifier

back to top

Names a variable.

VarIdentifier = "@" (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
@a

System variable identifier

back to top

Names a system variable

SysVarIdentifier = "@@" (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
@@host

Logic variable identifier

back to top

Names special variable for Logic rule or Fact.

LogicVarIdentifier = "$" (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
$x

Channel identifier

back to top

Names Channel.

ChannelIdentifier = "@>" (( Letter { Letter | DecimalDigit } ) | ( "`" { UnicodeChar } "`")) .
@>log

Keywords

back to top

Some words is used as keywords in syntax. But the words can be used as identifiers in other places.

actiondowninsertselect
actionsdurationisstate
addeachleavestates
aliaseliflibsynonym
appelselinvarterms
asensurenewtrue
awaitenternottry
breakerrornulluse
cancelexeconvar
canceledfactoncewait
catchfalseopweak
classforprioritywhere
completefunrangewhile
completedidlerejectwith
constraintsifrelworld
continueimportrelation
ctorinhrepeat
defaultinheritancereturn

Operators and punctuation

back to top

The following character sequences represent operators and punctuation:

-!{:<=
->?}=
,(*=>
;)/>
:[&>=
:}]+>>
:>{<|

Integer literal

back to top

An integer literal is a sequence of digits representing an integer constant. Now integer literal has only decimal base.

42

Floating-point literal

back to top

A floating-point literal is a decimal representation of a floating-point constant.

A floating-point literal consists of an integer part (decimal digits), a decimal point and a fractional part (decimal digits). Now floating-point literal has only decimal base.

36.6

String literal

back to top

A string literal represents a string constant obtained from concatenating a sequence of characters.

StringLiteral = (""" UnicodeChar [{ UnicodeChar }] """) | ("'" UnicodeChar [{ UnicodeChar }] "'") .
"abc"
'abc'

You can see examples here.

NULL literal

back to top

The NULL literal represents special value which means unknown or undefined data.

NullLiteral = "null" .
@a = null;
{: age(#Tom, null) :}

Objects declaration

back to top

Everything is an object. The classical type-instance relationship is using, but there is not strict and fundamental distinction between the two.

Some objects have a special meaning. For example Class represents a general concept which can have many instances.

Some objects have special engine support to make writing code easier and faster, or as entry point locators.

Object literal

back to top

Instantiates an object or collection and performs member assignments in a single statement.

    var @a = {
};
    var @a = {
    @b = 1;
};
`some fun`(
{
    @b = 1;
}
)

You can see examples here.

Class

back to top

Represents a general concept which can have many instances.

In general, It is similar to the concept 'class' in other OOP languages.

class exampleClass
{
}
class exampleClass is human, [0.1] animal
{
}

You can see examples here.

App

back to top

It is root object and entry point of App.

app PeaceKeeper
{
    on Enter =>
    {
    }
}
app PeaceKeeper is human
{
    on Enter =>
    {
    }
}

You can see examples here.

World

back to top

It is root object of World.

world `Lost town`
{
}

Lib

back to top

It is root object of Lib.

Target library can be imported by "import" statement.

lib lib2
{
}
lib lib1
{
    import lib2;
}

Conditional entity

back to top

Conditional entity represents an entity from the world around, which is described by condition. During code execution the condition entity will be resolved to concrete entity from the world around, which is fit to the condition.

#@(barrel);
#@{: barrel($_) :}
#@(hold(I, this) & weapon)
#@(hold(I, this) & (weapon & dog) )
#@(hold(#a, this) & (weapon & dog) )
#@(color = black)
#@{: >: { barrel($_) } :}

There exist two kind of expression for conditional entity:

The short expression is more graceful for describing conditional entity. But fact can describe conditional entity more precisely and with contractions which are absent in short expression now.

During resolving short expression is converted to fact. The following expressions describe the same condition:

#@(barrel);
#@{: barrel($_) :}
#@{: >: { barrel($_) } :}

Concept this is converted to $_:

#@(hold(I, this) & weapon)
#@{: hold(I, $_) & weapon($_) :}

Example of using:

@@host.take(#@(gun));

Conditional entity constraints

back to top

Constraints allow to resolve conditional entity to entity on game level that is the fittest to the situation.

Nearest

back to top

The conditional entity should be resolved to nearest game object on game level.

@@host.go(#@(barrel & nearest));
Random

back to top

The conditional entity should be resolved to random game object on game level.

@@host.go(#@(barrel & random));

Waypoint

back to top

Represents a point in space, abstracting from the game engine's coordinate system.

Coordinates of points are presented in a more convenient way, similar to that is used by people in everyday life.

Now It is supported the coordinate system what is similar to Polar coordinate system. The radial coordinate is the distance from center of NPC to target. And the angular coordinate (in degrees only) starts from the sagittal plane of NPC in front of the NPC.

SymOntoClay engine provides mapping to the game engine's coordinate system.

#@[25, 30]
#@[25]

You can see examples here.

Linguistic variable

back to top

Provides non-numeric fuzzy values.

You can read more details in here.

LinguisticVariableDomainDecl = "range" ( "(" | "[" ) ( "-∞" | "*" | NumberExpr ) "," ( "+∞" | "*" | NumberExpr ) ( ")" | "]" ) .
linvar age for range (0, 150]
{
    constraints:
        for inheritance;
        for relation age;
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}
linvar logic for range [0, 1]
{
    constraints:
        for inheritance;
    terms:
        minimal = L(0, 0.1);
        low = Trapezoid(0, 0.05, 0.3, 0.45);
        middle = Trapezoid(0.3, 0.4, 0.6, 0.7);
        high = Trapezoid(0.55, 0.7, 0.95, 1);
        maximal = S(0.9, 1);
}

You can see examples here.

Terms

back to top

Terms are non-numeric fuzzy values.

Term combines its name and membership function. See LinguisticVariableTermsItem in SymOntoClay's garammar description.

SymOntoClay still doesn't contain means for full defining function's body. The means will be added in the future, but now you can use Predefined membership functions instead of manual defining.

Each term must be terminated by ";".

linvar logic
{
    terms:
        minimal = L(0, 0.1);
        low = Trapezoid(0, 0.05, 0.3, 0.45);
        middle = Trapezoid(0.3, 0.4, 0.6, 0.7);
        high = Trapezoid(0.55, 0.7, 0.95, 1);
        maximal = S(0.9, 1);
}

If Linguistic variable consists only of terms, the terms can be put directly into body of the Linguistic variable.

linvar logic
{
    minimal = L(0, 0.1);
    low = Trapezoid(0, 0.05, 0.3, 0.45);
    middle = Trapezoid(0.3, 0.4, 0.6, 0.7);
    high = Trapezoid(0.55, 0.7, 0.95, 1);
    maximal = S(0.9, 1);
}

A term can be used in FuzzyExpr as NonNumericFuzzyExpr. It looks as using usual concept instead of numeric value.

app PeaceKeeper is [middle] exampleClass
{
}
{: age(#Tom, `teenager`) :}

You can see examples here and here.

Terms can be combined with fuzzy operators.

app PeaceKeeper is [very middle] exampleClass
{
}
{: age(#Tom, very `teenager`) :}

You can see examples here and here.

Predefined membership functions

back to top

SymOntoClay still doesn't contain means for full defining function's body. The means will be added in the future, but now you can use Predefined membership functions instead of manual defining.

There exist few predefined standard membership functions in the SymOntoClay. I am going to add more functions in the future.

L Function

back to top

F ( x , a , b ) = { 1  if  x a b - x b - a  if  a < x b 0  if  x > b

L Function graph
linvar age
{
    `teenager` = L(5, 10);
}

You can see examples here.

Trapezoid Function

back to top

F ( x , a , b , c , d ) = { 0  if  x a  or  x d x - a b - a  if  x a b 1  if  x b c d - x d - c  if  x c d

Trapezoid Function graph
linvar age
{
    `teenager` = Trapezoid(10, 12, 17, 20);
}

You can see examples here.

S Function

back to top

F ( x , a , m , b ) = { 0  if  x a 2 x - a b - a 2  if  x a m 1 - 2 x - a b - a 2  if  x m b 1  if  x b

The count of params can be reduced to F ( x , a , b ) . In this case m = a + b 2 .

S Function graph
linvar age
{
    `teenager` = S(12, 17, 22);
}
linvar age
{
    `teenager` = S(12, 22);
}

You can see examples here.

Predefined operators

back to top

Fuzzy logic allows us to add special logic operators to usual logic operators and, or, not. The additional unary operators modify result of membership function.

SymOntoClay still doesn't contain means for full defining function's body. The means will be added in the future, but now you can use Predefined operators instead of manual defining.

Operators can be used only in combination with terms.

app PeaceKeeper is [very middle] exampleClass
{
}
{: age(#Tom, very `teenager`) :}

You can see examples here and here.

Very

back to top

F ( x ) = x 2

app PeaceKeeper is [very middle] exampleClass
{
}
{: age(#Tom, very `teenager`) :}

You can see examples here and here.

Domain of a linguistic variable

back to top

Defines base set for linguistic variable. Thems are mapped to the set by membership functions.

Each linguistic variable has its own domain.

If linguistic variable doesn't have explicitly defined domain, the linguistic variable has implicit domain from -∞ to +∞.

You can define domain of linguistic variable explicitly by keyword range. See LinguisticVariableDomainDecl in SymOntoClay's garammar description.

You can see examples here.

Defining custom domain (range) for linguistic variable will be helpful for using terms of different linguistic variables with the same mames but different membership functions. For example, the term near can be different for distances on a table, in a room and in Space. Domain (range) allows us using term near as pure name without namespaces or something else.

This range is from negative infinity to positive infinity (Unbounded at both ends):

linvar age for range (-∞, +∞)
{
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

This range is also from negative infinity to positive infinity:

linvar age for range (*, *)
{
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

This range is open.

linvar age for range (0, 150)
{
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

This range is closed.

linvar age for range [0, 150]
{
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

This range is left-open and right-closed.

linvar age for range (0, 150]
{
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

This range is left-closed and right-open.

linvar age for range [0, 150)
{
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

Constraints

back to top

Defines conditions when the linguistic variable should be used.

Now you can define:

  • Using terms of linguistic variable for operations with inheritance.
  • Using terms of linguistic variable only in concrete logic relations.
linvar age
{
    constraints:
        for inheritance;
        for relation age;
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}
linvar age
{
    constraints:
        for inh;
        for rel age;
    terms:
        `teenager` = Trapezoid(10, 12, 17, 20);
}

You can see examples here.

Constraint for inheritance also generates constraint for relation "is" automatically.

Constraint for for relation "is" also generates constraint for inheritance automatically.

Defuzzification

back to top

Defuzzification transforms term of lingiustic variables (non numeric value) into numeric value.

You can read more details in here.

There are many different methods of defuzzification. SymOntoClay uses Centre of Gravity (CoG) for defuzzification.

Centre of Gravity (CoG)

back to top

Centre of Gravity (CoG) calculates by formula:

y = max min x μ x dx max min μ x dx

If term of linguistic variable is used with fuzzy operators, μ x must be combination of membership function and opertors.

Function

back to top

Function is first-class object.

Without parameters:

fun a() =>
{
    '`a` has been called!' >> @>log;
}
fun a()
{
    '`a` has been called!' >> @>log;
}

Minimal parameters declaration:

fun a(@param_1)
{
    '`a` has been called!' >> @>log;
    @param_1 >> @>log;
}
fun a(@param_1, @param_2)
{
    '`a` (2) has been called!' >> @>log;
    @param_1 >> @>log;
    @param_2 >> @>log;
}

Type of parameter:

Parameter's type uses in dispatching: what function should be used in this case.

It allows us to have many functions with the same name and count of parameters but with different types of parameters.

fun a(@param_1: string)
{
    '`a` (string) has been called!' >> @>log;
}

Also the type of parameter can be tuple of types.

fun a(@param_1: (number | string))
{
    '`a` (number | string) has been called!' >> @>log;
    @param_1 >> @>log;
}
fun a(@param_1: number | string)
{
    '`a` (number | string) has been called!' >> @>log;
    @param_1 >> @>log;
}

You can combine different ways.

fun a(@param_1: string, @param_2: number | string, @param_3: (number | string), @param_4)
{
    '`a` (string) has been called!' >> @>log;
}

Default value of parameter:

A value can be associated with parameter. The value is used when parameter is missed in function call.

fun a(@param_1 = 12)
{
    '`a` has been called!' >> @>log;
    @param_1 >> @>log;
}
fun a(@param_1: string = 'Hi!')
{
    '`a` (string) has been called!' >> @>log;
}
fun a(@param_1: (number | string) = 42)
{
    '`a` (number | string) has been called!' >> @>log;
    @param_1 >> @>log;
}
fun a(@param_1: number | string = 42)
{
    '`a` (number | string) has been called!' >> @>log;
    @param_1 >> @>log;
}
fun a(@param_1: string, @param_2: number | string = 'Hi!', @param_3: (number | string) = 10, @param_4)
{
    '`a` (string) has been called!' >> @>log;
}

You can see examples here.

Action

back to top

Action it is an object which can be call as function or method.

It is similar to C++ functor.

Action is automatically put into engine of method detection and can be called like usual method or function. Action doesn't require any manual initialization for call.

During Action call the instance of the Action is implicitly created. So all calls of Action are isolated from each other.

Entry point of action is function call overloading. One action can have many entry points with different parameters. Leaving entry point stops the action. Uncaught on action level Error in entry point crash the action can be caught on code level which called the action.

The main goal of Actions to provide triggers in context of the doing. Action's trigger starts when the Action is called. The triggers can check some condition and make additional doing related to main doing of the Action. Uncaught error in handlers of child triggers or functions which have been called from the handlers performs nothing on code level which called the action. When the action has been done then their triggers stop their activity and handlers which have been started previously.

Any time the action can be terminated from handlers of child triggers or functions which have been called from the action's entry points or handlers of child triggers.

The statement complete action terminates action by the reason of achieving goal of the action.

The statement break action breakes the action. It is similar to Error statement. But "Error statement" can break Action only in action's entry points or functions which have been called from the action's entry points. The statement break action can break Action in any place of the Action.

action Go
{
    op () =>
    {
        @@host.`go`(to: #@[200]);
    }
    on {: see(I, barrel) :} =>
    {
        complete action;
    }
}
action Go is `some base action`
{
    op () =>
    {
        @@host.`go`(to: #@[200]);
    }
    on {: see(I, barrel) :} =>
    {
        complete action;
    }
}
action `my Go 1` alias `Go`, Run
{
    op () =>
    {
        @@host.`go`(to: #@[200]);
    }
    on {: see(I, barrel) :} =>
    {
        complete action;
    }
}
action `my Go 1` alias `Go`, Run is `some base action`
{
    op () =>
    {
        @@host.`go`(to: #@[200]);
    }
    on {: see(I, barrel) :} =>
    {
        complete action;
    }
}

Sometime entry points of action mustn't do anything. In this case the action is only context for Its child triggers. Statement await provides waiting in entry point of the Action for termination the Action by child triggers.

action Go
{
    op () =>
    {
        await;
    }
    on {: see(I, barrel) :} =>
    {
        complete action;
    }
}

You can see examples here.

State

back to top

State describes behavior which can be turn on or turn off fast.

State is implementation of state pattern. It is close to the concept of finite-state machines.

States and App can be overviewed as partial object with switchable parts that are states. States have full access to App's members (even for private). State's members are isolated so do not visible for App and another states.

One State can be automatically activated during starting App by "set state" directive.

Target State can be activated any time from any place of code by "Set as state" statement.

State can can be automatically activated by conditions using Activating conditions.

State can can be automatically deactivated by conditions using Deactivating conditions.

One of States can be set as default state by "Set default state" directive on App declaration or anytime in any place of code by "Set as default state" statement. The default state will be activated if current state is closed without activation new state explicitly by "complete state" or "break state" statements.

state Attacking
{
}
state Attacking
{
    enter on:
    {: see(I, enemy) :}
    leave on:
    {: see(I, barrel) :}
    {: male(#Tom) :}
    {: parent(#Piter, #Tom) :}
    {: {son($x, $y)} -> { male($x) & parent($y, $x)} :}
    on Enter
    {
        'Begin Attacking Enter' >> @>log;
        'End Attacking Enter' >> @>log;
    }
}

You can see examples here.

Mutually exclusive states

back to top

Declares set of states what can not be active simultaneously. If one of states of the set is activated another active state of the set will be deactivated automatically.

There can be many sets of mutually exclusive states in the code. And one state can be in many sets of mutually exclusive states.

states { Idling, Patrolling, Attacking, Fleeing }
app PeaceKeeper
{
}
states { Idling, Attacking }
state Idling
{
}
state Attacking
{
}

You can see examples here.

Set default state

back to top

One of States can be set as default state.

The default state will be activated if current state is closed without activation new state explicitly.

There can be many directives on one object and on many levels of inheritance. There will be used only one directive which has maximal rank of inheritance. If object with maximal rank of inheritance has many directives there will be used last (bottom) directive.

SetDefaultStateDirective = "set" StateIdentifier "as" "default" "state" ";" .
set Idling as default state;
app PeaceKeeper
{
    set Idling as default state;
}

You can see examples here.

Set state

back to top

Declares target set which will be started automatically with starting App.

There can be many directives on one object and on many levels of inheritance. There will be used only one directive which has maximal rank of inheritance. If object with maximal rank of inheritance has many directives there will be used last (bottom) directive.

SetStateDirective = "set" StateIdentifier "as" "state" ";" .
set Patrolling as state;
app PeaceKeeper
{
    set Patrolling as state;
}

You can see examples here.

Activating conditions

back to top

Declares list of conditions for activating State.

The State will be activated automatically if It is inactive and at least one of Activating conditions is true.

enter on:
{: see(I, enemy) :}
state Attacking
{
    enter on:
    {: see(I, enemy) :}
    leave on:
    {: (kill(I, $x) | see(I, $x)) & enemy($x) :}
    on Enter
    {
        'Begin Attacking Enter' >> @>log;
        'End Attacking Enter' >> @>log;
    }
}

Activating condition can bind captured in logic variable value with an automatically created Field by BindingVariableItemDecl.

state Attacking
{
    enter on:
    {: see(I, $x) & enemy($x) :} ($x >> @target)
    on Enter
    {
        Kill(@target);
    }
}

In the example identifier of an detected enemy will be put into automatically created Field @target. The field can be used into the state Attacking for killing the detected enemy. So you should not write any additional code for detecting target entity in the state.

You can see examples here.

Deactivating conditions

back to top

Declares list of conditions for deactivating State.

The State will be deactivated automatically if It is active and at least one of Deactivating conditions is true.

leave on:
{: (kill(I, $x) | see(I, $x)) & enemy($x) :}
state Attacking
{
    enter on:
    {: see(I, enemy) :}
    leave on:
    {: (kill(I, $x) | see(I, $x)) & enemy($x) :}
    on Enter
    {
        'Begin Attacking Enter' >> @>log;
        'End Attacking Enter' >> @>log;
    }
}

You can see examples here.

Synonym

back to top

Synonym provides an alternative name for another indentifier.

synonym procreator for parent;
synonym #Tom for #Person123;
synonym param_1 for param_2;

You can see examples here.

Date and time

back to top

Currently only time is implemented in SymOntoClay. Date implementation requires calendar implementation, which is planned for future releases.

Time in SymOntoClay is not accurate. But its accuracy is enough for the needs of game development.

In the future, it is planned to create custom calendars with in-game time. Therefore, the concepts of "second" and "millisecond" are reserved for these calendars as subject-specific and which can differ significantly in duration from their seconds and milliseconds from the real world.

System units of time available for use:

  • tick. It is the basic calendar-independent system unit of time in SymOntoClay. One tick is approximately equal to 1 millisecond from the real world.
  • internal millisecond (system millisecond). Approximately equal to 1 millisecond from the real world.
  • internal second (system second). Approximately equal to 1 second from the real world.

Common objects’ parts

back to top

The following parts can be in all or almost all kinds of objects.

Inheritance

back to top

SymOntoClay DSL has multiple fuzzy prototype-based inheritance.

That is, one object can have many “is a” relationships with other objects. A descendant object is related to a base object by the “is a” relationship, and is not its clone.

Additionally each relation has a float-point rank in range from 0 to 1. 1 is the highest rank, and 0 indicates the total absence of an "is a" relationship between these objects. By default the inheritance rank equals 1.

In operations using inheritance, the total inheritance rank between two objects is calculated by formula:

Rank total = i = 1 n Rank i

In this formula Rank i is inheritance rank of one link in the entire inheritance chain between two objects.

If inheritance has formed a rhombus, and there are several chains with different rank, then rank of chain with the minimum count of chain elements is taken.

Inheritance relationship can be read with operator "is", and It can be changed run time with "set is" statement.

Member access modifiers

back to top

Use the access modifiers to specify one of the following declared accessibility levels for members.

Declared accessibility Meaning
public Access is not restricted.
protected Access is limited to the containing object derived from the containing object.
private Access is limited to the containing object.

Access modifiers are optional.

Code without access modifiers is protected.

class Cls1
{
    private:
    {: male(#Tom) :}
    {: parent(#Piter, #Tom) :}
    {: {son($x, $y)} -> { male($x) & parent($y, $x)} :}
    protected:
    fun a() =>
    {
        '`a` has been called!' >> @>log;
    }
    public:
    @a;
}
app PeaceKeeper is Cls1
{
    private:
    on Enter =>
    {
        'Begin' >> @>log;
        select {: son($x, $y) :} >> @>log;
        'End' >> @>log;
    }
}

You can see examples here.

Additional settings

back to top

Declares additional setting of object or member.

fun a() with priority = 1 {}
fun a() with priority 1 {}
fun a() with priority middle {}
on {: see(I, #a) :} with priority 1 => {}
on {: see(I, #a) :} as `trigger 1` alias `Alarm trigger`, trigger_5 with priority 1 => {}

You can see examples here.

Priority

back to top

Declares priority of object or member.

Now It is allowed only for method, function or trigger.

fun a() with priority = 1 {}
fun a() with priority 1 {}
fun a() with priority middle {}
on {: see(I, #a) :} with priority 1 => {}
on {: see(I, #a) :} as `trigger 1` alias `Alarm trigger`, trigger_5 with priority 1 => {}

You can see examples here.

Field

back to top

A field is a variable of any type that is declared directly in a object. Fields are members of their containing object.

In code a field can be used as usual variable.

FieldDecl = [ "var" ] VarIdentifier [ ":" ( ( "(" ConceptIdentifier [ { "|" ConceptIdentifier } ] ")" ) | ( ConceptIdentifier [ { "|" ConceptIdentifier } ] ) ) [ "=" Expr ] ";" .
app PeaceKeeper
{
    @b;
    on Enter =>
    {
        'Begin' >> @>log;
        @b >> @>log;
        'End' >> @>log;
    }
}
app PeaceKeeper
{
    var @b: number = 2;
    on Enter =>
    {
        'Begin' >> @>log;
        @b >> @>log;
        'End' >> @>log;
    }
}

You can see examples here.

Constructor

back to top

Whenever an instance is created, its constructor is called. An object may have multiple constructors that take different arguments. Constructors enable the programmer to set default values, limit instantiation, and write code that is flexible and easy to read.

ctor(@param: string)
{
}
ctor(@param: string)
: cls0('Cool!')
{
}
ctor(@param: string)
: cls1('The Beatles!'),
cls2(12)
{
}
ctor()
: ('Hi')
{
}

You can see examples here.

Method

back to top

A method is a function associated with a message and an object.

In another point of view, a method is an object's behavior.

Technically a method is an object's named slot which contains a pointer to a function.

In details the method's declaration grammar is described in function's chapter .

app PeaceKeeper
{
    fun a()
    {
        '`a` has been called!' >> @>log;
    }
}
app PeaceKeeper
{
    fun a(@param_1)
    {
        '`a` has been called!' >> @>log;
        @param_1 >> @>log;
    }
}

You can see examples here.

Idle actions

back to top

Declares list of actions which can be executed when NPC is idle.

idle actions
{
    go();
}
idle actions
{
    go()[: timeout=1000 :];
}
idle actions
{
    {: >: { direction($x1,#@(place & color = green)) & $x1 = go(someone,self) } o: 1 :};
}
idle actions
{
    {: >: { direction($x1,#@(place & color = green)) & $x1 = go(someone,self) } o: 1 :}[: timeout=1000 :];
}

You can see examples here.

Operator overloading

back to top

A predefined SymOntoClay operator can be overloaded.

Now only function call can be overloaded.

Function call overloading

back to top

An object can declare an operator () function, which provides function call semantics for the object.

op () =>
{
    @@host.`go`(to: #@[10]);
    await;
}
op (@param_1: string) =>
{
    @@host.`go`(to: #@[10]);
    await;
}
op (@param_1: string, @param_2: number | string, @param_3: (number | string), @param_4 = 12) =>
{
    @@host.`go`(to: #@[10]);
    await;
}

You can see examples here.

Trigger

back to top

A trigger is a code that is automatically executed in response to certain events.

Now both Logic coditional and Lifecycle triggers are available. In the future I am going to develop other kinds of triggers.

SetTriggerHandler will be executed when the trigger has been set.

DownTriggerHandler will be executed only in Logic conditional triggers when the trigger has been reset (down).

Also Logic conditional trigger can be an active rule.

Named triggers

back to top

Trigger name or alias can be used in trigger condition(TriggerLogicExpr). When the trigger is set it is true in that condition, otherwise false.

on {: see(I, #a) :} as `trigger 1` alias `Alarm trigger`, trigger_5 =>
{
}
on (trigger_5 & @a is 15)
{
}

Life cycle triggers

back to top

Every object has a life cycle.

If there exist inheritance relation between object, then life cycle triggers of base object execute before the life cycle triggers of descendant object.

Enter

back to top

Executes when an object has been activated.

Enter trigger of App is a first code which is executed for the App. So It is an entrypoint of the App.

Also It is an entrypoint of State and Action. Enter trigger executes each time then the State or Action has been activated.

app PeaceKeeper
{
    on Enter =>
    {
        'Begin' >> @>log;
        'End' >> @>log;
    }
}
state Attacking
{
    on Enter
    {
        'Begin Attacking Enter' >> @>log;
        'End Attacking Enter' >> @>log;
    }
}

In chain of inheritance each Enter trigger of the chain will be called in order from the farthest item to the root item.

You can see examples here.

Leave

back to top

Executes when an object has been activated.

This trigger allows to perform some finalization actions. For example to stop shooting when state of killing enemy has been finished. There can be more reasons to finish killing enemy: enemy has been died, NPC must run away. But in any case killing the enemy must be stopped.

state Patrolling
{
    on Leave
    {
        'Begin Patrolling Leave' >> @>log;
        'End Patrolling Leave' >> @>log;
    }
}
action Go
{
    on Enter =>
    {
        'Enter Go' >> @>log;
    }
    on Leave
    {
        'Leave Go' >> @>log;
    }
    op () =>
    {
        'Begin Go' >> @>log;
        'End Go' >> @>log;
    }
}

In chain of inheritance each Leave trigger of the chain will be called in order from the root item to the farthest item.

You can see examples here and here.

Conditional triggers

back to top

Conditional trigger sets and sometimes resets by condition. You can read more about how to set and reset trigger here.

Logic conditional triggers

back to top

Logic сonditional trigger sets and sometimes resets by logic condition. You can read more about how to set and reset trigger here.

Trigger's condition can be a fact, duration (only for reset), trigger name, field or combination of many different kinds of conditions. TriggerLogicExpr allows operators (exclude "=") and synchronous method call.

app PeaceKeeper
{
    on {: see(I, #`gun 1`) :} =>
    {
        'D' >> @>log;
    }
}
app PeaceKeeper
{
    @a = #`gun 1`;
    on {: see(I, @a) :} =>
    {
        'D' >> @>log;
    }
}
on {: see(I, #a) :} duration 1 (down) =>
{
}
app PeaceKeeper
{
    var @a = 15;
    on {: see(I, #a) :} as `trigger 1` alias `Alarm trigger`, trigger_5 =>
    {
        'S' >> @>log;
    }
    on (trigger_5 & @a is 15)
    {
        'D' >> @>log;
    }
}
on {: see(I, #a) :} duration 1 => {: pet(I, cat) :}, {: pet(I, dog) :}, {: {son($x, $y)} -> { male($x) & parent($y, $x) } :}

You can see examples here.

Explicit and implicit reset

back to top

Logic сonditional trigger fires (set) when logic condition is true and trigger is reset.

If the trigger has binding variables then SetTriggerHandler will be executed as many times as needed. If the trigger has already been set then the trigger continues to be set.

The trigger can be reset (down) in two ways:

  • Explicitly
  • Implicitly

Logic сonditional trigger can be reset explicitly by TriggersLogicDownCondition when the condition is true.

Logic сonditional trigger can be reset implicitly when the TriggersLogicSetCondition is false.

If Logic сonditional trigger has both TriggersLogicSetCondition and TriggersLogicDownCondition there can be a situation when the trigger should set and reset simultaneously. The situation is resolved by TriggersLogicDoubleConditionsStrategy.

There are strategies:

  • Equal - "(=)". Both conditions are equal. So set-condition sets trigger immediately and reset-condition resets trigger immediately.
  • Priority set - "(set)". Trigger can be reset only if set-condition is false.
  • Priority reset - "(down)". Trigger can be set only if reset-condition is false.
on {: see(I, #a) :} down on {: see(I, barrel) :} (set) =>
{
}
else
{
}
on {: see(I, #a) :} duration 1 (down) =>
{
}

Default strategy is Priority set.

on {: see(I, #a) :} down on {: see(I, barrel) :} =>
{
}
else
{
}
Reset by timeout

back to top

Logic сonditional trigger can be reset explicitly by timeout.

The timeout without units of measurement is specified in internal seconds.

on {: see(I, #a) :} duration 1 (down) =>
{
}

Timeout can be combined with other kinds of conditions.

on {: see(I, #a) :} down on (duration 1 | (`Alarm trigger` & @a is 15)) (down) =>
{
}
Binding variables

back to top

Conditional trigger can bind captured in logic variable value with imperative variable by BindingVariableItemDecl.

app PeaceKeeper
{
    {: barrel(#a) :}
    {: see(I, #a) :}
    on {: see(I, $x) & barrel($x) & !focus(I, friend) :} ($x >> @x) =>
    {
        @x >> @>log;
    }
}
#a

Reset-condition can have its own binding variables to use in reset-handler.

If trigger has only set-condition with a binding variables and reset-handler then the reset-handler can not use the binding variables of the set-condition. So the reset-handler is executed without any binding variables.

You can see examples here.

"each" timer

back to top

Generates recurring events at the specified interval.

The interval without units of measurement is specified in internal seconds.

on each 1
{
}

You can see examples here.

"once" timer

back to top

Generates an one-time event after a set interval.

The interval without units of measurement is specified in internal seconds.

on once 1
{
}

You can see examples here.

Triggers on adding facts

back to top

Triggers on adding facts fires before the fact will have been added into storage. It allows to modify, reject of just log the fact.

If fact is not rejected it will be added to storage. Then logic conditional trigger could be fired with the fact.

on add fact {: hear(I, $x) & gun($x) :} ($_ >> @x)
{
    @x.so = 1;
}
on add fact ($_ >> @x)
{
    @x.so = 1;
}
on add facts
{
    reject;
}

If one fact is processed by many Triggers on adding facts the result follows the rules:

  • Rejection has bigger priority then modification.
  • If a modality is modified in many triggers the result will have the biggest set value of the modality.
Fact modification

back to top

Now only modalities can be modified.

Modalities can be modified using variable with captured fact, PointerOpExpr and ModalityName.

on add fact {: hear(I, $x) & gun($x) :} ($_ >> @x)
{
    @x.o = 1;
}

1 is set as value of obligation modality.

on add fact {: hear(I, $x) & gun($x) :} ($_ >> @x)
{
    @x.so = middle;
}

`middle` is set as value of self obligation modality.

Fact rejecting

back to top

A fact can be rejected with "reject" statement. In this case the fact will not be added into storage and will be ignored.

on add fact {: hear(I, $x) & gun($x) :} ($_ >> @x)
{
    reject;
}

In this example the trigger rejects all facts which fit contition "{: hear(I, $x) & gun($x) :}";

Active rule

back to top

Active rule is a conditional trigger with special simple syntax which adds rules or facts when the trigger sets and removes the rules or facts when the trigger resets.

on {: see(I, #a) :} duration 1 => {: pet(I, cat) :}
on {: see(I, #a) :} duration 1 => {: pet(I, cat) :}, {: pet(I, dog) :}, {: {son($x, $y)} -> { male($x) & parent($y, $x) } :}

Error handling

back to top

Error is special situation what breaks the normal way of code execution. It is assumed the fired error cannot be handled and resolved in the place of occurrence. So error firing is an alternative way of execution from place of occurrence to handler which can react to the occured error.

Error handling makes NPC's logic and behaviour closer to human's logic and behaviour. For instance in some situations a human understands something like "It's error!", "It's definitely wrong way!", "It's obstacle!", stops wrong action and makes another decision. Congratulate! It's error handling!

The root of error handling is a fact what describes error, obstacle or danger situation.

Error statement captures the fact and fires error based on the fact. The normal way of execution brakes.

The error value combains the reason fact and information about execution during error: stack trace and place where this error occurred. But I think It will be better and convenient consider the error as usual fact on syntax level.

The fired error can be caught or ignored by try-catch statement. Otherwise the execution async function or trigger will be stopped.

As parallel the NPC can react to the error by conditional trigger.

Annotation

back to top

Annotation provides a powerful method of associating metadata, or declarative information, with code.

After an annotation is associated with a program entity, the annotation can be queried at run time by using a technique called reflection.

Now the reflection is still not available in user code. I am going to implement this in the future. The reflection can be consumed only automatically by engine's components.

Concept

back to top

Adds additional meaning of the code entity. This meaning will be used for transformations and linking by meaning.

[: experiencer :]
[: experiencer, subject :]

Key - value

back to top

Adds additional settings to code expressions.

go()[: timeout=1000 :];
go()[: priority = 1 :];
go()[: priority = 1, timeout=1000 :];
Timeout

back to top

Sets timeout for idle actions or asynchronous call.

Executing operation will be automatically canceled after target period.

The period without units of measurement is specified in internal seconds.

go~()[: timeout=1 :];
idle actions
{
    go()[: timeout=1 :];
}
idle actions
{
    {: >: { direction($x1,#@(place & color = green)) & $x1 = go(someone,self) } o: 1 :}[: timeout=1 :];
}

By default, timeout performs weak cancel. But this behavior can be changed with some concepts.

Using concept cancel produce cancellation (with canceling callers).

go()[: timeout=1, cancel :];

Using concept weak cancel produce weak cancellation (without canceling callers).

go()[: timeout=1, weak cancel :];
Priority

back to top

Sets priority of operation.

go~()[: priority = 1 :];

Events

back to top

Events are fired when a method finishes execution.

First of all, It allows us to detect and handle finish of execution synchronous functions. I think this will be easier than creating a variable, starting execution the function in asynchronous mode and waiting for finishing function the function.

Annotation events are allowed for both synchronous and asynchronous mode.

Annotation events can be executed in both synchronous and asynchronous mode. Asynchronous annotation event contains "~".

Synchronous annotation event:

on complete { }
on complete => { }

Asynchronous annotation event:

on complete ~ { }
on complete ~ => { }

In general It looks like this:

@@host.`rotate`(30)[: timeout=1000, on complete { complete action; } :];
Complete event

back to top

Fires if function is completed.

CompleteEventAnnotation = "on" ( "complete" | "completed" ) [ "~" ] [ "=>" ] "{" [ StatementsSet ] "}" .
on complete { }
on complete => { }
on complete ~ { }
on complete ~ => { }
on completed { }
on completed => { }
on completed ~ { }
on completed ~ => { }
@@host.`rotate`(30)[: timeout=1000, on complete { complete action; } :];

You can see examples here.

Weak cancel event

back to top

Fires if function is weak canceled (without canceling callers).

WeakCancelEventAnnotation = "on" "weak" ( "cancel" | "canceled" ) [ "~" ] [ "=>" ] "{" [ StatementsSet ] "}" .
on weak cancel { }
on weak cancel => { }
on weak cancel ~ { }
on weak cancel ~ => { }
on weak canceled { }
on weak canceled => { }
on weak canceled ~ { }
on weak canceled ~ => { }
Go()[: on weak cancel { 'on weak cancel' >> @>log; } :];
a()[: timeout = 100, on weak canceled { 'on weak canceled' >> @>log; } :];

Logic programming

back to top

SymOntoClay provides Logic programming means.

SymOntoClay's Logic programming are parallel to imperative programming.

Logic and imperative means connect by

Fact

back to top

Represents knowledge in declarative way. It is an element of Logic programming.

Now the fact is a predicate sentence with small syntactic sugar for better inclusion into SymOntoClay DSL.

For example, expression "cat (#Alisa)" is equivalent to "#Alisa is a cat".

In the future, I am going to move away from the predicate form to a more human-readable one. In this case, the predicate form will be saved as an alternative way of describing.

{: male(#Tom) :}
{: >:{ male(#Tom) } :}
{: parent(#Piter, #Tom) :}
{: $x = act(M16, shoot) & hear(I, $x) :}

Modalities (ModalityDeclSet) allows to describe an additional information about a fact outside FactSentence and without using another fact for describing this fact.

{: male(#Tom) o: 0 :}
{: parent(#Piter, #Tom) o: very middle :}
{: >: { direction($x1,#@{: >: { color($_,$x1) & place($_) & green($x1) } :}) & $x1 = go(someone,self) } o: 1 :}

You can see examples here.

Logic rule

back to top

Represents a logic conclusion rule. It allows to find and use facts which are not defined explicitly. It is an element of Logic programming.

A rule defines relation between two sets of facts during logic conclusion.

Logic searching uses depth-first search (DFS) with backtracking.

A rule consists of head (LogicRulePrimarySection) and body (LogicRuleSecondarySection). The head is true if the body is true. Both head and body are predicate sentences with small syntactic sugar for better inclusion into SymOntoClay DSL.

In the future, I am going to move away from the predicate form to a more human-readable one. In this case, the predicate form will be saved as an alternative way of describing.

Conjunctions can only appear in the body, not in the head of a rule. Only special variables can be used in the rule, the scope of which is limited by this rule.

Now logic searching happens when the select operator is only called explicitly. In the future, the area of using logic searching (conclusion) in SymOntoClay will be significantly expanded.

{: {son($x, $y)} -> { male($x) & parent($y, $x) } :}
{: >: {son($x, $y)} -> { male($x) & parent($y, $x) } :}

You can see examples here.

Modalities

back to top

Modalities allows to describe an additional information about a fact outside FactSentence and without using another fact for describing this fact.

Now modalities can be used only for facts.

Facts can use only constant expression in modality:

{: male(#Tom) o: low :}
{: parent(#Piter, #Tom) o: very middle :}
{: >: { direction($x1,#@{: >: { color($_,$x1) & place($_) & green($x1) } :}) & $x1 = go(someone,self) } o: 1 :}

Queries can use all types of expressions in modality.

on {: hear(I, $x) & gun($x) & distance(I, $x, $y) so: 1 :} ($x >> @x, $y >> @y)
{
}
select {: son($x, $y) o: { _ >= low } :} >> @>log;
select {: son($x, $y) o: { not ( _ < high ) } :} >> @>log;
select {: son($x, $y) o: { _ < high and _ > low } :} >> @>log;

Modalities can be modified in Triggers on adding facts.

on add fact {: hear(I, $x) & gun($x) :} ($_ >> @x)
{
    @x.so = 1;
}

Obligation modality

back to top

This modality is helpful for describing commands. For two facts with the same sentences high level of obligation modality defines a command and low or zero level of obligation modality defines a general description.

Obligation modality means a general obligation which meant by someone else. The NPC can have own personal obligation called Self obligation modality which can be different from Obligation modality.

{: male(#Tom) o: 0 :}
{: parent(#Piter, #Tom) o: very middle :}
{: >: { direction($x1,#@{: >: { color($_,$x1) & place($_) & green($x1) } :}) & $x1 = go(someone,self) } o: 1 :}

Self obligation modality

back to top

This modality is similar to Obligation modality but means own personal obligation of the NPC which can be different from Obligation modality.

For instance, enemy commander makes an order. Technically It is a fact width obligation modality = 1. But for the NPC self obligation modality of the fact will be 0, because this fact represent an order of enemy commander. So the NPC must not to execute the order.

{: male(#Tom) so: 0 :}
{: parent(#Piter, #Tom) so: very middle :}
{: >: { direction($x1,#@{: >: { color($_,$x1) & place($_) & green($x1) } :}) & $x1 = go(someone,self) } so: 1 :}

Relation metadata

back to top

Relation metadata provides machine-readable meaning of the relation.

Now It needs for conversion fact to text.

Meaning of relation's argument is provided by concepts in argument's annotation. You can read here about recommended concepts.

rel like ($x1 [: experiencer :], $x2 [: object :]) is state;

Standard inheritance in relation

back to top

Conversion fact to text consumes the following concepts as superclass for relations:

  • event - represents an event.
  • act - represents an action.
  • state - represents a state.

Standard relation argument roles

back to top

Conversion fact to text consumes the following concepts as meaning of relation's argument:

  • agent - someone or something who does an action.
  • subject - synonym of agent.
  • experiencer - someone or something who a state.
  • object - who was affected by the action.
  • owner - someone or something who has possessions.
  • possessions - something which is property of the owner.

Semantics of Logic sentence

back to top

Predicate calculus is very flexible way. But we must keep in our mind the meaning of predicate's parameters.

Here the meaning of predicate's parameters for SymOntoClay will be described.

In the future I am going to make logic sentences closer to natural language phrases.

Semantics of unary predicate

back to top

Unary predicate discribes inheritance relationship.

{: barrel(#`Barel 1`) :}

Backgroundly the unary predicate transforms to predicate "is".

{: is(#`Barel 1`, barrel, 1) :}

Semantics of binary predicate

back to top

In many cases binary predicate means relation between an object and its property.

{: color(#dog1, black) :}

It equals phrase "#dog1 has black color".

Also binary predicate means relation between two objects.

{: parent(#Piter, #Tom) :}

It equals phrase "#Piter is a parent of #Tom".

The predicate "is" has special meaning for defining inheritance relationship.

Semantics of ternary predicate

back to top

Meaning of ternary predicate is close to binary predicate.

The third parameter of ternary predicate means a value linked to the relation.

{: distance(I, enemy, 15) :}
It equals phrase "Distance between me and enemy is 15".

Semantics of predicate "is"

back to top

The predicate "is" has special meaning for defining inheritance relationship.

"is" is a ternary predicate. The third parameter means rank of inheritance.

{: is (cat, animal, 1) :}
{: is (cat, pet, 0.75) :}

Predicate "is" can be used as binary predicate. In this case rank of inheritance will be implicit and equal 1.

{: is (cat, animal) :}

Expressions

back to top

An expression specifies the computation of a value by applying operators and methods to operands.

Constant expression

back to top

Constant expressions may contain only literals and are evaluated at compile time.

25
30.2
"abc"

Fuzzy expression

back to top

Fuzzy expression can be NumberExpr in range [0, 1] or term of linguistic variable.

true represents 1. false represents 0.

0
0.5
1
teenager
very teenager
true
false

Logic expression

back to top

Grouping

back to top

Expressions in round brackets executes as single expression.

{: ( age(#Tom, $x) & distance(#Tom, $y) & $x is not $y ) | see(I, enemy) :}
#@(hold(I, this) & (weapon & dog) )
(3 + 5) * 2

Variable

back to top

A variable is a storage location for holding a value.

Both reading and writing are allowed for variables.

Default value of variable is NULL.

@x
@`target value`

You can see examples here.

System variable

back to top

It's a special variable, which value is only written by engine.

Only reading is allowed for system variables.

@@host

Available system variables

back to top

@@self

back to top

Contains a link to the current App.

You can see examples here.

@@host

back to top

Contains a link to the C# methods of this App which are defined at the Unity3D level of the NPC logic or Thing.

Allows to call low-level C# methods for interaction with Unity3D.

It does nothing when It launched with the "CLI run".

You can see examples here.

Logic variable

back to top

Logic variable can be used only in Logic expression and Binding variables in Logic сonditional triggers.

$x
$y

Available logic variables

back to top

$_

back to top

The logic variable is used in:

#@{: barrel($_) :}

Channel

back to top

Channels are a conduit through which you can send and receive values with the stream operator.

Now only sending has been implemented.

Available system channels

back to top

@>log

back to top

A channel for writing information to the log in text form.

Logging targets are determined by the engine settings.

'Begin' >> @>log;

You can see examples here.

@>say

back to top

A channel for writing information to game sound bus. The information can be perceived by other NPCs' trigges as speech.

For players this information can be shown as tooltips or subtitles or converted to voice.

'I like my cat.' >> @>say;
{: >: { like(i,#@{: >: { possess(i,$_) & cat($_) } :}) } :} >> @>say;
@a >> @>say;

String will be converted to fact automatically. So you do not have to create special trigger for the string value in parallel with trigger for the fact.

Anonymous function

back to top

fun()
{
    return 1;
}
app PeaceKeeper
{
    on Enter
    {
        @a = SomeFun();
        @a() >> @>log;
    }
    fun SomeFun()
    {
        return fun()
        {
            return 1;
        };
    }
}

Anonymous function can be passed as parameter, assigned to a variable, returned as a result or called immediately.

You can see examples here.

Operators

back to top

Operators combine operands into expressions.

Operator precedence

back to top

new
Precedence Operators Associativity
2

highest

. Left-to-right
()
select
?
insert
3 ! Left-to-right
Unary +
Unary -
new
5 * Left-to-right
/
6 + Left-to-right
-
9 > Left-to-right
>=
<
<=
10 is Left-to-right
is not
use is
14 & Left-to-right
15 | Left-to-right
16 >> Left-to-right
17 = Right-to-left
18 , Left-to-right

Operator "new"

back to top

The new operator creates a new instance of a type.

@a = new cls1;
@a = new cls1('Hi!');
@a = new @b;
@a = new @b'Hi!');

You can see examples here.

Assignment operators

back to top

Operator "="

back to top

Associativity: Right-to-left.

In imperative code:

Writes a value from the right operand to the left operand and returns the value of the right operand, which can be used in further calculations.

The left operand must be a variable.

@x = 1;
@r = @b = 1;

In logic expression:

Defines alias for logic expression. This alias can be used in logic expressions as usual Logic variable.

$x = act(M16, shoot)
{: $x = act(M16, shoot) & hear(I, $x) :}
{: $x = (act(M16, shoot) & gun(M16)) & hear(I, $x) :}
{: $x = {: act(M16, shoot) :} & hear(I, $x) :}

Now the alias can be created only for predicates.

In conditional entity expression:

It is a short representation relations between object and values in conditional entity expression instead of predicate.

#@(color = black)

It is the same like:

#@{: color($_, black) = :}

Relational operators

back to top

Operator "is"

back to top

Returns inheritance rank. The left operand contains a checked object. The right-hand operand contains a possible base object.

exampleClass is human >> @>log;
@a is 3
@a is teenager

Using "not" returns 1 if inheritance relationship is not exist between two operands otherwise returns 0.

exampleClass is not human >> @>log;
@a is not 3
@a is not teenager

You can see examples here.

Operator "is" is used in Logic expressions for comparison. The comparison uses inheritance relationship. Also operator "is" can compare NumericFuzzyExpr and NonNumericFuzzyExpr.

{: age(#Tom, $x) & distance(#Tom, $y) & $x is $y :}
{: age(#Tom, $x) & distance(#Tom, $y) & $x is not $y :}
{: distance(#Tom, $x) & $x is 12 :}
{: see(I, $x) & $x is barrel & distance(I, $x, $y) :}

You can see examples here.

Predicate

back to top

Represents relations between objects or objects and values.

Predicate can be only used in Logic expression.

You can read here about semantics of predicates.

Predicate's metadata can be described by Relation metadata.

male(#Tom)
parent(#Piter, #Tom)
son($x, $y)
age(#Tom, 12)
distance(I, #Tom, 12)
$z(#Alisa_12, $x)
hear(I, act(M16, shoot))
hear(I, act(M16, shoot) | act(M4A1, shoot))
hear(I, (act(M16, shoot) | act(M4A1, shoot)))
hear(I, {: act(M16, shoot) | act(M4A1, shoot) :})

You can see examples here.

Operator ">"

back to top

In Logic expression It has sucess if left-hand operand is greater than its right-hand operand, otherwise fail.

{: distance(#Tom, $x) & $x > 5 :}

You can see examples of rules, facts and logic queries here.

In imperative code It returns true if left-hand operand is greater than its right-hand operand, otherwise false.

@a > 5
@age > teenager

You can see examples of imperative code here.

Operator ">="

back to top

In Logic expression It has sucess if left-hand operand is greater than or equal to its right-hand operand, otherwise fail.

{: distance(#Tom, $x) & $x >= 5 :}

You can see examples of rules, facts and logic queries here.

In imperative code It returns true if left-hand operand is greater than or equal to its right-hand operand, otherwise false.

@a >= 5
@age >= teenager

You can see examples of imperative code here.

Operator "<"

back to top

In Logic expression It has sucess if left-hand operand is less than its right-hand operand, otherwise fail.

{: distance(#Tom, $x) & $x < 5 :}

You can see examples of rules, facts and logic queries here.

In imperative code It returns true if left-hand operand is less than its right-hand operand, otherwise false.

@a < 5
@age < teenager

You can see examples of imperative code here.

Operator "<="

back to top

In Logic expression It has sucess if left-hand operand is less than or equal to its right-hand operand, otherwise fail.

{: distance(#Tom, $x) & $x <= 5 :}

You can see examples of rules, facts and logic queries here.

In imperative code It returns true if left-hand operand is less than or equal to its right-hand operand, otherwise false.

@a <= 5
@age <= teenager

You can see examples of imperative code here.

Logic operators

back to top

Operator "&"

back to top

Operator of logical conjunction (logical and).

male($x) & parent($y, $x)
#@(hold(I, this) & weapon)
#@(hold(I, this) & (weapon & dog) )

You can see examples of rules, facts and logic queries here.

@a > 0 & @a < 5

You can see examples of imperative code here.

Operator "|"

back to top

Operator of logical disjunction (logical or).

{: ( age(#Tom, $x) & distance(#Tom, $y) & $x is not $y ) | see(I, enemy) :}
#@(hold(I, this) & (weapon | dog) )

You can see examples of rules, facts and logic queries here.

@a > 0 | @a < 5

You can see examples of imperative code here.

Operator "!"

back to top

Operator of logical negation (logical not).

{: see(I, $x) & barrel($x) & !focus(I, friend) :}

You can see examples of rules, facts and logic queries here.

!(@a > 5)
not(@a > 5)

You can see examples of imperative code here.

Arithmetic operators

back to top

Operator "+"

back to top

The addition operator "+" computes the sum of its operands.

If one (or two) operand is null the result also will be null.

If two operands are string the result will be concatenation of the strings. If an operand is string and another operand is not string the non string operand will be converted to string and the result will be concatenation.

1 + 1
@a + 1
@a + @b
@a + 'Hi' + 3

You can see examples here.

Operator unary "+"

back to top

The unary "+" operator computes absolute value (or modulus) of its operand.

If the operand is null the result also will be null.

+ @a
2 * + @a

You can see examples here.

Operator "-"

back to top

The subtraction operator "-" subtracts its right-hand operand from its left-hand operand

If one (or two) operand is null the result also will be null.

1 - 1
@a - 1
@a - @b

You can see examples here.

Operator unary "-"

back to top

The unary "-" operator computes the numeric negation of its operand. If the operand is already negative the operator returns the value of its operand.

If the operand is null the result also will be null.

- @a
2 * - @a

You can see examples here.

Operator "*"

back to top

The multiplication operator "*" computes the product of its operands.

If one (or two) operand is null the result also will be null.

1 * 1
@a * 1
@a * @b

You can see examples here.

Operator "/"

back to top

The division operator "/" divides its left-hand operand by its right-hand operand.

If the right-hand operand is 0 the result also will be null.

If one (or two) operand is null the result also will be null.

1 / 2
@a / 1
@a / @b

You can see examples here.

Member access operators

back to top

Operator "."

back to top

Gets a reference to a member (specified in the right operand) of an object (specified in the left operand).

The right-hand operand must contain the member's identifier.

@@host.`go`
@a.@b

Call operators

back to top

Operator "()"

back to top

Calls an object (or member reference) with parameters specified in round brackets.

Whether a call is synchronous or asynchronous is determined by the presence of a "~" or "~~" before the opening round bracket of the parameter list.

Named parameter:

a(param_1: 1);
a(param_1: 1, to: #@[25, 30]);
a(@param_1: 1);
a(@param_1: 1, @to: #@[25, 30]);

Positional parameters:

a(1);
a(1, dog);

Without parameters:

a();

A synchronous call is made when there is no "~" or "~~". In this case, the operator returns directly the result of the called method.

@@host.`go`(to: #@[25, 30]);
some_fun();

You can see examples here, here or here.

An asynchronous call is made when "~" or "~~" is present. In this case, the operator returns a reference to the object which represents the asynchronous operation.

"~" starts child asynchronous operation. The operation will be automatically cancelled after finishing parent operation.

@@host.`go`~(to: #@[25, 30]);
some_fun~();

"~~" starts independent asynchronous operation. The operation will executes after finishing parent operation.

@@host.`go`~~(to: #@[25, 30]);
some_fun~~();

You can see examples here, here or here.

Stream operators

back to top

Operator ">>"

back to top

Copies data from a source (SourceOfStreamOpExpr) to a destination DestOfStreamOpExpr.

The source (SourceOfStreamOpExpr) cannot be a channel. The destination (DestOfStreamOpExpr) must only be a channel.

In the future I am going to extend the functionality of this operator.

"End" >> @>log;
select {: son($x, $y) :} >> @>log;

Data source operators

back to top

select (Operator "?")

back to top

Performs a logical search and returns an object which contains the results of the search.

Now the result of a logical search can only be logged for demonstrating this search. In the future, I am going to develop means for practical using results of logical searching in game development.

SelectOpExpr = ( "select" | "?" ) ( FactDecl ) .

Searched values are defined by the question variables in the searching query.

select {: son($x, $y) :} >> @>log;
<yes>
$y = #piter; $x = #tom

You can see examples here.

? {: son($x, $y) :} >> @>log;
<yes>
$y = #piter; $x = #tom

You can see examples here.

Question variables can be used for searching predicates.

? {: $z(#Alisa_12, $x) :} >> @>log;
<yes>
$z = can(bird,fly); $x = fly

You can see examples here.

In the absence of question variables, only general result will be obtained: is it possible or not to find such facts in the knowledge database.

? {: can(#Alisa_12, fly) :} >> @>log;
<yes>
? {: can(#Alisa_12, @a) :} >> @>log;
<yes>

You can see examples here.

insert

back to top

Writes a fact into the knowledge storage of current App. Returns the added fact.

InsertOpExpr = "insert" FactDecl .
insert {: >: { bird (#1234) } :};
insert {: see(I, @a) :};

You can see examples here.

Statements

back to top

Statements control execution.

"import" statement

back to top

Imports target library.

Libraries which imported on world level are common for all apps of the world.

world `Lost town`
{
    import stdlib;
}
app PeaceKeeper
{
    import lib1;
}
lib lib1
{
    import lib2;
}
lib lib1
{
    import 'lib2';
}

Expression statement

back to top

Executes an expression line.

@@host.`go`~(to: #@[25, 30]);

Variable declaration statement

back to top

Declares a local variable. Optyonaly you can specify its types and initial value.

VarDeclStatement = [ "var" ] VarIdentifier [ ":" ( ( "(" ConceptIdentifier [ { "|" ConceptIdentifier } ] ")" ) | ( ConceptIdentifier [ { "|" ConceptIdentifier } ] ) ) [ "=" Expr ] ";" .
@b = 1;
var @a: string;
var @a: string | number;
var @a: (string | number);
@a: string | number;
var @a: number = 2;
var @a: number | string = 2;
var @a: (number | string) = 2;
@a: number = 2;

"error" statement

back to top

Signals the occurrence of an error during program execution.

error {: see(I, #a) :};
app PeaceKeeper
{
    on Enter =>
    {
        error {: see(I, #a) :};
    }
}

In details the error handling is described in the chapter "Error handling".

You can see examples here.

"try-catch" statement

back to top

This statement provides safe code execution, catching errors and blocks of guaranteed code execution.

The full statement contains try, catch, else and ensure blocks.

The statement can be call by blocks which It consists of. For example, try-catch, try-catch-else-ensure, try-ensure.

The try block contains the guarded code that may cause the error. The block is executed until an error is fired or it is completed successfully.

When an error is fired, the engine looks for the catch statement that handles this error. If the currently executing method does not contain such a catch block, the engine looks at the method that called the current method, and so on up the call stack. If no catch block is found, then the engine stops execution of the trigger.

The else block runs when try block finished without error.

The ensure block is guaranteed to be run.

The order of execution of blocks:

  • try-catch-else-ensure:
    • with error: trycatchensure
    • without error: tryelseensure
  • try-catch-ensure:
    • with error: trycatchensure
    • without error: tryensure
  • try-catch-else:
    • with error: trycatch
    • without error: tryelse
  • try-catch:
    • with error: trycatch
    • without error: try
  • try-else:
    • with error: try
    • without error: tryelse
  • try-ensure:
    • with error: tryensure
    • without error: tryensure
  • try-else-ensure:
    • with error: tryensure
    • without error: tryelseensure

Full statement (try-catch-else-ensure):

app PeaceKeeper
{
    fun a(@param_1) =>
    {
        '`a` has been called!' >> @>log;
        @param_1 >> @>log;
        error {: see(I, #a) :};
        'End of `a`' >> @>log;
    }
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            a(param_1: 1);
            'End' >> @>log;
        }
        catch
        {
            'catch' >> @>log;
        }
        catch(@e)
        {
            'catch(@e)' >> @>log;
            @e >> @>log;
        }
        catch(@e) where {: hit(enemy, I) :}
        {
            'catch(@e) where {: hit(enemy, I) :}' >> @>log;
        }
        catch(@e) where {: see(I, $x) :}
        {
            'catch(@e) where {: see(I, $x) :}' >> @>log;
            @e >> @>log;
        }
        else
        {
            'else' >> @>log;
        }
        ensure
        {
            'ensure' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

try-catch:

app PeaceKeeper
{
    fun a(@param_1) =>
    {
        '`a` has been called!' >> @>log;
        @param_1 >> @>log;
        error {: see(I, #a) :};
        'End of `a`' >> @>log;
    }
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            a(param_1: 1);
            'End' >> @>log;
        }
        catch
        {
            'catch' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

try-catch-ensure:

app PeaceKeeper
{
    fun a(@param_1) =>
    {
        '`a` has been called!' >> @>log;
        @param_1 >> @>log;
        error {: see(I, #a) :};
        'End of `a`' >> @>log;
    }
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            a(param_1: 1);
            'End' >> @>log;
        }
        catch
        {
            'catch' >> @>log;
        }
        ensure
        {
            'ensure' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

try-ensure:

app PeaceKeeper
{
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            error {: see(I, #a) :};
            'End' >> @>log;
        }
        ensure
        {
            'ensure' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

try-else:

app PeaceKeeper
{
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            error {: see(I, #a) :};
            'End' >> @>log;
        }
        else
        {
            'else' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

try-else-ensure:

app PeaceKeeper
{
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            error {: see(I, #a) :};
            'End' >> @>log;
        }
        else
        {
            'else' >> @>log;
        }
        ensure
        {
            'ensure' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

try:

app PeaceKeeper
{
    fun a(@param_1) =>
    {
        '`a` has been called!' >> @>log;
        @param_1 >> @>log;
        error {: see(I, #a) :};
        'End of `a`' >> @>log;
    }
    on Enter =>
    {
        try
        {
            'Begin' >> @>log;
            a(param_1: 1);
            'End' >> @>log;
        }
        'End of `Enter`' >> @>log;
    }
}

In details the error handling is described in the chapter "Error handling".

You can see examples here.

"set is" statement

back to top

Sets an inheritance relationship ("is a") between two objects.

set @@self is linux;
set exampleClass is human;
set exampleClass is [0.5] human;
set #`Alisa 12` is [0.6] human;

Using "not" removes the inheritance relationship.

set exampleClass is not human;

You can see examples here.

"await" statement

back to top

The await statement suspends evaluation of the enclosing method until the action will be completed, broken, canceled or weak canceled by handlers of child triggers or functions which have been called from the handlers.

Statements after await will not be executed.

AwaitStatement = "await" ";" .
op () =>
{
    @@host.`go`(to: #@[10]);
    await;
}
op () =>
{
    await;
}

"wait" statement

back to top

"wait" statement suspends excution of the enclosing method.

WaitStatement = "wait" Expr [{ "," Expr }] ";" .

You can see examples here.

Wait timeout

back to top

"wait" statement suspends excution of the enclosing method for the specified amount of time.

The amount of time without units of measurement is specified in internal seconds.

wait 10;
wait @timeout;

Wait callPointer

back to top

"wait" statement suspends excution of the enclosing method while callPointers is running.

@a = a~~();
wait @a;
@a = a~~();
@b = b~~();
wait @a, @b;

"complete action" statement

back to top

The statement complete action terminates action by the reason of achieving goal of the action.

CompleteActionStatement = "complete" "action" ";" .
on {: see(I, enemy) :} =>
{
    complete action;
}

You can see examples here.

"break action" statement

back to top

The statement break action breakes the action. It is similar to Error statement. But "Error statement" can break Action only in action's entry points or functions which have been called from the action's entry points. The statement break action can break Action in any place of the Action.

BreakActionStatement = "break" "action" FactDecl ";" .
on {: see(I, enemy) :} =>
{
    break action {: see(I, enemy) :};
}

You can see examples here.

"cancel action" statement

back to top

The statement cancel action canceles the action. All parents callers will also be canceled.

CancelActionStatement = "cancel" "action" ";" .
on {: see(I, $x) :} ($x >> @x) =>
{
    cancel action;
}

"weak cancel action" statement

back to top

The statement cancel action canceles the action. But all parents callers will not be canceled.

WeakCancelActionStatement = "weak" "cancel" "action" ";" .
on {: see(I, $x) :} ($x >> @x) =>
{
    weak cancel action;
}

"repeat" statement

back to top

"repeat" statement defines the infinite loop.

RepeatStatement = "repeat"
"{"
[ StatementsSet ]
"}" .
repeat
{
    @a >> @>log;
    @a = @a - 1;
    if(@a > 5)
    {
        continue;
    }
    break;
}

You can see examples here.

"while" statement

back to top

The while statement executes a statement or a block of statements while a specified logic expression evaluates to true (1). Because that expression is evaluated before each execution of the loop, a while loop executes zero or more times.

WhileStatement = "while" "(" ImperativeLogicExpr ")"
"{"
[ StatementsSet ]
"}" .
while (@a > 0)
{
    @a >> @>log;
    @a = @a - 1;
}

You can see examples here.

"return" statement

back to top

The return statement terminates execution of the function in which it appears and returns control and the function's result, if any, to the caller.

If a function member doesn't compute a value, you use the return statement without expression.

ReturnStatement = "return" [ Expr ] ";" .
fun a() =>
{
    '`a` has been called!' >> @>log;
    return;
}
fun a() =>
{
    '`a` has been called!' >> @>log;
    return 2;
}
fun a(@param) =>
{
    '`a` has been called!' >> @>log;
    return @param + 2;
}

You can see examples here.

"if-elif-else" statement

back to top

Selects a statement to execute based on the value of a logic expression.

IfElifElseStatement = "if" "(" ImperativeLogicExpr ")"
"{"
[ StatementsSet ]
"}"
[ "elif" "(" ImperativeLogicExpr ")"
"{"
[ StatementsSet ]
"}"
{ "elif" "(" ImperativeLogicExpr ")"
"{"
[ StatementsSet ]
"}"
}]
[ "else"
"{"
[ StatementsSet ]
"}" ] .

An if statement with an else part selects one of the two statements to execute based on the value of a Logic expression. An if statement executes if the Logic expression evaluates to true (1).

An if statement without an else part executes its body only if a Logic expression evaluates to true (1).

You can use elif statements to check multiple conditions.

if:

if(@a is 0)
{
    'Yes!' >> @>log;
}

if-else:

if({: >: { see(I, #`Barel 1`) } :})
{
    'Yes!' >> @>log;
    } else {
    'Else Yes!' >> @>log;
}

if-elif:

if({: >: { see(I, #`Barel 0`) } :})
{
    'Yes!' >> @>log;
    } elif ({: >: { see(I, #`Barel 1`) } :}) {
    'Elif 1 Yes!' >> @>log;
}

if-elif-elif:

if({: >: { see(I, #`Barel 0`) } :})
{
    'Yes!' >> @>log;
    } elif ({: >: { see(I, #`Barel 1`) } :}) {
    'Elif 1 Yes!' >> @>log;
    } elif ({: >: { see(I, #`Barel 2`) } :}) {
    'Elif 2 Yes!' >> @>log;
}

if-elif-elif-else:

if({: >: { see(I, #`Barel 0`) } :})
{
    'Yes!' >> @>log;
    } elif ({: >: { see(I, #`Barel 1`) } :}) {
    'Elif 1 Yes!' >> @>log;
    } elif ({: >: { see(I, #`Barel 2`) } :}) {
    'Elif 2 Yes!' >> @>log;
    } else{
    'Else Yes!' >> @>log;
}

if-elif-else:

if({: >: { see(I, #`Barel 0`) } :})
{
    'Yes!' >> @>log;
    } elif ({: >: { see(I, #`Barel 1`) } :}) {
    'Elif 1 Yes!' >> @>log;
    } else{
    'Else Yes!' >> @>log;
}

You can see examples here.

"continue loop" statement

back to top

The continue statement starts a new iteration of the closest enclosing iteration statement (that is, repeat, or while).

ContinueLoopStatement = "continue" ";" .
repeat
{
    @a >> @>log;
    @a = @a - 1;
    if(@a > 5)
    {
        continue;
    }
    break;
}

You can see examples here.

"break loop" statement

back to top

The break statement terminates the closest enclosing iteration statement (that is, repeat, or while). The break statement transfers control to the statement that follows the terminated statement, if any.

BreakLoopStatement = "break" ";" .
repeat
{
    @a >> @>log;
    @a = @a - 1;
    if(@a > 5)
    {
        continue;
    }
    break;
}

You can see examples here.

"Set as default state" statement

back to top

One of States can be set as default state.

The default state will be activated if current state is closed without activation new state explicitly.

SetAsDefaultStateStatement = "set" StateIdentifier "as" "default" "state" ";" .
set Idling as default state;
state Patrolling
{
    on Enter
    {
        'Begin Patrolling Enter' >> @>log;
        set Idling as default state;
        'End Patrolling Enter' >> @>log;
    }
}

You can see examples here.

"Set as state" statement

back to top

Activates target State explicitly.

Current State will be deactivated.

SetAsStateStatement = "set" StateIdentifier "as" "state" ";" .
set Idling as state;
state Patrolling
{
    on Enter
    {
        'Begin Patrolling Enter' >> @>log;
        set Idling as state;
    }
}

You can see examples here.

"complete state" statement

back to top

Finishes current State and markes It as completed.

CompleteStateStatement = "complete" "state" ";" .
complete state;
state Patrolling
{
    on Enter
    {
        'Begin Patrolling Enter' >> @>log;
        complete state;
    }
}

You can see examples here.

"break state" statement

back to top

Finishes current State and markes It as broken.

If "break state" statement has ReasonOfBreakStateStatement, the reason will be put into global storage and triggers are able to react to the the reason.

break state;
break state {: attack(I, enemy) :};
state Patrolling
{
    on Enter
    {
        'Begin Patrolling Enter' >> @>log;
        break state;
    }
}
state Patrolling
{
    on Enter
    {
        'Begin Patrolling Enter' >> @>log;
        break state {: attack(I, enemy) :};
    }
}

You can see examples here.

"reject" statement

back to top

Special statement for Trigger on adding facts which rejects added Fact. In this case the fact will not be added into storage and will be ignored.

RejectStatement = "reject" ";" .

The statement can be called only in body of Trigger on adding facts.

The statement does not break execution of the body of the trigger's handler.

on add fact {: hear(I, $x) & gun($x) :} ($_ >> @x)
{
    reject;
}

In this example the trigger rejects all facts which fit contition "{: hear(I, $x) & gun($x) :}";

"exec" statement

back to top

Converts Fact to imperative code and executes the code.

ExecStatement = "exec" Expr ";" .
exec @a;
exec {: >: { direction($x1,#@{: >: { color($_,$x1) & place($_) & green($x1) } :}) & $x1 = go(someone,self) } o: 1 :};

You can see examples here.

event declaration statement

back to top

Adds trigger to an object in function body.

EventDeclStatement = "on" Expr LifecycleEventName [ "=>" ] "{" [ StatementsSet ] "}" ";".
LifecycleEventName = "complete" | "completed" | "weak" "cancel" | "weak" "canceled" .
var @task = a~~();
on @task complete { 'on complete' >> @>log; };

Complete event

back to top

Fires if function is completed.

var @task = a~~();
on @task complete { 'on complete' >> @>log; };
var @task = a~~();
on @task completed { 'on completed' >> @>log; };

Weak cancel event

back to top

Fires if function is weak canceled (without canceling callers).

var @task = a~~() [: timeout = 100 :];
on @task weak cancel { 'on weak cancel' >> @>log; };
var @task = a~~() [: timeout = 100 :];
on @task weak canceled { 'on weak canceled' >> @>log; };