Expressions

Literals

The following literals can be used within KRL.

Booleans

The boolean values are true and false

Numbers

KRL supports integer and real values. A negative number is created by prepending the minus sign (-) to a number.

Strings

String are created by enclosing characters in double quote characters (").

Arrays

Arrays are created by enclosing comma-delimited expressions in square brackets like so:

["a", "b", "c"]

Arrays can be referenced in the usual manner:

a = [1,4,3,6,5];
b = a[1]

This would bind the value 4 to the variable b. Note that array references only work for arrays of one-dimension, so c[1][2] is not allowed (presuming c is an array of arrays).

Hashes

Hashes are creating by enclosing comma-delimted name-value pairs in curly braces like so:

{"foo" : "bar", "fizz" : 3, "flop" : [1, 2, 3]}

Extended Quoting

Some KRL statements and expressions make use of extended quotes which begin with << and end the >> For example:

pre {

   somevar = <<
<p>This is <em>some</em> HTML.<br/>
<a href="http://www.google.com">Search Google</a>
</p>
>>;

}

Extended quotes allow multiple line passages that contain the " symbol to be entered as variables. Values inside the extended quotes are treated as strings.

Inside the string, variables can be referenced using the #{<varname>} syntax for simple templating. These variables are interpreted in the browser, not on the server.

Regular Expressions

Regular expressions are delimited by the slash character ("/") and most closely follow the conventions for Perl regular expressions.  The following modifiers may appear after the trailing slash:

  • i - make the regular expression case insensitive
  • g - apply the regular expression globally

For example:

p.replace(/foo/,"bar")

would replace the first instance of "foo" in p with "bar".

p.replace(/foo/i, "bar")

would replace all instances of "foo" in p with "bar".

Simple Predicates

Several built-in operators allow testing for equality and inequality.

For numbers: <, >, <=, >=, ==, and !=

For strings: eq, neq, and like.

like takes a regular expression as its second argument and returns true if it matches the string given as it's first argument.

Arguments can be any valid expression.

The following are all valid tests for equality:

c == 5

page:var("city") eq "Blackfoot"

"Orem" neq location:city()

weather:curr_temp() < 90

location:city() + ", WA" eq city

5 * (weather:curr_temp() - 32) / 9 < 0

Predicate Expressions

Predicate expressions are created by joining simple predicates with conjunction (&&) and disjunction (||) in rules. The not operator can also be applied to negate the value of a predicate expression.

  • Negation is done using the keyword not
  • Expressions can parenthesized for grouping.

The following are examples of valid predicates:

location:city eq "Orem" || location:city eq "Provo"

not(location:city eq "Orem" || location:city eq "Provo")

location:state eq "UT" && not location:city eq "Provo"

Intrinsic Predicates

The following predicates are built into KRL. If you think of others we ought to have, just ask.

The predicates are grouped by data source. All predicates must be namespaced using the datasource name and a colon. Not all datasources have predicates.

Demographic Predicates

The data used by this data source is from the 2000 US Census. Demographic data is only available for US households at present.

Weather Predicates

All predicates are relative to local weather. That is weather where the user is.

Time Predicates

All of the following predicates are referenced to the user's local time. All times are 24-hour clock.

Economic Indicator Predicates

These are experimental and mostly for demonstration purposes.

Referer Predicates

What page did the user come from?

Location Predicates

What browser is the user using?

Arithmetic Expressions

KRL supports the standard arithmetic operators: +, -, *, and / using infix notation. Parentheses are used to group expressions when normal precedence rules are to be overridden:

pre {
  x = y + x;
  a = a * (b + c);
  p = q / 4;
}

String Expressions

KRL supports string concatenation using the + symbol:

pre {
  x = "Hello " + "World!";
}

Conditional Expressions

KRL provides support for conditional expressions with the following syntax:

<pred> => <expr> | <expr>

These can be nested to produce:

<pred0> => <expr0> |
<pred1> => <expr1> |
<pred2> => <expr2> |
 ...
          | <exprn>

Because of the limitations of the recursive descent parser, most predicates will need to be enclosed in parentheses:

pre {
  z = (x > y) => y | 3;
}

Functions

KRL supports functions as first-class objects in the expression language. KRL supports only anonymous functions, but they can be given names by binding them to a variable in a declaration. Here's an example:

pre {
  add5 = function(x) {
           x + 5
         };
}

Functions are declared using the keyword function followed by a possibly-empty, parentheses-delimited, comma-seperated list of parameters and a block of code. The code block of the function is set off by curly braces and contains a list of semi-colon separated declarations and a single expression. The declarations are optional, but every function must have an expression as the last thing in the code block. The value of this expression is returned as the value of the function. Here is the BNF:

function_def: 'function' '(' VAR(s? /,/) ')' '{' fundecls(?) expr '}' 

fundecls: decl(s? /;/) ';'

Functions are statically executed (e.g. the environment they are defined in, not the environment they are executed in determines the binding of free-variables) and can be recursive. The KNS execution environment limits the number of times a function can be called in any given ruleset evaluation to 1,000 to protect against run away code. Here's an example of a recursive function in KRL:

pre {
  fact = function(n) {
            (n <= 0) => 1
                      | n * fact(n-1)
         }
}

The preceding example also shows the syntax for function application. An expression resulting in a function is followed by a possibly-empty, parentheses-delimited, comma-seperated list of expressions. Function application is applicative (e.g. the expressions representing the function and the arguments are evaluated and then the result of executing the function expression (the operator or rator) is applied to the results of evaluating the arguments (the operands or rands). Obviously, the rator must be a function.

Consider the following example which uses Newton's method to calculate square roots (taken from Section 1.1.8 of Structure and Interpretation of Computer Programs):

  sqrt = function(x) {
    average = function(x,y) { (x + y) / 2 };
    good_enough = function(guess, x) {
       v = (guess * guess) - x;
       v < 0.01 && v > -0.01
    };
    improve = function(guess, x) {
       average(guess, (x / guess))
    }
    sqrt_iter = function(guess, x) {
       good_enough(guess, x) => guess
                              | sqrt_iter(improve(guess,x), x)
    };
    sqrt_iter(1.0, x)
  }

Functions can return functions as values and functions can be passed as the arguments to other functions and operators in KRL. The following example defined a generalized summation function that sums the numbers from a to b incrementing using inc and applying the function f to each term:

sum = function(f, a, next, b) {
  (a > b) => 0
           | f(a) + sum(f, next(a), inc, b)
};
inc = function(x) { x + 1 };
cube = function(x) { x * x * x };
sum_cubes = function(a, b) {
  sum(cube, a, inc, b)
}

We could define a function that creates incrementor functions. When given a number, it returns a function that increments by that value:

inc_generator = function(n) { function(x){ x + n } };
inc = inc_generator(1);
inc_by_2 = inc_generator(2);
inc_by_25 = inc_generator(25);

Note that function returns that create closures, like the ones above, may not execute correctly in the JavaScript environment, but they do execute correctly on the KNS server.

Data Sources

Datasource declarations take the following form:

<var> = <source>:<function>(<args>);

There are two kinds of data sources: intrinsic and user-defined.

Intrinsic Datasources

The following describes data sources available in KRL.

Weather

The <source> for weather is weather.

Location

The <source> for location is location.

Referer

The <source> for referer is referer.

Media Market

The <source> for media market is mediamarket.

Stocks

The <source> for stock is stocks.

Page

The <source> for page data sources is page.

User Agent

The <source> for page data sources is useragent.

User Defined Data Sources

As described in the documentation for the global declarations, users can define data sources. They are queried in the pre declarations to produce a complex data structure. For example, if a data source named library_search had been declared, it could be queried like so:

pre {
  book_data = datasource:library_search("q="+isbn);
}

The datasource takes a single parameter of either a string or a hash.

  • If the parameter is a string, then it is concatenated with the URL root given in the datasource declaration without modification. What you supply here, when appended to the datasource root URL must result in exactly the URL that you intend to call.
  • If the parameter is a hash, then the has is turned into a properly formatted HTTP QUERY string with names 7 values delimited by an equals sign (=) to create a name-value pair and each name-value pair delimited by an ampersand (&).

If you intend to use the results of the query in a Javascript expression to be executed on the server, note that the value of the right hand side is stored in KOBJ['data'][lhs] where lhs is the left hand side of the declaration. Note that because a ruleset might execute with any other ruleset, variable names should be chosen to avoid name clashes.

Complex data can be queried and used using the pick operator.

Persistent Variables

Note: only entity persistents are currently supported by the production KNS system.

There are two types of persistent variables:

  • Entity variables are used to record persisten data about individuals interacting with rules. Entity variables are identified by the domain ent.
  • Application variables are used to record persistent data about the application or ruleset. Application variables are identified by the domain app.

Persistent variables take three forms:

  • flags
  • counters
  • trails

Flags store boolean values. Counters store (and, obviously count and test) numeric values. Trails keep track of page visits and other state information. Trails are limited storing 20 places.

As an example, the following rule uses a counter to fire an action when an individual has visited a collection of web pages (anything in the archive directory) more than twice in the last three days:

  rule frequent_archive_visitor is active {
    select using "/archives/\d+/\d+/" setting ()

    pre {
      c = ent:archive_pages;
    }

    if ent:archive_pages > 2 within 3 days then {
      alert("You win the prize!  You've seen " + c + " pages from the archives!")
    }

    fired {
      clear ent:archive_pages;
    } else {
      ent:archive_pages += 1 from 1;
    }
  }

The following rule will create a counter of all the visitors to the archive pages and replace an page element with an id of page_count with the current count:

  rule count_archive_visitors is active {
    select using "/archives/\d+/\d+/" setting ()

    pre {
      c = app:visitor_count;
      res = <<
<div id="page_count">#{c}</div>
 >>
    }

    replace("#page_count", res)

    fired {
      app:visitor_count += 1 from 1;
    }
  }

Persistent variables can be used in expressions as part of a prelude section in a rule, in predicates as part of a conditional action, and in mutator statements as part of a postlude or callback.

Using Persistents in Preludes

Flags and counters can be used inside prelude expressions like any other variable. You cannot use them inside extended quotations as template variables. If the persistent variable does not exist before it is used, the default value is 0. Note that variable references for uninitialized persistent variables are not typed and so it is treated as a counter.

The following example shows the use of a persistent variable in a prelude statement:

pre {
 c = ent:archive_pages;
 d = <<
This is a page that has been accessed #{c} times
>>
}

Persistent trails have special operators for accessing individual places in the trail.

  • The history function takes an expression and a persistent variable. If the expression evaluates to n then the history function will return the n_th place on the trail. The most recent place is at history location 0.
  • The current function takes a persistent variable. The current function performs the same operation as the history function with a location of 0. That is, it returns the most recent place on the trail.

Using Persistents in Predicates

For most uses in predicates, persistents are accessed in the same way as in the prelude. So, the following action will fire when the persisent variable ent:archive_pages is greater than 3:

if ent:archive_pages > 3 then
   notify(...)

Persistent flags and counters can also be tested with an associated timeframe. For example, the following action will fire when persisent variable ent:archive_pages is greater than 3 and the last time it as set was within the last 2 hours:

if ent:archive_pages > 3 within 2 hours then
   notify(...)

The syntax of timeframe limited predicates is

PVAR (<= | >= | < | > | == | !=) EXPR within EXPR
   (years| months | weeks| days| hours | minutes| seconds)

where PVAR is a persistent flag or counter and EXPR is any valid KRL expression.

Persistent trails can be tested using the seen predicate. There are two forms.

The first form asks whether a regular expression has been seen within an optional timeframe (specified exactly as above). So you can say:

if seen "/archive/2006" in ent:my_trail then
   notify(...)

or

if seen "/archive/2006" in ent:my_trail within 3 days then
   notify(...)

The first arguments is a string that is interpreted as a regular expression.

The second form tests whether a place in a trail matching the first regular expression comes before or after a place in the same trail matching a second regular expression. This example

if seen "/archive/2006" before "/archive/2007" in ent:my_trail then
  notify(...)

would fire the action when a place matching the regular expression "/archive/2006" was placed on the trail before a place matching the regular expression "/archive/2007".

Mutating Persistents

Persistent variables, to be useful, must be mutated, or changed permanently. The following statements mutate persistent varible:

  • clear PVAR - clears (sets to nil) the persistent variable PVAR
  • set PVAR - set (sets to true) the persistent variable PVAR making it a flag.
  • PVAR (+=|-=) EXPR from EXPR - increments (or decrements) the persistent variable PVAR by the value given by the first expression. If the value is null when the statement executes, the value of the second expression is used to initialize the persistent.
  • mark PVAR - mark the trail using the current caller URL as the place.
  • mark PVAR with EXPR - mark the trail using the value of the expression as the place.
  • forget STRING in PVAR - forget the place in the trail marked by the string when interpreted as a regular expression.

Persistent variable mutator statements can appear in postlude sections or callback sections of rules.

Built-In Packages and Functions

The syntax for using a built-in function is

<package-name>:<function-name>(<arg0>...<argn>)

Math

The package name is math. The following functions are available:

  • random(<num>) - generate a random number. The value is between 0 and the number given as an argument.
pre {
  r = math:random(999)
}

would generate a random number between 0 and 999 and bind it to r.

Page IDs

You can use the contents of elements on the page with a specific CSS ID using the page ID delclaration. For example, the following comment will make the contents of the element with the ID itemnum available as the variable item_no.

item_no = page:id("itemnum")

Note that page IDs cannot be used in predicates since the values are not available on the server, but only on the client. They can be used in declarations and as arguments to actions.

Operators

Operators can be applied to expressions. The syntax puts the operator following a period after the expression in a post-fix notation like so

store.pick("$..book[0,1]")

For now, the only way to chain operators is to fully parenthesize the expression like so:

(store.pick("$..book[0,1]")).length()

instead of

store.pick("$..book[0,1]").length()

as

Coerce objects of one type to be an object of another. For example, the following constructs a string and then turns it into a regular expression that can be used by the replace operator.

("/q=" + q + "/i").as("regexp")

The following coercions are allowed:

str num regexp array hash
str * * *
num * *
regexp * *
array *
hash *

Unsupported coercions will produce a warning and return the element with it's type unchanged.

length

Return the length of an array.

pick

The pick operator is used to pick out portions of a JSON data structure. Pick can be applied to any valid expression that returns JSON. Complex expressions should be contained in parentheses.

The pick operator takes a single string as an argument that represents a JSONPath expression that will select some portion of the JSON in the target expression (before the period).

For example, suppose that book_data contains the following JSON:

{"responseHeader":{
   "status":0,
   "QTime":0,
   "params":{
      "q":"0316160202",
      "wt":"json"}},
 "response":{
   "numFound":1,
   "start":0,
   "docs":[
      {"isbn":"0316160202",
       "title":"Eclipse",
       "url":"http://library.minlib.net/search/i?SEARCH=0316160202"}]}}

The following table shows some sample JSON picks on this data and the results

Expression Result
book_data.pick("$..docs[0].url") http://library.minlib.net/search/i?SEARCH=0316160202
book_data.pick("$..docs[0].title") "Eclipse"
book_data.pick("$..numFound") 1

JSONPath always returns a "collection" or array. That is less than helpful in many circumstances inside KRL, so the pick operator will reduce single element arrays to just the singleton member.

Note: if you're trying to match data with a period (.) in the name, the period will be interpreted as a JSONPath operator unless you escape it with a backslash like so:

bar.pick("$..www\.kynetx\.com[0].text")

replace

The replace operator replaces the portion of a string matching a regular expression with another string.

str_expr.replace(/regexp/, "str")
  • The target (before the period) must be a string.
  • The first argument must be a regular expression. The i and g operators (for case insensativity and global replacement respectively) are supported.
  • The second argument is a string to use for replacement. The $n syntax for replacement of captured text from the regular expression is supported.

The following examples show how replace can be used. Suppose that my_str contains the string "This is a string" and my_url contains the string 'http://www.amazon.com/gp/products/123456789/':

my_str.replace(/is/,"ese") // returns 'These is a string'

my_str.replace(/is/g,"ese") // returns 'These ese a string'

my_str.replace(/this/,"do you want a") // returns 'This is a string' (no change)

my_str.replace(/this/i,"do you want a") // returns 'do you want a is a string'

my_str.replace(/Th(is)/,"Nothing $1") // returns 'Nothing is is a string'

my_url.replace(/http:\/\/([A-Za-z0-9.-]+)\/.*/,"$1") // returns 'www.amazon.com'

Note that this does not mutate the original string, but rather returns a new string that is built by making the substitution in the original.