WIP.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlContext.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlContext.scala
index 1d09238..32fa895 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlContext.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlContext.scala
@@ -19,6 +19,9 @@
 
 import org.apache.nlpcraft.common.ScalaMeta
 import org.apache.nlpcraft.model.NCRequest
+import org.apache.nlpcraft.model.intent.compiler.NCIdlStackItem
+
+import scala.collection.mutable
 
 /**
  *
@@ -26,11 +29,13 @@
  * @param convMeta Conversation metadata.
  * @param fragMeta Optional fragment (argument) metadata passed during intent fragment reference.
  * @param req Server request holder.
+ * @param vars Variable storage.
  */
 case class NCIdlContext(
     intentMeta: ScalaMeta = Map.empty[String, Object],
     convMeta: ScalaMeta = Map.empty[String, Object],
     fragMeta: ScalaMeta = Map.empty[String, Object],
-    req: NCRequest
+    req: NCRequest,
+    vars: mutable.Map[String, NCIdlStackItem]
 )
 
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTokenPredicate.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlFunction.scala
similarity index 86%
rename from nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTokenPredicate.scala
rename to nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlFunction.scala
index d585a8a..a876960 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTokenPredicate.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlFunction.scala
@@ -22,4 +22,4 @@
 /**
  *
  */
-trait NCIdlTokenPredicate extends ((NCToken, NCIdlContext) ⇒ (Boolean /* Predicate. */ , Int /* How many times a token was used. */ ))
+trait NCIdlFunction[T] extends ((NCToken, NCIdlContext) ⇒ (T , Int /* How many times a token was used. */ ))
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlSynonym.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlSynonym.scala
index 4abe52a..c092801 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlSynonym.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlSynonym.scala
@@ -17,8 +17,6 @@
 
 package org.apache.nlpcraft.model.intent
 
-import org.apache.nlpcraft.model.NCToken
-
 /**
  * DSl synonym.
  *
@@ -28,7 +26,7 @@
 case class NCIdlSynonym(
     origin: String,
     alias: Option[String],
-    pred: NCIdlTokenPredicate,
+    pred: NCIdlFunction[Boolean],
 ) {
     require(origin != null)
     require(pred != null)
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTerm.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTerm.scala
index 5e24e8c..8d33c1a 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTerm.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/NCIdlTerm.scala
@@ -24,7 +24,8 @@
  *
  * @param idl
  * @param id Optional ID of this term.
- * @param pred
+ * @param decls Term optional declarations.
+ * @param pred Term predicate.
  * @param min
  * @param max
  * @param conv
@@ -33,7 +34,8 @@
 case class NCIdlTerm(
     idl: String,
     id: Option[String],
-    pred: NCIdlTokenPredicate,
+    decls: List[NCIdlFunction[Object]],
+    pred: NCIdlFunction[Boolean],
     min: Int,
     max: Int,
     conv: Boolean,
@@ -51,6 +53,7 @@
         NCIdlTerm(
             idl,
             id,
+            decls,
             pred,
             min,
             max,
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/compiler/NCIdlCompiler.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/compiler/NCIdlCompiler.scala
index a49a71c..356288f 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/compiler/NCIdlCompiler.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/compiler/NCIdlCompiler.scala
@@ -22,10 +22,10 @@
 import org.antlr.v4.runtime._
 import org.antlr.v4.runtime.{ParserRuleContext ⇒ PRC}
 import org.apache.nlpcraft.common._
-import org.apache.nlpcraft.model.intent.compiler.antlr4.{NCIdlBaseListener, NCIdlLexer, NCIdlParser ⇒ IDP}
+import org.apache.nlpcraft.model.intent.compiler.antlr4.{NCIdlBaseListener, NCIdlLexer, NCIdlParser, NCIdlParser ⇒ IDP}
 import org.apache.nlpcraft.model.intent.compiler.{NCIdlCompilerGlobal ⇒ Global}
 import org.apache.nlpcraft.model._
-import org.apache.nlpcraft.model.intent.{NCIdlContext, NCIdlIntent, NCIdlSynonym, NCIdlTerm, NCIdlTokenPredicate}
+import org.apache.nlpcraft.model.intent.{NCIdlContext, NCIdlIntent, NCIdlSynonym, NCIdlTerm, NCIdlFunction}
 
 import java.io._
 import java.net._
@@ -71,6 +71,7 @@
         private val terms = ArrayBuffer.empty[NCIdlTerm]
 
         // Currently term.
+        private var vars = mutable.HashMap.empty[String, NCIdlFunction[Object]]
         private var termId: String = _
         private var termConv: Boolean = _
         private var min = 1
@@ -81,7 +82,7 @@
         private var refMtdName: Option[String] = None
 
         // List of instructions for the current expression.
-        private var instrs = mutable.Buffer.empty[I]
+        private var expr = mutable.Buffer.empty[I]
 
 
         /**
@@ -99,14 +100,14 @@
         /*
          * Shared/common implementation.
          */
