This is an automated email from the ASF dual-hosted git repository.

aradzinski pushed a commit to branch NLPCRAFT-206
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git


The following commit(s) were added to refs/heads/NLPCRAFT-206 by this push:
     new f94daf8  WIP.
f94daf8 is described below

commit f94daf85c0817e339a9ccb243dc1b73cc219c07a
Author: Aaron Radzinski <[email protected]>
AuthorDate: Thu Feb 25 17:20:47 2021 -0800

    WIP.
---
 .../nlpcraft/common/makro/NCMacroCompiler.scala    |  13 +-
 .../model/intent/impl/antlr4/NCIntentDsl.g4        |  10 +-
 .../model/intent/impl/ver2/NCBaseDslCompiler.scala | 787 ++++++++++-----------
 .../intent/impl/ver2/NCIntentDslCompiler.scala     |  93 ++-
 .../model/intent/dsl/NCDslCompilerSpec.scala       |  72 +-
 5 files changed, 520 insertions(+), 455 deletions(-)

diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
index a8d9d7c..340b3e6 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
@@ -211,7 +211,7 @@ object NCMacroCompiler extends LazyLogging {
             line: Int,
             charPos: Int,
             msg: String,
-            e: RecognitionException): Unit =  throw new 
NCE(mkCompilerError(msg, line, charPos, in))
+            e: RecognitionException): Unit = throw new 
NCE(mkCompilerError(msg, line, charPos, in))
     }
     
     /**
@@ -224,16 +224,17 @@ object NCMacroCompiler extends LazyLogging {
     private def mkCompilerError(
         msg: String,
         line: Int,
-        charPos: Int,
+        charPos: Int, // 1, 2, ...
         in: String
     ): String = {
         val dash = "-" * in.length
-        val pos = Math.max(1, charPos)
-        val charPosPtr = dash.substring(0, pos - 1) + r("^") + 
dash.substring(pos)
+        val pos = Math.max(0, charPos - 1)
+        val posPtr = dash.substring(0, pos) + r("^") + dash.substring(pos + 1)
+        val inPtr = in.substring(0, pos) + r(in.charAt(pos)) + 
in.substring(pos + 1)
     
         s"Macro compiler error at line $line:$charPos - $msg\n" +
-        s"  |-- ${c("Macro:")} $in\n" +
-        s"  +-- ${c("Error:")} $charPosPtr"
+        s"  |-- ${c("Macro:")}    $inPtr\n" +
+        s"  +-- ${c("Location:")} $posPtr"
     }
     
     /**
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/antlr4/NCIntentDsl.g4
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/antlr4/NCIntentDsl.g4
index 278746f..f273be2 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/antlr4/NCIntentDsl.g4
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/antlr4/NCIntentDsl.g4
@@ -129,11 +129,19 @@ EXP: [Ee] [+\-]? INT;
 fragment UNI_CHAR // International chars.
     : '\u00B7'
     | '\u0300'..'\u036F'
+    | '\u00A0'..'\u00FF' /* Latin-1 Supplement. */
+    | '\u0100'..'\u017F' /* Latin Extended-A. */
+    | '\u0180'..'\u024F' /* Latin Extended-B. */
+    | '\u1E02'..'\u1EF3' /* Latin Extended Additional. */
+    | '\u0259'..'\u0292' /* IPA Extensions. */
+    | '\u02B0'..'\u02FF' /* Spacing modifier letters. */
     | '\u203F'..'\u2040'
     | '\u00C0'..'\u00D6'
     | '\u00D8'..'\u00F6'
     | '\u00F8'..'\u02FF'
-    | '\u0370'..'\u037D'
+    | '\u0370'..'\u03FF' /* Greek and Coptic. */
+    | '\u1F01'..'\u1FFF' /* Greek Extended. */
+    | '\u0400'..'\u04FF' /* Cyrillic. */
     | '\u037F'..'\u1FFF'
     | '\u200C'..'\u200D'
     | '\u2070'..'\u218F'
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCBaseDslCompiler.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCBaseDslCompiler.scala
index 298cbbb..eb0a48e 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCBaseDslCompiler.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCBaseDslCompiler.scala
@@ -17,19 +17,35 @@
 
 package org.apache.nlpcraft.model.intent.impl.ver2
 
+import org.antlr.v4.runtime.ParserRuleContext
 import org.antlr.v4.runtime.tree.{TerminalNode ⇒ TN}
 import org.apache.commons.lang3.StringUtils
 import org.apache.nlpcraft.model.NCToken
 import org.apache.nlpcraft.model.intent.utils.ver2.{NCDslTermContext, 
NCDslTermRetVal}
 import org.apache.nlpcraft.common._
 
