DragonFly On-Line Manual Pages
KLELLANG(1) libklel KLELLANG(1)
INTRODUCTION
This manual page documents the language as understood by the KL-EL
library. For documentation on how to embed the KL-EL compiler and
interpreter in an application, see klelapi(3). For a tutorial on how
to use the library, see kleltut(3).
The language understood by KL-EL is an expression language. Everything
in the language is an expression that has a value. The purpose of the
library is to allow these expressions to be input at runtime and have
the application make use of the resulting values in some fashion.
There are two kinds of KL-EL expressions: value expressions (often
referred to as simply "expressions") and guarded commands. Guarded
commands are more complicated, but they are also very useful in certain
situations. Because guarded commands are more complex than value
expressions, they will be covered second.
Expressions in KL-EL are not sensitive to whitespace; whitespace is
only significant inside literal strings.
A NOTE ON TYPES
Every value in KL-EL has one and only one type, and the type of a given
expression never changes. That makes KL-EL strongly typed, and this
typing discipline is possibly its most interesting feature. KL-EL was
designed to be useful in applications where an expression is compiled
only once but executed thousands or millions of times with different
parameters, and possibly on ephemeral or forensic data. If
typechecking were not performed until evaluation, it is possible that a
failure could occur after millions of executions on some set of inputs,
rendering the entire run (or a large portion of it) invalid. In the
best case, the expression could be fixed and execution could pick up
where it left off, but in the worst case, it may be impossible to
recover the values used previously or begin execution again. Checking
types at compile time also makes things faster for multiple executions
of a given expression, since types do not need to be checked on each
execution.
KL-EL understands four types:
Booleans (either true or false)
Integers (signed 64-bit)
Reals (IEEE double-precision floating point)
Strings (sequences of bytes with no length limitations, embedded NULL
bytes are allowed)
Functions are provided to transform values of one type to another type
as necessary. In general, there is no concept of type promotion.
Expressions like
"123" + 5
don't work since addition is not defined for strings. There is a
slight relaxation of this restriction for expressions involving a
mixture of integers and reals -- integers are silently promoted to real
values when they are used in arithmetic expressions involving reals.
This is the only exception to the typing discipline. Note that the
exception only applies to arithmetic expressions -- functions expecting
real arguments will not accept integer arguments.
NAMES
Any top-level expression can be named, and this name can be extracted
from the compiled expression at runtime. This is useful in situations
where there may be many unrelated expressions (say, in a configuration
file), and it would be useful to identify expressions in diagnostics or
other output.
To name an expression, simply prefix it with a name and a colon. A
name in this case may consist of letters, numbers, and underscores, and
must begin with a letter. For example, this gives an expression "1 +
1" the name "Example":
Example : 1 + 1
Once again, note that only top-level expressions may be named.
VALUE EXPRESSIONS
Value expressions are simple expressions that, when evaluated, produce
a value. There are seven different kinds of value expressions:
Literals
Variables
Function calls
Unary (arithmetic negation, logical negation, bitwise negation)
Binary (arithmetic, string concatenation, comparison)
Ternary (conditional evaluation)
Let (lexical scoping)
Each of these expression types is covered below. For more information
on how to retrieve the value of an evaluated expression using the
library, see klelapi(3).
LITERAL BOOLEANS
There is no literal syntax for Boolean values. The standard variables
"true" and "false" are defined and have their expected meanings.
LITERAL NUMBERS
Numbers can be written in octal, decimal, or hexadecimal. Octal
numbers are prefixed with '0o', and hexadecimal numbers are prefixed
with '0x'. Sequences of decimal digits with no prefix are assumed to
be in decimal.
Real numbers are written in decimal, with '.' as the decimal point.
The decimal point and one digit after it are obligatory. An optional
exponent can be provided by suffixing the number with 'e', an optional
minus sign (indicating a negative exponent), and at least one decimal
digit.
LITERAL STRINGS
Literal strings are written enclosed in double quotes. Any character
may appear between the double quotes except for newlines, double
quotes, backslashes, percent signs, and literal zeros (NULL bytes). By
using escapes, these and other characters can be conveniently inserted.
The following escapes are defined:
\\ (backslash)
\" (double quote)
\r (carriage return)
\n (linefeed)
\% (percent sign)
\xHH (the byte whose value is HH in hexadecimal)
Literal strings may also contain interpolations. Interpolations are of
one of the forms
%{name}
%(name)
It is because of this syntax that percent signs are recognized as an
escapable character. Interpolations take the value of the named
variable, convert that value to a string, and embed the stringified
value into the string produced by evaluating the string literal.
Variable names enclosed in curly brackets have their value inserted as
is, while those enclosed in parentheses have their values inserted
after any special characters have been quoted (this is referred to as a
quoted interpolation). In this case "special characters" and the
character used to quote them are defined by the application using the
library, with sensible defaults.
VARIABLES
Variables can be used anywhere where an expression is expected.
Variables are of static type, but their values may change at any time.
Variables are either exported from the application using the library
(referred to as the "host" or "host application"), or they are
introduced by a let expression (see below). Applications using the
library can export any number of variables into the KL-EL environment,
allowing for simple interaction between the host application and KL-EL.
For information on how applications export variables into the KL-EL
environment, see klelapi(3).
KL-EL comes with a standard library that defines several variables.
New variables can be introduced inside an expression by introducing a
new scope using a let expression; these expressions are discussed
below.
FUNCTION CALLS
It is not possible to define a function in KL-EL, but the application
using the library can export as many functions as it needs. KL-EL also
comes with a standard library of functions. The functions in this
library are automatically available for use in any expression unless
specifically disallowed by the host application.
A function call uses syntax familiar from C:
name(arg0, arg1, ... argN)
Due to the way types are represented in the type checker, functions are
limited to thirteen arguments. It is important to note that functions
are not first-class values; they can neither be passed as arguments to
nor returned as the result of other functions.
For more information on how to export a function into the KL-EL
environment, see klelapi(3).
UNARY OPERATIONS
Below three (3) unary operators are defined in terms of their operator
symbol, operand type, and result type. The type codes B, I, and R
represent boolean, integer, and real types, respectively.
======= DEFINITION =======
o l r r
p e i e
e f g s
r t h u
a t t l
t t
o
r
====== OPERATION ===========================
Arithmetic Negation - I I
Arithmetic Negation - R R
Bitwise NOT ~ I I
Boolean Negation ! B B
Note that unary operators bind more tightly than any binary operator.
BINARY OPERATIONS
Below twenty-one (21) binary operators are defined in terms of their
operator symbol, operand types, and result type. These operators are
listed in decreasing order of precedence. The type codes B, I, R, and
S represent boolean, integer, real, and string types, respectively.
======= DEFINITION =======
o l r r
p e i e
e f g s
r t h u
a t t l
t t
o
r
====== OPERATION ===========================
Multiplication * I I I
Multiplication * R I R
Multiplication * I R R
Multiplication * R R R
Division / I I I
Division / R I R
Division / I R R
Division / R R R
Modular Division % I I I
Logical Conjunction && B B B
Bitwise Roll Left << I I I
Bitwise Roll Right >> I I I
String Concatenation . S S S
Addition + I I I
Addition + R I R
Addition + I R R
Addition + R R R
Subtraction - I I I
Subtraction - R I R
Subtraction - I R R
Subtraction - R R R
Logical Disjunction || B B B
Bitwise AND & I I I
Bitwise XOR ^ I I I
Bitwise OR | I I I
Less than < I I B
Less than < R R B
Less than < S S B
Greater than > I I B
Greater than > R R B
Greater than > S S B
Less than or equal <= I I B
Less than or equal <= R R B
Less than or equal <= S S B
Greater than or equal >= I I B
Greater than or equal >= R R B
Greater than or equal >= S S B
Equal == B B B
Equal == I I B
Equal == R R B
Equal == S S B
Not equal != B B B
Not equal != I I B
Not equal != R R B
Not equal != S S B
Regex match =~ S S B
Regex non-match !~ S S B
Note that the environment's localization settings (particularly
collation order) may affect the result when comparing strings using the
relational operators.
For regular expression matches, the right operand is the expression; it
is expected to be a string in PCRE syntax. For more information, see
pcre(3).
Parentheses can be used to override the normal order of operations when
necessary.
THE TERNARY OPERATOR
KL-EL defines a single ternary operator, '?'. Ternary expressions are
of the form:
predicate ? expr1 : expr2
where predicate is an expression of Boolean type and expr1 and expr2
are two expressions of the same type. If predicate evaluates to true,
expr1 is evaluated, otherwise expr2 is evaluated. Note that
parenthesis are generally required around predicates unless they are
syntactically trivial. The type of a ternary expression is the type of
expr1 or expr2.
LET EXPRESSIONS
KL-EL is lexically scoped in the ALGOL tradition. New environments are
created using let expressions. These expressions are of the form:
let var = expr1 in expr2
The value of this expression is the value of expr2 with every
occurrence of var substituted by the value of expr1. As let
expressions are normal expressions, they can be nested. In particular,
this is a valid (and common) idiom:
let var1 = expr1 in
let var2 = expr2 in
let var3 = expr3 in
var1 + var2 + var3
Variables in nested scopes can shadow variables in outer scopes, though
this functionality is best used sparingly. Variables exported from the
host application are in the outermost scope.
GUARDED COMMANDS
Aside from value expressions, KL-EL supports guarded commands. A
guarded command consists of a guard of boolean type, a call to the
special function eval, and an optional list of return codes. The
intent of guarded commands is to provide the host application with a
command (where the meaning of "command" is defined by the host
application), along with a set of supporting arguments, that is valid
only when the guard expression is valid.
Guarded commands have one of the following forms:
if (guard) then eval(interpreter, program, arg0, ... argN)
if (guard) then eval(interpreter, program, arg0, ... argN) pass [code0, ... codeN]
if (guard) then eval(interpreter, program, arg0, ... argN) fail [code0, ... codeN]
In these forms guard must be a value expression of boolean type,
interpreter and program must be constant strings, and codes must be
literal integers. The remaining arguments may be of any type.
Guarded commands are useful in situations where it is important to
distinguish between the action and the guard. Take for example a file-
finding application. In this hypothetical application, a guarded
command is used to specify a command to execute on every file that
matches its expression thereby allowing the application to customize
its behavior based on observed file attributes.
In a guarded command, the arguments to the eval function and the code
values are passed as-is to the host application. It is up to the
application to determine their meaning. For a discussion on how
guarded commands are represented in host applications, see klelapi(3).
It is important to note that guarded commands cannot be nested inside
other expressions or guarded commands.
SEE ALSO
klel-expr(1), klelapi(3), klelstdlib(3), kleltut(3), pcre(3)
1.1.0 2015-09-16 KLELLANG(1)