VScript - Our Internal DSL for Client Scripts
VScript logic is represented as JSON statement objects within an array. A statement object represents a single statement and may have sub statements A statement object begins with a single directive to identify it. There can be multiple keys in the root of a statement object, but the first identfies it unrecognised directives are ignored and case does not matter. Items are executed in the order they appear in the array with the exception of the BEFORE and AFTER statements which act as constructors and destructors.
The psuedo-language used by VScript is designed from the ground up to be extensible. The base language is very minimal and most functionality is implemented in our "standard" library. Script execution happens in five phases:
- BEFORE directive (if found) is executed.
- Main script is executed up until an INPUT directive is located.
- The Display Buffer is filled with in scope INPUT directives and execution is paused until the user performs an action.
- The action or actions the user chose is executed.
- AFTER directive (if found) is executed.
Variables
Variables in VScript can be strings, numbers, booleans or arrays. There are two classes of variable:
- Internal to Script
- Event State (Global)
To access or create internal variables you would use the VAR directive.
VAR
The var statement can be used to access the value or set the value of a variable. When setting a value it does not have to be a constant, you can also assign the return value of a function. Variables are created on first access, and are null before a value is assigned. Global variables are prefixed with '!'.
Example:
//To get a variable's value:
{"VAR": "product"} //would be null right now
//To set a variable's value:
{"VAR": "product", "IS": 23}
//
//A global variable:
{"VAR": "!CALL_ATTEMPTS"}
LOOKUP
The LOOKUP statement allows you to retrieve values from the global event.
Example:
{"LOOKUP": "user.name"} //returns the full name of the authorized user
Please note that any variables that refer to a product will only return valid values during the account loop.
List of Variables
See the Document titled Variables
Control Flow
BEFORE
With a BEFORE block you can run some code before a question is displayed Additionaly the special variable FROM is available with the previous question id
Example:
{
"BEFORE": [
... //any action(s)
]
}
AFTER
With an AFTER you can run some code when a question is "exited" by any means. The special variables FROM and DEST are available with the current question id and the destination.
Example:
{
"AFTER": [
... //any action(s)
]
}
IF..THEN..ELSE
IF takes an array with 3 values. Both the first and third arguments to the IF statement can be an array and they will be treated as boolean so you can create complex tests. If the array evaluates to true then the THEN statement is executed, otherwise the ELSE statement (if present) is executed. Both the THEN and ELSE statements can be a single statement (object) or a list of statements (array).
Example:
{
"IF": [{"VAR":"counter"}, "EQ", {"CONSTANT":"1"}]
}
// nesting conditions
{
"IF": [[{"VAR":"counter"},{"CONSTANT":"1"}],"AND",[{"VAR":"item"},{"CONSTANT":"Some Text"}]]
}
Here is a whiteboard more throroughly explaining the rule of three:
ELSE-IF
Similar to a switch you can also add alternative conditions to an IF statement. For this we use the ELSE-IF sub-directive inside an IF directive, which hold an array of tests and what to run if successful.
Example of ELSE-IF
{
"IF": [{"VAR":"counter"},"EQ", {"CONSTANT":"1"}],
"THEN": [
...
],
"ELSE-IF: [{
"CONDITION": [{"VAR":"counter"},"EQ", {"CONSTANT":"2"}],
"THEN": [
...
]
},{
"CONDITION": [{"VAR":"counter"},"EQ", {"CONSTANT":"3"}],
"THEN": [
...
]
}],
"ELSE": [
...
]
}
The ELSE block will only be executed if the main IF and all ELSE-IF statements are not true.
Operators available for IF and ELSE-IF
- GT -- The left value is Greater Than the right value
- GTE -- The left value is Greater Than or Equal to the right value
- LTE -- the left value is Less Than or Equal to the right value
- LT -- the left value is Less Than the right value
- EQ -- the left value is EQual to the right value
- NE -- the left value is Not Equal to the right value
- AND -- both inputs are true
- OR -- one or both inputs are true
- XOR -- only one input is true
- NOR -- both inputs are false
- NAND -- both inputs are not true
- IN -- the left value is contained in the array in the right value
- NOT_IN -- the left valie is not contained in the array in the right value
Operators can be nested to achieve a greater level of specificity.
##### Operator Nesting Example
javascript
{
"IF": [
[{"LOOKUP":"value"}, "GT", {"CONSTANT":3}],
"AND",
[{"LOOKUP":"value"}, "LTE", {"CONSTANT":7}]
]
}
This will evaluate to true if the {LOOKUP:value} is a number from 4 to 7.
WHILE Loop
The while loop allows a group of statements to be repeated
Example:
{
"WHILE": "truth statement like IF",
"DO": [
"statements to repeat"
]
}
SWITCH
Similar to a IF statement, the SWITCH statement performs an action or actions based on the value of a variable. You can specify an action to run if none of the provided choices match the variable's value by adding a default section to the SWITCH directive. Adding a default is optional.
Example:
[{
"SWITCH": {
"variable": "product",
"default": [
{
"move-to": "1.3.1"
}
],
"choices": [
{
"value": 1,
"action": {
"move-to": "1.4.1"
}
},{
"value": 2,
"actions": [
{
"VAR": "some_variable",
"IS": "some_value"
},
{
"move-to": "1.5.1"
}
]
}
]
}
}]
Reusable Code
Defining Functions
The DEFFUN directive allows the user to create reusable functions.
Example:
{
"DEFFUN": "new-function-name",
"parameters": [{"name":"first_param","type":"text"}, {"name":"second_param","type":"integer", "default": 0}],
"returns": "type or null for void",
"body": [
... //statements here, to return a value assign the special variable RETURN a value
]
}
Calling Functions
The FUNC directive is used to call a predefined function, it takes the name of the function to call and optionally an array of parameters.
Example:
{
"FUNC": "empty",
"parameters": [""]
}
Defining External APIs
The DEFAPI directive defines an external api. Support for formats other than JSON is limited at this point. JSON return values are collected using JSONPath to select the required fields. JSONPath is similar in nature to XPath and the documentation can be found here: http://goessner.net/articles/JsonPath/
Example:
{
"DEFAPI": "my-api-name",
"base": "https://your.service.url/api/v1/",
"global_headers": {
"X-Requested-By": "DataExchange",
"X-Vendor": {"var": "VENDOR_NAME"}
},
"endpoints": [
{
"name": "method-name",
"url": "/validate-rate",
"method": "POST",
"format": "json",
"parameters": ["SELECTED_RATE","SERVICE_ADDRESS_1","SERVICE_ADDRESS_2","SERVICE_CITY","SERVICE_ZIP","SERVICE_STATE"],
"data": [
{
"type": "object",
"name": "data",
"value": [{
"type": "value",
"name": "rate",
"value": "SELECTED_RATE"
},{
"type": "array",
"name": "service_addr",
"value": ["SERVICE_ADDRESS_1","SERVICE_ADDRESS_2","SERVICE_CITY","SERVICE_ZIP","SERVICE_STATE"]
}]
}
],
"return": {
"format": "json", //can be json, xml, raw
"error_path": "$.error", //depends on format, jsonpath, xmlpath or a search string i.e. ERROR
"return_value": {
"variable": "RATE_IS_VALID",
"path": "$.rate.is_valid"
}
}
}
]
}
Calling an API
The CALL statement is used call a previously defined API.
{ //using the API defined above
"CALL": {
"api": "my-api-name",
"method": "my-api-method",
"parameters": [...] //list of parameters
}
}
//This would sent a post request of a json document like:
{
"data": {
"rate": "The rate name or code",
"service_addr": ["1234 Main St", "Apt 2", "Englewood", "07631", "NJ"]
}
}
//And expect a response like:
{
"error": "null if no error or a message",
"rate": {
"is_valid": true
}
}
Input Directives
When an input directive is encountered in a scope the program will finish processing the current scope and then display any requested input directives. These are provided by the view layer and may not all be supported. Remember that unsupported directives are not an error, they are simply ignored.
BUTTON
The button directive is used to add simple buttons to a question. It takes the text of the button, an action statement(s) and an optional type. Types map to Bootstrap style, positive to "btn-success" and negative to "btn-danger".
Custom EzTPV Support
Not Supported at this time.
Simple yes/no example
[
{
"BUTTON": {
"text": "Yes",
"action": {
"move-to": "1.2.2"
},
"type": "positive"
}
},
{
"BUTTON": {
"text": "No",
"action": {
"move-to": "END_CALL",
"disposition": "DID_NOT_AGREE"
},
"type": "negative"
}
}
]
INPUT
Request input from the user and perform actions based on it. Supported types are: text, digits, number, custom (regex in validate key). Supports min/max limits, string length for text/digits/custom, value for number.
Custom EzTPV Support
Supported. In this mode only the "label" and "variable" parameters are supported. In addition, the "label" parameter is slightly changed to support mutliple languages, see Custom EzTPV Example below.
Example:
{
"INPUT": {
"type": "text",
"label": "placeholder text",
"validate": "custom put the regex here",
"limits": {
"min": 1,
"max": 10
},
"messages": {
"length": "Must be between 1 and 10 letters long",
"invalid": "Please enter a valid account number"
},
"variable": "where to store result / get initial value",
"action": {
... //action or actions to run once variable is filled
}
}
}
Custom EzTPV Example:
{
"INPUT": {
"label": {
"english": "placeholder text",
"spanish": "placeholder text in spanish"
},
"variable": "where to store result / get initial value",
}
}
IDENTIFICATION
Present an editor for the tpv agent to enter/verify identification(s) provided by the customer
Custom EzTPV Support
Not supported at this time.
Example:
{
"IDENTIFICATION": {
"action": {
... // action or actions to run once id(s) are valid, saved and continue clicked
}
}
}
CHOOSE
Request input from the user in the form of a dropdown or list select. The optional multiple key controls whether the interface is shown as a dropdown or list.
Custom EzTPV Support
Supported. In this mode only the "options" and "variable" parameters are supported
Example:
{
"CHOOSE": {
"multiple": false,
"options": [
{
"text": "Option 1",
"value": 1
},
{
"text": "Option 2",
"value": 2
}
],
"variable": "where to store / get initial",
"action": {
//action
}
}
}
DIALOG
The dialog directive is used to display a dialog to the end user. it takes an optional title, the dialog text and an array of buttons. Buttons consist of a text field and an action field, the action may either be a statement or an array of statements. The dialog is closed after button press regardless.
Custom EzTPV Support
Not Supported at this time.
Example:
{
"DIALOG": {
"text": "Unable to locate record id {RECORD_ID}",
"buttons": [
{
"text": "Retry",
"action": "CLOSE_DIALOG"
},
{
"text": "Skip",
"action": {
"move-to": "1.2.1"
}
}
]
}
}
ADDRESS
The address directive is used to display an address to the end user with the ability to edit it. It you do not want the address to be edited you must use one of the address variables available for the script and not this direcive. Address directive consists of a title which should be the simplest description for this address, i.e. Service or Billing and the LOOKUP variable to get and store the address to and the action to perform once the address is saved or verified.
Custom EzTPV Support
Supported, only the "variable" parameter is supported in this environment.
Example
{
"ADDRESS": {
"title": "Billing",
"variable": "account.bill_address",
"action": [
{
"move-to": "1.2.1"
}
]
}
}
Array Directives
Array:Create
Creates an empty array.
{
"VAR": "!myArray",
"IS": {"ARRAY:CREATE": null}
}
Array:Length
Returns the number of elements in the passed array
{
"ARRAY:LENGTH": {"VAR":"!myArray}
}
Array:Push
Pushes a value onto the end of the specified array.
{
"ARRAY:PUSH": "Test",
"ARRAY": {"VAR": "!myArray}
}
Array:Pop
Returns and removes the last item from the array.
{
"ARRAY:POP": {"VAR": "!myArray"}
}
Array:At
Allows reading and writing arrays at a specified index.
// write
{
"ARRAY:AT": {"VAR": "!myArray"},
"INDEX": 0, // array indexing starts at 0 for the first element
"IS": "Test"
}
//read
{
"ARRAY:AT": {"VAR": "!myArray"},
"INDEX": 0, // array indexing starts at 0 for the first element
}
Array:Concat
Combines the passed arrays into a single array and returns it.
{
"ARRAY:CONCAT": [[1, 2, 3], [4, 5]]
} // returns [1,2,3,4,5]
Array:Unique
Returns a copy of the passed array with duplicate items removed.
{
"ARRAY:UNIQUE": [1,2, 2, 3, 4, 5, 5]
} // returns [1,2,3,4,5]
Array:Slice
Returns a portion of an array.
{
"ARRAY:SLICE": [1, 2, 3, 4, 5],
"START": 1,
"END": 3
} // returns [2,3]
{
"ARRAY:SLICE": [1, 2, 3, 4, 5],
// The start index parameter is optional
"END": 3
} // returns [1,2,3]
Array:Contains
Returns true if the specified array contains the specified item.
{
"ARRAY:CONTAINS": 3,
"ARRAY": [1,2,3]
} // true
{
"ARRAY:CONTAINS": 4,
"ARRAY": [1,2,3]
} // false
Array:Join
Joins the elements of the array with the specified glue string.
{
"ARRAY:JOIN": ["Hello", "World"],
"WITH": ", "
} // returns "Hello, World"
Array:Map
{
"ARRAY:MAP": [1, 2, 3],
"DO": [
{"*": [{"VAR": "!!value"}, 2]}
]
} // returns [2, 4, 6]
Array:Reduce
{
"ARRAY:REDUCE": [1, 2, 3],
"DO": [
{"+": [{"VAR":"!!accumulator"}, {"VAR":"!!currentValue"}]}
]
} // returns 6
Array:Reverse
Returns a copy of the array that is reversed.
{
"ARRAY:REVERSE": [1,2,3]
} // returns [3,2,1]
Date Directives
NOW
This directive returns the current Date and Time.
{
"NOW": null
}
DATE-PARSE
This directive validates the passed date/time and returns it. If the date is not valid this directive will return false.
{
"DATE-PARSE": "2020-13-01"
}
// returns false because there is no 13th month
DAY-OF-WEEK
This directive returns a numerical value representing what day of the week the passed date is from 1 to 7 with Monday = 1 and Sunday = 7.
{
"DAY-OF-WEEK": "2020-02-14"
}
// returns 5 because Valentine's day 2020 was on a Friday
DATE-ADD
This directive allows manipulating the passed date.
{
"DATE-ADD": "2020-01-01",
"UNIT": "days",
"COUNT": 9
}
// would return 2020-01-10
// accepted values for UNIT are: years, months, weeks, business-days, days, hours, minutes, seconds
Note: the business-days option only confirms the resulting date is not on a weekend.
DATE-SUB
This directive allows manipulating the passed date.
{
"DATE-SUB": "2020-01-10",
"UNIT": "days",
"COUNT": 9
}
// would return 2020-01-01
// accepted values for UNIT are: years, months, weeks, business-days, days, hours, minutes, seconds
Note: the business-days option only confirms the resulting date is not on a weekend.
DATE-IS-HOLIDAY
This directive will return true if the specified date is a holiday on the provided list.
Valid lists currently are: federal
The LIST option is optional and defaults to federal
{
"DATE-IS-HOLIDAY": "2020-02-14",
"LIST": "federal"
}
DATE-FORMAT
This directive transforms the passed date into the format specified. Format string is specified in the table here.
{
"DATE-FORMAT": "2020-05-20",
"AS": "MM-DD-YYYY"
}
DATE-DIFF
This directive returns the difference between two dates in days.
{
"DATE-DIFF": {
"FROM": "2020-01-01",
"TO": "2020-01-10"
}
}
// returns -9 because the FROM date is 9 days before the TO date
DATE-IS-SAME
This directive compares two dates and returns true if they are the same day.
{
"DATE-IS-SAME": {
"A": "2020-02-01",
"B": "2020-02-01"
}
} // true
{
"DATE-IS-SAME": {
"A": "2020-02-01",
"B": "2020-02-02"
}
} // false
DATE-IS-BEFORE
This directive compares two dates and returns true if A comes before B.
{
"DATE-IS-BEFORE": {
"A": "2020-02-01",
"B": "2020-02-02"
}
} // true
{
"DATE-IS-BEFORE": {
"A": "2020-02-11",
"B": "2020-02-02"
}
} // false
DATE-IS-AFTER
This directive compares two dates and returns true if A comes after B
{
"DATE-IS-AFTER": {
"A": "2020-02-01",
"B": "2020-02-02"
}
} // false
{
"DATE-IS-AFTER": {
"A": "2020-02-10",
"B": "2020-02-02"
}
} // true
DATE-IS-SAME-OR-BEFORE
This directive compares two dates and returns true if A is the same day as B or if A comes before B
{
"DATE-IS-SAME-OR-BEFORE": {
"A": "2020-02-01",
"B": "2020-02-02"
}
} //true
{
"DATE-IS-SAME-OR-BEFORE": {
"A": "2020-02-01",
"B": "2020-02-01"
}
} // true
{
"DATE-IS-SAME-OR-BEFORE": {
"A": "2020-02-30",
"B": "2020-02-01
}
} // false
DATE-IS-SAME-OR-AFTER
This directive compares two dates and returns true if A is the same day as B or A comes after B
{
"DATE-IS-SAME-OR-AFTER": {
"A": "2020-02-01",
"B": "2020-02-01"
}
} // true
{
"DATE-IS-SAME-OR-AFTER": {
"A": "2020-02-02",
"B": "2020-02-01"
}
} // true
{
"DATE-IS-SAME-OR-AFTER": {
"A": "2020-02-01",
"B": "2020-02-02"
}
} //false
Other Directives
IS-EMPTY
Reduces it's parameter and returns true if it is an empty type/value, i.e. null, undefined, [], {}, '', 0
NOT-EMPTY
Reduces it's parameter and returns false if it is an empty type/value, i.e. null, undefined, [], {}, '', 0
MOVE-TO
The move-to directive's value is the num-dot-num-dot-num question id to go to.
DO-CREDIT-CHECK
This directive initiates the brand specific actions necessary to perform a credit check and then runs its "THEN" directives.
{
"DO-CREDIT-CHECK": null,
"THEN": [
...
]
}
START-WARM-TRANSFER
This directive converts the current call into a conference and attempts to add a third party to that conference.
{
"START-WARM-TRANSFER": "number to call"
}
CALL-END
The call-end directive's value is the id of a preset disposition or null to allow choosing an option. This occurs without prompting the user.
CALL-RETRY
This directive is intended to be used during disposition resolution. The value of the directive is ignored and will cause the callback to retry.
CALL-HANGUP
This directive takes no parameter (pass null) and simply causes the phone system to disconnect the call after prompting the user.
CALL-INITIATE
This directive's value should be a phone number in E.164 format. The phone system will disconnect any active call and then dial the passed number.
DISPOSITION
The disposition directive is currently the same as the CALL-END directive with the exception that is prompts the user before disconnecting the call and the disposition parameter is the disposition id NOT the name.
REGEX-TEST
The regex-test directive checks whether the target string matches the provided regular expression. It returns a Boolean value. The FLAGS option is optional and can be left out entirely, recognized values are:
- g - global match; find all matches rather than stopping after the first match
- i - ignore case
- m - multiline; treat beginning and end characters (^ and $) as working over multiple lines (i.e., match the beginning or end of each line (delimited by \n or \r), not only the very beginning or end of the whole input string)
- u - unicode; treat pattern as a sequence of unicode code points
- y - sticky; matches only from the index of the last match of this regular expression in the target string (and does not attempt to match from any later indexes).
Example:
{
"REGEX-TEST": "regular expression",
"FLAGS": "i",
"TARGET": {"var": "product"}
}
Math
All these math functions take an array as a parameter and will work with any number of parameters. Given the array [1, 2, 3, 4], you can expect the following to occur:
(((1 op 2) op 3) op 4)
All values or statements must resolve to a number or you will get NaN as a result.
Example:
{"+": array} // add the array items together
{"-": array} // subtract the array items from the others
{"*": array} // multiply the array items in order
{"/": array} // divide the array items in order
{"%": array} // return the modulus of the array in order
{"^": array} // Exponent operator, raise the first array item by the second.
These unary operators take the name of a variable as the only parameter.
//Increment Value by 1
{"inc": varname}
//Decrement Value by 1
{"dec": varname}
String Directives
String Coerce
All parameters will be fully resolved and converted to strings then they will be joined by a space. The array [1,2,3] would return the string "1 2 3". The seperator param is optional and defaults to space, leading and trailing whitespace is removed after coercion.
Example:
{"$": array, sep: ' '}
Trim
Removes extra whitespace from the ends of the specified string
Example:
{"string:trim": "test string "}
// will return "test string"
To Uppercase
Converts all characters in a string to their uppercase equivalents.
Example:
{"string:uppercase": "test"}
// will return "TEST"
To Lowercase
Converts all characters in a string to their lowercase equivalents.
Example:
{"string:lowercase": "Test"}
// will return "test"
Get String Length
Counts the number of characters in a string
Example:
{"string:length": "test"}
// will return 4
Starts With
Tests if a string begins with another string
Example:
{"string:starts": "test string", "with": "test"}
// will return true
{"string:starts": "test string", "with": "jest"}
// will return false
Ends With
Tests if a string ends with another string
Example:
{"string:ends": "test string", "with": "string"}
// will return true
{"string:ends": "test string", "with": "strung"}
// will return false
Replace
Search for and replace text in a string
Example:
{"string:replace": "test", "in": "test string", "with": "jest"}
// will return "jest string"
Includes Text
Tests if a string includes some text anywhere in the string
Example:
{"string:includes": "test", "in":"this is a test string"}
// will return true
Template
Build a more complex string from a template and parameters
Example:
{"string:template": "{0} your test {1}", "with": ["Billy", "passed"]}
// will return "Billy your test passed"
Custom Field Validation Functions
[ // This simple example simply checks if the input is empty and shows how to return if it is valid or not
{
"IF": [
{
"IS-EMPTY": {
"VAR": "!input"
}
},
"eq",
true
],
"THEN": [
{
"VAR": "!valid",
"IS": false
}
],
"ELSE": [
{
"VAR": "!valid",
"IS": true
}
]
}
]
Example: Disposition Resolution to Call Back Customer
Disposition resolutions should return true to end the interaction.
!CALL_ATTEMPTS
is a special variable indicating the number of outbound call attempts in the current session.!READY_FOR_CX
is a special variable indicating if the agent portion of the call is complete.
[
{
"IF":[
[{ "VAR":"!CALL_ATTEMPTS" }, "LT", 4],
'AND',
{ "VAR": "!READY_FOR_CX" }
],
"THEN":[
{
"CALL-HANGUP":true
},
{
"CALL-RETRY":true
},
{
"VAR":"RETURN",
"IS":false
}
],
"ELSE":[
{
"VAR":"RETURN",
"IS":true
}
]
}
]
Advanced Example: Fibonacci Number Finder
This function takes a single parameter which is the digit number to return. It is not fast, expect large values to take some time or potentially overflow the execution stack.
[
{
"deffun":"fib",
"p":[
{
"name":"n"
}
],
"r":"number",
"b":[
{
"if":[
{
"var":"n"
},
"LTE",
2
],
"then":{
"var":"RETURN",
"is":1
},
"else":{
"var":"RETURN",
"is":{
"function":"add",
"p":[
{
"function":"fib",
"p":[
{
"function":"sub",
"p":[
{
"var":"n"
},
1
]
}
]
},
{
"function":"fib",
"p":[
{
"function":"sub",
"p":[
{
"var":"n"
},
2
]
}
]
}
]
}
}
}
]
}
]