in nlpcraft/src/main/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCompiler.scala [104:268]
private def json2Obj(json: String)(ctx: ParserRuleContext): Map[String, Object] =
try NCUtils.jsonToScalaMap(json)
catch case e: Exception => SE(s"Invalid JSON (${e.getMessage})")(ctx)
override def exitUnaryExpr(ctx: IDP.UnaryExprContext): Unit = expr += parseUnaryExpr(ctx.MINUS(), ctx.NOT())(ctx)
override def exitMultDivModExpr(ctx: IDP.MultDivModExprContext): Unit = expr += parseMultDivModExpr(ctx.MULT(), ctx.MOD(), ctx.DIV())(ctx)
override def exitPlusMinusExpr(ctx: IDP.PlusMinusExprContext): Unit = expr += parsePlusMinusExpr(ctx.PLUS(), ctx.MINUS())(ctx)
override def exitCompExpr(ctx: IDP.CompExprContext): Unit = expr += parseCompExpr(ctx.LT(), ctx.GT(), ctx.LTEQ(), ctx.GTEQ())(ctx)
override def exitAndOrExpr(ctx: IDP.AndOrExprContext): Unit = expr += parseAndOrExpr(ctx.AND, ctx.OR())(ctx)
override def exitEqNeqExpr(ctx: IDP.EqNeqExprContext): Unit = expr += parseEqNeqExpr(ctx.EQ, ctx.NEQ())(ctx)
override def exitAtom(ctx: IDP.AtomContext): Unit = expr += parseAtom(ctx.getText)(ctx)
override def exitTermEq(ctx: IDP.TermEqContext): Unit = termConv = ctx.TILDA() != null
override def exitFragMeta(ctx: IDP.FragMetaContext): Unit = fragMeta = json2Obj(ctx.jsonObj().getText)(ctx)
override def exitMetaDecl(ctx: IDP.MetaDeclContext): Unit = intentMeta = json2Obj(ctx.jsonObj().getText)(ctx)
override def exitOptDecl (ctx: IDP.OptDeclContext): Unit = intentOpts = convertToOptions(json2Obj(ctx.jsonObj().getText)(ctx))(ctx)
override def exitIntentId(ctx: IDP.IntentIdContext): Unit = intentId = ctx.id().getText
override def exitCallExpr(ctx: IDP.CallExprContext): Unit =
val fun =
if ctx.FUN_NAME() != null then ctx.FUN_NAME().getText
else "ent_type"
expr += parseCallExpr(fun)(ctx)
private def convertToOptions(json: Map[String, Object])(ctx: IDP.OptDeclContext): NCIDLIntentOptions =
val opts = new NCIDLIntentOptions()
def boolVal(k: String, v: Object): Boolean =
v match
case b: java.lang.Boolean if b != null => b
case _ => SE(s"Expecting boolean value for intent option: $k")(ctx)
import NCIDLIntentOptions.*
for ((k, v) <- json)
if k == JSON_ORDERED then opts.ordered = boolVal(k, v)
else if k == JSON_UNUSED_FREE_WORDS then opts.ignoreUnusedFreeWords = boolVal(k, v)
else if k == JSON_UNUSED_ENTS then opts.ignoreUnusedEntities = boolVal(k, v)
else if k == JSON_ALLOW_STM_ONLY then opts.allowStmEntityOnly = boolVal(k, v)
else SE(s"Unknown intent option: $k")(ctx)
opts
override def enterCallExpr(ctx: IDP.CallExprContext): Unit =
expr += ((_, stack: NCIDLStack, _) => stack.push(stack.PLIST_MARKER))
/**
*
* @param min
* @param max
*/
private def setMinMax(min: Int, max: Int): Unit =
this.min = min
this.max = max
override def exitVarRef(ctx: IDP.VarRefContext): Unit =
val varName = ctx.id().getText
if !vars.contains(varName) then SE(s"Undefined variable: @$varName")(ctx)
val instr: SI = (ent: NCIDLEntity, stack: S, idlCtx: NCIDLContext) => stack.push(() => idlCtx.vars(varName)(ent, idlCtx))
expr += instr
override def exitVarDecl(ctx: IDP.VarDeclContext): Unit =
val varName = ctx.id().getText
if vars.contains(varName) then SE(s"Duplicate variable: @$varName")(ctx)
vars += varName -> exprToFunction("Variable declaration", _ => true)(ctx)
expr.clear()
override def exitMinMaxShortcut(ctx: IDP.MinMaxShortcutContext): Unit =
if ctx.PLUS() != null then setMinMax(1, MINMAX_MAX)
else if ctx.MULT() != null then setMinMax(0, MINMAX_MAX)
else if ctx.QUESTION() != null then setMinMax(0, 1)
else assert(false)
override def exitMinMaxRange(ctx: IDP.MinMaxRangeContext): Unit =
val minStr = ctx.getChild(1).getText.trim
val maxStr = ctx.getChild(3).getText.trim
try
val min = java.lang.Integer.parseInt(minStr)
val max = java.lang.Integer.parseInt(maxStr)
if min < 0 then SE(s"Min value cannot be negative: $min")(ctx)
if min > max then SE(s"Min value '$min' cannot be greater than max value '$max'.")(ctx)
if max > MINMAX_MAX then SE(s"Max value '$max' cannot be greater than '$MINMAX_MAX'.")(ctx)
setMinMax(min, max)
// Errors should be caught during compilation phase.
catch case _: NumberFormatException => assert(false)
override def exitTermId(ctx: IDP.TermIdContext): Unit =
termId = ctx.id().getText
if terms.exists(t => t.id === termId) then SE(s"Duplicate intent term ID: $termId")(ctx.id())
override def exitFragId(ctx: IDP.FragIdContext): Unit =
fragId = ctx.id().getText
override def exitFragRef(ctx: IDP.FragRefContext): Unit =
val id = ctx.id().getText
fragCache.get(id) match
case Some(frag) =>
val meta = if fragMeta == null then Map.empty[String, Any] else fragMeta
for (fragTerm <- frag.terms)
if terms.exists(t => t.id === fragTerm.id) then SE(s"Duplicate term ID '${fragTerm.id.get}' in fragment '$id'.")(ctx.id())
else terms += fragTerm.cloneWithFragMeta(meta)
case None => SE(s"Unknown intent fragment ID: $id")(ctx.id())
fragMeta = null
override def exitFlowDecl(ctx: IDP.FlowDeclContext): Unit =
val regex = NCUtils.trimQuotes(ctx.qstring().getText)
if regex != null && regex.length > 2 then flowRegex = Option.when(regex.nonEmpty)(regex)
if flowRegex.isDefined then // Pre-check.
try Pattern.compile(flowRegex.get)
catch case e: PatternSyntaxException => SE(s"${e.getDescription} in intent flow regex '${e.getPattern}' near index ${e.getIndex}.")(ctx.qstring())
override def exitTerm(ctx: IDP.TermContext): Unit =
if min < 0 || min > max then SE(s"Invalid intent term min quantifiers: $min (must be min >= 0 && min <= max).")(ctx.minMax())
if max < 1 then SE(s"Invalid intent term max quantifiers: $max (must be max >= 1).")(ctx.minMax())
val pred: NCIDLFunction = exprToFunction("Intent term", isBool)(ctx.expr())
// Add term.
terms += NCIDLTerm(
ctx.getText,
termId.?,
vars.toMap,
pred,
min,
max,
termConv
)
// Reset term vars.
setMinMax(1, 1)
termId = null
expr.clear()
vars.clear()
/**
*
* @param subj
* @param check
* @param ctx
*/
private def exprToFunction(subj: String, check: Object => Boolean)(implicit ctx: PRC): NCIDLFunction =
val code = mutable.Buffer.empty[SI]
code ++= expr
(ent: NCIDLEntity, termCtx: NCIDLContext) => {
val stack = new S()
// Execute all instructions.
code.foreach(_ (ent, stack, termCtx))
// Pop final result from stack.
val x = stack.pop()()
val v = x.value
// Check final value's type.
if !check(v) then RE(s"$subj returned value of unexpected type '$v' in: ${ctx.getText}")
Z(v, x.entUse)
}