-import java.lang.{Double ⇒ JDouble, IllegalArgumentException ⇒ IAE, Long ⇒ 
JLong}
+import java.lang.{Double ⇒ JDouble, Long ⇒ JLong}
 import java.time.LocalDate
 import java.util.{Collections, ArrayList ⇒ JArrayList, HashMap ⇒ JHashMap}
 import scala.collection.mutable
 
 //noinspection DuplicatedCode
 trait NCBaseDslCompiler {
+    def syntaxError(errMsg: String, line: Int, pos: Int): NCE
+    def runtimeError(errMsg: String, cause: Exception = null): NCE
+
+    /**
+     *
+     * @param errMsg
+     * @param ctx
+     * @return
+     */
+    def newSyntaxError(errMsg: String)(implicit ctx: ParserRuleContext): NCE = 
{
+        val tok = ctx.start
+
+        syntaxError(errMsg, tok.getLine, tok.getCharPositionInLine)
+    }
+
     type StackType = mutable.ArrayStack[NCDslTermRetVal]
     type Instr = (NCToken, StackType,  NCDslTermContext) ⇒ Unit
 
@@ -51,18 +67,19 @@ trait NCBaseDslCompiler {
     def pushBool(any: Boolean, usedTok: Boolean)(implicit stack: StackType): 
Unit =
         stack.push(NCDslTermRetVal(Boolean.box(any), usedTok))
 
-    def errUnaryOp(op: String, v: AnyRef): IAE =
-        new IAE(s"Unexpected '$op' DSL operation for value: $v")
-    def errBinaryOp(op: String, v1: AnyRef, v2: AnyRef): IAE =
-        new IAE(s"Unexpected '$op' DSL operation for values: $v1, $v2")
-    def errUnknownFun(fun: String): IAE =
-        new IAE(s"Unknown DSL function: $fun()")
-    def errMinParamNum(min: Int, fun: String): IAE =
-        new IAE(s"Invalid number of parameters for DSL function ($min is 
required): $fun()")
-    def errParamNum(fun: String): IAE =
-        new IAE(s"Invalid number of parameters for DSL function: $fun()")
-    def errParamType(fun: String, param: AnyRef, expectType: String): IAE =
-        new IAE(s"Expecting '$expectType' type of parameter for DSL function 
'$fun()', found: $param")
+    // Runtime errors.
+    def rtUnaryOpError(op: String, v: AnyRef): NCE =
+        runtimeError(s"Unexpected '$op' DSL operation for value: $v")
+    def rtBinaryOpError(op: String, v1: AnyRef, v2: AnyRef): NCE =
+        runtimeError(s"Unexpected '$op' DSL operation for values: $v1, $v2")
+    def rtUnknownFunError(fun: String): NCE =
+        runtimeError(s"Unknown DSL function: $fun()")
+    def rtMinParamNumError(min: Int, fun: String): NCE =
+        runtimeError(s"Invalid number of parameters for DSL function ($min is 
required): $fun()")
+    def rtParamNumError(fun: String): NCE =
+        runtimeError(s"Invalid number of parameters for DSL function: $fun()")
+    def rtParamTypeError(fun: String, param: AnyRef, expectType: String): NCE =
+        runtimeError(s"Expecting '$expectType' type of parameter for DSL 
function '$fun()', found: $param")
 
     /**
      *
@@ -126,7 +143,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushBool(asJDouble(v1) < 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushBool(asJDouble(v1) < 
asJDouble(v2), usedTok)
             else
-                throw errBinaryOp("<", v1, v2)
+                throw rtBinaryOpError("<", v1, v2)
         }
         else if (gt != null) {
             if (isJLong(v1) && isJLong(v2)) pushBool(asJLong(v1) > 
asJLong(v2), usedTok)
@@ -134,7 +151,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushBool(asJDouble(v1) > 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushBool(asJDouble(v1) > 
asJDouble(v2), usedTok)
             else
-                throw errBinaryOp(">", v1, v2)
+                throw rtBinaryOpError(">", v1, v2)
         }
         else if (lteq != null) {
             if (isJLong(v1) && isJLong(v2)) pushBool(asJLong(v1) <= 
asJLong(v2), usedTok)
@@ -142,7 +159,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushBool(asJDouble(v1) <= 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushBool(asJDouble(v1) <= 
asJDouble(v2), usedTok)
             else
-                throw errBinaryOp("<=", v1, v2)
+                throw rtBinaryOpError("<=", v1, v2)
         }
         else {
             assert(gteq != null)
@@ -152,7 +169,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushBool(asJDouble(v1) >= 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushBool(asJDouble(v1) >= 
asJDouble(v2), usedTok)
             else
-                throw errBinaryOp(">=", v1, v2)
+                throw rtBinaryOpError(">=", v1, v2)
         }
     }
 
@@ -174,12 +191,12 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushDouble(asJDouble(v1) * 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushDouble(asJDouble(v1) 
* asJDouble(v2), usedTok)
             else
-                throw errBinaryOp("*", v1, v2)
+                throw rtBinaryOpError("*", v1, v2)
         }
         else if (mod != null) {
             if (isJLong(v1) && isJLong(v2)) pushLong(asJLong(v1) % 
asJLong(v2), usedTok)
             else
-                throw errBinaryOp("%", v1, v2)
+                throw rtBinaryOpError("%", v1, v2)
         }
         else {
             assert(div != null)
@@ -189,7 +206,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushDouble(asJDouble(v1) / 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushDouble(asJDouble(v1) 
/ asJDouble(v2), usedTok)
             else
-                throw errBinaryOp("/", v1, v2)
+                throw rtBinaryOpError("/", v1, v2)
         }
     }
 
@@ -205,7 +222,7 @@ trait NCBaseDslCompiler {
         val (v1, v2, f1, f2) = pop2()
 
         if (!isBoolean(v1) || !isBoolean(v2))
-            throw errBinaryOp(if (and != null) "&&" else "||", v1, v2)
+            throw rtBinaryOpError(if (and != null) "&&" else "||", v1, v2)
 
         if (and != null)
             pushBool(asBool(v1) && asBool(v2), f1 || f2) // Note logical OR 
for used token flag.
@@ -234,7 +251,7 @@ trait NCBaseDslCompiler {
             if (isJLong(v1) && isJLong(v2))
                 asJLong(v1) == asJLong(v2)
             else
-                throw errBinaryOp(op, v1, v2)
+                throw rtBinaryOpError(op, v1, v2)
 
         }
 
@@ -265,7 +282,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushDouble(asJDouble(v1) + 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushDouble(asJDouble(v1) 
+ asJDouble(v2), usedTok)
             else
-                throw errBinaryOp("+", v1, v2)
+                throw rtBinaryOpError("+", v1, v2)
         }
         else {
             assert(minus != null)
@@ -275,7 +292,7 @@ trait NCBaseDslCompiler {
             else if (isJDouble(v1) && isJLong(v2)) pushDouble(asJDouble(v1) - 
asJLong(v2), usedTok)
             else if (isJDouble(v1) && isJDouble(v2)) pushDouble(asJDouble(v1) 
- asJDouble(v2), usedTok)
             else
-                throw errBinaryOp("-", v1, v2)
+                throw rtBinaryOpError("-", v1, v2)
         }
     }
 
@@ -294,14 +311,14 @@ trait NCBaseDslCompiler {
             if (isJDouble(v)) pushDouble(-asJDouble(v), usedTok)
             else if (isJLong(v)) pushLong(-asJLong(v), usedTok)
             else
-                throw errUnaryOp("-", v)
+                throw rtUnaryOpError("-", v)
         }
         else {
             assert(not != null)
 
             if (isBoolean(v)) pushBool(!asBool(v), usedTok)
             else
-                throw errUnaryOp("!", v)
+                throw rtUnaryOpError("!", v)
         }
     }
 
@@ -334,419 +351,385 @@ trait NCBaseDslCompiler {
 
         (_, stack, _) ⇒ pushAny(atom, false)(stack)
     }
-    
-    private final val FUNCTIONS = Set(
-        "meta_token",       "meta_model",       "meta_intent",      
"meta_req",         "meta_user",
-        "meta_company",     "meta_sys",         "meta_conv",        "json",    
         "if",
-        "id",               "ancestors",        "parent",           "groups",  
         "value",
-        "aliases",          "start_idx",        "end_idx",          "req_id",  
         "req_normtext",
-        "req_tstamp",       "req_addr",         "req_agent",        "user_id", 
         "user_fname",
-        "user_lname",       "user_email",       "user_admin",       
"user_signup_tstamp",   "comp_id",
-        "comp_name",        "comp_website",     "comp_country",     
"comp_region",      "comp_city",
-        "comp_addr",        "comp_postcode",    "trim",             "strip",   
         "uppercase",
-        "lowercase",        "is_alpha",         "is_alphanum",      
"is_whitespace",    "is_num",
-        "is_numspace",      "is_alphaspace",    "is_alphanumspace", 
"substring",        "index",
-        "regex",            "soundex",          "split",            
"split_trim",       "replace",
-        "abs",              "ceil",             "floor",            "rint",    
         "round",
-        "signum",           "sqrt",             "cbrt",             "pi",      
         "euler",
-        "acos",             "asin",             "atan",             "cos",     
         "sin",
-        "tan",              "cosh",             "sinh",             "tanh",    
         "atn2",
-        "degrees",          "radians",          "exp",              "expm1",   
         "hypot",
-        "log",              "log10",            "log1p",            "pow",     
         "rand",
-        "square",           "list",             "map",              "get",     
         "index",
-        "has",              "tail",             "add",              "remove",  
         "first",
-        "last",             "keys",             "values",           "length",  
         "count",
-        "take",             "drop",             "size",             "length",  
         "reverse",
-        "is_empty",         "non_empty",        "to_string",        "avg",     
         "max",
-        "min",              "stdev",            "sum",              "year",    
         "month",
-        "day_of_month",     "day_of_week",      "day_of_year",      "hour",    
         "min",
-        "sec",              "week_of_month",    "week_of_year",     "quarter", 
         "msec",
-        "now"
-    )
 
     /**
      *
      * @param id
      * @return
      */
-    def parseCallExpr(id: TN): Instr = {
+    def parseCallExpr(id: TN): Instr = (tok, stack: StackType, termCtx) ⇒ {
         val fun = id.getText
 
-        if (!FUNCTIONS.contains(fun))
-            throw errUnknownFun(fun)
+        implicit val evidence = stack
 
-        (tok, stack: StackType, termCtx) ⇒ {
-            implicit val evidence = stack
+        def ensureStack(min: Int): Unit =
+            if (stack.size < min)
+                throw rtMinParamNumError(min, fun)
 
-            def ensureStack(min: Int): Unit =
-                if (stack.size < min)
-                    throw errMinParamNum(min, fun)
+        def get1Str(): (String, Boolean) = {
+            ensureStack(1)
 
-            def get1Str(): (String, Boolean) = {
-                ensureStack(1)
+            val (v, f) = pop1()
 
-                val (v, f) = pop1()
+            if (!isString(v))
+                throw rtParamTypeError(fun, v, "string")
 
-                if (!isString(v))
-                    throw errParamType(fun, v, "string")
-
-                (asString(v), f)
-            }
-            def get1Double(): (JDouble, Boolean) = {
-                ensureStack(1)
-
-                val (v, f) = pop1()
+            (asString(v), f)
+        }
+        def get1Double(): (JDouble, Boolean) = {
+            ensureStack(1)
 
-                if (!isJDouble(v))
-                    throw errParamType(fun, v, "double")
+            val (v, f) = pop1()
 
-                (asJDouble(v), f)
-            }
-            def get2Doubles(): (JDouble, JDouble, Boolean) = {
-                ensureStack(2)
+            if (!isJDouble(v))
+                throw rtParamTypeError(fun, v, "double")
 
-                val (v1, v2, f1, f2) = pop2()
+            (asJDouble(v), f)
+        }
+        def get2Doubles(): (JDouble, JDouble, Boolean) = {
+            ensureStack(2)
 
-                if (!isJDouble(v1))
-                    throw errParamType(fun, v1, "double")
-                if (!isJDouble(v2))
-                    throw errParamType(fun, v2, "double")
+            val (v1, v2, f1, f2) = pop2()
 
-                (asJDouble(v1), asJDouble(v2), f1 || f2)
-            }
-            def get1Any(): (AnyRef, Boolean) = {
-                ensureStack(1)
+            if (!isJDouble(v1))
+                throw rtParamTypeError(fun, v1, "double")
+            if (!isJDouble(v2))
+                throw rtParamTypeError(fun, v2, "double")
 
-                pop1()
-            }
-
-            /*
-             * String operations.
-             */
-            def doTrim(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(s.trim, f) }
-            def doUppercase(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(s.toUpperCase, f) }
-            def doLowercase(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(s.toLowerCase, f) }
-            def doIsAlpha(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlpha(asString(s)), f) }
-            def doIsNum(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isNumeric(asString(s)), f) }
-            def doIsAlphaNum(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlphanumeric(asString(s)), f) }
-            def doIsWhitespace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isWhitespace(asString(s)), f) }
-            def doIsAlphaSpace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlphaSpace(asString(s)), f) }
-            def doIsAlphaNumSpace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlphanumericSpace(asString(s)), f) }
-            def doIsNumSpace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isNumericSpace(asString(s)), f) }
-
-            def doSplit(): Unit = {
-                ensureStack(2)
-
-                val (v1, v2, f1, f2) = pop2()
-
-                if (!isString(v1))
-                    errParamType(fun, v1, "string")
-                if (!isString(v2))
-                    errParamType(fun, v2, "string")
-
-                asString(v1).split(asString(v2)).foreach { pushAny(_, f1 || 
f2) }
-            }
-            def doSplitTrim(): Unit = {
-                ensureStack(2)
+            (asJDouble(v1), asJDouble(v2), f1 || f2)
+        }
+        def get1Any(): (AnyRef, Boolean) = {
+            ensureStack(1)
 
-                val (v1, v2, f1, f2) = pop2()
+            pop1()
+        }
 
