Dynamic-link grammars

You can import grammars dynamically, so that parts of a grammar can be precompiled, while other parts get compiled and bound into the whole at runtime. For example, you could dynamically add vocabulary to a grammar that has already been compiled.

Applications can use dynamic-link grammars to accomplish these objectives:

  • Define personalized grammars at runtime. For example, a banking application could load the appropriate account information for each user.
  • Design grammar modules. Such grammar modules can be used in various combinations and shared across applications.
  • Reduce CPU usage at runtime. When static components of the grammar are precompiled, applications do not need to recompile those components during telephone calls. This benefit is not available to multiple-language grammars, which require a complete recompilation when a component grammar is bound into the whole.
  • Reduce memory usage at runtime. As opposed to an entirely static grammar, each subgrammar of a dynamic grammar is stored separately. This enables the cache to share components without using extra memory. Otherwise, if two slightly-different versions of a static grammar are needed, the entire grammar must be copied in memory.

A typical scenario for dynamic linking is an application with a large grammar that is static (unchanging), and a smaller branch of the grammar that is dynamic (changes based on information that becomes available at runtime). Examples:

  • A large stock trading grammar with a small grammar of user accounts. This combination would allow callers to buy and sell stocks from individual accounts in their portfolio. The list of accounts changes for each caller.
  • A large grammar with small grammars that add different command words in different situations.

Characteristics of a dynamic-link grammar include:

  • Late binding: Some component grammars are precompiled, while others are bound at runtime.
  • Fast activation of personalized grammars: Grammars can be created as special-purpose modules (for example, a grammar based on a user’s profile), and the application can bind the appropriate module into the whole based on the needs of the moment at runtime.

Example application, a problem, and possible solutions

Consider a single, large stock grammar (all the names and symbols of stocks traded for any given stock exchange), and many small “nickname” grammars (informal names for the stocks owned by each application user). How could an application manage these grammars?

Some possible solutions (from least desirable to most) include the following:

  • The application could generate the entire stock grammar and associated nicknames at runtime. However, this approach adds CPU and memory load for each caller, and may cause noticeable delays while callers wait for the grammars to compile, load, and activate.
  • The application can use parallel grammars: it can pre-compile the stock grammar (to save runtime CPU), generate the nickname grammars at runtime (small CPU cost), and activate the grammars in parallel.

    In general this is an excellent solution, because parallel grammars tend to exhibit better performance than large single grammars. But in this example, there would be no binding between the two grammars, and their design is likely to require overlapping functionality (redundancies) between them. Because the grammars are separate, Recognizer finds matches in one grammar or the other, and the application must interpret the results and associate nicknames with stock names. There is also extra resource cost required for the support and maintenance of two separate grammars.

  • Finally, the application can create a dynamic-link grammar; it can pre-compile the stock grammar and bind a nickname grammar at runtime. There is no overlap in functionality of the grammars. Upon recognition of a stock name, symbol, or nickname, the application receives a single result.

Design of dynamic grammars

In its simplest form, dynamic linking works as follows: a grammar contains a non-terminal that is not resolved until the application binds a second grammar, which defines the rules for that non-terminal. If the binding never occurs, the non-terminal remains unresolved and the unbound reference operates as a reference to VOID (see the VOID rule).

A default grammar can be set to handle the case where the binding is not explicitly defined. See Setting a default grammar for the binding.

For example, you could compile, activate, and even recognize the grammar that contains the rule and never bind the $date non-terminal (in which case $date never matches speech input). Alternatively, you could resolve $date in a separate Date grammar (perhaps a multi-purpose and re-usable module).

To use dynamic linking successfully:

  • Divide your grammars into distinct modules, with some modules targeted for precompilation and others for runtime compilation.
  • In precompiled grammars, define all non-terminals that require resolution.
  • Provide grammars for resolving the unresolved non-terminals.
  • Ensure that application developers have the necessary documentation to fit the grammars together and use them effectively; this includes descriptions of any useful tags in the modular grammars (discussed below).

Implementing dynamic linking (syntax)

To implement a modular design of grammars, use the appropriate syntax in several locations:

  • The binding grammar must contain placeholder rules.
  • The bound grammars must contain the resolved rules.
  • Optionally, you can pass variables between grammars.
  • URIs must be used to import (bind) the participating grammars.

The following subsections describe each of these items.

Writing placeholder rules

The binding grammar must contain rules that act as placeholder for the dynamic linking that will take place after compilation. This excerpt from a binding grammar shows a placeholder rule:

<ruleref uri="SWI_import:dyngram"/>

Where:

  • SWI_import indicates that an imported grammar will resolve the rule.
  • dyngram is the placeholder for the unresolved rule.

Any grammar can contain such a placeholder.

If you are familiar with web programming and internet protocols, consider that "SWI_import" is a transport protocol with a function similar to "http:".

The preceding example has one limitation: by default, it resolves using the ROOT rule in the imported grammar (which is not named here). Placeholders are more useful when the application developer knows the contents of the imported grammar and can indicate a specific rule. For example:

<ruleref uri="SWI_import:dyngram#acct"/>

