Values and Constants
Type system
As of now, data contracts in diagrams do not have typings.
When exporting Typescript code, they are declared as any
. However, FSM itself (it's States
and Actions
) is typed along with Automata instance.
For other languages, Yantrix aims to provide compatibility with elementary types, existing in most of them:
- Numbers: integer and floats are processed as similarly as possible for a given language
- Strings: strings are processed as immutables, i.e. every string function should return a new string rather than modify one of its arguments
- Lists (Tuples): Lists and Tuples are processed as similarly as possible for a given language. They also are immutable and can only be a read-only argument of the calling function
- Objects(Dictionaries): implementation is highly language-dependent and aims to reproduce the behaviour of Typescript
Record
implementation as closely as possible. ForFunctions
implementation Object arguments should be defined as immutable and read-only. - Binary — a special type that is used for conditional logic. It is essentially a Number but is interpreted as a boolean by the following rules:
- If the value is
Null
or unset, theBinary
is falsy - If the value is 0 or less, the
Binary
is falsy - If the value is 1 or greater, the
Binary
is truthy - Otherwise, the value is rounded by mathematic rules and then is compared again
- If the value is
- A special Function type exists for Functions, when they are used as another
Function
parameter, such as inmap
- andfilter
-style iterators.
Properties
Properties are named members of Data Objects
. Inside Yantrix intestines Payload
,Context
and Event Meta
are dictionaries, and top-level properties of them can be extracted by name. Deeper nested values (i.e., when property is of Object
type) are extracted with special Functions
The properties extracted from Data Objects
, which are destructured from them based on their Keys. Listing Keys is like declaring variables, which are members of a dereferenced Data Object. Key list is defined as the following:
KEY_ITEM::=<TARGET_PROPERTY>[=<EXPRESSION>]
KEY_LIST::=KEY_ITEM[,KEY_ITEM...]
Expression
is derived from Data Object
properties, bound to any of them, see Expressions
Referencing Data Objects
The choice of Data Object
to attach a property is defined by the directive.
Context
#varName
references a property of Context
inside any Expression
. If the Context
with given name is not available in the calling directive, the property reference is set to Null
, unless provided with a default value
#{KEY_LIST}
is used to reference numerous properties of Context
, mostly in Reducers
and Event Emitters
.
''' declare a Context with three members
#{a,b,c}
''' Declare a Context with three more members, that copy value from previous ones
#{oldA, oldB, oldC} <= #a,#b,#c
''' fire an Event with a numeric property, derived from a function applied to those
emit/someEvent (eventCode) <= #{encode(oldA, oldB, oldC)}
Payload
$varName
references a property of Payload
inside any Expression
. If the Payload
with given name is not available in the calling directive, the property reference is set to Null
, unless provided with a default value
(KEY_LIST)
is used to reference numerous properties of Payload
on the left side of <=
of subscribe
directives
''' Expression that invokes a Function upon a Payload property
func($payloadValue)
''' assign three Payload properties to respective Context properties
#{a,b,c} <= $payloadA, $payloadB, $payloadC
''' Translate an Event with its Meta to Action with Payload
subscribe/someEvent SomeAction ( payloadValue ) <= ( eventMetaValue )
Default values
Default values are assigned to vars when the requested property is absent in Data Object
or equals to Null
(an empty pointer). To assign a default value the KEY_ITEM
is followed by =
and a Constant
or Expression
. For instance:
#{anyValue, stringValue = 'foobar', numericValue = cos(div(%%pi,6))}
Default values can be used both in Source Objects
and in Target Objects
in Reducers
, but they behave differently on either side of <=
token: left-side defaults are used when corresponding right side expression converges to Null
, while right-side defaults are used when attached references are absent in Data Object
or equal to Null
.
In the following example value1
and value2
will not be set to 1
if they already have values, but they will acquire 1
as a starting value when the Context is created for the first time
#{value1, value2} <= #value1 = 1, #value2 = 1
''' same as
#{value1 = 1, value2 = 1} <= #value1, #value2
''' same as shortcut
#{value1 = 1, value2 = 1 }
a difference emerges is there's a Function
in Expression
:
''' in this example, value2 can become Null after the computation
''' if "funcValue" call returns Null, even though value2 has a default value
#{value1, value2} <= #value1 = 1, funcValue(#value2 = 1)
''' however, if also assigned on the left side, it resolves to a default value
#{value1, value2 = 1} <= #value1 = 1, funcValue(#value2 = 1)
When used on both sides of a Reducer
, the Source Object
execution takes precedence when assigning default values, i.e., the code #{a = 1} <= $b = func(2)
is processed in the following manner:
b
is read fromPayload
. If it's absent orNull
, thefunc
is invoked with a parameter of2
. The resulting value is assigned to$b
.- if the value is set, it's assigned to
a
property of the resultingContext
. If the expressions resolves toNull
, that property is filled with value of1
Thus, if no Payload
was given, or it doesn't have b
key, Context
.a
contents will depend on what is returned by func(2)
call.
Constant declaration
All but Object types can be created in-place, as the default value of Context
or Payload
property or as a Function
argument
1
or1.05
is the way to create a Number. Other numeric representations (e.g. hex, exponential, etc.) are supported as long as the target language supports them. Numbers are automatically typecasted to Binary if needed'foo'
creates a String primitive[]
creates an empty List%%foo
is a compile-time Constant, that is passed to a Codegen along with a diagram, primarily being used for behaviors that are dependent on environment and/or CI/CD operations.
Examples
#{a, b, c}
describes a Context
with named properties a
,b
and c
. This notation is a syntactic sugar for #{a, b, c} <= #a, #b, #c
, meaning that values are plain copied from the preceding Context
during the transaction
#{a, b} <= $a, $b
is similar but copies values from Payload
, received by a transaction.
It could be #{a, b} <= $c, $d
, in which case the properties are copied in the correspoding order: Context.a
receives the value of Payload.c
, and Context.b
is updated with Payload.d
.
The same is possible with Context
too, and basically every Expression
can be used on the right side of reducer: #{newA,newB} <= someFunction(#oldA), #oldB = 2
does the following:
- passes
previousContext.oldA
tosomeFunction
and puts the output value tonewContext.newA
- assigns
previousContext.oldB
value tonewContext.newB
, or uses2
numeric primitive instead, ifpreviousContext.oldB
was not defined or set toNull
More lifelike examples:
#{id, name}
: copies corresponding fields from previousContext
#{id, name} <= #id, trim(#name)
does the same, but appliesString.trim()
to source value#{id, name} <= #id, concat(trim(#name),' ',trim(#lastname))
: extracts two properties from the source object, trims them and joins them with a predefined string constant#{counter} <= add(#counter, $increment)
: addsincrement
property of aPayload
to theContext
'scounter
property