This is an automated email from the ASF dual-hosted git repository.
aradzinski 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 7fa5ef7 WIP
7fa5ef7 is described below
commit 7fa5ef7b07cc81bacae038ab48511f220a18e7cc
Author: Aaron Radzinski <[email protected]>
AuthorDate: Sat Jan 22 19:08:02 2022 -0800
WIP
---
nlpcraft/pom.xml | 5 +
.../nlpcraft/internal/intent/NCIDLContext.scala | 4 +-
.../intent/compiler/NCIDLCodeGenerator.scala | 1297 ++++++++++++++++++++
.../nlpcraft/internal/makro/NCMacroCompiler.scala | 1 -
.../apache/nlpcraft/internal/util/NCUtils.scala | 155 ++-
pom.xml | 7 +
6 files changed, 1437 insertions(+), 32 deletions(-)
diff --git a/nlpcraft/pom.xml b/nlpcraft/pom.xml
index 2a2371d..2c75806 100644
--- a/nlpcraft/pom.xml
+++ b/nlpcraft/pom.xml
@@ -85,6 +85,11 @@
===================
-->
<dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ </dependency>
+
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
diff --git
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/NCIDLContext.scala
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/NCIDLContext.scala
index 87d0c68..865e05e 100644
---
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/NCIDLContext.scala
+++
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/NCIDLContext.scala
@@ -23,7 +23,7 @@ import scala.collection.mutable
/**
*
- * @param toks User input tokens.
+ * @param ents Entities.
* @param intentMeta Intent metadata.
* @param convMeta Conversation metadata.
* @param fragMeta Fragment (argument) metadata passed during intent fragment
reference.
@@ -31,7 +31,7 @@ import scala.collection.mutable
* @param vars Intent variable storage.
*/
case class NCIDLContext(
- toks: Seq[NCEntity] = Seq.empty,
+ ents: Seq[NCEntity] = Seq.empty,
intentMeta: Map[String, Object] = Map.empty,
convMeta: Map[String, Object] = Map.empty,
fragMeta: Map[String, Object] = Map.empty,
diff --git
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCodeGenerator.scala
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCodeGenerator.scala
new file mode 100644
index 0000000..7c86730
--- /dev/null
+++
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCodeGenerator.scala
@@ -0,0 +1,1297 @@
+/*
+ * 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.commons.lang3.StringUtils
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.internal.util.*
+import org.antlr.v4.runtime.{ParserRuleContext => PRC}
+import org.antlr.v4.runtime.tree.{TerminalNode => TN}
+import org.apache.commons.collections.CollectionUtils
+import org.apache.nlpcraft.internal.intent.*
+import org.apache.nlpcraft.internal.intent.{NCIDLStackItem => Z}
+
+import java.lang.{Byte => JByte, Double => JDouble, Float => JFloat, Integer
=> JInt, Long => JLong, Short => JShort}
+import java.time.temporal.IsoFields
+import java.time.{LocalDate, LocalTime}
+import java.util
+import java.util.{Calendar, Collections, Collection => JColl, List => JList,
Map => JMap}
+
+import scala.jdk.CollectionConverters.CollectionHasAsScala
+import scala.jdk.CollectionConverters.SeqHasAsJava
+
+trait NCIDLCodeGenerator:
+ type S = NCIDLStack
+ type ST = NCIDLStackType
+ type SI = (NCToken, S, NCIDLContext) => Unit
+
+ def syntaxError(errMsg: String, srcName: String, line: Int, pos: Int):
NCException
+ def runtimeError(errMsg: String, srcName: String, line: Int, pos: Int,
cause: Exception = null): NCException
+
+ /**
+ *
+ * @param errMsg
+ * @param ctx
+ * @return
+ */
+ def newSyntaxError(errMsg: String)(implicit ctx: PRC): NCException =
+ val tok = ctx.start
+ syntaxError(errMsg, tok.getTokenSource.getSourceName, tok.getLine,
tok.getCharPositionInLine)
+
+ /**
+ *
+ * @param errMsg
+ * @param cause
+ * @param ctx
+ * @return
+ */
+ def newRuntimeError(errMsg: String, cause: Exception = null)(implicit ctx:
PRC): NCException =
+ val tok = ctx.start
+ runtimeError(errMsg, tok.getTokenSource.getSourceName, tok.getLine,
tok.getCharPositionInLine, cause)
+
+ /**
+ * Check if given object is mathematically an integer number.
+ *
+ * @param v
+ * @return
+ */
+ //noinspection ComparingUnrelatedTypes
+ def isInt(v: Object): Boolean =
+ v.isInstanceOf[JLong] || v.isInstanceOf[JInt] || v.isInstanceOf[JByte]
|| v.isInstanceOf[JShort]
+
+ /**
+ * Check if given object is mathematically an real number.
+ *
+ * @param v
+ * @return
+ */
+ //noinspection ComparingUnrelatedTypes
+ def isReal(v: Object): Boolean = v.isInstanceOf[JDouble] ||
v.isInstanceOf[JFloat]
+
+ /**
+ *
+ * @param v
+ * @return
+ */
+ def asInt(v: Object): JLong = v match
+ case l: JLong => l
+ case i: JInt => i.longValue()
+ case b: JByte => b.longValue()
+ case s: JShort => s.longValue()
+ case _ => throw new AssertionError(s"Unexpected int value: $v")
+
+ /**
+ *
+ * @param v
+ * @return
+ */
+ def asReal(v: Object): JDouble = v match
+ case l: JLong => l.doubleValue()
+ case i: JInt => i.doubleValue()
+ case b: JByte => b.doubleValue()
+ case s: JShort => s.doubleValue()
+ case d: JDouble => d
+ case f: JFloat => f.doubleValue()
+ case _ => throw new AssertionError(s"Unexpected real value: $v")
+
+ /**
+ *
+ * @param v
+ * @return
+ */
+ def box(v: Object): Object =
+ if v == null then null
+ else if isInt(v) then asInt(v)
+ else if isReal(v) then asReal(v)
+ else if isList(v) || isMap(v) then v
+ else if isJColl(v) then // Convert any other Java collections to
ArrayList.
+ new java.util.ArrayList(asJColl(v)).asInstanceOf[Object]
+ else
+ v
+
+ //noinspection ComparingUnrelatedTypes
+ def isBool(v: Object): Boolean = v.isInstanceOf[Boolean]
+ def isList(v: Object): Boolean = v.isInstanceOf[JList[_]]
+ def isJColl(v: Object): Boolean = v.isInstanceOf[JColl[_]]
+ def isMap(v: Object): Boolean = v.isInstanceOf[JMap[_, _]]
+ def isStr(v: Object): Boolean = v.isInstanceOf[String]
+ def isToken(v: Object): Boolean = v.isInstanceOf[NCToken]
+
+ def asList(v: Object): JList[_] = v.asInstanceOf[JList[_]]
+ def asJColl(v: Object): JColl[_] = v.asInstanceOf[JColl[_]]
+ def asMap(v: Object): JMap[_, _] = v.asInstanceOf[JMap[_, _]]
+ def asStr(v: Object): String = v.asInstanceOf[String]
+ def asToken(v: Object): NCToken = v.asInstanceOf[NCToken]
+ def asBool(v: Object): Boolean = v.asInstanceOf[Boolean]
+
+ // Runtime errors.
+ def rtUnaryOpError(op: String, v: Object)(implicit ctx: PRC): NCException =
+ newRuntimeError(s"Unexpected '$op' IDL operation for value: $v")
+ def rtBinaryOpError(op: String, v1: Object, v2: Object)(implicit ctx:
PRC): NCException =
+ newRuntimeError(s"Unexpected '$op' IDL operation for values: $v1, $v2")
+ def rtUnknownFunError(fun: String)(implicit ctx: PRC): NCException =
+ newRuntimeError(s"Unknown IDL function: $fun()")
+ def rtMissingParamError(argNum: Int, fun: String)(implicit ctx: PRC):
NCException =
+ newRuntimeError(s"Missing parameters for IDL function ($argNum is
required): $fun()")
+ def rtTooManyParamsError(argNum: Int, fun: String)(implicit ctx: PRC):
NCException =
+ newRuntimeError(s"Too many parameters for IDL function ($argNum is
required): $fun()")
+ def rtParamTypeError(fun: String, invalid: Object, expectType:
String)(implicit ctx: PRC): NCException =
+ newRuntimeError(s"Expected '$expectType' type of parameter for IDL
function '$fun()', found: $invalid")
+ def rtParamNullError(fun: String)(implicit ctx: PRC): NCException =
+ newRuntimeError(s"Unexpected 'null' parameter for IDL function:
$fun()")
+ def rtListTypeError(fun: String, cause: Exception)(implicit ctx: PRC):
NCException =
+ newRuntimeError(s"Expected uniform list type for IDL function
'$fun()', found polymorphic list.", cause)
+ def rtFunError(fun: String, cause: Exception)(implicit ctx: PRC):
NCException =
+ newRuntimeError(s"Runtime error in IDL function: $fun()", cause)
+ def rtUnavailFunError(fun: String)(implicit ctx: PRC): NCException =
+ newRuntimeError(s"Function '$fun()' is unavailable in this IDL
context.")
+
+ /**
+ *
+ * @param stack
+ * @return
+ */
+ def pop1()(implicit stack: S, ctx: PRC): ST =
+ require(stack.nonEmpty, ctx.getText)
+ stack.pop()
+
+ /**
+ *
+ * @param stack
+ * @return
+ */
+ def pop2()(implicit stack: S, ctx: PRC): (ST, ST) =
+ require(stack.size >= 2, ctx.getText)
+
+ // Stack pops in reverse order of push...
+ val v2 = stack.pop()
+ val v1 = stack.pop()
+
+ (v1, v2)
+
+ /**
+ *
+ * @param stack
+ * @return
+ */
+ def pop3()(implicit stack: S, ctx: PRC): (ST, ST, ST) =
+ require(stack.size >= 3, ctx.getText)
+
+ // Stack pops in reverse order of push...
+ val v3 = stack.pop()
+ val v2 = stack.pop()
+ val v1 = stack.pop()
+
+ (v1, v2, v3)
+
+ /**
+ *
+ * @param x1
+ * @param x2
+ * @return
+ */
+ def extract2(x1: ST, x2: ST): (Object, Object, Int) =
+ val Z(v1, n1) = x1()
+ val Z(v2, n2) = x2()
+
+ (v1, v2, n1 + n2)
+
+ /**
+ *
+ * @param x1
+ * @param x2
+ * @param x3
+ * @return
+ */
+ def extract3(x1: ST, x2: ST, x3: ST): (Object, Object, Object, Int) =
+ val Z(v1, n1) = x1()
+ val Z(v2, n2) = x2()
+ val Z(v3, n3) = x3()
+
+ (v1, v2, v3, n1 + n2 + n3)
+
+ /**
+ *
+ * @param lt
+ * @param gt
+ * @param lteq
+ * @param gteq
+ */
+ def parseCompExpr(lt: TN, gt: TN, lteq: TN, gteq: TN)(implicit ctx: PRC):
SI = (_, stack: S, _) =>
+ val (x1, x2) = pop2()(stack, ctx)
+
+ if lt != null then
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+ val f =
+ if isInt(v1) && isInt(v2) then asInt(v1) < asInt(v2)
+ else if isInt(v1) && isReal(v2) then asInt(v1) < asReal(v2)
+ else if isReal(v1) && isInt(v2) then asReal(v1) < asInt(v2)
+ else if isReal(v1) && isReal(v2) then asReal(v1) <
asReal(v2)
+ else
+ throw rtBinaryOpError("<", v1, v2)
+
+ Z(f, n)
+ })
+ else if gt != null then
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+ val f =
+ if isInt(v1) && isInt(v2) then asInt(v1) > asInt(v2)
+ else if isInt(v1) && isReal(v2) then asInt(v1) > asReal(v2)
+ else if isReal(v1) && isInt(v2) then asReal(v1) > asInt(v2)
+ else if isReal(v1) && isReal(v2) then asReal(v1) >
asReal(v2)
+ else
+ throw rtBinaryOpError(">", v1, v2)
+
+ Z(f, n)
+ })
+ else if lteq != null then
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+ val f =
+ if isInt(v1) && isInt(v2) then asInt(v1) <= asInt(v2)
+ else if isInt(v1) && isReal(v2) then asInt(v1) <=
asReal(v2)
+ else if isReal(v1) && isInt(v2) then asReal(v1) <=
asInt(v2)
+ else if isReal(v1) && isReal(v2) then asReal(v1) <=
asReal(v2)
+ else
+ throw rtBinaryOpError("<=", v1, v2)
+
+ Z(f, n)
+ })
+ else
+ require(gteq != null)
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ val f =
+ if isInt(v1) && isInt(v2) then asInt(v1) >= asInt(v2)
+ else if isInt(v1) && isReal(v2) then asInt(v1) >=
asReal(v2)
+ else if isReal(v1) && isInt(v2) then asReal(v1) >=
asInt(v2)
+ else if isReal(v1) && isReal(v2) then asReal(v1) >=
asReal(v2)
+ else
+ throw rtBinaryOpError(">=", v1, v2)
+
+ Z(f, n)
+ })
+
+ /**
+ *
+ * @param mult
+ * @param mod
+ * @param div
+ */
+ def parseMultDivModExpr(mult: TN, mod: TN, div: TN)(implicit ctx: PRC): SI
= (_, stack: S, _) => {
+ val (x1, x2) = pop2()(stack, ctx)
+
+ if mult != null then
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+ if isInt(v1) && isInt(v2) then Z(asInt(v1) * asInt(v2), n)
+ else if isInt(v1) && isReal(v2) then Z(asInt(v1) * asReal(v2),
n)
+ else if isReal(v1) && isInt(v2) then Z(asReal(v1) * asInt(v2),
n)
+ else if isReal(v1) && isReal(v2) then Z(asReal(v1) *
asReal(v2), n)
+ else
+ throw rtBinaryOpError("*", v1, v2)
+ })
+ else if (mod != null)
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ if (isInt(v1) && isInt(v2)) Z(asInt(v1) % asInt(v2), n)
+ else
+ throw rtBinaryOpError("%", v1, v2)
+ })
+ else {
+ assert(div != null)
+
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ if (isInt(v1) && isInt(v2)) Z(asInt(v1) / asInt(v2), n)
+ else if (isInt(v1) && isReal(v2)) Z(asInt(v1) / asReal(v2), n)
+ else if (isReal(v1) && isInt(v2)) Z(asReal(v1) / asInt(v2), n)
+ else if (isReal(v1) && isReal(v2)) Z(asReal(v1) / asReal(v2),
n)
+ else
+ throw rtBinaryOpError("/", v1, v2)
+ })
+ }
+ }
+
+ /**
+ *
+ * @param and
+ * @param or
+ * @return
+ */
+ def parseAndOrExpr(and: TN, or: TN)(implicit ctx: PRC): SI = (_, stack: S,
_) => {
+ val (x1, x2) = pop2()(stack, ctx)
+
+ stack.push(() => {
+ val (op, flag) = if (and != null) ("&&", false) else ("||", true)
+
+ val Z(v1, n1) = x1()
+
+ if (!isBool(v1))
+ throw rtBinaryOpError(op, v1, x2().value)
+
+ // NOTE: check v1 first and only if it is {true|false} check the
v2.
+ if (asBool(v1) == flag)
+ Z(flag, n1)
+ else {
+ val Z(v2, n2) = x2()
+
+ if (!isBool(v2))
+ throw rtBinaryOpError(op, v2, v1)
+
+ Z(asBool(v2), n1 + n2)
+ }
+ })
+ }
+
+ /**
+ *
+ * @param eq
+ * @param neq
+ * @return
+ */
+ def parseEqNeqExpr(eq: TN, neq: TN)(implicit ctx: PRC): SI = (_, stack: S,
_) => {
+ val (x1, x2) = pop2()(stack, ctx)
+
+ def doEq(v1: Object, v2: Object): Boolean = {
+ //noinspection ComparingUnrelatedTypes
+ if (v1 eq v2) true
+ else if (v1 == null && v2 == null) true
+ else if ((v1 == null && v2 != null) || (v1 != null && v2 == null))
false
+ else if (isInt(v1) && isInt(v2)) asInt(v1) == asInt(v2)
+ else if (isReal(v1) && isReal(v2)) asReal(v1) == asReal(v2)
+ else if (isBool(v1) && isBool(v2)) asBool(v1) == asBool(v2)
+ else if (isStr(v1) && isStr(v2)) asStr(v1) == asStr(v2)
+ else if (isList(v1) && isList(v2))
CollectionUtils.isEqualCollection(asList(v1), asList(v2))
+ else if ((isInt(v1) && isReal(v2)) || (isReal(v1) && isInt(v2)))
asReal(v1) == asReal(v2)
+ else
+ v1.equals(v2)
+ }
+
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ val f =
+ if (eq != null)
+ doEq(v1, v2)
+ else {
+ assert(neq != null)
+
+ !doEq(v1, v2)
+ }
+
+ Z(f, n)
+ })
+ }
+
+ /**
+ *
+ * @param plus
+ * @param minus
+ */
+ def parsePlusMinusExpr(plus: TN, minus: TN)(implicit ctx: PRC): SI = (_,
stack: S, _) => {
+ val (x1, x2) = pop2()(stack, ctx)
+
+ if (plus != null)
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ if (isStr(v1) && isStr(v2)) Z(asStr(v1) + asStr(v2), n)
+ else if (isInt(v1) && isInt(v2)) Z(asInt(v1) + asInt(v2), n)
+ else if (isInt(v1) && isReal(v2)) Z(asInt(v1) + asReal(v2), n)
+ else if (isReal(v1) && isInt(v2)) Z(asReal(v1) + asInt(v2), n)
+ else if (isReal(v1) && isReal(v2)) Z(asReal(v1) + asReal(v2),
n)
+ else
+ throw rtBinaryOpError("+", v1, v2)
+ })
+ else {
+ assert(minus != null)
+
+ stack.push(() => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ if (isInt(v1) && isInt(v2)) Z(asInt(v1) - asInt(v2), n)
+ else if (isInt(v1) && isReal(v2)) Z(asInt(v1) - asReal(v2), n)
+ else if (isReal(v1) && isInt(v2)) Z(asReal(v1) - asInt(v2), n)
+ else if (isReal(v1) && isReal(v2)) Z(asReal(v1) - asReal(v2),
n)
+ else
+ throw rtBinaryOpError("-", v1, v2)
+ })
+ }
+ }
+
+ /**
+ * @param minus
+ * @param not
+ * @return
+ */
+ def parseUnaryExpr(minus: TN, not: TN)(implicit ctx: PRC): SI = (_, stack:
S, _) => {
+ val x = pop1()(stack, ctx)
+
+ if (minus != null)
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isReal(v)) Z(-asReal(v), n)
+ else if (isInt(v)) Z(-asInt(v), n)
+ else
+ throw rtUnaryOpError("-", v)
+ })
+ else {
+ assert(not != null)
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isBool(v)) Z(!asBool(v), n)
+ else
+ throw rtUnaryOpError("!", v)
+ })
+ }
+ }
+
+ /**
+ *
+ * @param txt
+ * @return
+ */
+ def parseAtom(txt: String)(implicit ctx: PRC): SI = {
+ val atom =
+ if (txt == "null") null // Try 'null'.
+ else if (txt == "true") Boolean.box(true) // Try 'boolean'.
+ else if (txt == "false") Boolean.box(false) // Try 'boolean'.
+ // Only numeric or string values below...
+ else {
+ // Strip '_' from numeric values.
+ val num = txt.replaceAll("_", "")
+
+ try
+ Long.box(JLong.parseLong(num)) // Try 'long'.
+ catch {
+ case _: NumberFormatException =>
+ try
+ Double.box(JDouble.parseDouble(num)) // Try
'double'.
+ catch {
+ case _: NumberFormatException =>
NCUtils.escapesQuotes(txt) // String in the end.
+ }
+ }
+ }
+
+ (_, stack, _) => stack.push(() => Z(atom, 0))
+ }
+
+ /**
+ *
+ * @param fun
+ * @param ctx
+ * @return
+ */
+ def parseCallExpr(fun: String)(implicit ctx: PRC): SI = (tok, stack: S,
idlCtx) => {
+ implicit val evidence: S = stack
+
+ def popMarker(argNum: Int): Unit = if (pop1() != stack.PLIST_MARKER)
throw rtTooManyParamsError(argNum, fun)
+ def arg[X](argNum: Int, f: () => X): X = {
+ if (stack.size < argNum + 1) // +1 for stack frame marker.
+ throw rtMissingParamError(argNum, fun)
+
+ val x = f()
+
+ x match {
+ case p: Product =>
+ for (e <- p.productIterator)
+ if (e == stack.PLIST_MARKER)
+ rtMissingParamError(argNum, fun)
+ case _ =>
+ if (x.asInstanceOf[ST] == stack.PLIST_MARKER)
+ rtMissingParamError(argNum, fun)
+ }
+
+ // Make sure to pop up the parameter list stack frame marker.
+ popMarker(argNum)
+
+ x
+ }
+ def arg1(): ST = arg(1, pop1)
+ def arg2(): (ST, ST) = arg(2, pop2)
+ def arg3(): (ST, ST, ST) = arg(3, pop3)
+ def arg1Tok(): ST =
+ if (stack.nonEmpty && stack.top == stack.PLIST_MARKER) {
+ popMarker(1)
+
+ () => Z(tok, 1)
+ }
+ else
+ arg1()
+
+ def toX[T](typ: String, v: Object, is: Object => Boolean, as: Object
=> T): T = {
+ if (v == null)
+ throw rtParamNullError(fun)
+ else if (!is(v))
+ throw rtParamTypeError(fun, v, typ)
+
+ as(v)
+ }
+ def toStr(v: Object): String = toX("string", v, isStr, asStr)
+ def toInt(v: Object): JInt = toX("int", v, isInt, asInt).toInt
+ def toList(v: Object): JList[_] = toX("list", v, isList, asList)
+ def toMap(v: Object): JMap[_, _] = toX("map", v, isMap, asMap)
+ def toToken(v: Object): NCToken = toX("token", v, isToken, asToken)
+ def toBool(v: Object): Boolean = toX("boolean", v, isBool, asBool)
+ def toDouble(v: Object): JDouble = toX("double or int", v, x =>
isInt(x) || isReal(x), asReal)
+
+ def doSplit(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ Z(util.Arrays.asList(toStr(v1).split(toStr(v2)):_*), n)
+ }
+ )
+ }
+
+ def doSplitTrim(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+
Z(util.Arrays.asList(toStr(v1).split(toStr(v2)).toList.map(_.strip):_*), n)
+ }
+ )
+ }
+
+ def doStartsWith(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ Z(toStr(v1).startsWith(toStr(v2)), n)
+ }
+ )
+ }
+
+ def doEndsWith(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ Z(toStr(v1).endsWith(toStr(v2)), n)
+ }
+ )
+ }
+
+ def doContains(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ Z(toStr(v1).contains(toStr(v2)),n)
+ }
+ )
+ }
+
+ def doIndexOf(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ Z(toStr(v1).indexOf(toStr(v2)), n)
+ }
+ )
+ }
+
+ def doSubstr(): Unit = {
+ val (x1, x2, x3) = arg3()
+
+ stack.push(
+ () => {
+ val (v1, v2, v3, n) = extract3(x1, x2, x3)
+
+ Z(toStr(v1).substring(toInt(v2), toInt(v3)), n)
+ }
+ )
+ }
+
+ def doRegex(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(
+ () => {
+ val (v1, v2, n) = extract2(x1, x2)
+
+ Z(toStr(v1).matches(toStr(v2)), n)
+ }
+ )
+ }
+
+ def doReplace(): Unit = {
+ val (x1, x2, x3) = arg3()
+
+ stack.push(
+ () => {
+ val (v1, v2, v3, n) = extract3(x1, x2, x3)
+
+ Z(toStr(v1).replaceAll(toStr(v2), toStr(v3)), n)
+ }
+ )
+ }
+
+ def doList(): Unit = {
+ val dump = new S() // Empty list is allowed.
+
+ while (stack.nonEmpty && stack.top != stack.PLIST_MARKER)
+ dump += stack.pop()
+
+ require(stack.nonEmpty)
+
+ // Pop frame marker.
+ pop1()
+
+ stack.push(() => {
+ val jl = new util.ArrayList[Object]()
+ var z = 0
+
+ dump.toSeq.reverse.foreach { x =>
+ val Z(v, n) = x()
+
+ z += n
+
+ jl.add(v)
+ }
+
+ Z(jl, z)
+ })
+ }
+
+ def doReverse(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val jl = toList(v)
+
+ Collections.reverse(jl)
+
+ Z(jl, n)
+ })
+ }
+
+ def doMin(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val lst = toList(v).asInstanceOf[util.List[Object]]
+
+ try
+ if (lst.isEmpty)
+ throw newRuntimeError(s"Unexpected empty list in IDL
function: $fun()")
+ else
+ Z(Collections.min(lst, null), n)
+ catch {
+ case e: Exception => throw rtListTypeError(fun, e)
+ }
+ })
+ }
+
+ def doAvg(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val lst = toList(v).asInstanceOf[util.List[Object]]
+
+ try
+ if (lst.isEmpty)
+ throw newRuntimeError(s"Unexpected empty list in IDL
function: $fun()")
+ else {
+ val seq: Seq[Double] = lst.asScala.map(p =>
JDouble.valueOf(p.toString).doubleValue()).toSeq
+
+ Z(seq.sum / seq.length, n)
+ }
+ catch {
+ case e: Exception => throw rtListTypeError(fun, e)
+ }
+ })
+ }
+
+ def doStdev(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val lst = toList(v).asInstanceOf[util.List[Object]]
+
+ try
+ if (lst.isEmpty)
+ throw newRuntimeError(s"Unexpected empty list in IDL
function: $fun()")
+ else {
+ val seq: Seq[Double] = lst.asScala.map(p =>
JDouble.valueOf(p.toString).doubleValue()).toSeq
+
+ val mean = seq.sum / seq.length
+ val stdDev = Math.sqrt(seq.map( _ - mean).map(t => t *
t).sum / seq.length)
+
+ Z(stdDev, n)
+ }
+ catch {
+ case e: Exception => throw rtListTypeError(fun, e)
+ }
+ })
+ }
+
+ def doToString(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isList(v)) {
+ val jl = new util.ArrayList[Object]()
+
+ for (d <- toList(v).asScala.map(_.toString))
+ jl.add(d)
+
+ Z(jl, n)
+ }
+ else
+ Z(v.toString, n)
+ })
+ }
+
+ def doToDouble(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isInt(v))
+ Z(asInt(v).toDouble, n)
+ else if (isStr(v))
+ try
+ Z(toStr(v).toDouble, n)
+ catch {
+ case e: Exception => throw newRuntimeError(s"Invalid
double value '$v' in IDL function: $fun()", e)
+ }
+ else
+ throw rtParamTypeError(fun, v, "int or string")
+ })
+ }
+
+ def doToInt(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isReal(v))
+ Z(Math.round(asReal(v)), n)
+ else if (isStr(v))
+ try
+ Z(toStr(v).toLong, n)
+ catch {
+ case e: Exception => throw newRuntimeError(s"Invalid
int value '$v' in IDL function: $fun()", e)
+ }
+ else
+ throw rtParamTypeError(fun, v, "double or string")
+ })
+ }
+
+ def doMax(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val lst = toList(v).asInstanceOf[util.List[Object]]
+
+ try
+ if (lst.isEmpty)
+ throw newRuntimeError(s"Unexpected empty list in IDL
function: $fun()")
+ else
+ Z(Collections.max(lst, null), n)
+ catch {
+ case e: Exception => throw rtListTypeError(fun, e)
+ }
+ })
+ }
+
+ def doSort(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val jl = toList(v)
+
+ try
+ jl.sort(null) // Use natural order.
+ catch {
+ case e: Exception => throw rtListTypeError(fun, e)
+ }
+
+ Z(jl, n)
+ })
+ }
+
+ def doDistinct(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ val jl = new util.ArrayList[Object]()
+
+ for (d <- toList(v).asScala.toSeq.distinct)
+ jl.add(d.asInstanceOf[Object])
+
+ Z(jl, n)
+ })
+ }
+
+ def doConcat(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val (lst1, lst2, n) = extract2(x1, x2)
+
+ val jl = new util.ArrayList[Object]()
+
+ for (d <- toList(lst1).asScala ++ toList(lst2).asScala)
+ jl.add(d.asInstanceOf[Object])
+
+ Z(jl, n)
+ })
+ }
+
+ def doHas(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val (lst, obj, n) = extract2(x1, x2)
+
+ Z(toList(lst).contains(box(obj)), n)
+ })
+ }
+
+ def doHasAll(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val (lst1, lst2, n) = extract2(x1, x2)
+
+ Z(toList(lst1).containsAll(toList(lst2)), n)
+ })
+ }
+
+ def doHasAny(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val (lst1, lst2, n) = extract2(x1, x2)
+
+ Z(CollectionUtils.containsAny(toList(lst1), toList(lst2)), n)
+ })
+ }
+
+ def doGet(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val (col, key, n) = extract2(x1, x2)
+
+ if (isList(col)) {
+ if (isInt(key))
+
Z(asList(col).get(asInt(key).intValue()).asInstanceOf[Object], n)
+ else
+ throw rtParamTypeError(fun, key, "numeric")
+ }
+ else if (isMap(col))
+ Z(asMap(col).get(box(key)).asInstanceOf[Object], n)
+ else
+ throw rtParamTypeError(fun, col, "list or map")
+ })
+ }
+
+ def doAbs(): Unit = arg1() match {
+ case x => stack.push(() => {
+ val Z(v, n) = x()
+
+ v match {
+ case a: JLong => Z(Math.abs(a), n)
+ case a: JDouble => Z(Math.abs(a), n)
+ case _ => throw rtParamTypeError(fun, v, "numeric")
+ }
+ })
+ }
+
+ def doSquare(): Unit = arg1() match {
+ case x => stack.push(() => {
+ val Z(v, n) = x()
+
+ v match {
+ case a: JLong => Z(a * a, n)
+ case a: JDouble => Z(a * a, n)
+ case _ => throw rtParamTypeError(fun, v, "numeric")
+ }
+ })
+ }
+
+ def doIf(): Unit = {
+ val (x1, x2, x3) = arg3()
+
+ stack.push(() => {
+ val Z(v1, n1) = x1()
+
+ if (toBool(v1)) {
+ val Z(v2, n2) = x2()
+
+ Z(v2, n1 + n2)
+ }
+ else {
+ val Z(v3, n3) = x3()
+
+ Z(v3, n1 + n3)
+ }
+ })
+ }
+
+ def doOrElse(): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val Z(v1, n1) = x1()
+
+ if (v1 != null)
+ Z(v1, n1)
+ else
+ x2()
+ })
+ }
+
+ def doIsBefore(f: (NCToken, String) => Boolean): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(arg, n) = x()
+
+ Z(idlCtx.ents.exists(t => t.getIndex > tok.getIndex && f(t,
toStr(arg))), n)
+ })
+ }
+
+ def doIsAfter(f: (NCToken, String) => Boolean): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(arg, n) = x()
+
+ Z(idlCtx.ents.exists(t => t.getIndex < tok.getIndex && f(t,
toStr(arg))), n)
+ })
+ }
+
+ def doIsBetween(f: (NCToken, String) => Boolean): Unit = {
+ val (x1, x2) = arg2()
+
+ stack.push(() => {
+ val (a1, a2, n) = extract2(x1, x2)
+
+ Z(
+ idlCtx.ents.exists(t => t.getIndex < tok.getIndex && f(t,
toStr(a1)))
+ &&
+ idlCtx.ents.exists(t => t.getIndex > tok.getIndex &&
f(t, toStr(a2)))
+ ,
+ n
+ )
+ })
+ }
+
+ def doForAll(f: (NCToken, String) => Boolean): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(arg, n) = x()
+
+ Z(idlCtx.ents.filter(f(_, toStr(arg))).asJava, n)
+ })
+ }
+
+ def doLength(): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isList(v))
+ Z(asList(v).size(), n)
+ else if (isMap(v))
+ Z(asMap(v).size(), n)
+ else if (isStr(v))
+ Z(asStr(v).length, n)
+ else
+ throw rtParamTypeError(fun, v, "string or list")
+ })
+ }
+
+ def doIsEmpty(empty: Boolean): Unit = {
+ val x = arg1()
+
+ stack.push(() => {
+ val Z(v, n) = x()
+
+ if (isList(v))
+ Z(asList(v).isEmpty == empty, n)
+ else if (isMap(v))
+ Z(asMap(v).isEmpty == empty, n)
+ else if (isStr(v))
+ Z(asStr(v).isEmpty == empty, n)
+ else
+ throw rtParamTypeError(fun, v, "string or list")
+ })
+ }
+
+ def z[Y](args: () => Y, body: Y => Z): Unit = { val x = args();
stack.push(() => body(x)) }
+ def z0(body: () => Z): Unit = { popMarker(0); stack.push(() => body())
}
+
+ def checkAvail(): Unit =
+ if (idlCtx.ents.isEmpty)
+ throw rtUnavailFunError(fun)
+
+ try
+ fun match {
+ // Metadata access.
+ case "meta_model" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(tok.getModel.meta[Object](toStr(v))), 0) })
+ case "meta_req" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(idlCtx.req.getRequestData.get(toStr(v))), 0) })
+ case "meta_user" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(idlCtx.req.getUser.meta(toStr(v))), 0) })
+ case "meta_company" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(idlCtx.req.getCompany.meta(toStr(v))), 0) })
+ case "meta_intent" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(idlCtx.intentMeta.get(toStr(v)).orNull), 0) })
+ case "meta_conv" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(idlCtx.convMeta.get(toStr(v)).orNull), 0) })
+ case "meta_frag" => z[ST](arg1, { x => val Z(v, f) = x();
Z(box(idlCtx.fragMeta.get(toStr(v)).orNull), f) })
+ case "meta_sys" => z[ST](arg1, { x => val Z(v, _) = x();
Z(box(NCUtils.sysEnv(toStr(v)).orNull), 0) })
+
+ // Converts JSON to map.
+ case "json" => z[ST](arg1, { x => val Z(v, f) = x();
Z(NCUtils.jsonToJavaMap(asStr(v)), f) })
+
+ // Inline if-statement.
+ case "if" => doIf()
+
+ case "or_else" => doOrElse()
+
+// // Token functions.
+// case "tok_id" => arg1Tok() match { case x => stack.push(()
=> { Z(toToken(x().value).getId, 1) }) }
+// case "tok_lemma" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getLemma, 1) }) }
+// case "tok_stem" => arg1Tok() match { case x => stack.push(()
=> { Z(toToken(x().value).getStem, 1) }) }
+// case "tok_pos" => arg1Tok() match { case x => stack.push(()
=> { Z(toToken(x().value).getPos, 1) }) }
+// case "tok_txt" => arg1Tok() match { case x => stack.push(()
=> { Z(toToken(x().value).getOriginalText, 1) }) }
+// case "tok_norm_txt" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getNormalizedText, 1) }) }
+// case "tok_req_id" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getServerRequestId, 1) }) }
+// case "tok_sparsity" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getSparsity, 1) }) }
+// case "tok_unid" => arg1Tok() match { case x => stack.push(()
=> { Z(toToken(x().value).getUnid, 1) }) }
+//
+// case "tok_index" => checkAvail(); arg1Tok() match { case x
=> stack.push(() => { Z(toToken(x().value).getIndex, 1) }) }
+// case "tok_is_last" => checkAvail(); arg1Tok() match { case x
=> stack.push(() => { Z(toToken(x().value).getIndex == idlCtx.ents.size - 1, 1)
}) }
+// case "tok_is_first" => checkAvail(); arg1Tok() match { case
x => stack.push(() => { Z(toToken(x().value).getIndex == 0, 1) }) }
+// case "tok_is_before_id" => checkAvail(); doIsBefore((tok,
id) => tok.getId == id)
+// case "tok_is_before_group" => checkAvail(); doIsBefore((tok,
grpId) => tok.getGroups.contains(grpId))
+// case "tok_is_before_parent" => checkAvail();
doIsBefore((tok, id) => tok.getParentId == id)
+// case "tok_is_after_id" => checkAvail(); doIsAfter((tok, id)
=> tok.getId == id)
+// case "tok_is_after_group" => checkAvail(); doIsAfter((tok,
grpId) => tok.getGroups.contains(grpId))
+// case "tok_is_after_parent" => checkAvail(); doIsAfter((tok,
id) => tok.getParentId == id)
+// case "tok_is_between_ids" => checkAvail(); doIsBetween((tok,
id) => tok.getId == id)
+// case "tok_is_between_groups" => checkAvail();
doIsBetween((tok, grpId) => tok.getGroups.contains(grpId))
+// case "tok_is_between_parents" => checkAvail();
doIsBetween((tok, id) => tok.getParentId == id)
+//
+// case "tok_is_abstract" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isAbstract, 1) }) }
+// case "tok_is_bracketed" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isBracketed, 1) }) }
+// case "tok_is_direct" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isDirect, 1) }) }
+// case "tok_is_permutated" => arg1Tok() match { case x =>
stack.push(() => { Z(!toToken(x().value).isDirect, 1) }) }
+// case "tok_is_english" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isEnglish, 1) }) }
+// case "tok_is_freeword" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isFreeWord, 1) }) }
+// case "tok_is_quoted" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isQuoted, 1) }) }
+// case "tok_is_stopword" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isStopWord, 1) }) }
+// case "tok_is_swear" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isSwear, 1) }) }
+// case "tok_is_user" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isUserDefined, 1) }) }
+// case "tok_is_wordnet" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).isWordnet, 1) }) }
+// case "tok_ancestors" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getAncestors, 1) }) }
+// case "tok_parent" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getParentId, 1) }) }
+// case "tok_groups" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getGroups, 1) }) }
+// case "tok_value" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getValue, 1) }) }
+// case "tok_aliases" => arg1Tok() match { case x =>
stack.push(() => { Z(box(toToken(x().value).getAliases), 1) }) }
+// case "tok_start_idx" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getStartCharIndex, 1) }) }
+// case "tok_end_idx" => arg1Tok() match { case x =>
stack.push(() => { Z(toToken(x().value).getEndCharIndex, 1) }) }
+// case "tok_this" => z0(() => Z(tok, 1))
+// case "tok_has_part" => doHasPart()
+// case "tok_find_part" => doFindPart()
+// case "tok_find_parts" => doFindParts()
+//
+// case "tok_count" => checkAvail(); z0(() =>
Z(idlCtx.ents.size, 0))
+// case "tok_all" => checkAvail(); z0(() =>
Z(idlCtx.ents.asJava, 0))
+// case "tok_all_for_id" => checkAvail(); doForAll((tok, id) =>
tok.getId == id)
+// case "tok_all_for_parent" => checkAvail(); doForAll((tok,
id) => tok.getParentId == id)
+// case "tok_all_for_group" => checkAvail(); doForAll((tok,
grp) => tok.getGroups.contains(grp))
+
+ // Request data.
+ case "req_id" => z0(() => Z(idlCtx.req.getServerRequestId, 0))
+ case "req_normtext" => z0(() =>
Z(idlCtx.req.getNormalizedText, 0))
+ case "req_tstamp" => z0(() =>
Z(idlCtx.req.getReceiveTimestamp, 0))
+ case "req_addr" => z0(() =>
Z(idlCtx.req.getRemoteAddress.orElse(null), 0))
+ case "req_agent" => z0(() =>
Z(idlCtx.req.getClientAgent.orElse(null), 0))
+
+ // User data.
+ case "user_id" => z0(() => Z(idlCtx.req.getUser.getId, 0))
+ case "user_fname" => z0(() =>
Z(idlCtx.req.getUser.getFirstName.orElse(null), 0))
+ case "user_lname" => z0(() =>
Z(idlCtx.req.getUser.getLastName.orElse(null), 0))
+ case "user_email" => z0(() =>
Z(idlCtx.req.getUser.getEmail.orElse(null), 0))
+ case "user_admin" => z0(() => Z(idlCtx.req.getUser.isAdmin, 0))
+ case "user_signup_tstamp" => z0(() =>
Z(idlCtx.req.getUser.getSignupTimestamp, 0))
+
+ // Company data.
+ case "comp_id" => z0(() => Z(idlCtx.req.getCompany.getId, 0))
+ case "comp_name" => z0(() => Z(idlCtx.req.getCompany.getName,
0))
+ case "comp_website" => z0(() =>
Z(idlCtx.req.getCompany.getWebsite.orElse(null), 0))
+ case "comp_country" => z0(() =>
Z(idlCtx.req.getCompany.getCountry.orElse(null), 0))
+ case "comp_region" => z0(() =>
Z(idlCtx.req.getCompany.getRegion.orElse(null), 0))
+ case "comp_city" => z0(() =>
Z(idlCtx.req.getCompany.getCity.orElse(null), 0))
+ case "comp_addr" => z0(() =>
Z(idlCtx.req.getCompany.getAddress.orElse(null), 0))
+ case "comp_postcode" => z0(() =>
Z(idlCtx.req.getCompany.getPostalCode.orElse(null), 0))
+
+ // String functions.
+ case "trim" | "strip" => z[ST](arg1, { x => val Z(v, f) = x();
Z(toStr(v).trim, f) })
+ case "uppercase" => z[ST](arg1, { x => val Z(v, f) = x();
Z(toStr(v).toUpperCase, f) })
+ case "lowercase" => z[ST](arg1, { x => val Z(v, f) = x();
Z(toStr(v).toLowerCase, f) })
+ case "is_alpha" => z[ST](arg1, { x => val Z(v, f) = x();
Z(StringUtils.isAlpha(toStr(v)), f) })
+ case "is_alphanum" => z[ST](arg1, { x => val Z(v, f) = x();
Z(StringUtils.isAlphanumeric(toStr(v)), f) })
+ case "is_whitespace" => z[ST](arg1, { x => val Z(v, f) = x();
Z(StringUtils.isWhitespace(toStr(v)), f) })
+ case "is_num" => z[ST](arg1, { x => val Z(v, f) = x();
Z(StringUtils.isNumeric(toStr(v)), f) })
+ case "is_numspace" => z[ST](arg1, { x => val Z(v, f) = x();
Z(StringUtils.isNumericSpace(toStr(v)), f) })
+ case "is_alphaspace" => z[ST](arg1, { x => val Z(v, f) = x();
Z(StringUtils.isAlphaSpace(toStr(v)), f) })
+ case "is_alphanumspace" => z[ST](arg1, { x => val Z(v, f) =
x(); Z(StringUtils.isAlphanumericSpace(toStr(v)), f) })
+ case "split" => doSplit()
+ case "split_trim" => doSplitTrim()
+ case "starts_with" => doStartsWith()
+ case "ends_with" => doEndsWith()
+ case "contains" => doContains()
+ case "index_of" => doIndexOf()
+ case "substr" => doSubstr()
+ case "regex" => doRegex()
+ case "replace" => doReplace()
+ case "to_double" => doToDouble()
+ case "to_int" => doToInt()
+
+ // Math functions.
+ case "abs" => doAbs()
+ case "ceil" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.ceil(toDouble(v)), f) })
+ case "floor" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.floor(toDouble(v)), f) })
+ case "rint" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.rint(toDouble(v)), f) })
+ case "round" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.round(toDouble(v)), f) })
+ case "signum" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.signum(toDouble(v)), f) })
+ case "sqrt" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.sqrt(toDouble(v)), f) })
+ case "cbrt" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.cbrt(toDouble(v)), f) })
+ case "acos" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.acos(toDouble(v)), f) })
+ case "asin" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.asin(toDouble(v)), f) })
+ case "atan" => z[ST](arg1, { x => val Z(v, f) = x(); Z(
Math.atan(toDouble(v)), f) })
+ case "cos" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.cos(toDouble(v)), f) })
+ case "sin" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.sin(toDouble(v)), f) })
+ case "tan" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.tan(toDouble(v)), f) })
+ case "cosh" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.cosh(toDouble(v)), f) })
+ case "sinh" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.sinh(toDouble(v)), f) })
+ case "tanh" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.tanh(toDouble(v)), f) })
+ case "atan2" => z[(ST, ST)](arg2, { x => val (v1, v2, n) =
extract2(x._1, x._2); Z(Math.atan2(toDouble(v1), toDouble(v2)), n) })
+ case "degrees" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.toDegrees(toDouble(v)), f) })
+ case "radians" => z[ST](arg1, { x => val Z(v, f) = x(); Z(
Math.toRadians(toDouble(v)), f) })
+ case "exp" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.exp(toDouble(v)), f) })
+ case "expm1" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.expm1(toDouble(v)), f) })
+ case "hypot" => z[(ST, ST)](arg2, { x => val (v1, v2, n) =
extract2(x._1, x._2); Z(Math.hypot(toDouble(v1), toDouble(v2)), n) })
+ case "log" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.log(toDouble(v)), f) })
+ case "log10" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.log10(toDouble(v)), f) })
+ case "log1p" => z[ST](arg1, { x => val Z(v, f) = x();
Z(Math.log1p(toDouble(v)), f) })
+ case "pow" => z[(ST, ST)](arg2, { x => val (v1, v2, n) =
extract2(x._1, x._2); Z(Math.pow(toDouble(v1), toDouble(v2)), n) })
+ case "square" => doSquare()
+ case "pi" => z0(() => Z(Math.PI, 0))
+ case "euler" => z0(() => Z(Math.E, 0))
+ case "rand" => z0(() => Z(Math.random, 0))
+
+ // Collection functions.
+ case "list" => doList()
+ case "get" => doGet() // Works for both lists (int index) and
maps (object key).
+ case "has" => doHas() // Only works for lists.
+ case "has_any" => doHasAny()
+ case "has_all" => doHasAll()
+ case "first" => z[ST](arg1, { x => val Z(v, n) = x(); val lst
= toList(v); Z(if (lst.isEmpty) null else lst.get(0).asInstanceOf[Object], n)})
+ case "last" => z[ST](arg1, { x => val Z(v, n) = x(); val lst =
toList(v); Z(if (lst.isEmpty) null else lst.get(lst.size() -
1).asInstanceOf[Object], n)})
+ case "keys" => z[ST](arg1, { x => val Z(v, n) = x(); Z(new
util.ArrayList(toMap(v).keySet()), n) })
+ case "values" => z[ST](arg1, { x => val Z(v, n) = x(); Z(new
util.ArrayList(toMap(v).values()), n) })
+ case "reverse" => doReverse()
+ case "sort" => doSort()
+ case "is_empty" => doIsEmpty(true)
+ case "non_empty" => doIsEmpty(false)
+ case "distinct" => doDistinct()
+ case "concat" => doConcat()
+
+ // Applies to strings as well.
+ case "size" | "count" | "length" => doLength()
+
+ // Misc.
+ case "to_string" => doToString()
+
+ // Statistical operations on lists.
+ case "max" => doMax()
+ case "min" => doMin()
+ case "avg" => doAvg()
+ case "stdev" => doStdev()
+
+ // Date-time functions.
+ case "year" => z0(() => Z(LocalDate.now.getYear, 0)) // 2021.
+ case "month" => z0(() => Z(LocalDate.now.getMonthValue, 0)) //
1 ... 12.
+ case "day_of_month" => z0(() => Z(LocalDate.now.getDayOfMonth,
0)) // 1 ... 31.
+ case "day_of_week" => z0(() =>
Z(LocalDate.now.getDayOfWeek.getValue, 0))
+ case "day_of_year" => z0(() => Z(LocalDate.now.getDayOfYear,
0))
+ case "hour" => z0(() => Z(LocalTime.now.getHour, 0))
+ case "minute" => z0(() => Z(LocalTime.now.getMinute, 0))
+ case "second" => z0(() => Z(LocalTime.now.getSecond, 0))
+ case "week_of_month" => z0(() =>
Z(Calendar.getInstance().get(Calendar.WEEK_OF_MONTH), 0))
+ case "week_of_year" => z0(() =>
Z(Calendar.getInstance().get(Calendar.WEEK_OF_YEAR), 0))
+ case "quarter" => z0(() =>
Z(LocalDate.now().get(IsoFields.QUARTER_OF_YEAR), 0))
+ case "now" => z0(() => Z(NCUtils.now(), 0)) // Epoc time.
+
+ case _ => throw rtUnknownFunError(fun) // Assertion.
+ }
+ catch {
+ case e: NCException => throw e // Rethrow.
+ case e: Exception => throw rtFunError(fun, e)
+ }
+ }
+
diff --git
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/makro/NCMacroCompiler.scala
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/makro/NCMacroCompiler.scala
index 7fcca67..fe17509 100644
---
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/makro/NCMacroCompiler.scala
+++
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/makro/NCMacroCompiler.scala
@@ -22,7 +22,6 @@ import com.typesafe.scalalogging.LazyLogging
import org.antlr.v4.runtime.tree.ParseTreeWalker
import org.antlr.v4.runtime.*
import org.apache.nlpcraft.internal.*
-import org.apache.nlpcraft.internal.ansi.NCAnsi.*
import org.apache.nlpcraft.internal.antlr4.*
import NCMacroCompiler.FiniteStateMachine
import org.apache.nlpcraft.internal.makro.antlr4.*
diff --git
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/util/NCUtils.scala
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/util/NCUtils.scala
index c3867ca..05ccd48 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/util/NCUtils.scala
@@ -19,17 +19,18 @@ package org.apache.nlpcraft.internal.util
import com.typesafe.scalalogging.*
import org.apache.nlpcraft.*
-import org.apache.nlpcraft.internal.ansi.NCAnsi.*
-
+import org.apache.nlpcraft.internal.ansi.*
+import com.google.gson.*
import java.io.*
import java.net.*
-import java.util.concurrent.{CopyOnWriteArrayList, ExecutorService, TimeUnit}
+import java.util.concurrent.{CopyOnWriteArrayList, ExecutorService, TimeUnit}
// Avoids conflicts.
import java.util.regex.Pattern
import java.util.zip.*
import java.util.{Random, UUID}
import scala.annotation.tailrec
import scala.collection.{IndexedSeq, Seq, mutable}
import scala.concurrent.*
+import scala.jdk.CollectionConverters.*
import scala.concurrent.duration.Duration
import scala.io.*
import scala.sys.SystemProperties
@@ -42,26 +43,27 @@ object NCUtils extends LazyLogging:
final val NL = System getProperty "line.separator"
private val RND = new Random()
private val sysProps = new SystemProperties
+ private final lazy val GSON = new
GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create()
private final val ANSI_SEQ = Pattern.compile("\u001B\\[[?;\\d]*[a-zA-Z]")
- private val ANSI_FG_8BIT_COLORS = for (i <- 16 to 255) yield ansi256Fg(i)
- private val ANSI_BG_8BIT_COLORS = for (i <- 16 to 255) yield ansi256Bg(i)
+ private val ANSI_FG_8BIT_COLORS = for (i <- 16 to 255) yield
NCAnsi.ansi256Fg(i)
+ private val ANSI_BG_8BIT_COLORS = for (i <- 16 to 255) yield
NCAnsi.ansi256Bg(i)
private val ANSI_FG_4BIT_COLORS = Seq(
- ansiRedFg,
- ansiGreenFg,
- ansiBlueFg,
- ansiYellowFg,
- ansiWhiteFg,
- ansiBlackFg,
- ansiCyanFg
+ NCAnsi.ansiRedFg,
+ NCAnsi.ansiGreenFg,
+ NCAnsi.ansiBlueFg,
+ NCAnsi.ansiYellowFg,
+ NCAnsi.ansiWhiteFg,
+ NCAnsi.ansiBlackFg,
+ NCAnsi.ansiCyanFg
)
private val ANSI_BG_4BIT_COLORS = Seq(
- ansiRedBg,
- ansiGreenBg,
- ansiBlueBg,
- ansiYellowBg,
- ansiWhiteBg,
- ansiBlackBg,
- ansiCyanBg
+ NCAnsi.ansiRedBg,
+ NCAnsi.ansiGreenBg,
+ NCAnsi.ansiBlueBg,
+ NCAnsi.ansiYellowBg,
+ NCAnsi.ansiWhiteBg,
+ NCAnsi.ansiBlackBg,
+ NCAnsi.ansiCyanBg
)
private val ANSI_4BIT_COLORS = for (fg <- ANSI_FG_4BIT_COLORS; bg <-
ANSI_BG_4BIT_COLORS) yield s"$fg$bg"
@@ -117,6 +119,7 @@ object NCUtils extends LazyLogging:
* Prints 4-bit ASCII-logo.
*/
def asciiLogo4Bit(): String =
+ import NCAnsi.*
raw"$ansiBlueFg _ ____ $ansiCyanFg ______ ______
$ansiReset$NL" +
raw"$ansiBlueFg / | / / /___ $ansiCyanFg/ ____/________ _/ __/ /_
$ansiReset$NL" +
raw"$ansiBlueFg / |/ / / __ \$ansiCyanFg/ / / ___/ __ `/ /_/ __/
$ansiReset$NL" +
@@ -128,6 +131,7 @@ object NCUtils extends LazyLogging:
* Prints 8-bit ASCII-logo.
*/
def asciiLogo8Bit1(): String =
+ import NCAnsi.*
fgRainbow4Bit(
raw"${ansi256Fg(28)} _ ____ ______ ______
$ansiReset$NL" +
raw"${ansi256Fg(64)} / | / / /___ / ____/________ _/ __/ /_
$ansiReset$NL" +
@@ -174,9 +178,9 @@ object NCUtils extends LazyLogging:
val idx = zip._2
val color = startColor + idx % (endColor - startColor + 1)
- buf ++= s"${ansi256Fg(color)}$ch"
+ buf ++= s"${NCAnsi.ansi256Fg(color)}$ch"
})
- .toString + ansiReset
+ .toString + NCAnsi.ansiReset
/**
*
@@ -191,9 +195,9 @@ object NCUtils extends LazyLogging:
val idx = zip._2
val color = startColor + idx % (endColor - startColor + 1)
- buf ++= s"${ansi256Bg(color)}$ch"
+ buf ++= s"${NCAnsi.ansi256Bg(color)}$ch"
})
- .toString + ansiReset
+ .toString + NCAnsi.ansiReset
/**
*
@@ -241,7 +245,7 @@ object NCUtils extends LazyLogging:
s.zipWithIndex.foldLeft(new StringBuilder())((buf, zip) => {
buf ++= s"${colors(RND.nextInt(colors.size))}$addOn${zip._1}"
})
- .toString + ansiReset
+ .toString + NCAnsi.ansiReset
/**
*
@@ -254,7 +258,99 @@ object NCUtils extends LazyLogging:
s.zipWithIndex.foldLeft(new StringBuilder())((buf, zip) => {
buf ++= s"${colors(zip._2 % colors.size)}$addOn${zip._1}"
})
- .toString + ansiReset
+ .toString + NCAnsi.ansiReset
+
+ /**
+ *
+ * @param json
+ * @return
+ */
+ def prettyJson(json: String): String =
+ if json == null || json.isEmpty then ""
+ else
+ try
+
GSON.toJson(GSON.getAdapter(classOf[JsonElement]).fromJson(json))
+ // Fix the problem with escaping '<' and '>' which is only
+ // a theoretical problem for browsers displaying JSON.
+ .replace("\\u003c", "<")
+ .replace("\\u003e", ">")
+ catch case _: Exception => ""
+
+ /**
+ *
+ * @param json
+ * @return
+ */
+ def isValidJson(json: String): Boolean =
+
scala.util.Try(GSON.getAdapter(classOf[JsonElement]).fromJson(json)).isSuccess
+
+ /**
+ *
+ * @param json
+ * @param field
+ * @return
+ */
+ @throws[Exception]
+ def getJsonStringField(json: String, field: String): String =
+
GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsString
+
+ /**
+ *
+ * @param json
+ * @param field
+ * @return
+ */
+ @throws[Exception]
+ def getJsonIntField(json: String, field: String): Int =
+
GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsInt
+
+ /**
+ *
+ * @param json
+ * @tparam T
+ * @return
+ */
+ def jsonToObject[T](json: String, typ: java.lang.reflect.Type): T =
+ GSON.fromJson(json, typ)
+
+ /**
+ *
+ * @param json
+ * @tparam T
+ * @return
+ */
+ def jsonToObject[T](json: String, cls: Class[T]): T =
+ GSON.fromJson(json, cls)
+
+ /**
+ * Shortcut to convert given JSON to Scala map with default mapping.
+ *
+ * @param json JSON to convert.
+ * @return
+ */
+ @throws[Exception]
+ def jsonToScalaMap(json: String): Map[String, Object] =
+ GSON.fromJson(json, classOf[java.util.HashMap[String,
Object]]).asScala.toMap
+
+ /**
+ * Shortcut to convert given JSON to Java map with default mapping.
+ *
+ * @param json JSON to convert.
+ * @return
+ */
+ def jsonToJavaMap(json: String): java.util.Map[String, Object] =
+ try GSON.fromJson(json, classOf[java.util.HashMap[String, Object]])
+ catch case e: Exception => E(s"Cannot deserialize JSON to map:
'$json'", e)
+
+ /**
+ *
+ * @param json
+ * @param field
+ * @return
+ */
+ def getJsonBooleanField(json: String, field: String): Boolean =
+ try
GSON.getAdapter(classOf[JsonElement]).fromJson(json).getAsJsonObject.get(field).getAsBoolean
+ catch case e: Exception => E(s"Cannot extract JSON field '$field'
from: '$json'", e)
/**
* ANSI color JSON string.
@@ -271,13 +367,13 @@ object NCUtils extends LazyLogging:
ch match
case ':' if !inQuotes => buf ++= r(":"); isValue = true
case '[' | ']' | '{' | '}' if !inQuotes => buf ++= y(s"$ch");
isValue = false
- case ',' if !inQuotes => buf ++= ansi256Fg(213, s"$ch");
isValue = false
+ case ',' if !inQuotes => buf ++= NCAnsi.ansi256Fg(213,
s"$ch"); isValue = false
case '"' =>
if inQuotes then
- buf ++= ansi256Fg(105, s"$ch")
+ buf ++= NCAnsi.ansi256Fg(105, s"$ch")
else
- buf ++= s"${ansi256Fg(105)}$ch"
- buf ++= (if isValue then G else ansiCyanFg)
+ buf ++= s"${NCAnsi.ansi256Fg(105)}$ch"
+ buf ++= (if isValue then G else NCAnsi.ansiCyanFg)
inQuotes = !inQuotes
@@ -451,6 +547,7 @@ object NCUtils extends LazyLogging:
* @param e
*/
private def prettyErrorImpl(logger: PrettyErrorLogger, title: String, e:
Throwable): Unit =
+ import NCAnsi.*
logger.log(title)
val INDENT = 2
diff --git a/pom.xml b/pom.xml
index 4de72bc..ca9e879 100644
--- a/pom.xml
+++ b/pom.xml
@@ -109,6 +109,7 @@
<jackson.yaml.ver>2.13.1</jackson.yaml.ver>
<apache.opennlp.ver>1.9.4</apache.opennlp.ver>
<jmh.version>1.34</jmh.version>
+ <gson.ver>2.8.5</gson.ver>
<!-- Force specific encoding on text resources. -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -161,6 +162,12 @@
-->
<dependency>
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>${gson.ver}</version>
+ </dependency>
+
+ <dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons.io.ver}</version>