-                if (!isString(v1))
-                    errParamType(fun, v1, "string")
-                if (!isString(v2))
-                    errParamType(fun, v2, "string")
+        /*
+         * String operations.
+         */
+        def doTrim(): Unit = get1Str() match { case (s, f) ⇒ pushAny(s.trim, 
f) }
+        def doUppercase(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(s.toUpperCase, f) }
+        def doLowercase(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(s.toLowerCase, f) }
+        def doIsAlpha(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlpha(asString(s)), f) }
+        def doIsNum(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isNumeric(asString(s)), f) }
+        def doIsAlphaNum(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlphanumeric(asString(s)), f) }
+        def doIsWhitespace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isWhitespace(asString(s)), f) }
+        def doIsAlphaSpace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlphaSpace(asString(s)), f) }
+        def doIsAlphaNumSpace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isAlphanumericSpace(asString(s)), f) }
+        def doIsNumSpace(): Unit = get1Str() match { case (s, f) ⇒ 
pushBool(StringUtils.isNumericSpace(asString(s)), f) }
+
+        def doSplit(): Unit = {
+            ensureStack(2)
+
+            val (v1, v2, f1, f2) = pop2()
+
+            if (!isString(v1))
+                rtParamTypeError(fun, v1, "string")
+            if (!isString(v2))
+                rtParamTypeError(fun, v2, "string")
+
+            asString(v1).split(asString(v2)).foreach { pushAny(_, f1 || f2) }
+        }
+        def doSplitTrim(): Unit = {
+            ensureStack(2)
 
-                asString(v1).split(asString(v2)).foreach { s ⇒ 
pushAny(s.strip, f1 || f2) }
-            }
+            val (v1, v2, f1, f2) = pop2()
 
-            /*
-             * Collection, statistical operations.
-             */
-            def doList(): Unit = {
-                val jl = new JArrayList[Object]() // Empty list is allowed.
-                var f = false
+            if (!isString(v1))
+                rtParamTypeError(fun, v1, "string")
+            if (!isString(v2))
+                rtParamTypeError(fun, v2, "string")
 
-                stack.drain { x ⇒
-                    jl.add(x.retVal)
-                    f = f || x.usedTok
-                }
+            asString(v1).split(asString(v2)).foreach { s ⇒ pushAny(s.strip, f1 
|| f2) }
+        }
 