-        override def exitUnaryExpr(ctx: IDP.UnaryExprContext): Unit = instrs += parseUnaryExpr(ctx.MINUS(), ctx.NOT())(ctx)
-        override def exitMultDivModExpr(ctx: IDP.MultDivModExprContext): Unit = instrs += parseMultDivModExpr(ctx.MULT(), ctx.MOD(), ctx.DIV())(ctx)
-        override def exitPlusMinusExpr(ctx: IDP.PlusMinusExprContext): Unit = instrs += parsePlusMinusExpr(ctx.PLUS(), ctx.MINUS())(ctx)
-        override def exitCompExpr(ctx: IDP.CompExprContext): Unit = instrs += parseCompExpr(ctx.LT(), ctx.GT(), ctx.LTEQ(), ctx.GTEQ())(ctx)
-        override def exitAndOrExpr(ctx: IDP.AndOrExprContext): Unit = instrs += parseAndOrExpr(ctx.AND, ctx.OR())(ctx)
-        override def exitEqNeqExpr(ctx: IDP.EqNeqExprContext): Unit = instrs += parseEqNeqExpr(ctx.EQ, ctx.NEQ())(ctx)
-        override def exitCallExpr(ctx: IDP.CallExprContext): Unit = instrs += parseCallExpr(ctx.FUN_NAME())(ctx)
-        override def exitAtom(ctx: IDP.AtomContext): Unit = instrs += parseAtom(ctx.getText)(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 exitCallExpr(ctx: IDP.CallExprContext): Unit = expr += parseCallExpr(ctx.FUN_NAME())(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 = U.jsonToScalaMap(ctx.jsonObj().getText)
         override def exitMetaDecl(ctx: IDP.MetaDeclContext): Unit = intentMeta = U.jsonToScalaMap(ctx.jsonObj().getText)
@@ -115,7 +116,7 @@
         override def exitAlias(ctx: IDP.AliasContext): Unit = alias = ctx.id().getText
 
         override def enterCallExpr(ctx: IDP.CallExprContext): Unit =
-            instrs += ((_, stack: NCIdlStack, _) ⇒ stack.push(stack.PLIST_MARKER))
+            expr += ((_, stack: NCIdlStack, _) ⇒ stack.push(stack.PLIST_MARKER))
 
         /**
          *
@@ -127,6 +128,34 @@
             this.max = max
         }
 
+        override def exitVarRef(ctx: NCIdlParser.VarRefContext): Unit = {
+
+        }
+
+        override def exitVarDecl(ctx: NCIdlParser.VarDeclContext): Unit = {
+            val varName = ctx.id().getText
+
+            if (vars.contains(varName))
+                throw newSyntaxError(s"Duplicate variable: $varName")(ctx)
+
+            vars += varName
+
+            val fun = exprToFunction[Object]("Variable declaration", _ ⇒ true, x ⇒ x)(ctx)
+
+            val instr = (tok: NCToken, ctx: NCIdlContext) ⇒ {
+                val (res, tokUses) = fun(tok, ctx)
+
+                (null, 0)
+            }
+
+
+
+
+
+
+            expr.clear()
+        }
+
         override def exitMinMaxShortcut(ctx: IDP.MinMaxShortcutContext): Unit = {
             if (ctx.PLUS() != null)
                 setMinMax(1, MINMAX_MAX)
@@ -178,9 +207,9 @@
         override def exitSynonym(ctx: IDP.SynonymContext): Unit = {
             implicit val evidence: PRC = ctx
 
-            val pred = instrToPredicate("Synonym")
+            val pred = exprToFunction("Synonym", isBool, asBool)
             val capture = alias
-            val wrapper: NCIdlTokenPredicate = (tok: NCToken, ctx: NCIdlContext) ⇒ {
+            val wrapper: NCIdlFunction[Boolean] = (tok: NCToken, ctx: NCIdlContext) ⇒ {
                 val (res, tokUses) = pred(tok, ctx)
 
                 // Store predicate's alias, if any, in token metadata if this token satisfies this predicate.
@@ -202,7 +231,7 @@
             synonym = NCIdlSynonym(origin, Option(alias), wrapper)
 
             alias = null
-            instrs.clear()
+            expr.clear()
         }
 
         override def exitFragId(ctx: IDP.FragIdContext): Unit = {
@@ -254,7 +283,7 @@
             if (max < 1)
                 throw newSyntaxError(s"Invalid intent term max quantifiers: $max (must be max >= 1).")(ctx.minMax())
 
-            val pred: NCIdlTokenPredicate = if (refMtdName.isDefined) { // User-code defined term.
+            val pred: NCIdlFunction[Boolean] = if (refMtdName.isDefined) { // User-code defined term.
                 // Closure copies.
                 val clsName = refClsName.orNull
                 val mtdName = refMtdName.orNull
@@ -289,7 +318,7 @@
                 }
             }
             else  // IDL term.
-                instrToPredicate("Intent term")(ctx.expr())
+                exprToFunction("Intent term", isBool, asBool)(ctx.expr())
 
             // Add term.
             terms += NCIdlTerm(
@@ -304,7 +333,8 @@
             // Reset term vars.
             setMinMax(1, 1)
             termId = null
-            instrs.clear()
+            expr.clear()
+            vars.clear()
             refClsName = None
             refMtdName = None
         }
@@ -312,12 +342,23 @@
         /**
          *
          * @param subj
+         * @param check
+         * @param cast
+         * @param ctx
+         * @tparam T
          * @return
          */
-        private def instrToPredicate(subj: String)(implicit ctx: PRC): NCIdlTokenPredicate = {
+        private def exprToFunction[T](
+            subj: String,
+            check: Object ⇒ Boolean,
+            cast: Object ⇒ T
+        )
+        (
+            implicit ctx: PRC
+        ): NCIdlFunction[T] = {
             val code = mutable.Buffer.empty[I]
 
-            code ++= instrs
+            code ++= expr
 
             (tok: NCToken, termCtx: NCIdlContext) ⇒ {
                 val stack = new S()
@@ -329,10 +370,10 @@
                 val x = stack.pop()()
                 val v = x.value
 
-                if (!isBool(v))
-                    throw newRuntimeError(s"$subj did not return boolean value: ${ctx.getText}")
+                if (!check(v))
+                    throw newRuntimeError(s"$subj returned value of unexpected type '$v' in: ${ctx.getText}")
 
-                (asBool(v), x.tokUse)
+                (cast(v), x.tokUse)
             }
         }
 
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolverEngine.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolverEngine.scala
index 99f8576..6cd92b5 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolverEngine.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/model/intent/solver/NCIntentSolverEngine.scala
@@ -26,6 +26,7 @@
 import org.apache.nlpcraft.model.{NCContext, NCDialogFlowItem, NCIntentMatch, NCResult, NCToken}
 import org.apache.nlpcraft.probe.mgrs.dialogflow.NCDialogFlowManager
 import org.apache.nlpcraft.model.impl.NCTokenPimp._
+import org.apache.nlpcraft.model.intent.compiler.NCIdlStackItem
 import org.apache.nlpcraft.model.intent.{NCIdlContext, NCIdlIntent, NCIdlTerm}
 
 import java.util.function.Function
@@ -494,7 +495,8 @@
             val termCtx = NCIdlContext(
                 intentMeta = intent.meta,
                 convMeta = if (x.isEmpty) Map.empty[String, Object] else x.asScala.toMap[String, Object],
-                req = ctx.getRequest
+                req = ctx.getRequest,
+                vars = mutable.HashMap.empty[String, NCIdlStackItem]
             )
 
             // Check terms.
diff --git a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeSynonymChunk.scala b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeSynonymChunk.scala
index cefb16a..ebc1d8e 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeSynonymChunk.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/probe/mgrs/NCProbeSynonymChunk.scala
@@ -17,7 +17,7 @@
 
 package org.apache.nlpcraft.probe.mgrs
 
-import org.apache.nlpcraft.model.intent.NCIdlTokenPredicate
+import org.apache.nlpcraft.model.intent.NCIdlFunction
 import org.apache.nlpcraft.probe.mgrs.NCProbeSynonymChunkKind._
 
 import java.util.regex.Pattern
@@ -39,7 +39,7 @@
     wordStem: String = null, // Only for kind == TEXT.
     posTag: String = null,
     regex: Pattern = null,
-    idlPred: NCIdlTokenPredicate = null
+    idlPred: NCIdlFunction = null
 ) {
     require(origText != null)
     require(kind != null)