LuaXP Expressions

Syntax

LuaXP's expression syntax is, I suppose, what one might call "natural." It uses typical infix expression syntax, such as 4 * (3 + 7) / 5, in which the binary operators are placed between their operands (e.g. 3 + 7), as opposed to RPN (postfix) in which the operator follows its operand(s) (e.g. the 2 3 + form common to many older HP calculators).

The syntax is basic, as the intention is that the parser be lightweight and fast. It has generally been my view that the project should remain on a diet free from syntactic sugar, and that other methods can be used to address matters that might be tempting to otherwise address by complicating the syntax. For example, LuaXP uses a select() function to locate a value in a table with an attribute (key) matching a certain value, equivalent to jQuery's $("[attribute=value]") syntax. And where one could go a hundred directions coming up with syntax for iteration, LuaXP again solves this simply with its iterate() function, which takes an array and an expression (to be performed on each element) as arguments.

For those that like that kind of thing, here is a very rough BNF for the parser:

<expression> ::= <number> | <string> | <boolean>         /* constants */
               | <identifier>                            /* variables */
               | <identifier> "[" <expression> "]"       /* array index */
               | <identifier> "(" <argument-list> ")"    /* function call */
               | "[" <string> "]"                        /* quoted identifier */
               | <expression> <binary-operator> <expression>
               | <unary-operator> <expression>
               | "(" <expression> ")"
               
<argument-list> ::= "" | <expression-list>
                  
<expression-list> ::= <expression> [ "," <expression-list> ]

<unary-operator> ::= "-" | "+" 
                   | "!"     /* negation, logical (boolean operand) or bitwise (numeric operand)
                   | "#"     /* length, string or table/array */

<binary-operator> ::= "+" | "-" | "*" | "/" | "%"
                    | "&" | "|" | "^"               /* logical (boolean operands), bitwise (numeric operands) */
                    | "<" | "<=" | ">" | ">=" | "==" | "<>" | "!="
                    | "."                           /* tree traversal, e.g. a.b.c.d */
                    | "="                           /* assignment */

<number> ::= <decimal-integer>
           | "0x" <hexadecimal-integer>
           | "0b" <binary-integer>
           | "0" <octal-integer>
           | <decimal-rational-number>
         
<string> ::= "'" <characters> "'"
           | '"' <characters> '"'

<boolean> ::= "true"
            | "false"
           
<identifier> ::= <letter> { <letter> | <digit> | "_" }

Types

LuaXP supports the Lua primitive types number, string, and boolean. LuaXP also supports tables/arrays.

Strings may start and end with either double-quote (") or single-quote('), and may contain the other that is not used as the delimiter.

Numbers can be decimal with optional fraction and exponent, or integer hexadecimal, octal, or binary. Hexadecimal integers are specified with the "0x" prefix (e.g. 0x1F); binary starts with the "0b" prefix (e.g. 0b11011001); octal numbers start with a zero only, so 0377 is understood to be octal 377 (decimal 255) and not decimal 377.

Booleans may either true or false (the reserved words are not case-sensitive).

LuaXP supports tables and arrays using Lua tables for underlying representation, so they work in the same way. However, there is currently no way to create a key/value pair table in LuaXP, although they can be referenced if provided from external data. Arrays can be created using LuaXP's list() function.

Coersion (Type Conversion)

If a boolean operand is required, certain strings and numbers can be converted to boolean. The string values "true" and "1", as well as any non-zero number, will be coerced to boolean true. The string values "false" and "0", as well as the number 0, will be coerced to boolean false. Otherwise, the empty string coerces to false, and non-empty strings coerce to true. The null value coerces to boolean false.

If a number operand is required, the following apply: boolean true is coerced to the number 1; boolean false is coerced to the number 0; strings are converted to numbers if possible (this may cause an error to be thrown if the number cannot be converted). The null value cannot be coerced to a number and will result in an error being thrown.

If a string operand is required, then boolean values true and false are coerced to "true" and "false", respectively; null is coerced to the empty string (""); and numbers are converted to strings using the Lua-native tonumber() function (decimal only).

Operators

Arithmetic Operators

LuaXP uses the usual arithmetic operators: binary + (addition), - (subtraction), * (multiplication), / (division), % (modulus), and unary - (negation). The operands are numbers or a string that can be converted to a number, and the result is a number. Exponentiation is provided by the use of the pow() function.

Exception: if both operands to the "+" operator are strings (even if they are strings convertible to numbers), they are concatenated. That is, "120" + "45" becomes "12045" and not (number) 165. To achieve the latter result, use the tonumber() function on one or both operands.

Relational Operators

The typical relational operators <, <=, >, >=, == (equality), and != (inequality) are supported. The aliases <> and ~= are supported for inequality. The result of these operations is always a boolean.

Logical and Bitwise Operators

The operators are & (and), | (or), and ! (not) are logical operators that return a boolean result, unless both operands are numeric, in which case the operation is bitwise. That is true & false is false, 8 & true is true, but 8 or 1 is 9.

Assignment

The single = is the assignment operator ( i = 5 ). LuaXP expressions can make assignments to create variables and store values to be referenced elsewhere. When multiple expressions are evaluated using the same context, any variables created by an expression are available to those executed later.

Precedence

The highest precedence goes to *, / and % equally, followed by (lower precedence) + and binary - equally, followed by <, <=, >, >= equally, followed by == and <> equally, followed by &, then ^, then |, and finally = (assignment).

Identifiers

Identifiers are used to refer to variables and function names. There are two ways to reference an identifier: directly, and quoted.

Direct references are simply those where the name is provided in the expression. For example, this expression refers to pi directly: "cos( angle * pi / 180)". Direct identifiers may only contain the alphanumeric characters (A-Z, 0-9), and underscore (_). If an identifier is required to use other characters, the quoted identifier syntax (below) must be used.

Quoted identifiers are quoted strings surrounded in brackets, such as ["weather-icon"]. A quoted identifier may be used anywhere a regular identifier is used. However, there is currently a parsing limitation in that an array subscript may not be used in conjunction with a quoted identifier. That is, ["weather-icon"][4] would not be parsed correctly and throw an error.

Identifier values come either from internal definitions, or the passed context for the evaluation. LuaXP allows the passing of a table containing external data values (and custom function definitions, see Function Calls, below). Any reference to an identifier that is not resolved to a LuaXP reserved word is looked-up in the provided context, and if found, its value is put into the evaluation. Here is an example:

    context = {}
    context.myname = "Wilson"

    LuaXP = require("LuaXP")
    print( LuaXP.evaluate( '"My name is " + myname', context ) ) -- prints: My name is Wilson

Identifiers that reference tables may navigate them either as arrays using array subscripts (for example, rooms[3]), or using the dot (.) operator to subreference to key/value pairs. For example:

    context = {}
    context.name = { first="Bethany", last="Wilson" }

    LuaXP = require("LuaXP")
    print( LuaXP.evaluate( '"Hello there, " + name.first + ", your last name is " + name.last', context ) )
    -- The above prints: Hello there, Bethany, your last name is Wilson

Function Calls

A function call is made by specifying the function name (identifier) followed by a parentheses-enclosed list of arguments. Each argument may itself be an expression, including other function calls. The following is an example of such nested function calls:

    strftime("%c", time())

LuaXP has a small library of pre-defined functions, and provides for user-defined functions. See Using LuaXP for information on how to do this.