-                Collections.reverse(jl)
+        /*
+         * Collection, statistical operations.
+         */
+        def doList(): Unit = {
+            val jl = new JArrayList[Object]() // Empty list is allowed.
+            var f = false
 
-                pushAny(jl, f)
+            stack.drain { x ⇒
+                jl.add(x.retVal)
+                f = f || x.usedTok
             }
-            def doMap(): Unit = {
-                if (stack.size % 2 != 0)
-                    errParamNum(fun)
 
-                val jm = new JHashMap[Object, Object]()
-                var f = false
+            Collections.reverse(jl)
 
-                val keys = mutable.Buffer.empty[AnyRef]
-                val vals = mutable.Buffer.empty[AnyRef]
+            pushAny(jl, f)
+        }
+        def doMap(): Unit = {
+            if (stack.size % 2 != 0)
+                rtParamNumError(fun)
 
-                var idx = 0
+            val jm = new JHashMap[Object, Object]()
+            var f = false
 
-                stack.drain { x ⇒
-                    if (idx % 2 == 0) keys += x.retVal else vals += x.retVal
-                    f = f || x.usedTok
+            val keys = mutable.Buffer.empty[AnyRef]
+            val vals = mutable.Buffer.empty[AnyRef]
 
-                    idx += 1
-                }
+            var idx = 0
 
-                for ((k, v) ← keys zip vals)
-                    jm.put(k, v)
+            stack.drain { x ⇒
+                if (idx % 2 == 0) keys += x.retVal else vals += x.retVal
+                f = f || x.usedTok
 
-                pushAny(jm, f)
+                idx += 1
             }
 
-            /*
-             * Metadata operations.
-             */
-            def doTokenMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(tok.meta(s), true) }
-            def doModelMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(tok.getModel.meta(s), false) }
-            def doReqMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.reqMeta.get(s).orNull, false) }
-            def doSysMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(U.sysEnv(s).orNull, false) }
-            def doUserMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.usrMeta.get(s).orNull, false) }
-            def doConvMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.convMeta.get(s).orNull, false) }
-            def doCompMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.compMeta.get(s).orNull, false) }
-            def doIntentMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.intentMeta.get(s).orNull, false) }
-
-            /*
-             * Math operations.
-             */
-            def doAbs(): Unit = get1Any() match {
-                case (a: JLong, f) ⇒ pushLong(Math.abs(a), f)
-                case (a: JDouble, f) ⇒ pushDouble(Math.abs(a), f)
-                case x ⇒ errParamType(fun, x, "numeric")
-            }
-            def doSquare(): Unit = get1Any() match {
-                case (a: JLong, f) ⇒ pushLong(a * a, f)
-                case (a: JDouble, f) ⇒ pushDouble(a * a, f)
-                case x ⇒ errParamType(fun, x, "numeric")
-            }
-            def doCeil(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.ceil(a), f) }
-            def doFloor(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.floor(a), f) }
-            def doSignum(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.signum(a), f) }
-            def doAcos(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.acos(a), f) }
-            def doAsin(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.asin(a), f) }
-            def doSin(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.sin(a), f) }
-            def doCos(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.cos(a), f) }
-            def doRint(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.rint(a), f) }
-            def doRound(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushLong(Math.round(a), f) }
-            def doSqrt(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.sqrt(a), f) }
-            def doCbrt(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.cbrt(a), f) }
-            def doAtan(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.atan(a), f) }
-            def doTan(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.tan(a), f) }
-            def doCosh(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.cosh(a), f) }
-            def doSinh(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.sinh(a), f) }
-            def doTanh(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.tanh(a), f) }
-            def doLog(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.log(a), f) }
-            def doLog1p(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.log1p(a), f) }
-            def doLog10(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.log10(a), f) }
-            def doDegrees(): Unit = get1Double() match { case (a: JDouble, f) 
⇒ pushDouble(Math.toDegrees(a), f) }
-            def doRadians(): Unit = get1Double() match { case (a: JDouble, f) 
⇒ pushDouble(Math.toRadians(a), f) }
-            def doExp(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.exp(a), f) }
-            def doExpm1(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.expm1(a), f) }
-            def doRandom(): Unit = pushDouble(Math.random, false)
-            def doPi(): Unit = pushDouble(Math.PI, false)
-            def doEuler(): Unit = pushDouble(Math.E, false)
-            def doPow(): Unit = get2Doubles() match { case (a1: JDouble, a2: 
JDouble, f) ⇒ pushDouble(Math.pow(a1, a2), f) }
-            def doHypot(): Unit = get2Doubles() match { case (a1: JDouble, a2: 
JDouble, f) ⇒ pushDouble(Math.hypot(a1, a2), f) }
-            def doAtan2(): Unit = get2Doubles() match { case (a1: JDouble, a2: 
JDouble, f) ⇒ pushDouble(Math.atan2(a1, a2), f) }
-
-            /*
-             * User operations.
-             */
-            def doUserId(): Unit = pushLong(termCtx.req.getUser.getId, false)
-
-            /*
-             * Company operations.
-             */
-            def doCompId(): Unit = pushLong(termCtx.req.getCompany.getId, 
false)
-
-            /*
-             * Request operations.
-             */
-            def doReqId(): Unit = pushAny(termCtx.req.getServerRequestId, 
false)
-
-            /*
-             * Date-time operations.
-             */
-            def doYear(): Unit = pushLong(LocalDate.now.getYear,false)
-            def doMonth(): Unit = pushLong(LocalDate.now.getMonthValue,false)
-            def doDayOfMonth(): Unit = 
pushLong(LocalDate.now.getDayOfMonth,false)
-            def doDayOfWeek(): Unit = 
pushLong(LocalDate.now.getDayOfWeek.getValue,false)
-            def doDayOfYear(): Unit = 
pushLong(LocalDate.now.getDayOfYear,false)
-
-            def doJson(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(U.jsonToJavaMap(asString(s)), f) }
-            def doIf(): Unit = {
-                ensureStack(3)
-
-                val (v1, v2, v3, f1, f2, f3) = pop3()
-
-                if (!isBoolean(v1))
-                    throw errParamType(fun, v1, "boolean")
-
-                if (asBool(v1))
-                    pushAny(v2, f1 || f2)
-                else
-                    pushAny(v3, f1 || f3)
-            }
+            for ((k, v) ← keys zip vals)
+                jm.put(k, v)
 
