Expressions and pattern matching Pub

There are three kinds of expressions available in different contexts:

  • value expressions, used whenever you're computing values, like for a $send
  • match expressions, used when you're trying to match a value, like in $expect or $when`
  • conditions, used in $if

Value expressions

These are used when you're computing values to pass to messages and generally work as you would expect. Here are some examples. See more working tests with supported expressions at expr story++ and expr spec++.

Principles

The expressions are meant to feel natural, so simple stuff like 1 + 2 > 5 works as expected (yeah, false). You can have named operators, such as 1 contains 5, as the language pretends to be human friendly, for instance $expect (result is number).

Also, you can throw in JS expressions any time you need extra expression power or access to basics already provided there, by using a js: prefix, see examples below. Note that this is best avoided, as JS is much slower.

Operator precedence:

Associativity is left to right and precedence is as follows:

  • multiplication: *,/
  • addition etc: +,-,||,|
  • conditionals: >,<,>=,<=,==,!=,~=,~path,?=,is,not,in,notIn,contains,containsNot
  • booleans: and,or,xor
  • array ops: map,flatMap,flatten,filter,exists,fold
  • typecast: as

There are some weirdnesses, so for instance 1 + 2 is 3 works ok, but 1 > 2 is false doesn't in some contexts, although you may have expected it to... so when outside the bounds of normal, either stay conservative and don't skimp on brackets (1 > 2) is false and/or double-check things in the fiddle - it's super-easy to!

Quick expression guide

This is a quick reference guide for expressions - there are lots of examples with comments in expr story++.

// constants and values
$send ctx.set (
  constants = 321, 
  addition = last+first,
  jsonBlock={
    "accountNumber": "1729534",
    "start_num": 123
	},
  qualified = jsonBlock.accountNumber,
  interpolation="is${1+2}",
  url="${HOST}/search?q=dieselapps",
  builtInFunctions1 = sizeOf(x=cart.items),
  builtInFunctions2 = typeOf(x=cart.items),
  
  anArray      = [1,"a",3],
  anotherArray = [5 .. (99/2)], // will generate an array easily
  elem1        = anArray[1],

  // json operations
  cart=cart || {id:customer, items:[] } ,
  cart=cart+{items:{sku : sku, quantity : quantity} } ,
  
  sku1 = cart["items"].sku,
  sku2 = cart[someVariableFieldName].subfield,
  
  // embedded Javascript expressions
  simpleJs = js:wix.diesel.env ,
  res78=js:cart.items[0].sku ,
  
  res40=js:email.replace(/(\w+)@(\w+).com/, "$1") ,
  
  later=js:{var d = new Date(); d.setSeconds(d.getSeconds() + 10); d.toISOString();} ,

  a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1),
  
  a285 = [1 .. sizeOfStuff] map (x=> x + 1),
  
  isThere = x in [1,2,3],
  notThere = x not in [1,2,3],

  size = size || 4     // JS style if defined OR default. WARN: Do not use || for "or", use "or" for boolean expr
)

$when something.hapenned
=> $if (AUTH ~= "Basic[^ ].*") (AUTH = "Basic " + AUTH[5..])

$when query.elk (search, size?) // add _id to the set of results from an elasticsearch query
=> elk.query(index=TABLE, search, size = size || 9)
=> (payload = payload.hits.hits map (z => z._source.report + {_id:z._id} ))


Basic types

The built-in types understood are:

  • boolean
  • number
  • string
  • json
  • array
  • date

For each of these types, the operators behave in a straight forward manner.

Using Types

The expressions are type-aware. Common sense type casts are done (string plus number is a string).

Use as to force a typecast 5 as string.

Use typeOf to get the type of something.

Use is or not to check the type of something age is Number.

Custom types and classes can be defined and used, see Domain Modelling. All defined types are derived from Json and look at feel like Json.

Strings

