Copyright © 2019 the Contributors to the Dialogue Manager Programming Language (DMPL) Specification, published by the Conversational Interfaces Working Group under the W3C Community Final Specification Agreement (FSA). A human-readable summary is available.
This specification defines the syntax and semantics of a dialogue manager programming language (DMPL). The user specifies the states (i.e. fluents), how they change (i.e. actions), and which are preferred (i.e. utility). These three components characterize a task, so we call DMPL a task-oriented programming language. The interpreter of the language should resolve the task by identifying and executing actions that maximize utility.
This specification was published by the Conversational Interfaces Working Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Final Specification Agreement (FSA) other conditions apply. Learn more about W3C Community and Business Groups.
If you wish to make comments regarding this document, please send them to public-conv@w3.org (subscribe, archives).
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
This document is limited to specifying the syntax and semantics for a task-oriented language for interactive behavior. One example application of DMPL is an authoring tool to be used on a regular basis by a non-technical content writer. Another use-case of DMPL is exporting and importing dialogue content authored by writers from different web platforms. Technologies closely related to these use-cases are in scope.
DMPL is a task-oriented programming language, which may be better explained by extending Microsoft's Function Programming vs. Imperative Programming table, shown below.
| Imperative | Functional | Task-Oriented | |
|---|---|---|---|
| Programmer Focus | How to perform tasks (algorithms) and how to track changes in state | What information is desired and what transformations are required | Why an action is taken and how information changes |
| State Changes | Important | Non-existant (for purely functional languages) | Important |
| Order of Execution | Programmer is mostly responsible | Hybrid resposibility | Compiler is mostly responsible |
| Loops | For/While, Recursion | Recursion | None, loops are implicit |
| Primary Manipulation Unit | Instances of structures or classes | Functions | Utility |
This section of the document defines the types used within this specification using EBNF notation with corresponding railroad diagrams.
literal
A literal is a terminal atom that may be assigned to a variable,
used in a dictionary, or used as an operand to build an expression.
literal::=string|number|boolean| 'null'
boolean
A boolean may take on values true or false.
boolean::= 'true' | 'false'
name
A name is a sequence of characters conforming to those in the XML standard.
name::= [ http://www.w3.org/TR/xml-names/#NT-NCName ]
quote_start
A quote_start is the starting quotation mark, which indicates the beginning of a string.
quote_start::= '"' '`'
quote_close
A quote_close is the ending quotation mark, which indicates the termination of a string.
quote_close::= '`' '"'
string
A string literal represents a finite sequence of characters.
It is surrounded by starting and ending quotation marks.
string::=quote_startnamequote_close
"`hello`"
digit
A digit represents a real valued integer in the range [0-9].
digit::= [0-9]
number
A number literal may represent either a positive or negative decimal value.
number::= '-'? (digit+ ( '.'digit* )? | '.'digit+ )
0.2
variable
A variable is a named reference to the result of evaluating an expression.
variable:: = '"'name'"'
"num_correct_answers" // => 3
variable should not be confused with a string, which uses backticks (`) like so:
"`num_correct_answers`" // => "num_correct_answers"
dictionary
A dictionary is a structure that maps data in key-value pairs.
The keys and values are all evaluated.
The keys can be either strings or variables that evaluate to strings.
dictionary: : = '{' (string|variable) ':'expression( ',' (string|variable) ':'expression)* '}'
{"`age`": "x", "`score`": 10, "`name`": "`Joe`"}
expression
An expression may be evaluated, and that result is stored in memory to be referenced later.
An expression may be denoted by a JSON array, in which case it is also called a function.
The first element of the array represents the operator of the function.
The operator can be either a string or a variable that evaluates to a string.
All remaining elements of the array constitute the operands.
expression::=literal|variable|dictionary| '[' (literal|variable) ( ','expression)* ']'
| Return Type | Operator | Example |
|---|---|---|
string |
|
|
number |
|
|
number |
|
|
number |
|
|
number |
|
|
number |
|
|
| list | |
|
| list | |
|
| list | |
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
boolean |
|
|
| dynamic | |
|
| dynamic | |
|
statement
A statement may be a non-terminal such as do or fork, or a terminal such as effect.
Optionally, statements may contain condition, await, or once flags.
statement::= '{' (condition',' )? (await',' )? (once',' )? (effect|do|fork) '}'
condition
A condition is a boolean expression that runs the statement if the expression evaluates to true.
A condition check takes priority over other parts of the statement.
If a statement does not explicitly contain a condition, then that statement's condition is trivially true.
condition: : = '"if"' ':'expression
"if": [">", "num_wrong_answers", 3]
await
An await blocks execution until a boolean expression evaluates to true.
await::= '"await"' ':'expression
"await": ["input"]
once
A once flag specified whether the statement can only be run once.
By default, the once flag is set to false.
once::= '"once"' ':'boolean
"once": true
effect
An effect is a terminal node, meaning it does not produce more statements. There are 6 types of effects.
effect::=act|set|def|run|use|pop
act
An act represents an action specified by evaluating the expression.
The result is interpreted by a client.
For example, the payload of the act statement may be represented using
Behavior Markup Language (BML).
The client is then responsible for realizing the behavior defined in the act message.
act::= '"@act"' ':'expression
"@act": {
"`object`": "`tutor`",
"`action`": "`say`",
"`params`": {"`intent`": "`greeting`"}
}
set
A set updates the values of the variables,
where the names of the variables and the updated values are specified by evaluating the corresponding expressions.
The expression specifying the names of the variables is allowed to contain variables
(although static code analysis and possible optimizations would be more difficult),
and must evaluate to a string, a list consisting of only strings, or a dictionary consisting of only strings.
set::= '"@set"' ':'expression',' '"val"' ':'expression
"@set": "`is_user_greeted`", "val": true
"@set": ["", "`is_user_greeted`", "`num_questions`"], "val": ["", true, 7]
def
A def defines a new expression operator that can be used later in the code.
The signature of the new expression operator is specified by an expression that must evaluate to a list of strings,
which maps to the operator name followed by the argument names.
These argument names are valid only for this scope, and may shadow global variables.
The result is returned by a pop statement.
def::= '"@def"' ':'expression',' '"val"' ':'statement
"@def": ["", "`inc`", "`x`"], "val": {"@pop": ["+", 1, "x"]}
run
A run calls DMPL code in another component, optionally passing in arguments.
The name of the called component is specified by an expression that must evaluate to a string.
The current variables stored in memory are remembered later.
run::= '"@run"' ':'expression( ',' '"args"' ':'expression)?
"@run": "`Outro`"
"@run": "`Question`", "args": ["", "`What's the largest planet?`", "`Jupiter`"]
use
A use imports variables and expression operators defined in another component.
The name of the imported component is specified by an expression that must evaluate to a string.
The variable and expression operator names can be optionally specified, or all variables and expression operators will be imported.
It's similar to from os import path notation in Python.
use::= '"@use"' ':'expression( ',' '"import"' ':'expression)?
"@use": "`MathExpressions`", "import": ["", "`inc`", "`square`", "`exp`"]
pop
A pop quits execution on the current DMPL code, pops the fluent-state from the stack, and resumes execution from the previous run location.
pop::= '"@pop"' ':'expression
"@pop": true
do
A do statement specifies a list of statements to be executed.
do::= '"@do"' ':' '[' (statement( ','statement)* )? ']'
"@do": [
{"@act": "a"},
{"@act": "b"},
{"@act": "c"}
]
fork
A fork statement specifies the branch-like behavior of the list of statements that follow.
Only one is chosen, and the strategy to pick one may be provided using the scheme attribute.
By default, a greedy scheme is used, meaning the first statement whose condition is met will be chosen.
More complicated schemes, such as depth-first-search or Monte Carlo Tree Search may be indicated,
leaving the implementation up to the interpreter.
fork::= '"@fork"' ':' '[' (statement( ','statement)* )? ']' ( ','scheme)?
When no scheme is provided, a fork is essentially the traditional if/elif/else logical flow:
"@fork": [
{"if": "is_user_greeted", "@act": "a"},
{"if": [">", "num_wrong_answers", 3], "@act": "b"},
{"@act": "c"}
]
Providing a scheme allows stochastic behavior to take place:
"scheme": {"depth": 3}, "@fork": [
{"@set": "`do_action_1`", "val": "true"},
{"@set": "`do_action_2`", "val": "true"},
{"@set": "`do_action_3`", "val": "true"},
{"if": "is_entry_condition_met", "@set": "`do_action_4`", "val": "true"},
{"if": false, "@act": "`never reached`"}
]
scheme
A scheme is associated with a fork statement, and it describes the fork branch resolution strategy.
scheme::= '"scheme"' ':'expression
{"depth": 3}
This section details DMPL conforming example programs.
The following sends "hello world" to the action realizer.
{
"@act": "`hello world`"
}
Two actions can be sent sequentially.
{
"@do": [
{"@act": "`hello`"},
{"@act": "`world`"}
]
}
Fork statements allow branching logic.
{
"@fork": [
{"if": ["==", ["+", 2, 2], 4], "@act": "`this statement is run`"},
{"@act": "`never reached`"}
]
}
Variables can be set and accessed like so.
{
"@do": [
{"@set": "`a`", "val": 1},
{"@set": "`b`", "val": 2},
{"@set": "`c`", "val": ["+", "a", "b"]}
]
}
Here we define a new expression called inc which takes an argument called x.
{
"@do": [
{"@def": ["", "`inc`", "`x`"], "val": {"@pop": ["+", 1, "x"]}},
{"@act", ["`inc`", 5]}
]
}
The set statement allows unpacking a list.
{
"@do": [
{"@set": "`content`", "val": ["", "`What's the biggest planet?`", "`Jupiter`"]},
{"@set": ["", "`Prompt`", "`Correct-Answer`"], "val": "`content`"}
]
}
Run an external file and pass in arguments. The return value can be considered in the following fork statement.
{"@do":
{"@run": "`Question`", "args": ["", "`What's the biggest planet?`", "`Jupiter`"]},
{"await": ["return"], "@fork": [
{"if": ["return", true], "@set": "`score`", "val": 100},
{"@set": "`score`", "val": 0}
]}
}
Expressions and variabels may be imported from other files.
{
"@do": [
{"@use": "`MathExpressions`", "import": ["", "`square`", "`exp`"]},
{"@act": ["==", ["`square`", 6], ["`exp`", 6, 2]]}
]
}
To halt until a user input is supplied, the await flag on a fork may be used.
{
"@do": [
{"@act": "`are you ready?`"},
{"await": ["input"], "@fork": [
{"if": ["input", "`yes`"], "@act": "`great`"},
{"if": ["input", "`no`"], "@act": "`no problem`"}
]}
]
}
More immersive dialogue can be achieved by setting and checking variables, such as greeted.
{
"scheme": {"depth": 2}, "@fork": [
{"if": ["!", ["exists", "`greeted`"]], "@set": "`greeted`", "val": false},
{"if": ["!", "greeted"], "@do": [
{"@act": "`hello`"},
{"@set": "`greeted`", "val": true}
]},
{"if": "greeted", "@do": [
{"@act": "`how are you?`"},
{"@set": "`asked_feelings`", "val": true},
{"await": ["input"], "@fork": [
{"if": ["input", "`good`"], "@act": "`great`"}
]}
]}
]
}
More complete question and answering scenarios may be constructed.
{
"@fork": [
{"if": ["!", ["exists", "`Init`"]], "@do": [
{"@set": "`Init`", "val": true},
{"@set": "`Wrong-Counter`", "val": 0},
{"@set": "`Is-Hint1-Given`", "val": false},
{"@set": "`Is-Hint2-Given`", "val": false},
{"@set": "`Is-Answer-Given`", "val": false},
{"@set": "`Num-Hints-Given`", "val": 0},
{"@set": "`Question`", "val": "`What's the biggest planet?`"},
{"@set": "`CA`", "val": "`planet.jupiter`"},
{"@set": "`CA-Response`", "val": "`Exactly!`"},
{"@set": "`WA1`", "val": "`planet`"},
{"@set": "`WA1-Response`", "val": "`Nope. That's not the biggest`"},
{"@set": "`WA2`", "val": "`nonplanet`"},
{"@set": "`WA2-Response`", "val": "`That's not a planet`"},
{"@set": "`Hint1`", "val": "`It has a big red spot`"},
{"@set": "`Hint2`", "val": "`It's name begins with the letter J`"},
{"@set": "`Answer`", "val": "`The biggest planet is Jupiter`"},
{"@set": "`Hint-Announcement`", "val": "`Here's a hint`"}
]},
{"@fork": [
{"if": ["&&", ["<=", "Wrong-Counter", 3], ["!", "Is-Answer-Given"]], "@do": [
{"@act": "Question"},
{"@fork": [
{"if": ["input", "CA"], "@do": [
{"@act": "CA-Response"},
{"@set": "`Is-Answer-Given`", "val": true}
]},
{"if": ["input", "WA1"], "@do": [
{"@act": "WA1-Response"},
{"@set": "`Wrong-Counter`", "val": ["+", 1, "Wrong-Counter"]}
]},
{"if": ["input", "WA2"], "@do": [
{"@act": "WA2-Response"},
{"@set": "`Wrong-Counter`", "val": ["+", 1, "Wrong-Counter"]}
]},
{"@do": [
{"@set": "`Wrong-Counter`", "val": ["+", 1, "Wrong-Counter"]}
]}
], "await": ["input"]}
]},
{"if": ["&&", ["!", "Is-Hint1-Given"], ["==", "Wrong-Counter", 1]], "@do": [
{"@act": "Hint-Announcement"},
{"@act": "Hint1"},
{"@set": "`Is-Hint1-Given`", "val": true}
]},
{"if": ["&&", ["!", "Is-Hint2-Given"], ["==", "Wrong-Counter", 2]], "@do": [
{"@act": "Hint-Announcement"},
{"@act": "Hint2"},
{"@set": "`Is-Hint2-Given`", "val": true}
]},
{"if": ["&&", ["!", "Is-Answer-Given"], ["==", "Wrong-Counter", 3]], "@do": [
{"@act": "Answer"},
{"@set": "`Is-Answer-Given`", "val": true}
]}
], "scheme": {"depth": 3}}
]
}
Here's how to handle global interruptions from the user.
{"scheme": {depth: 1}, "@fork": [
{"@do": [
{"@act": "`What's your favorite color?`"},
{"await": ["input"], "@fork": [
{"if": ["input", "`red`"], "@act": "`That's my favorite color, too!`"}
{"@act": "`I've never heard of it`"}
]}
]},
{"if": ["input", "`want to quit`"], "@act": "bye"}
]}
The primitive data types used and defined within this spec were influenced by ISO/IEC 11404. The conforming DMPL JSON syntax was influenced by json.org and jsonapi.org. The JSON Syntax defined within this document was developed using EBNF. The railroad diagrams were genereated using raildroad diagram generator.