-            fun match {
-                // Metadata access.
-                case "meta_token" ⇒ doTokenMeta()
-                case "meta_model" ⇒ doModelMeta()
-                case "meta_intent" ⇒ doIntentMeta()
-                case "meta_req" ⇒ doReqMeta()
-                case "meta_user" ⇒ doUserMeta()
-                case "meta_company" ⇒ doCompMeta()
-                case "meta_sys" ⇒ doSysMeta()
-                case "meta_conv" ⇒ doConvMeta()
-
-                // Converts JSON to map.
-                case "json" ⇒ doJson()
-
-                // Inline if-statement.
-                case "if" ⇒ doIf()
-
-                // Token functions.
-                case "id" ⇒ pushAny(tok.getId, true)
-                case "ancestors" ⇒ pushAny(tok.getAncestors, true)
-                case "parent" ⇒ pushAny(tok.getParentId, true)
-                case "groups" ⇒ pushAny(tok.getGroups, true)
-                case "value" ⇒ pushAny(tok.getValue, true)
-                case "aliases" ⇒ pushAny(tok.getAliases, true)
-                case "start_idx" ⇒ pushLong(tok.getStartCharIndex, true)
-                case "end_idx" ⇒ pushLong(tok.getEndCharIndex, true)
-
-                // Request data.
-                case "req_id" ⇒ doReqId()
-                case "req_normtext" ⇒
-                case "req_tstamp" ⇒
-                case "req_addr" ⇒
-                case "req_agent" ⇒
-
-                // User data.
-                case "user_id" ⇒ doUserId()
-                case "user_fname" ⇒
-                case "user_lname" ⇒
-                case "user_email" ⇒
-                case "user_admin" ⇒
-                case "user_signup_tstamp" ⇒
-
-                // Company data.
-                case "comp_id" ⇒ doCompId()
-                case "comp_name" ⇒
-                case "comp_website" ⇒
-                case "comp_country" ⇒
-                case "comp_region" ⇒
-                case "comp_city" ⇒
-                case "comp_addr" ⇒
-                case "comp_postcode" ⇒
-
-                // String functions.
-                case "trim" ⇒ doTrim()
-                case "strip" ⇒ doTrim()
-                case "uppercase" ⇒ doUppercase()
-                case "lowercase" ⇒ doLowercase()
-                case "is_alpha" ⇒ doIsAlpha()
-                case "is_alphanum" ⇒ doIsAlphaNum()
-                case "is_whitespace" ⇒ doIsWhitespace()
-                case "is_num" ⇒ doIsNum()
-                case "is_numspace" ⇒ doIsNumSpace()
-                case "is_alphaspace" ⇒ doIsAlphaSpace()
-                case "is_alphanumspace" ⇒ doIsAlphaNumSpace()
-                case "substring" ⇒
-                case "index" ⇒
-                case "regex" ⇒
-                case "soundex" ⇒
-                case "split" ⇒ doSplit()
-                case "split_trim" ⇒ doSplitTrim()
-                case "replace" ⇒
-
-                // Math functions.
-                case "abs" ⇒ doAbs()
-                case "ceil" ⇒ doCeil()
-                case "floor" ⇒ doFloor()
-                case "rint" ⇒ doRint()
-                case "round" ⇒ doRound()
-                case "signum" ⇒ doSignum()
-                case "sqrt" ⇒ doSqrt()
-                case "cbrt" ⇒ doCbrt()
-                case "pi" ⇒ doPi()
-                case "euler" ⇒ doEuler()
-                case "acos" ⇒ doAcos()
-                case "asin" ⇒ doAsin()
-                case "atan" ⇒ doAtan()
-                case "cos" ⇒ doCos()
-                case "sin" ⇒ doSin()
-                case "tan" ⇒ doTan()
-                case "cosh" ⇒ doCosh()
-                case "sinh" ⇒ doSinh()
-                case "tanh" ⇒ doTanh()
-                case "atn2" ⇒ doAtan2()
-                case "degrees" ⇒ doDegrees()
-                case "radians" ⇒ doRadians()
-                case "exp" ⇒ doExp()
-                case "expm1" ⇒ doExpm1()
-                case "hypot" ⇒ doHypot()
-                case "log" ⇒ doLog()
-                case "log10" ⇒ doLog10()
-                case "log1p" ⇒ doLog1p()
-                case "pow" ⇒ doPow()
-                case "rand" ⇒ doRandom()
-                case "square" ⇒ doSquare()
-
-                // Collection functions.
-                case "list" ⇒ doList()
-                case "map" ⇒ doMap()
-                case "get" ⇒
-                case "index" ⇒
-                case "has" ⇒
-                case "tail" ⇒
-                case "add" ⇒
-                case "remove" ⇒
-                case "first" ⇒
-                case "last" ⇒
-                case "keys" ⇒
-                case "values" ⇒
-                case "length" ⇒
-                case "count" ⇒
-                case "take" ⇒
-                case "drop" ⇒
-                case "size" ⇒
-                case "length" ⇒
-                case "reverse" ⇒
-                case "is_empty" ⇒
-                case "non_empty" ⇒
-                case "to_string" ⇒
-
-                // Statistical operations.
-                case "avg" ⇒
-                case "max" ⇒ // Works for numerics as well.
-                case "min" ⇒ // Works for numerics as well.
-                case "stdev" ⇒
-                case "sum" ⇒
-
-                // Date-time functions.
-                case "year" ⇒ doYear() // 2021.
-                case "month" ⇒ doMonth() // 1 ... 12.
-                case "day_of_month" ⇒ doDayOfMonth() // 1 ... 31.
-                case "day_of_week" ⇒ doDayOfWeek()
-                case "day_of_year" ⇒ doDayOfYear()
-                case "hour" ⇒
-                case "min" ⇒
-                case "sec" ⇒
-                case "week_of_month" ⇒
-                case "week_of_year" ⇒
-                case "quarter" ⇒
-                case "msec" ⇒
-                case "now" ⇒ // Epoc time.
-
-                case _ ⇒ throw errUnknownFun(fun) // Assertion.
-            }
+            pushAny(jm, f)
+        }
+
+        /*
+         * Metadata operations.
+         */
+        def doTokenMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(tok.meta(s), true) }
+        def doModelMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(tok.getModel.meta(s), false) }
+        def doReqMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.reqMeta.get(s).orNull, false) }
+        def doSysMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(U.sysEnv(s).orNull, false) }
+        def doUserMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.usrMeta.get(s).orNull, false) }
+        def doConvMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.convMeta.get(s).orNull, false) }
+        def doCompMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.compMeta.get(s).orNull, false) }
+        def doIntentMeta(): Unit = get1Str() match { case (s, _) ⇒ 
pushAny(termCtx.intentMeta.get(s).orNull, false) }
+
+        /*
+         * Math operations.
+         */
+        def doAbs(): Unit = get1Any() match {
+            case (a: JLong, f) ⇒ pushLong(Math.abs(a), f)
+            case (a: JDouble, f) ⇒ pushDouble(Math.abs(a), f)
+            case x ⇒ rtParamTypeError(fun, x, "numeric")
+        }
+        def doSquare(): Unit = get1Any() match {
+            case (a: JLong, f) ⇒ pushLong(a * a, f)
+            case (a: JDouble, f) ⇒ pushDouble(a * a, f)
+            case x ⇒ rtParamTypeError(fun, x, "numeric")
+        }
+        def doCeil(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.ceil(a), f) }
+        def doFloor(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.floor(a), f) }
+        def doSignum(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.signum(a), f) }
+        def doAcos(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.acos(a), f) }
+        def doAsin(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.asin(a), f) }
+        def doSin(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.sin(a), f) }
+        def doCos(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.cos(a), f) }
+        def doRint(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.rint(a), f) }
+        def doRound(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushLong(Math.round(a), f) }
+        def doSqrt(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.sqrt(a), f) }
+        def doCbrt(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.cbrt(a), f) }
+        def doAtan(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.atan(a), f) }
+        def doTan(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.tan(a), f) }
+        def doCosh(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.cosh(a), f) }
+        def doSinh(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.sinh(a), f) }
+        def doTanh(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.tanh(a), f) }
+        def doLog(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.log(a), f) }
+        def doLog1p(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.log1p(a), f) }
+        def doLog10(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.log10(a), f) }
+        def doDegrees(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.toDegrees(a), f) }
+        def doRadians(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.toRadians(a), f) }
+        def doExp(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.exp(a), f) }
+        def doExpm1(): Unit = get1Double() match { case (a: JDouble, f) ⇒ 
pushDouble(Math.expm1(a), f) }
+        def doRandom(): Unit = pushDouble(Math.random, false)
+        def doPi(): Unit = pushDouble(Math.PI, false)
+        def doEuler(): Unit = pushDouble(Math.E, false)
+        def doPow(): Unit = get2Doubles() match { case (a1: JDouble, a2: 
JDouble, f) ⇒ pushDouble(Math.pow(a1, a2), f) }
+        def doHypot(): Unit = get2Doubles() match { case (a1: JDouble, a2: 
JDouble, f) ⇒ pushDouble(Math.hypot(a1, a2), f) }
+        def doAtan2(): Unit = get2Doubles() match { case (a1: JDouble, a2: 
JDouble, f) ⇒ pushDouble(Math.atan2(a1, a2), f) }
+
+        /*
+         * User operations.
+         */
+        def doUserId(): Unit = pushLong(termCtx.req.getUser.getId, false)
+
+        /*
+         * Company operations.
+         */
+        def doCompId(): Unit = pushLong(termCtx.req.getCompany.getId, false)
+
+        /*
+         * Request operations.
+         */
+        def doReqId(): Unit = pushAny(termCtx.req.getServerRequestId, false)
+
+        /*
+         * Date-time operations.
+         */
+        def doYear(): Unit = pushLong(LocalDate.now.getYear,false)
+        def doMonth(): Unit = pushLong(LocalDate.now.getMonthValue,false)
+        def doDayOfMonth(): Unit = pushLong(LocalDate.now.getDayOfMonth,false)
+        def doDayOfWeek(): Unit = 
pushLong(LocalDate.now.getDayOfWeek.getValue,false)
+        def doDayOfYear(): Unit = pushLong(LocalDate.now.getDayOfYear,false)
+
+        def doJson(): Unit = get1Str() match { case (s, f) ⇒ 
pushAny(U.jsonToJavaMap(asString(s)), f) }
+        def doIf(): Unit = {
+            ensureStack(3)
+
+            val (v1, v2, v3, f1, f2, f3) = pop3()
+
+            if (!isBoolean(v1))
+                throw rtParamTypeError(fun, v1, "boolean")
+
+            if (asBool(v1))
+                pushAny(v2, f1 || f2)
+            else
+                pushAny(v3, f1 || f3)
+        }
+
+        fun match {
+            // Metadata access.
+            case "meta_token" ⇒ doTokenMeta()
+            case "meta_model" ⇒ doModelMeta()
+            case "meta_intent" ⇒ doIntentMeta()
+            case "meta_req" ⇒ doReqMeta()
+            case "meta_user" ⇒ doUserMeta()
+            case "meta_company" ⇒ doCompMeta()
+            case "meta_sys" ⇒ doSysMeta()
+            case "meta_conv" ⇒ doConvMeta()
+
+            // Converts JSON to map.
+            case "json" ⇒ doJson()
+
+            // Inline if-statement.
+            case "if" ⇒ doIf()
+
+            // Token functions.
+            case "id" ⇒ pushAny(tok.getId, true)
+            case "ancestors" ⇒ pushAny(tok.getAncestors, true)
+            case "parent" ⇒ pushAny(tok.getParentId, true)
+            case "groups" ⇒ pushAny(tok.getGroups, true)
+            case "value" ⇒ pushAny(tok.getValue, true)
+            case "aliases" ⇒ pushAny(tok.getAliases, true)
+            case "start_idx" ⇒ pushLong(tok.getStartCharIndex, true)
+            case "end_idx" ⇒ pushLong(tok.getEndCharIndex, true)
+
+            // Request data.
+            case "req_id" ⇒ doReqId()
+            case "req_normtext" ⇒
+            case "req_tstamp" ⇒
+            case "req_addr" ⇒
+            case "req_agent" ⇒
+
+            // User data.
+            case "user_id" ⇒ doUserId()
+            case "user_fname" ⇒
+            case "user_lname" ⇒
+            case "user_email" ⇒
+            case "user_admin" ⇒
+            case "user_signup_tstamp" ⇒
+
+            // Company data.
+            case "comp_id" ⇒ doCompId()
+            case "comp_name" ⇒
+            case "comp_website" ⇒
+            case "comp_country" ⇒
+            case "comp_region" ⇒
+            case "comp_city" ⇒
+            case "comp_addr" ⇒
+            case "comp_postcode" ⇒
+
+            // String functions.
+            case "trim" ⇒ doTrim()
+            case "strip" ⇒ doTrim()
+            case "uppercase" ⇒ doUppercase()
+            case "lowercase" ⇒ doLowercase()
+            case "is_alpha" ⇒ doIsAlpha()
+            case "is_alphanum" ⇒ doIsAlphaNum()
+            case "is_whitespace" ⇒ doIsWhitespace()
+            case "is_num" ⇒ doIsNum()
+            case "is_numspace" ⇒ doIsNumSpace()
+            case "is_alphaspace" ⇒ doIsAlphaSpace()
+            case "is_alphanumspace" ⇒ doIsAlphaNumSpace()
+            case "substring" ⇒
+            case "index" ⇒
+            case "regex" ⇒
+            case "soundex" ⇒
+            case "split" ⇒ doSplit()
+            case "split_trim" ⇒ doSplitTrim()
+            case "replace" ⇒
+
+            // Math functions.
+            case "abs" ⇒ doAbs()
+            case "ceil" ⇒ doCeil()
+            case "floor" ⇒ doFloor()
+            case "rint" ⇒ doRint()
+            case "round" ⇒ doRound()
+            case "signum" ⇒ doSignum()
+            case "sqrt" ⇒ doSqrt()
+            case "cbrt" ⇒ doCbrt()
+            case "pi" ⇒ doPi()
+            case "euler" ⇒ doEuler()
+            case "acos" ⇒ doAcos()
+            case "asin" ⇒ doAsin()
+            case "atan" ⇒ doAtan()
+            case "cos" ⇒ doCos()
+            case "sin" ⇒ doSin()
+            case "tan" ⇒ doTan()
+            case "cosh" ⇒ doCosh()
+            case "sinh" ⇒ doSinh()
+            case "tanh" ⇒ doTanh()
+            case "atn2" ⇒ doAtan2()
+            case "degrees" ⇒ doDegrees()
+            case "radians" ⇒ doRadians()
+            case "exp" ⇒ doExp()
+            case "expm1" ⇒ doExpm1()
+            case "hypot" ⇒ doHypot()
+            case "log" ⇒ doLog()
+            case "log10" ⇒ doLog10()
+            case "log1p" ⇒ doLog1p()
+            case "pow" ⇒ doPow()
+            case "rand" ⇒ doRandom()
+            case "square" ⇒ doSquare()
+
+            // Collection functions.
+            case "list" ⇒ doList()
+            case "map" ⇒ doMap()
+            case "get" ⇒
+            case "index" ⇒
+            case "has" ⇒
+            case "tail" ⇒
+            case "add" ⇒
+            case "remove" ⇒
+            case "first" ⇒
+            case "last" ⇒
+            case "keys" ⇒
+            case "values" ⇒
+            case "length" ⇒
+            case "count" ⇒
+            case "take" ⇒
+            case "drop" ⇒
+            case "size" ⇒
+            case "length" ⇒
+            case "reverse" ⇒
+            case "is_empty" ⇒
+            case "non_empty" ⇒
+            case "to_string" ⇒
+
+            // Statistical operations.
+            case "avg" ⇒
+            case "max" ⇒ // Works for numerics as well.
+            case "min" ⇒ // Works for numerics as well.
+            case "stdev" ⇒
+            case "sum" ⇒
+
+            // Date-time functions.
+            case "year" ⇒ doYear() // 2021.
+            case "month" ⇒ doMonth() // 1 ... 12.
+            case "day_of_month" ⇒ doDayOfMonth() // 1 ... 31.
+            case "day_of_week" ⇒ doDayOfWeek()
+            case "day_of_year" ⇒ doDayOfYear()
+            case "hour" ⇒
+            case "min" ⇒
+            case "sec" ⇒
+            case "week_of_month" ⇒
+            case "week_of_year" ⇒
+            case "quarter" ⇒
+            case "msec" ⇒
+            case "now" ⇒ // Epoc time.
+
+            case _ ⇒ throw rtUnknownFunError(fun) // Assertion.
         }
     }
 }
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCIntentDslCompiler.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCIntentDslCompiler.scala
index 0a687c9..4bbbb8c 100644
--- 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCIntentDslCompiler.scala
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/impl/ver2/NCIntentDslCompiler.scala
@@ -25,7 +25,6 @@ import 
org.apache.nlpcraft.model.intent.impl.antlr4.{NCIntentDslParser ⇒ Parse
 import org.apache.nlpcraft.model.intent.utils.ver2._
 import org.apache.nlpcraft.model.{NCMetadata, NCRequest, NCToken, 
NCTokenPredicateContext, NCTokenPredicateResult}
 
-import java.lang.{IllegalArgumentException ⇒ IAE}
 import java.util.Optional
 import java.util.regex.{Pattern, PatternSyntaxException}
 import scala.collection.mutable
@@ -36,12 +35,12 @@ object NCIntentDslCompiler extends LazyLogging {
     // Compiler cache.
     private val cache = new mutable.HashMap[String, NCDslIntent]
 
-    private var mdlId: String = _
-
     /**
      *
+     * @param dsl
+     * @param mdlId
      */
-    class FiniteStateMachine(dsl: String) extends NCIntentDslBaseListener with 
NCBaseDslCompiler {
+    class FiniteStateMachine(dsl: String, mdlId: String) extends 
NCIntentDslBaseListener with NCBaseDslCompiler {
         // Intent components.
         private var id: String = _
         private var ordered: Boolean = false
@@ -107,6 +106,8 @@ object NCIntentDslCompiler extends LazyLogging {
         override def exitOrderedDecl(ctx: Parser.OrderedDeclContext): Unit = 
ordered = ctx.BOOL().getText == "true"
 
         override def exitFlowDecl(ctx: Parser.FlowDeclContext): Unit = {
+            implicit val evidence: ParserRuleContext = ctx
+
             val regex = ctx.qstring().getText
 
             if (regex != null && regex.length > 2) 
@@ -117,15 +118,17 @@ object NCIntentDslCompiler extends LazyLogging {
                     Pattern.compile(flowRegex.get)
                 catch {
                     case e: PatternSyntaxException ⇒
-                        throw new IAE(s"${e.getDescription} in DSL intent flow 
regex '${e.getPattern}' near index ${e.getIndex}.")
+                        newSyntaxError(s"${e.getDescription} in DSL intent 
flow regex '${e.getPattern}' near index ${e.getIndex}.")
                 }
         }
 
         override def exitTerm(ctx: Parser.TermContext): Unit = {
+            implicit val evidence: ParserRuleContext = ctx
+
             if (min < 0 || min > max)
-                throw new IAE(s"Invalid DSL intent term min quantifiers: $min 
(must be min >= 0 && min <= max).")
+                throw newSyntaxError(s"Invalid DSL intent term min 
quantifiers: $min (must be min >= 0 && min <= max).")
             if (max < 1)
-                throw new IAE(s"Invalid DSL intent term max quantifiers: $max 
(must be max >= 1).")
+                throw newSyntaxError(s"Invalid DSL intent term max 
quantifiers: $max (must be max >= 1).")
 
             val pred =
                 if (termMtdName != null) { // User-code defined term.
@@ -152,7 +155,8 @@ object NCIntentDslCompiler extends LazyLogging {
                             (res.getResult, res.wasTokenUsed())
                         }
                         catch {
-                            case e: Exception ⇒ throw new IAE(s"Failed to 
invoke custom DSL intent term: $mdlCls.$termMtdName", e)
+                            case e: Exception ⇒
+                                throw runtimeError(s"Failed to invoke custom 
DSL intent term: $mdlCls.$termMtdName", e)
                         }
                     }
                 }
@@ -169,7 +173,7 @@ object NCIntentDslCompiler extends LazyLogging {
                         val x = stack.pop()
 
                         if (!isBoolean(x.retVal))
-                            throw new IAE(s"DSL intent term does not return 
boolean value: ${ctx.getText}")
+                            throw runtimeError(s"DSL intent term does not 
return boolean value: ${ctx.getText}")
 
                         (asBool(x.retVal), x.usedTok)
                     }
@@ -202,24 +206,51 @@ object NCIntentDslCompiler extends LazyLogging {
 
             NCDslIntent(dsl, id, ordered, if (meta == null) Map.empty else 
meta, flowRegex, terms.toArray)
         }
+
+        override def syntaxError(errMsg: String, line: Int, pos: Int): NCE = 
throw new NCE(mkSyntaxError(errMsg, line, pos, dsl, mdlId))
+        override def runtimeError(errMsg: String, cause: Exception = null): 
NCE = throw new NCE(mkRuntimeError(errMsg, dsl, mdlId), cause)
     }
 
     /**
-     * Custom error handler.
+     *
+     * @param msg
+     * @param line
+     * @param charPos
+     * @param dsl
+     * @param mdlId
+     * @return
      */
-    class CompilerErrorListener(dsl: String) extends BaseErrorListener {
-        /**
-         *
-         * @param len
-         * @param pos
-         * @return
-         */
-        private def makeCharPosPointer(len: Int, pos: Int): String = {
-            val s = (for (_ ← 1 to len) yield '-').mkString("")
+    private def mkSyntaxError(msg: String, line: Int, charPos: Int, dsl: 
String, mdlId: String): String = {
+        val dash = "-" * dsl.length
+        val pos = Math.max(0, charPos - 1)
+        val posPtr = dash.substring(0, pos) + r("^") + dash.substring(pos + 1)
+        val dslPtr = dsl.substring(0, pos) + r(dsl.charAt(pos)) + 
dsl.substring(pos + 1)
+
+        s"Intent DSL syntax error at line $line:$charPos - $msg\n" +
+        s"  |-- ${c("Model:")}    $mdlId\n" +
+        s"  |-- ${c("Intent:")}   $dslPtr\n" +
+        s"  +-- ${c("Location:")} $posPtr"
+    }
 
-            s.substring(0, pos - 1) + '^' + s.substring(pos)
-        }
+    /**
+     *
+     * @param msg
+     * @param dsl
+     * @param mdlId
+     * @return
+     */
+    private def mkRuntimeError(msg: String, dsl: String, mdlId: String): 
String =
+        s"$msg\n" +
+        s"  |-- ${c("Model:")}  $mdlId\n" +
+        s"  +-- ${c("Intent:")} $dsl\n"
 
+    /**
+     * Custom error handler.
+     *
+     * @param dsl
+     * @param mdlId
+     */
+    class CompilerErrorListener(dsl: String, mdlId: String) extends 
BaseErrorListener {
         /**
          *
          * @param recognizer
@@ -235,15 +266,7 @@ object NCIntentDslCompiler extends LazyLogging {
             line: Int,
             charPos: Int,
             msg: String,
-            e: RecognitionException): Unit = {
-
-            val errMsg = s"Intent DSL syntax error at line $line:$charPos - 
$msg\n" +
-                s"  |-- ${c("Model:")}  $mdlId\n" +
-                s"  |-- ${c("Intent:")} $dsl\n" +
-                s"  +-- ${c("Error:")}  ${makeCharPosPointer(dsl.length, 
charPos)}"
-
-            throw new NCE(errMsg)
-        }
+            e: RecognitionException): Unit = throw new NCE(mkSyntaxError(msg, 
line, charPos, dsl, mdlId))
     }
 
     /**
@@ -258,9 +281,7 @@ object NCIntentDslCompiler extends LazyLogging {
     ): NCDslIntent = {
         require(dsl != null)
 
-        val src = dsl.strip()
-
-        this.mdlId = mdlId
+        val src = dsl.strip().replace("\r\n", " ").replace("\n", " ")
 
         val intent: NCDslIntent = cache.getOrElseUpdate(src, {
             // ANTLR4 armature.
@@ -271,11 +292,11 @@ object NCIntentDslCompiler extends LazyLogging {
             // Set custom error handlers.
             lexer.removeErrorListeners()
             parser.removeErrorListeners()
-            lexer.addErrorListener(new CompilerErrorListener(src))
-            parser.addErrorListener(new CompilerErrorListener(src))
+            lexer.addErrorListener(new CompilerErrorListener(src, mdlId))
+            parser.addErrorListener(new CompilerErrorListener(src, mdlId))
 
             // State automata.
-            val fsm = new FiniteStateMachine(src)
+            val fsm = new FiniteStateMachine(src, mdlId)
 
             // Parse the input DSL and walk built AST.
             (new ParseTreeWalker).walk(fsm, parser.intent())
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/model/intent/dsl/NCDslCompilerSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/model/intent/dsl/NCDslCompilerSpec.scala
index f2b0f23..9e2ddf2 100644
--- 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/model/intent/dsl/NCDslCompilerSpec.scala
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/model/intent/dsl/NCDslCompilerSpec.scala
@@ -25,22 +25,74 @@ import org.junit.jupiter.api.Test
  * Tests for DSL compiler.
  */
 class NCDslCompilerSpec {
+    /**
+     *
+     * @param dsl
+     */
+    private def checkOk(dsl: String): Unit = {
+        try {
+            NCIntentDslCompiler.compile(dsl, "mdl.id")
+
+            assert(true)
+        }
+        catch {
+            case _: Exception ⇒ assert(false)
+        }
+    }
+
+    /**
+     *
+     * @param txt
+     */
+    private def checkError(txt: String): Unit = {
+        try {
+            NCIntentDslCompiler.compile(txt, "mdl.id")
+
+            assert(false)
+        } catch {
+            case e: NCE ⇒
+                println(e.getMessage)
+                assert(true)
+        }
+    }
+
     @Test
     @throws[NCException]
-    def test(): Unit = {
-        val intent = NCIntentDslCompiler.compile(
+    def testOk(): Unit = {
+        checkOk(
             """
-              |intent=i1 flow="a[^0-9]b" meta={'a': true, 'b': {'arr': [1, 2, 
3]}} term(t1)={2 == 2 && size(id()) != -25}
-              |""".stripMargin, "mdl.id"
+              |intent=i1
+              |flow="a[^0-9]b"
+              |meta={'a': true, 'b': {'arr': [1, 2, 3]}}
+              |term(t1)={2 == 2 && size(id()) != -25}
+              |""".stripMargin
         )
-        val intent2 = NCIntentDslCompiler.compile(
+        checkOk(
             """
-              |intent=i1 flow="a[^0-9]b" term(t1)={has(json("{'a': true, 
'b\'2': {'arr': [1, 2, 3]}}"), map("k1\"", 'v1\'v1', "k2", "v2"))}
-              |""".stripMargin, "mdl.id"
+              |intent=i1
+              |flow="a[^0-9]b"
+              |term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 3]}}"), 
map("k1\"", 'v1\'v1', "k2", "v2"))}
+              |""".stripMargin
         )
+    }
 
-        () // Breakpoint.
-
-        // val ret = intent2.terms.head.pred(_, _, _)
+    @Test
+    @throws[NCException]
+    def testFail(): Unit = {
+        checkError(
+            """
+              |intent=i1
+              |flow="a[^0-9]b"
+              |meta={{'a': true, 'b': {'arr': [1, 2, 3]}}
+              |term(t1)={2 == 2 && size(id()) != -25}
+              |""".stripMargin
+        )
+        checkError(
+            """
+              |intent=i1
+              |flow="a[^0-9]b"
+              |term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 3]}}"), 
map("k1\"", 'v1\'v1', "k2", "v2"))}[1:2]
+              |""".stripMargin
+        )
     }
 }

Reply via email to