Strings are simple constants, with escaping and natural inline expressions:

  • simple special characters:
    • \b, \n, \t, \r, \f
  • anything preceded by \ is escaped, and not interpreted
    • classic escaped characthers: \\, \", \'
    • this is a difference from JAva, where only certain escape sequences are valid - here anything preceded by \ is escaped
  • natural interpolation "${name}" is expanded - note that the curly braces are mandatory.
    • escaping interpolation: "$${name}" is not expanded - it's escaped

Multi-line strings:

Use """ to delimitate multi-line strings - same rules above apply. This is quite good for simple templates, as interpolation works anywhere.

Arrays

Arrays can be statically defined, generated or come from a function/method:

$val arr1 = ["345", "asdf"]
$val arr2 = [5 .. 8]
$val arr3 = split (a="1,2,3,4", separator=",")

You can test if an element is in or not in an array:

$when test.diesel.isin(x in ["123", "234"], y not in [1,2])

Dates

Dates are ISO format, there is a built-in function now() and the operators work intuitively. Here are some examples of date-related expressions. This is equivalent to this js expression: js:new Date().toISOString().

$var date = now()
$var future = now() + "5 seconds"
$var sameFuture = now() + "5s"
$var past = now() - "5s"

$val millis = toMillis(a=now())
$val alsoNow = millis as Date

Operators:

  • + you can add a date with a duration
  • - you can subtract a date with a duration
  • you can compare dates (==, > etc)
  • toMillis () can transform a date into milliseconds
  • "as Date" can transform a number of milliseconds in a date,

Conditions

Conditions are used in $ifc and here are some examples:

=> if (not false) (b=b+1, a02)
=> if (not a ~= b ) (b=b+1, a07)
=> if ( a < b or a > b ) (b=b+1, a12)
=> if ( a < b and (a > b or a < b) ) (b=b+1, a13)

There is also another if, using the match expressions: $ifm. The default for $if is the $ifc with boolean expressions.

See expr story++, which is a live test of all expressions supported.

Boolean operators

Besides the regular comparison operators, we have these.

Array operators

$when test.diesel.isin(x in ["123", "234"], y not in [1,2])

$expect (["123", "234"] contains x, [1,2] containsNot y)

Note that you can't use contains with $when - the limitation there is that the named parameter has to be first element in the expression.

contains and containsNot

These are type-aware:

  • for String they simply look if the string contains or not the substring
  • for Array they compare the entire element
  • for JSON or Map, they look if there is a key with the name

Complex types

Complex types can be defined and used like so:

$class DieselTestStudent (name, address:DieselTestAddress)
$class DieselTestAddress (street, no)

$send ctx.echo(m = new DieselTestStudent {
  name: "John",
  address: new DieselTestAddress {
    street: "Baker St",
    no: "121b"
  }
})

See Domain Modelling. All defined types are derived from Json and look at feel like Json.

Object functions

map will work on objects too - it takes a function with one argument which will be an object with two attributes: key and value:

$val query={key:"elf", age:1000}

$send ctx.set (y = query map (x => x.key + ":\"" + x.value + "\""))
$send ctx.echo (x = y mkString " AND ")

Accessing objects

Objects are JSON and are used much like a sensical Javascript would:

$val x = {a:1,b:"asdf"}
$val y = {a:1,b:"asdf"}

$send ctx.echo(z = x + y) //  merge two objects

$val n = x.a

Arrays

Arrays (or lists) are dynamic in size and look and feel much like in JS:

  a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1)
  x in [1,2]
  x not in [1,2]
  [3,4] contains y
  [1,2] containsNot y
  sizeOf([1,2,3])

Array operators

The array operations are: map,flatMap,flatten,filter,exists. The right side must be a lambda function:

  • map will transform the values
  • flatten will flatten an array of arrays into a single array
  • flatMap transform and flatten
  • filter keeps only the items meeting the predicate
  • exists true if an element meets the criteria
  • mkString takes a right-hand constant and will create a nice string
  • fold start from payload and reduce the array using the lambda
  • indexBy turn an array of objects into an object indexed by a key
  a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1)
  a284 = [1,2] + [3] filter (x=> x > 1) map (x=> x + 1)
$send ctx.set (payload = 0)
$send ctx.set (z51 = [1,2,3,4] fold x => (payload + x))
$expect (z51 is 10)
$val z = (activeAlarms indexBy x => x.deviceId)

$val zz = (activeAlarms indexBy "deviceId")

Note that map can also be applied to objects, in which case the x will be a tuple with {key, value} as in the expression below.

$val AUTH_PERMS = payload.roles indexBy "name" map x => (x.value.permissions + {name:x.key}) indexBy "name"

as - typecasts

You can typecast an expression, to change the type or make sure it's of a certain type. The right side expression can be:

  • a name representing a basic type x as number
  • a string constant representing an extended type `x as "application/pdf")
  • a name representing a class in the current domain x as Student
  • if none of the above works, the right side is then evaluated as an expression and we'll try again: x as avalue where avalue is "Student" will be re-evaluated as x as Student

asAttrs

This is a special transformation that can be applied to a json document, to flatten it into a list of parameters that can be used to call a message:

$when something.hapenned ()
=> (j = {a:1, b:2})
=> something.else (j.asAttrs)

j would be flattened and a and b would be parameters to the new message something.else.

Properties

You can get system properties with diesel.props.system which will populate the payload with an object containing all system properties, IF running in self-hosted mode.

Likewise, you can load a property file with diesel.props.file only in self-hosted mode. Or a JSON file via diesel.props.jsonFile message, also only in self-hosted mode.

Rule pattern matching

Matching rules for decomposition is done according to these pattern matching rules.

Simple match

$when dieseltest.rule1(a,b)
=> (rule1ab=a+b)

This matches the message by name and list of values, no ambiguity.

Regex match

You can match the entity with a * - this will match a.sendtest as well as a.b.c.sendtest:

$when *.sendtest
=> (rule12=true)

Or, you can match the action with a * - but note that this matches only the last word, i.e. the "action" - so it will match dieseltestsendtest.dothis, but not dieseltestsendtest.a.b.c.dothis:

$when dieseltestsendtest.*
=> (rule16=true)

$when dieseltest.send.multiple.*
=> (rule18=true)

JS Full match

You can use a full and more complex js-like match for both entity and action together:

$when /dieseltest.*/
=> (rule19=true)

$when /dieseltest\..*/
=> (rule19a=true)

The second rule above will match dieseltest.a.b.c.action but not dieseltests.a.b.c.action.

Conditions

...can be builtinto the list of arguments, like diesel.isLocalhost is false below - that is not an argument that is passed in, but a general condition:

$when diesel.realm.configure(realm is "devnetlinq", diesel.isLocalhost is false)

$expect and matches

These are match expressions, used in $expect, $when and $mock. Their purpose is to match a parameter by name with a value expression. Some can also extract more parameters (say using regular expressions wiht named groups).

The full options around the expect are: $expect not IF MSG (matches) IF

  • not does what you think it does
  • IF you can add conditions - when this expect should be taken into account
  • optional MSG is a message, will test if that message is in th epreviuos trace
  • the matches are evaluated
  • you can put an IF at the end, makes for better reading, use just one place for IF, not both

Note that the expressions in the expect are NOT regular conditional expressions, but match expressions, which are quite alike but not the same thing...

$expect (name) // name is present
$expect (name == "Jane") // equal
$expect (name is "Jane") // equal
$expect (subId ~= "sub[0-9]*") // matches
$expect (subId matches "sub[0-9]*") // matches
$expect (subId matches "sub[0-9]*") // matches

Type checking

$expect (age is Number)
$expect (persons is Array)
$expect (name is String)
$expect (x1 is Student) // class defined in a domain model
$expect (x1 is Json)

// extract more parameters - will also populate an acctId when matching the regex
$mock diesel.rest (path ~= "/getAccount1/(?<acctId>\\d+)") => (payload={
    "status": "ok",
    "someStats": "67",
    "accountId" : acctId
})

Note that while `diesel.rest` is a reserved message, you can use this implicit group parsing into variable in any message:

$when expr.captureGroups (path ~= "/getAccount1/(?<acctId>\\d+)") => (payload={
    "accountId" : acctId
})

To avoid having to declare a rule just to do regex group decomposition, there is a `ctx.regex`:

$send ctx.set (payload = "/getAccount1/99")
$send ctx.regex(regex = "/getAccount1/(?<acctId3>\\d+)")
$expect (acctId3 is "99")

Or if you don't want to overwrite payload:
$send ctx.set (payload = "pa")
$send ctx.regex(regex = "/getAccount1/(?<acctId4>\\d+)", payload="/getAccount1/999")
$expect (acctId4 is "999")
$expect (payload is "pa")

Negative and other ideas:
$send ctx.set(d1 = now() - "1 second", d2 = now() + "1 second")
$expect (d1 < d2)
$expect not (d1 > d2)
$expect ((d1 > d2) is false)

Here's some other variants, with a more intuitive language:

$expect (name is empty) // name is NOT present OR is present but empty (type-aware)
$expect (name is defined) // name is present but may be empty
$expect (name not defined) // name is NOT present
$expect (name not empty) // name is present AND has some value non-empty (type-aware)

Note that you can also use other values for comparison - so take care to not use common reserved words for values:

$val jane = new Person{name:"Jane"}
$expect (guest is jane) // name is NOT present

Note that $expect uses a match expression, so you can use a list of parameters and the result is composed:

$expect (name is "Jane", subId is defined) // both must match

Built-in functions

There are a few built-in functions you can use in a variety of situations in a common manner x=sizeOf(anArray)

NOTE that these are sensitive to the order of arguments, unlike messages!

uuid

Returns a UUID using Mongo helpers (see object id in Mongo) - it is shorter than other UUIDs, when you don't need to encode the world!

now

Returns current time in ISO format

today

Returns current time in ISO format, date portion only

hashcode

Returns hashcode of argument.

cmp

Compare two arguments, returning -1, 0, 1: cmp(op=">", a, b)

sprintf

Classic string format: sprintf(format="%d", value=x)

matches, replaceAll, replaceFirst

Test if first parm matches second - equivalent of the matches, replaceAll and replaceFirst Java/Scala String method. The name of the parameters does not matter, the order does.

$send ctx.set (re176=matches(a="abc", b="[abc]+"))
$expect (re176 is true)

$send ctx.set (re176=matches(a="abc", b="[bc]+"))
$expect (re176 is false)

$send ctx.set (re176=replaceAll(a="abc", b="[bc]+", c="xx"))
$expect (re176 is "axx")

enc

Encrypt the argument.

urlencode

UTF-8 URL encoder.

trim, toUpper, toLower

Trim a string. Convert to upper or lower.

split

Split a string into array: split(string, regex)

rangeList

Create a list from a range. The range is exclusive: rangeList(from=1, to=3) would be [1,2]

sizeOf

Calculates the size of an array/list (number of elements) or of an object (number of attributes) or length of a string.

$val  builtInFunctions1 = sizeOf(x=cart.items),
$val  builtInFunctions2 = typeOf(x=cart.items),

typeOf

Returns the type of a parameter: typeOf(x=4)

See as operator as well, for typecasts.

accessor

Use it to access a JSON object with an expression:

$send ctx.echo(x5=accessor(obj={a:{a:{a:1}}, b:{b:{b:2}}}, accessor="a.a.a"))

flatten

Flatten a lists of lists (or array of arrays).

accessor

toMillis

Convert an ISO Z-datetime to milliseconds.

math functions

Are: math.min, math.max, math.average, math.sum

=> (theBigOne = math.max (x = [3,2,1]))

They take an array of numbers.


Was this useful?    

By: Razie | 2017-08-31 .. 2023-10-03 | Tags: academy , reference


Viewed 883 times ( | History | Print ) this page.

You need to log in to post a comment!

© Copyright DieselApps, 2012-2024, all rights reserved.