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

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


The following commit(s) were added to refs/heads/NLPCRAFT-474 by this push:
     new 0df8361  IDL initial tests.
0df8361 is described below

commit 0df836154093ee67e3882fa4ad7504c50f189797
Author: Sergey Kamov <skhdlem...@gmail.com>
AuthorDate: Tue Jan 25 20:12:12 2022 +0300

    IDL initial tests.
---
 .../intent/compiler/NCIdlCompilerSpec.scala        | 296 +++++++++++++++++++++
 .../intent/compiler/functions/NCIdlFunctions.scala | 203 ++++++++++++++
 .../functions/NCIdlFunctionsCollections.scala      |  79 ++++++
 .../compiler/functions/NCIdlFunctionsDate.scala    |  61 +++++
 .../compiler/functions/NCIdlFunctionsEntity.scala  | 255 ++++++++++++++++++
 .../compiler/functions/NCIdlFunctionsMath.scala    |  92 +++++++
 .../compiler/functions/NCIdlFunctionsMeta.scala    |  86 ++++++
 .../compiler/functions/NCIdlFunctionsOther.scala   |  73 +++++
 .../compiler/functions/NCIdlFunctionsRequest.scala |  53 ++++
 .../compiler/functions/NCIdlFunctionsStat.scala    |  53 ++++
 .../compiler/functions/NCIdlFunctionsStrings.scala |  92 +++++++
 .../functions/NCIdlFunctionsTokensUsed.scala       |  61 +++++
 .../nlpcraft/internal/intent/compiler/test_ok.idl  |  84 ++++++
 13 files changed, 1488 insertions(+)

diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIdlCompilerSpec.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIdlCompilerSpec.scala
new file mode 100644
index 0000000..abca683
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIdlCompilerSpec.scala
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.nlp.util.opennlp.*
+import org.junit.jupiter.api.Test
+
+/**
+ * Tests for IDL compiler.
+ */
+class NCIdlCompilerSpec:
+    private final val MODEL_ID = "test.mdl.id"
+
+    /**
+     *
+     * @param idl
+     */
+    private def checkCompileOk(idl: String): Unit =
+        try
+            NCIDLCompiler.compile(idl, CFG, MODEL_ID)
+
+            assert(true)
+        catch
+            case e: Exception => assert(assertion = false, e)
+
+    /**
+     *
+     * @param txt
+     */
+    private def checkCompileError(txt: String): Unit =
+        try
+            NCIDLCompiler.compile(txt, CFG, MODEL_ID)
+
+            assert(false)
+        catch
+            case e: NCException =>
+                println(e.getMessage)
+                assert(true)
+
+    @Test
+    @throws[NCException]
+    def testInlineCompileOk(): Unit =
+        NCIDLCompilerGlobal.clearCache(MODEL_ID)
+    
+        checkCompileOk(
+            """
+              
|import('org/apache/nlpcraft/model/intent/idl/compiler/test_ok.idl')
+              |""".stripMargin
+        )
+        checkCompileOk(
+            """
+              |intent=i1
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+        checkCompileOk(
+            """
+              |intent=i1
+              |     flow="a[^0-9]b"
+              |     term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 
3]}}"), list("موسكو\"", 'v1\'v1', "k2", "v2"))}
+              |""".stripMargin
+        )
+        checkCompileOk(
+            """
+              |// Some comments.
+              |fragment=f1
+              |     term(ft1)={2==2} /* Term block comment. */
+              |     term~/class#method/
+              |/*
+              | * +=====================+
+              | * | block comments......|
+              | * +=====================+
+              | */
+              |intent=i1
+              |     options={
+              |         'ordered': false,
+              |         'unused_free_words': true,
+              |         'unused_sys_toks': true,
+              |         'unused_usr_toks': false,
+              |         'allow_stm_only': false
+              |     }
+              |     flow="a[^0-9]b" // Flow comment.
+              |     term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 
3]}}"), list("موسكو\"", 'v1\'v1', "k2", "v2"))}
+              |     fragment(f1, {'a': true, 'b': ["s1", "s2"]}) /* Another 
fragment. */
+              |""".stripMargin
+        )
+        checkCompileOk(
+            """
+              |fragment=f21
+              |     term(f21_t1)={2==2}
+              |     term~/class#method/
+              |
+              |fragment=f22
+              |     term(f22_t1)={2==2_000_000.23}
+              |     fragment(f21)
+              |     term~/class#method/
+              |
+              |intent=i1
+              |     options={}
+              |     flow="a[^0-9]b"
+              |     term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 
3]}}"), list("موسكو\"", 'v1\'v1', "k2", "v2"))}
+              |     fragment(f21, {'a': true, 'b': ["s1", "s2"]})
+              |""".stripMargin
+        )
+
+    @Test
+    @throws[NCException]
+    def testInlineCompileFail(): Unit =
+        NCIDLCompilerGlobal.clearCache(MODEL_ID)
+
+        checkCompileError(
+            """
+              |intent=i1
+              |     options={'ordered': 1}
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+
+        checkCompileError(
+            """
+              |intent=i1
+              |     options={'ordered1': false}
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+
+        checkCompileError(
+            """
+              |intent=i1
+              |     options={'ordered': false, 'unknown': 1}
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+
+        checkCompileError(
+            """
+              |intent=i1
+              |     options={'ordered': false_1} # Broken JSON.
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+
+        checkCompileError(
+            """
+              |intent=i1
+              |     options={'ordered': null}
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+
+        checkCompileError(
+            """
+              |intent=i1
+              |     options={'ordered': false, 'ordered': true}
+              |     flow="a[^0-9]b"
+              |     meta={'a': true, 'b': {'Москва': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+
+        checkCompileError(
+            """
+              |intent=i1
+              |/*
+              | * +=====================+
+              | * | block comments......|
+              | * +=====================+
+              | */
+              |     flow="a[^0-9]b"
+              |     meta={{'a': true, 'b': {'arr': [1, 2, 3]}}
+              |     term(t1)={2 == 2 && size(#) != -25}
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     meta={'a': true, 'b': {'arr': [1, 2, 3]}}
+              |     term(t1)={
+              |         @x == 2 && size(#) != -25
+              |     }
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     flow="a[^0-9b"
+              |     term(t1)={true}
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     flow="a[^0-9b]"
+              |     term(t1)={
+              |         @x = 2
+              |         @x = 2
+              |
+              |         true
+              |     }
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     flow="a[^0-9b]"
+              |     term(t1)={
+              |         true
+              |
+              |         @x = 2
+              |     }
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     term(t1)={true}[2,1]
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     flow="a[^0-9b]"
+              |     term(t1)={true}
+              |     term(t1)={true}
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     flow="a[^0-9b]"
+              |     term(t1)={true}
+              |intent=i1
+              |     flow="a[^0-9b]"
+              |     term(t1)={true}
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |intent=i1
+              |     flow="a[^0-9]b"
+              |     term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 
3]}}"), list("k1\"", 'v1\'v1', "k2", "v2"))}[1:2]
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |fragment=f1
+              |     term(t1)={2==2}
+              |     term~/class#method/
+              |
+              |intent=i1
+              |     flow="a[^0-9]b"
+              |     term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 
3]}}"), list("موسكو\"", 'v1\'v1', "k2", "v2"))}
+              |     fragment(f1, {'a': true, 'b': ["s1", "s2"]})
+              |""".stripMargin
+        )
+        checkCompileError(
+            """
+              |fragment=f111
+              |     options={'ordered': 1}
+              |     term(t1)={2==2}
+              |     term~/class#method/
+              |
+              |intent=i1
+              |     flow="a[^0-9]b"
+              |     term(t1)={has(json("{'a': true, 'b\'2': {'arr': [1, 2, 
3]}}"), list("موسكو\"", 'v1\'v1', "k2", "v2"))}
+              |     fragment(f1_, {'a': true, 'b': ["s1", "s2"]})
+              |""".stripMargin
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctions.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctions.scala
new file mode 100644
index 0000000..3b6dbb1
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctions.scala
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.internal.intent.*
+import org.apache.nlpcraft.internal.intent.compiler.*
+import org.apache.nlpcraft.internal.intent.compiler.functions.*
+import org.apache.nlpcraft.nlp.util.opennlp.*
+import org.junit.jupiter.api.BeforeEach
+
+import scala.jdk.CollectionConverters.*
+import scala.language.implicitConversions
+
+private[functions] object NCIdlFunctions:
+    private final val MODEL_ID = "test.mdl.id"
+
+    case class TestDesc(
+        truth: String,
+        entity: Option[NCEntity] = None,
+        idlCtx: NCIDLContext,
+        isCustom: Boolean = false,
+        expectedRes: Boolean = true,
+        tokensUsed: Option[Int] = None
+    ):
+        // It should be lazy for errors verification methods.
+        lazy val term: NCIDLTerm =
+            val (s1, s2) = if isCustom then ('/', '/') else ('{', '}')
+
+            val intents = NCIDLCompiler.compile(s"intent=i 
term(t)=$s1$truth$s2", CFG, MODEL_ID)
+
+            require(intents.size == 1)
+            require(intents.head.terms.sizeIs == 1)
+
+            intents.head.terms.head
+
+        override def toString: String =
+            entity match
+                case Some(e) => s"Predicate [body='$truth', token=${e2s(e)}]"
+                case None => s"Predicate '$truth'"
+
+    object TestDesc:
+        def apply(truth: String): TestDesc =
+            new TestDesc(truth = truth, idlCtx = mkIdlContext())
+
+        def apply(truth: String, entity: NCEntity, idlCtx: NCIDLContext): 
TestDesc =
+            new TestDesc(truth = truth, entity = Option(entity), idlCtx = 
idlCtx)
+
+        def apply(truth: String, entity: NCEntity): TestDesc =
+            new TestDesc(truth = truth, entity = Option(entity), idlCtx = 
mkIdlContext(entities = Seq(entity)))
+
+    given Conversion[String, TestDesc] with
+        def apply(s: String): TestDesc = TestDesc(s)
+
+    private def e2s(t: NCEntity): String =
+        // TODO:
+        t.toString
+
+    //        def nvl(s: String, name: String): String = if s != null then s 
else s"$name (not set)"
+    //
+    //        s"text=${nvl(t.getOriginalText, "text")} [${nvl(t.getId, "id")}]"
+
+    def mkIdlContext(
+        entities: Seq[NCEntity] = Seq.empty,
+        reqSrvReqId: String = null,
+        reqNormText: String = null,
+        reqTstamp: Long = 0,
+        reqAddr: String = null,
+        reqAgent: String = null,
+        reqData: Map[String, AnyRef] = Map.empty,
+        intentMeta: Map[String, AnyRef] = Map.empty,
+        convMeta: Map[String, AnyRef] = Map.empty,
+        fragMeta: Map[String, AnyRef] = Map.empty
+    ): NCIDLContext =
+        NCIDLContext(
+            CFG,
+            entities,
+            intentMeta = intentMeta,
+            convMeta = convMeta,
+            fragMeta = fragMeta,
+            req = new NCRequest:
+                override def getUserId: String = "userID" // TODO:
+                override def getRequestId: String = reqSrvReqId
+                override def getText: String = reqNormText
+                override def getReceiveTimestamp: Long = reqTstamp
+                override def getRequestData: java.util.Map[String, AnyRef] = 
reqData.asJava
+        )
+
+    // TODO:
+    def mkEntity(
+        id: String = null,
+        srvReqId: String = null,
+        parentId: String = null,
+        value: String = null,
+        txt: String = null,
+        normTxt: String = null,
+        start: Int = 0,
+        end: Int = 0,
+        groups: Seq[String] = Seq.empty,
+        ancestors: Seq[String] = Seq.empty,
+        aliases: Set[String] = Set.empty,
+        partTokens: Seq[NCToken] = Seq.empty,
+        `abstract`: Boolean = false,
+        meta: Map[String, AnyRef] = Map.empty[String, AnyRef]
+    ): NCEntity =
+    // TODO:
+        null
+//        val map = new util.HashMap[String, AnyRef]
+//
+//        map.putAll(meta.asJava)
+//
+//        map.put("nlpcraft:nlp:origtext", txt)
+//        map.put("nlpcraft:nlp:normtext", normTxt)
+//
+//        new NCPropertyMapAdapter with NCEntity():
+//            override def getTokens: util.List[NCToken] = ???
+//            override def getRequestId: String = ???
+//            override def getId: String = ???
+//
+
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+
+/**
+  * Tests for IDL functions.
+  */
+private[functions] trait NCIdlFunctions:
+    @BeforeEach
+    def before(): Unit = NCIDLCompilerGlobal.clearCache(MODEL_ID)
+
+    /**
+      *
+      * @param funcs
+      */
+    protected def test(funcs: TestDesc*): Unit =
+        for (f <- funcs)
+            val item =
+                try
+                    // Process declarations.
+                    f.idlCtx.vars ++= f.term.decls
+
+                    // Execute term's predicate.
+                    // TODO: index
+                    
f.term.pred.apply(NCIDLEntity(f.entity.getOrElse(mkEntity()), 0), f.idlCtx)
+                catch
+                    case e: NCException => throw e
+                    case e: Exception => throw new Exception(s"Execution error 
processing: $f", e)
+
+            item.value match
+                case b: java.lang.Boolean => require(if f.expectedRes then b 
else !b, s"Unexpected '$b' result for: $f")
+                case _ =>
+                    require(
+                        requirement = false,
+                        s"Unexpected result type [resType=${
+                            if item.value == null then "null"
+                            else item.value.getClass.getName
+                        }, resValue=${item.value}, function=$f]"
+                    )
+
+            f.tokensUsed match
+                case Some(exp) =>
+                    require(
+                        exp == item.entUse,
+                        s"Unexpected tokens used [expectedTokensUsed=$exp, 
resultEntityUsed=${item.entUse}, function=$f]"
+                    )
+
+                case None => // No-op.
+
+    /**
+      *
+      * @param funcs
+      */
+    protected def expectError(funcs: TestDesc*): Unit =
+        for (f <- funcs)
+            try
+                test(f)
+
+                require(false)
+            catch
+                case e: Exception =>
+                    println(s"Expected error: ${e.getLocalizedMessage}")
+
+                    var cause = e.getCause
+
+                    while (cause != null)
+                        println(s"  Cause: ${cause.getLocalizedMessage} 
(${cause.getClass.getName})")
+
+                        cause = cause.getCause
\ No newline at end of file
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsCollections.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsCollections.scala
new file mode 100644
index 0000000..9f2498b
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsCollections.scala
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.*
+import org.junit.jupiter.api.Test
+
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'collections' functions.
+  */
+class NCIdlFunctionsCollections extends NCIdlFunctions:
+    private final val js = "{\"k1\": \"v1\"}"
+
+    @Test
+    def test(): Unit =
+        test(
+            "list(1, 2, 3) == list(1, 2, 3)",
+            "list(1.0, 2, 3) == list(1.0, 2, 3)",
+            "list(1, 2, 3) != list(1.0, 2, 3)",
+            "get(list(1, 2, 3), 1) == 2",
+            "has(list(1, 2, 3), 1) == true",
+            "has(list(1.1, 2.1, 3.1), 1.1) == true",
+            "has(list(1.0, 2.0, 3.0), 1.0) == true",
+            "has(list(1.0, 2.0, 3.0), 1) == false", // Different types.
+            "has(list('1', '2', '3'), '1') == true",
+            "has(list(1, 2, 3), 5) == false",
+            "has(list(1.1, 2.1, 3.1), 5.1) == false",
+            "has(list('1', '2', '3'), '5') == false",
+            "has_any(list('1', '2', '3'), list('1', '20', '30')) == true",
+            "has_any(list('1', '2', '3'), list('10', '20', '30')) == false",
+            "has_all(list('1', '2', '3'), list('1', '20', '30')) == false",
+            "has_all(list('1', '2', '3'), list('1', '2', '3')) == true",
+            "size(list()) == 0",
+            "list(1, 2, 3) == list(1, 2, 3)",
+            "first(list(1, 2, 3)) == 1",
+            "get(list(1, 2, 3), 0) == 1",
+            "@lst = list(1, 2, 3) first(reverse(@lst)) == last(@lst)",
+            "last(list(1, 2, 3)) == 3",
+            "is_empty(list()) == true",
+            "is_empty(list(1)) == false",
+            "non_empty(list()) == false",
+            "non_empty(list(1)) == true",
+            "reverse(list(1.0, 2, 3)) == list(3, 2, 1.0)",
+            "sort(list(2, 1, 3)) == list(1, 2, 3)",
+            "sort(list('c', 'a', 'b')) == list('a', 'b', 'c')",
+            "size(list(2.0, 1, 3)) == 3",
+            "length(list(2.0, 1, 3)) == 3",
+            "count(list(2.0, 1, 3)) == 3",
+            "size(list()) == 0",
+            "length(list()) == 0",
+            "count(list()) == 0",
+            s"keys(json('$js')) == list('k1')",
+            s"values(json('$js')) == list('v1')",
+            "sort(distinct(list(1, 2, 3, 3, 2))) == sort(list(1, 2, 3))",
+            "distinct(list(1.0, 2.0, 3.0, 3.0, 2.0)) == list(1.0, 2.0, 3.0)",
+            "distinct(list('1', '2', '3', '3', '2')) == list('1', '2', '3')",
+            "sort(concat(list(1, 2, 3), list(1, 2, 3))) == sort(list(1, 2, 3, 
1, 2, 3))",
+            "concat(list(1, 2, 3), list()) == list(1, 2, 3)",
+            "concat(list(), list()) == list()",
+            "concat(list(1, 2), list(3.0)) == list(1, 2, 3.0)"
+        )
+
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsDate.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsDate.scala
new file mode 100644
index 0000000..35f4c37
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsDate.scala
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.apache.nlpcraft.internal.util.NCUtils
+import org.junit.jupiter.api.Test
+
+import java.time.*
+import java.time.temporal.IsoFields
+import java.util.Calendar as C
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'dates' functions.
+  */
+class NCIdlFunctionsDate extends NCIdlFunctions:
+    @Test
+    def test(): Unit =
+        def test0(): Unit =
+            val d = LocalDate.now
+            val t = LocalTime.now
+            val c = C.getInstance()
+
+            test(
+                s"year() - ${d.getYear} == 0",
+                s"month() - ${d.getMonthValue} == 0",
+                s"day_of_month() - ${d.getDayOfMonth} == 0",
+                s"day_of_week() - ${d.getDayOfWeek.getValue} == 0",
+                s"day_of_year() - ${d.getDayOfYear} == 0",
+                s"hour() - ${t.getHour} == 0",
+                s"minute() - ${t.getMinute} == 0",
+                s"second() - ${t.getSecond} < 5",
+                s"week_of_month() - ${c.get(C.WEEK_OF_MONTH)} == 0",
+                s"week_of_year() - ${c.get(C.WEEK_OF_YEAR)} == 0",
+                s"quarter() - ${d.get(IsoFields.QUARTER_OF_YEAR)} == 0",
+                s"now() - ${NCUtils.now()} < 5000"
+            )
+
+        try
+            test0()
+        catch
+            case _: AssertionError =>
+                // Some field more than `second` can be changed. One more 
attempt.
+                test0()
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsEntity.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsEntity.scala
new file mode 100644
index 0000000..545f35d
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsEntity.scala
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import java.lang.{Boolean as JBool, Integer as JInt}
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'entities' functions.
+  */
+class NCIdlFunctionsEntity extends NCIdlFunctions:
+    private final val meta: Map[String, AnyRef] = Map(
+        "nlpcraft:nlp:stopword" -> JBool.TRUE,
+        "nlpcraft:nlp:freeword" -> JBool.TRUE,
+        "nlpcraft:nlp:origtext" -> "orig text",
+        "nlpcraft:nlp:index" -> JInt.valueOf(11),
+        "nlpcraft:nlp:normtext" -> "norm text",
+        "nlpcraft:nlp:direct" -> JBool.TRUE,
+        "nlpcraft:nlp:english" -> JBool.TRUE,
+        "nlpcraft:nlp:swear" -> JBool.TRUE,
+        "nlpcraft:nlp:quoted" -> JBool.TRUE,
+        "nlpcraft:nlp:bracketed" -> JBool.TRUE,
+        "nlpcraft:nlp:dict" -> JBool.TRUE,
+        "nlpcraft:nlp:lemma" -> "lemma",
+        "nlpcraft:nlp:stem" -> "stem",
+        "nlpcraft:nlp:sparsity" -> JInt.valueOf(112),
+        "nlpcraft:nlp:pos" -> "pos",
+        "nlpcraft:nlp:unid" -> "21421"
+    )
+
+    private def mkMeta(truth: String): TestDesc = TestDesc(truth = truth, 
entity = mkEntity(meta = meta))
+
+    @Test
+    def testMainTokenProperties(): Unit =
+        test(
+            TestDesc(
+                truth = "# == 'a'",
+                entity = mkEntity(id = "a")
+            ),
+            mkMeta(truth = s"tok_lemma == '${meta("nlpcraft:nlp:lemma")}'"),
+            mkMeta(truth = s"tok_stem == '${meta("nlpcraft:nlp:stem")}'"),
+            mkMeta(truth = s"tok_pos == '${meta("nlpcraft:nlp:pos")}'"),
+            mkMeta(truth = s"tok_sparsity == 
${meta("nlpcraft:nlp:sparsity")}"),
+            mkMeta(truth = s"tok_unid == '${meta("nlpcraft:nlp:unid")}'"),
+            TestDesc(
+                truth = s"tok_is_abstract()",
+                entity = mkEntity(`abstract` = true)
+            ),
+            mkMeta(truth = s"tok_is_abstract == false"),
+            mkMeta(truth = s"tok_is_bracketed == 
${meta("nlpcraft:nlp:bracketed")}"),
+            mkMeta(truth = s"tok_is_direct == ${meta("nlpcraft:nlp:direct")}"),
+            mkMeta(truth = s"tok_is_permutated != 
${meta("nlpcraft:nlp:direct")}"),
+            mkMeta(truth = s"tok_is_english == 
${meta("nlpcraft:nlp:english")}"),
+            mkMeta(truth = s"tok_is_freeword == 
${meta("nlpcraft:nlp:freeword")}"),
+            mkMeta(truth = s"tok_is_quoted == ${meta("nlpcraft:nlp:quoted")}"),
+            mkMeta(truth = s"tok_is_stopword == 
${meta("nlpcraft:nlp:stopword")}"),
+            mkMeta(truth = s"tok_is_swear == ${meta("nlpcraft:nlp:swear")}"),
+            TestDesc(
+                truth = s"tok_is_user()",
+                entity = mkEntity(id = "aa")
+            ),
+            TestDesc(
+                truth = s"!tok_is_user()",
+                entity = mkEntity(id = "nlpcraft:nlp")
+            ),
+            mkMeta(truth = s"tok_is_wordnet() == 
${meta("nlpcraft:nlp:dict")}"),
+            TestDesc(
+                truth = s"tok_ancestors() == list('1', '2')",
+                entity = mkEntity(ancestors = Seq("1", "2"))
+            ),
+            TestDesc(
+                truth = s"tok_parent() == 'parentId'",
+                entity = mkEntity(parentId = "parentId")
+            ),
+            TestDesc(
+                truth = "tok_groups() == list('1', '2')",
+                entity = mkEntity(groups = Seq("1", "2"))
+            ),
+            TestDesc(
+                truth = "tok_value() == 'value'",
+                entity = mkEntity(value = "value")
+            ),
+            TestDesc(
+                truth = "tok_value() == null",
+                entity = mkEntity()
+            ),
+            TestDesc(
+                truth = "tok_start_idx() == 123",
+                entity = mkEntity(start = 123)
+            ),
+            TestDesc(
+                truth = "tok_end_idx() == 123",
+                entity = mkEntity(end = 123)
+            ),
+            TestDesc(truth = "tok_this() == tok_this()", idlCtx = 
mkIdlContext())
+        )
+
+    @Test
+    def testTokenFirstLast(): Unit =
+        val e = mkEntity(id = "a")
+
+        // TODO:
+        //tok.getMetadata.put("nlpcraft:nlp:index", 0)
+
+        test(
+            TestDesc(
+                truth = "tok_is_first()",
+                entity = e,
+                idlCtx = mkIdlContext(entities = Seq(e))
+            ),
+            TestDesc(
+                truth = "tok_is_last()",
+                entity = e,
+                idlCtx = mkIdlContext(entities = Seq(e))
+            )
+        )
+
+    @Test
+    def testTokenBeforeId(): Unit =
+        val e1 = mkEntity(id = "1")
+        val e2 = mkEntity(id = "2")
+
+        // TODO:
+//        e1.getMetadata.put("nlpcraft:nlp:index", 0)
+//        e2.getMetadata.put("nlpcraft:nlp:index", 1)
+
+        test(
+            TestDesc(
+                truth = "tok_is_before_id('2')",
+                entity = e1,
+                idlCtx = mkIdlContext(Seq(e1, e2))
+            )
+        )
+
+    @Test
+    def testTokenAfterId(): Unit =
+        val e1 = mkEntity(id = "1")
+        val e2 = mkEntity(id = "2")
+
+        // TODO:
+//        e1.getMetadata.put("nlpcraft:nlp:index", 0)
+//        e2.getMetadata.put("nlpcraft:nlp:index", 1)
+
+        test(
+            TestDesc(
+                truth = "tok_is_after_id('1')",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2))
+            )
+        )
+
+    @Test
+    def testTokenBetweenIds(): Unit =
+        val e1 = mkEntity(id = "1", groups = Seq("grp1"))
+        val e2 = mkEntity(id = "2", groups = Seq("grp2"))
+        val e3 = mkEntity(id = "3", groups = Seq("grp3"))
+
+        // TODO:
+//        e1.getMetadata.put("nlpcraft:nlp:index", 0)
+//        e2.getMetadata.put("nlpcraft:nlp:index", 1)
+//        e3.getMetadata.put("nlpcraft:nlp:index", 2)
+
+        test(
+            TestDesc(
+                truth = "tok_is_between_ids('1', '3')",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            ),
+            TestDesc(
+                truth = "tok_is_between_groups('grp1', 'grp3')",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            )
+        )
+
+    @Test
+    def testTokenCount(): Unit =
+        val e1 = mkEntity(id = "1")
+        val e2 = mkEntity(id = "2")
+
+        test(
+            TestDesc(
+                truth = "tok_count() == 2",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2))
+            )
+        )
+
+    @Test
+    def testTokenText(): Unit =
+        val e = mkEntity(id = "1", txt="txt", normTxt = "normTxt")
+
+        test(
+            TestDesc(
+                truth = "tok_txt() == 'txt'",
+                entity = e
+            ),
+            TestDesc(
+                truth = "tok_norm_txt() == 'normTxt'",
+                entity = e
+            )
+        )
+
+    @Test
+    def testTokenForAll(): Unit =
+        val e1 = mkEntity(id = "1", parentId = "x")
+        val e2 = mkEntity(id = "2", groups = Seq("g", "z", "w"))
+        val e3 = mkEntity(id = "2")
+
+        test(
+            TestDesc(
+                truth = "size(tok_all_for_id('1')) == 1",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            ),
+            TestDesc(
+                truth = "size(tok_all_for_parent('x')) == 1",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            ),
+            TestDesc(
+                truth = "size(tok_all()) == 3",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            ),
+            TestDesc(
+                truth = "tok_count == size(tok_all())",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            ),
+            TestDesc(
+                truth =
+                    "size(tok_all_for_group('g')) == 1 && 
#(first(tok_all_for_group('w'))) == '2' && 
is_empty(tok_all_for_group('unknown'))",
+                entity = e2,
+                idlCtx = mkIdlContext(Seq(e1, e2, e3))
+            )
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsMath.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsMath.scala
new file mode 100644
index 0000000..6726536
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsMath.scala
@@ -0,0 +1,92 @@
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'math' functions.
+  */
+class NCIdlFunctionsMath extends NCIdlFunctions:
+    @Test
+    def testError(): Unit =
+        expectError(
+            // Invalid name.
+            "xxx(1, 1)",
+            "xxx()",
+            // Invalid arguments count.
+            "atan(1, 1) == 1",
+            "pi(1)"
+        )
+
+    @Test
+    def test(): Unit =
+        test(
+            "abs(2) == 2",
+            "abs(-2.2) == 2.2",
+            "ceil(1.8) == 2.0",
+            "floor(1.1) == 1.0",
+            "rint(1.8) == 2.0",
+            "round(1.8) == 2",
+            "to_double(25) == 25.0",
+            "to_double(25) == 25",
+            "to_double('25.25') == 25.25",
+            "to_int(25.02) == 25",
+            "to_int('101') == 101",
+            "round(to_double(25)) == 25",
+            "signum(-1.8) == -1.0",
+            "sqrt(4) - 2 < 0.001",
+            "cbrt(8) - 2 < 0.001",
+            "acos(8.1) != acos(9.1)",
+            "asin(8.1) != asin(9)",
+            "atan(8) != atan(9.1)",
+            "cos(1.5708) < 0.001",
+            "cos(1.5708) < 0.001",
+            "sin(1.5708) > 0.999",
+            "sin(1.5708) > 0.999",
+            "tan(8) != tan(9)",
+            "cosh(8) != cosh(9)",
+            "sinh(8) != sinh(9)",
+            "tanh(8) != tanh(9)",
+            "atan2(8, 2) != atan2(9, 2)",
+            "atan2(8.1, 2.1) != atan2(9.1, 2.1)",
+            "degrees(1.5708) - 90 < 0.001",
+            "radians(90) - 1.5708 < 0.001",
+            "exp(2) != exp(3)",
+            "expm1(8) != expm1(9)",
+            "hypot(2, 3) - 3.606  < 0.001",
+            "log(8) != log(9)",
+            "log10(8) != log10(9)",
+            "log1p(8) != log1p(9)",
+            "pow(2, 2) - 4 < 0.001",
+            "pow(2.0, 2.0) - 4 < 0.001",
+            "pow(2, 2.0) - 4 < 0.001",
+            "pow(2.0, 2) - 4 < 0.001",
+            "pow(2.0, 2) - 4 < 0.001",
+            "rand() < 1",
+            "pi() - 3.142 < 0.01",
+            "euler() - 2.718 < 0.01",
+            "to_double(2) - 2.0 < 0.01",
+            "1.1 == 1.1",
+            "1.0 == 1",
+            "1 == 1.0"
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsMeta.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsMeta.scala
new file mode 100644
index 0000000..b4e7d3c
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsMeta.scala
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.internal.intent.NCIDLContext
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import java.util
+import java.util.Optional
+import scala.jdk.CollectionConverters.MapHasAsJava
+import scala.language.implicitConversions
+import scala.sys.SystemProperties
+
+/**
+  * Tests for 'meta' functions.
+  */
+class NCIdlFunctionsMeta extends NCIdlFunctions:
+    @Test
+    def testMetaSys(): Unit =
+        val sys = new SystemProperties
+
+        sys.put("k1", "v1")
+
+        try
+            testValue("meta_sys")
+        finally
+            sys.remove("k1")
+
+    @Test
+    def testMetaToken(): Unit =
+        testValue(
+            "meta_tok",
+            entity = Option(mkEntity(meta = Map("k1" -> "v1")))
+        )
+
+    @Test
+    def testMetaRequest(): Unit =
+        testValue(
+            "meta_req",
+            mkIdlContext(reqData = Map("k1" -> "v1"))
+        )
+
+    @Test
+    def testMetaConv(): Unit =
+        testValue(
+            "meta_conv",
+            mkIdlContext(convMeta = Map("k1" -> "v1"))
+        )
+
+    @Test
+    def testMetaFrag(): Unit =
+        testValue(
+            "meta_frag",
+            mkIdlContext(fragMeta = Map("k1" -> "v1"))
+        )
+
+    // Simplified test.
+    @Test
+    def testMetaModel(): Unit = testNoValue("meta_model", mkIdlContext())
+
+    // Simplified test.
+    @Test
+    def testMetaIntent(): Unit = testNoValue("meta_intent", mkIdlContext())
+
+    private def testValue(f: String, idlCtx: => NCIDLContext = mkIdlContext(), 
entity: Option[NCEntity] = None): Unit =
+        test(TestDesc(truth = s"$f('k1') == 'v1'", entity = entity, idlCtx = 
idlCtx))
+
+    private def testNoValue(f: String, idlCtx: => NCIDLContext = 
mkIdlContext(), entity: Option[NCEntity] = None): Unit =
+        test(TestDesc(truth = s"$f('k1') == null", entity = entity, idlCtx = 
idlCtx))
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsOther.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsOther.scala
new file mode 100644
index 0000000..c7e57ad
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsOther.scala
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import scala.language.implicitConversions
+import scala.sys.SystemProperties
+
+/**
+  * Tests for 'other' functions.
+  */
+class NCIdlFunctionsOther extends NCIdlFunctions:
+    @Test
+    def test1(): Unit =
+        // If.
+        test(
+            TestDesc(truth = "if(true, 1, 0) == 1"),
+            TestDesc(truth = "if(false, 1, 0) == 0"),
+            TestDesc(truth = "or_else(null, false) == false"),
+            TestDesc(truth = "or_else('s', list(1, 2, 3)) == 's'"),
+            TestDesc(truth = "or_else(meta_model('unknown_prop'), list(1, 2, 
3)) == list(1, 2, 3)")
+        )
+    
+    @Test
+    def test2(): Unit =
+        val sys = new SystemProperties
+
+        sys.put("k1", "v1")
+
+        try 
+            val js = "{\"k1\": \"v1\"}"
+
+            // JSON.
+            test(
+                s"has(keys(json('$js')), 'k1') == true",
+                s"has(keys(json('$js')), 'k2') == false"
+            )
+        finally
+            sys.remove("k1")
+
+    @Test
+    def test3(): Unit =
+        test(
+            "to_string(list(1, 2, 3)) == list('1', '2', '3')",
+            "to_string(3.123) == '3.123'"
+        )
+
+    @Test
+    def test4(): Unit =
+        test(
+            "true",
+            "true == true",
+            "false == false",
+            "false != true",
+            "true != false"
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsRequest.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsRequest.scala
new file mode 100644
index 0000000..d606c80
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsRequest.scala
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'requests' functions.
+  */
+class NCIdlFunctionsRequest extends NCIdlFunctions:
+    @Test
+    def test(): Unit =
+        val reqSrvReqId = "id"
+        val reqNormText = "some text"
+        val reqTstamp: java.lang.Long = 123
+        val reqAddr = "address"
+        val reqAgent = "agent"
+
+        val idlCtx = mkIdlContext(
+            reqSrvReqId = reqSrvReqId,
+            reqNormText = reqNormText,
+            reqTstamp = reqTstamp,
+            reqAddr = reqAddr,
+            reqAgent = reqAgent
+        )
+
+        def mkTestDesc(truth: String): TestDesc = TestDesc(truth = truth, 
idlCtx = idlCtx)
+
+        test(
+            mkTestDesc(s"req_id == '$reqSrvReqId'"),
+            mkTestDesc(s"req_normtext == '$reqNormText'"),
+            mkTestDesc(s"req_tstamp == $reqTstamp"),
+            mkTestDesc(s"req_addr == '$reqAddr'"),
+            mkTestDesc(s"req_agent == '$reqAgent'")
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsStat.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsStat.scala
new file mode 100644
index 0000000..b5e5f3d
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsStat.scala
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'stat' functions.
+  */
+class NCIdlFunctionsStat extends NCIdlFunctions:
+    @Test
+    def testError(): Unit =
+        expectError(
+            "avg(list()) == 2",
+            "avg(list('A')) == 2",
+            "stdev(list()) == 2",
+            "stdev(list('A')) == 2"
+        )
+
+    @Test
+    def test(): Unit =
+        test(
+            "max(list(1, 2, 3)) == 3",
+            "max(list(1.0, 2.0, 3.0)) == 3.0",
+            "min(list(1, 2, 3)) == 1",
+            "min(list(1.0, 2.0, 3.0)) == 1.0",
+            "avg(list(1.0, 2.0, 3.0)) == 2.0",
+            "avg(list(1, 2, 3)) == 2.0",
+            "avg(list(1.2, 2.2, 3.2)) == 2.2",
+            "avg(list(1, 2.2, 3.1)) == 2.1",
+            "stdev(list(1, 2.2, 3.1)) > 0",
+            "stdev(list(1, 2, 3)) > 0",
+            "stdev(list(0.0, 0.0, 0.0)) == 0.0",
+            "stdev(list(0, 0, 0)) == 0.0"
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsStrings.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsStrings.scala
new file mode 100644
index 0000000..c3e33c3
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsStrings.scala
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+import scala.language.implicitConversions
+
+/**
+  * Tests for 'strings' functions.
+  */
+class NCIdlFunctionsStrings extends NCIdlFunctions:
+    @Test
+    def test(): Unit =
+        test(
+            "trim(' a b  ') == 'a b'",
+            "strip(' a b  ') == 'a b'",
+            "uppercase('aB') == 'AB'",
+            "lowercase('aB') == 'ab'",
+            "is_alpha('aB') == true",
+            "is_alpha('aB1') == false",
+            "is_alphanum('aB1') == true",
+            "is_whitespace('A') == false",
+            "is_num('a') == false",
+            "is_num('1') == true",
+            "is_numspace('A') == false",
+            "is_alphaspace('A') == true",
+            "is_alphanumspace('A') == true",
+            "is_alphanumspace('1 A') == true",
+            "starts_with('ab', 'a') == true",
+            "starts_with('ab', 'b') == false",
+            "ends_with('ab', 'a') == false",
+            "ends_with('ab', 'b') == true",
+            "contains('ab', 'a') == true",
+            "contains('ab', 'bc') == false",
+            "index_of('ab', 'b') == 1",
+            "index_of('ab', 'bc') == -1",
+            "substr('abc', 0, 1) == 'a'",
+            "regex('textabc', '^text.*$') == true",
+            "regex('_textabc', '^text.*$') == false",
+            "substr('abc', 0, 2) == 'ab'",
+            "replace('abc', 'a', 'X') == 'Xbc'",
+            "replace('abc', '0',  '0') == 'abc'",
+            "split('1 A', ' ') == list('1', 'A')",
+            "split_trim('1 A    ', ' ') == list('1', 'A')",
+            "is_empty('a') == false",
+            "non_empty('a') == true",
+            "non_empty('a ') == true",
+            "is_empty('') == true",
+            "length(' ') == 1",
+            "length('') == 0",
+            "to_double('1.1') == 1.1",
+            "to_double('1') == 1",
+            "to_double('1') == 1.0",
+            "to_int('1') == 1",
+            "to_int('1') == 1.0",
+
+            // Whitespaces.
+            "replace('abc', 'ab',  '') == 'c'",
+            "substr('abc', 1, 3) == 'bc'",
+            "is_alphanumspace(' ') == true",
+            "is_alphanumspace('  ') == true",
+            "is_alphanumspace(' ') == true",
+            "is_whitespace(' ') == true",
+            "trim('   ') == ''"
+        )
+
+    @Test
+    def testError(): Unit =
+        expectError(
+            "substr('abc', 10, 30) == 'bc'",
+            "split('1 A') == true",
+            "split_trim('1 A') == true",
+            "to_double('1, 1') == true",
+            "to_double('A') == true"
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsTokensUsed.scala
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsTokensUsed.scala
new file mode 100644
index 0000000..dabbae9
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/functions/NCIdlFunctionsTokensUsed.scala
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.intent.compiler.functions
+
+import org.apache.nlpcraft.internal.intent.compiler.functions.NCIdlFunctions.*
+import org.junit.jupiter.api.Test
+
+/**
+  * Tests for 'tokens used' result.
+  */
+class NCIdlFunctionsTokensUsed extends NCIdlFunctions:
+    @Test
+    def test(): Unit =
+        val entityIdA = mkEntity(id = "a")
+        val entityAb = mkEntity(id = "a", parentId = "b")
+        test(
+            TestDesc(
+                truth = "1 == 1",
+                idlCtx = mkIdlContext(),
+                tokensUsed = Option(0)
+            ),
+            TestDesc(
+                truth = "# == 'a'",
+                entity = Option(entityIdA),
+                idlCtx = mkIdlContext(Seq(entityIdA)),
+                tokensUsed = Option(1)
+            ),
+            TestDesc(
+                truth = "# == 'a' && # == 'a'",
+                entity = Option(entityIdA),
+                idlCtx = mkIdlContext(Seq(entityIdA)),
+                tokensUsed = Option(2)
+            ),
+            TestDesc(
+                truth = "# == 'a' && tok_parent == 'b'",
+                entity = Option(entityAb),
+                idlCtx = mkIdlContext(Seq(entityAb)),
+                tokensUsed = Option(2)
+            ),
+            TestDesc(
+                truth = "# == 'a' && # == 'a' && tok_parent == 'b'",
+                entity = Option(entityAb),
+                idlCtx = mkIdlContext(Seq(entityAb)),
+                tokensUsed = Option(3)
+            )
+        )
diff --git 
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/test_ok.idl
 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/test_ok.idl
new file mode 100644
index 0000000..fdb2e83
--- /dev/null
+++ 
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/test_ok.idl
@@ -0,0 +1,84 @@
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// ============================
+// Test intents and predicates.
+// ============================
+
+// Re-usable predicate #1.
+fragment=p1
+    term={tok_id(tok_find_part(tok_this(), "alias")) == 2}
+    term={meta_frag('a') && has_any(get(meta_frag('b'), 'Москва'), list(1, 2))}
+    term(userDefined)=/org.apache.MyShit#myMethod/
+
+// Intent #1.
+intent=i1
+    flow=/org.package#method1/ // User-code flow predicate.
+    fragment(p1, {'a': true, 'b': {'Москва': [1, 2, 3]}}) /*  Macro-expansion. 
*/
+    term~{length("some text") > 0} // Normal term.
+    term={has_all(list(1, 2, 3, 4, 5), list(3, 5))}
+    term={if(2==2, "string", list(1, 2, 3))}
+
+// Intent #2.
+intent=i2
+    flow="a[^0-9]b"
+    meta={'a': 42, 'b': {'Москва': [1, 2, 3]}}
+    term(t1)={2 == 2 && !# != -25 && meta_model('a') == 42}
+    term(t2)={
+        @a = meta_model('a')
+        @list = list(1, 2, 3, 4)
+
+        @a == 42 && has_all(@list, list(3, 2))
+    }
+
+intent=i3
+    flow="a[^0-9]b"
+    term(t1)={
+        @x = 2
+        @xx = ((@x * @x) / 2) * 3
+
+        @xx == 6 && has(
+            json(meta_req('user_json_payload')),
+            list("موسكو\"", 'v1\'v1', "k2", "v2")
+        )
+    }
+
+intent=i4 flow=/#flowModelMethod/ term(t1)=/#termModelMethod/
+
+intent=i5
+    flow="a[^0-9]b"
+    meta={'a': 42, 'b': {'Москва': [1, 2, 3]}}
+    term(t1)={month >= 6 && !(#) != -25 && meta_model('a') == 42}
+    term(t2)={
+        @a = meta_model('a')
+        @list = list(1, 2, 3, 4)
+
+        @a == 42 && has_all(@list, list(3, 2))
+    }
+
+intent=i6
+    flow=/#flowModelMethod/
+    term(t1)=/org.mypackage.MyClass#termMethod/
+    term(t2)={
+        @x = 2
+        @xx = ((@x * @x) / 2) * 3
+
+        @xx == 6 && has(
+            json(meta_req('user_json_payload')),
+            list("موسكو\"", 'v1\'v1', "k2", "v2")
+        )
+    }
\ No newline at end of file

Reply via email to