private def json2Obj()

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)
            }