When a URI binds a grammar for dyngram above, the resolution of the placeholder is found in the acct rule.

Resolving rules

The bound grammar contains the rule that resolves the placeholder. This excerpt shows the public acct rule that resolves dyngram:

<rule id="acct" scope="public">
 <item>
  checking account
  <tag>
   ACCT=’79-310132’;
   BAL=’4032.45’;
  </tag>
 </item>
 <item>
  savings account
  <tag>
   ACCT=’89-310132’;
   BAL=’22115.18’;
  </tag>
 </item>

Passing variables

The importing (binding) grammar can access any results from the imported (bound) grammar. Below, the <ruleref> element defines which accounts are allowed in the imported grammar (checking and savings). Any recognition that contains a disallowed account is rejected.

<ruleref uri="SWI_import:dyngram?SWI_vars.allowedaccounts=+checking+savings+"/>

Below the variable is used in the dynamically linked grammar. It uses the SWI_vars.allowedaccounts variable to test whether each of three accounts is valid. In this example, the brokerage account is disallowed, because it is not listed as one of the options listed above for the SWI_vars.allowedaccounts variable.

<?xml version='1.0' encoding='UTF-8'?>
<grammar xml:lang="en-US" version="1.0" root="custacct"
   tag-format="swi-semantics/1.0"
   xmlns="http://www.w3.org/2001/06/grammar">
<rule id="custacct" scope="public">
 Pay the bill from my
 <one-of>
  <item>
   checking account
   <tag>
    if (SWI_vars.allowedaccounts.indexOf(
     'checking ') == -1) {SWI_disallow=1;
    } else {
     TYPE='Checking';
     ACCT='79-310132';
     BAL='4032.45';
    }
   </tag>
  </item>
 <item>
  savings account
  <tag>
   if (SWI_vars.allowedaccounts.indexOf(
    'savings ') == -1) {
     SWI_disallow=1;
    } else {
     TYPE='Savings';
     ACCT='89-310132';
     BAL='22115.18';
    }
   </tag>
  </item>
 <item>
  brokerage account
  <tag>
   if (SWI_vars.allowedaccounts.indexOf(
    'brokerage ') == -1) {
     SWI_disallow=1;
    } else {
     TYPE='Brokerage';
     ACCT='99-310132';
     BAL='50115.18';
    }
   </tag>
  </item>
 </one-of>
</rule>
</grammar>

The parent grammar can now retrieve the results of the dynamically bound grammar using the following script syntax:

<tag>
 type=custacct.TYPE;
 acct=custacct.ACCT;
 bal=custacct.BAL;
</tag>

Passing variables to built-in grammars

Do not include the SWI_vars prefix when passing in information.

The following example is legal:

<ruleref uri="builtin:grammar/digits?maxlength=7;"/>

The example given below is not legal, because SWI_vars cannot be used for built-in grammars:

<ruleref uri="builtin:grammar/digits?SWI_vars.maxlength=7;"/>

Binding grammars with a URI

To bind grammars together, your URI specification uses the SWI_import property.

This example URI binds a dynamic grammar (BobSmith.grxml, which is personalized to contain the user’s account names) into an application grammar (theapp.gram):

http://public/grammars/theapp.gram
  ?SWI_import.dyngram=
   http://public/grammars/BobSmith.grxml

In the example, SWI_import does more than specify the grammar files. It also indicates that dyngram (a placeholder rule in theapp.gram) whose resolution is in the imported file. Thus, application developers must know the names of placeholder rules inside the importing grammars.

Note: When using SWI_import, you can nest URIs within URIs, and this results in a specification that contains ambiguous characters or characters that are normally invalid in a URI. To solve this problem, the specification must substitute escape sequences for those characters. The following example illustrates how to accomplish this: first, by showing the desired URI (with invalid characters) and then showing the corrected specification (with valid escape sequences).

This example URI sets a variable in the imported grammar:

http://public/grammars/theapp.gram
 ?SWI_import.dyngram=
  http://public/grammars/user.grxml#acct
 ?SWI_vars.minbalance='250.00'

Above, the pound symbol (#) is ambiguous (it’s not clear if it is part of theapp.grxml address or the user.grxml address). To use this URI, you can substitute escape sequences for the question mark, equals sign, and pound symbol (%3F, %3D, and %23 respectively). The corrected URI is:

http://public/grammars/theapp.gram
 %3FSWI_import.dyngram=
  http://public/grammars/user.grxml%23acct
 %3FSWI_vars.minbalance%3D'250.00'

As described in Resolving rules, the importing grammar can also set variables. If the same variable is set by the grammar and the URI, the URI takes precedence.

Setting a default grammar for the binding

The URI in the binding grammar can specify a default grammar to handle the situation where the placeholder non-terminal is not explicitly resolved by the external URI. For example:

<ruleref uri="SWI_import:mydynimport?SWI.default=B2.grxml"/>

Above, the URI creates a placeholder for a rule called mydynimport. At runtime, if the application does not provide a URI to bind the rule, then Recognizer uses B2.grxml instead.