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

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


The following commit(s) were added to refs/heads/master by this push:
     new 5fa2579  WIP
5fa2579 is described below

commit 5fa2579d1db5bb9b38e596bdbaa8f0071c15ec5a
Author: Aaron Radzinski <[email protected]>
AuthorDate: Tue Oct 5 13:20:53 2021 -0700

    WIP
---
 nlpcraft/pom.xml                                   |   4 +
 .../nlpcraft/common/makro/NCMacroCompiler.scala    | 242 ++++++++
 .../nlpcraft/common/makro/NCMacroJavaParser.java   |  77 +++
 .../common/makro/NCMacroJavaParserTrait.java       |  59 ++
 .../nlpcraft/common/makro/NCMacroParser.scala      | 197 +++++++
 .../nlpcraft/common/makro/antlr4/NCMacroDsl.g4     |  82 +++
 .../nlpcraft/common/makro/antlr4/NCMacroDsl.interp |  47 ++
 .../nlpcraft/common/makro/antlr4/NCMacroDsl.tokens |  22 +
 .../makro/antlr4/NCMacroDslBaseListener.java       | 135 +++++
 .../common/makro/antlr4/NCMacroDslLexer.interp     |  62 ++
 .../common/makro/antlr4/NCMacroDslLexer.java       | 148 +++++
 .../common/makro/antlr4/NCMacroDslLexer.tokens     |  22 +
 .../common/makro/antlr4/NCMacroDslListener.java    |  90 +++
 .../common/makro/antlr4/NCMacroDslParser.java      | 641 +++++++++++++++++++++
 .../org/apache/nlpcraft/common/util/NCUtils.scala  |  16 +-
 pom.xml                                            |  12 +
 16 files changed, 1855 insertions(+), 1 deletion(-)

diff --git a/nlpcraft/pom.xml b/nlpcraft/pom.xml
index 8bd3382..94a9ea2 100644
--- a/nlpcraft/pom.xml
+++ b/nlpcraft/pom.xml
@@ -84,6 +84,10 @@
          Other dependencies.
          ===================
         -->
+        <dependency>
+            <groupId>org.antlr</groupId>
+            <artifactId>antlr4-runtime</artifactId>
+        </dependency>
 
         <!--
          JUnit & ScalaTest dependencies.
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
new file mode 100644
index 0000000..87f0f45
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroCompiler.scala
@@ -0,0 +1,242 @@
+/*
+ * 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.common.makro
+
+import com.typesafe.scalalogging.LazyLogging
+import org.antlr.v4.runtime.tree.ParseTreeWalker
+import org.antlr.v4.runtime.*
+import org.apache.nlpcraft.common.*
+import org.apache.nlpcraft.common.ansi.NCAnsi.*
+import org.apache.nlpcraft.common.antlr4.*
+import org.apache.nlpcraft.common.makro.NCMacroCompiler.FiniteStateMachine
+import org.apache.nlpcraft.common.makro.antlr4.*
+import org.apache.nlpcraft.common.util.NCUtils
+
+import scala.collection.mutable
+
+/**
+  *
+  */
+object NCMacroCompiler extends LazyLogging:
+    private final val MAX_SYN = 10000
+    private final val MAX_QTY = 100
+
+    /**
+      *
+      * @param buffer
+      * @param isGroup
+      */
+    case class StackItem (
+        var buffer: mutable.Buffer[String],
+        isGroup: Boolean
+    )
+
+    /**
+      *
+      * @param parser
+      * @param in
+      */
+    class FiniteStateMachine(parser: NCMacroDslParser, in: String) extends 
NCMacroDslBaseListener:
+        private val stack = new mutable.Stack[StackItem]
+        private var expandedSyns: Set[String] = _
+        private var min = 1
+        private var max = 1
+
+        /**
+          *
+          * @param optS
+          * @param s
+          * @return
+          */
+        private def concat(optS: String, s: String): String = if 
(optS.isEmpty) s else optS + " " + s
+
+        /**
+          *
+          * @param errMsg
+          * @param ctx
+          * @return
+          */
+        private def compilerError(errMsg: String)(implicit ctx: 
ParserRuleContext): NCException =
+            val tok = ctx.stop
+            new NCException(mkCompilerError(errMsg, tok.getLine, 
tok.getCharPositionInLine, in))
+
+        /**
+          *
+          * @param buf
+          * @param ctx
+          */
+        private def checkMaxSyn(buf: mutable.Buffer[String])(implicit ctx: 
ParserRuleContext): Unit =
+            if (buf.size > MAX_SYN)
+                throw compilerError(s"Exceeded max number ($MAX_SYN) of macro 
expansions: ${buf.size}")
+
+        override def enterExpr(ctx: NCMacroDslParser.ExprContext): Unit =
+            val buf = mutable.Buffer.empty[String]
+            // NOTE: do not allow expression's buffer to be empty.
+            // Add harmless empty string.
+            buf += ""
+            stack.push(StackItem(buf, isGroup = false))
+
+        override def enterGroup(ctx: NCMacroDslParser.GroupContext): Unit =
+            // NOTE: group cannot be empty based on the BNF grammar.
+            stack.push(StackItem(mutable.Buffer.empty[String], isGroup = true))
+
+        override def exitExpr(ctx: NCMacroDslParser.ExprContext): Unit =
+            implicit val evidence: ParserRuleContext = ctx
+
+            if stack.size > 1 then
+                val expr = stack.pop()
+                val prn = stack.top
+                checkMaxSyn(expr.buffer)
+                require(expr.buffer.nonEmpty)
+
+                if prn.isGroup then
+                    prn.buffer ++= expr.buffer
+                else
+                    prn.buffer = for (z <- expr.buffer; i <- 
prn.buffer.indices) yield concat(prn.buffer(i), z)
+
+        override def exitMinMax(ctx: NCMacroDslParser.MinMaxContext): Unit =
+            implicit val evidence: ParserRuleContext = ctx
+
+            if ctx.minMaxShortcut() != null then
+                ctx.minMaxShortcut().getText match
+                    case "?" => min = 0; max = 1
+                    case c => throw compilerError(s"Invalid min/max shortcut 
'$c' in: ${ctx.getText}")
+            else if ctx.MINMAX() != null then
+                var s = ctx.MINMAX().getText
+                val orig = s
+
+                s = s.substring(1, s.length - 1)
+                val comma = s.indexOf(',')
+                if comma == -1 || comma == 0 || comma == s.length - 1 then
+                    throw compilerError(s"Invalid min/max quantifier: $orig")
+
+                try
+                    min = java.lang.Integer.parseInt(s.substring(0, 
comma).trim)
+                catch
+                    case _: NumberFormatException => throw 
compilerError(s"Invalid min quantifier: $orig")
+
+                try
+                    max = java.lang.Integer.parseInt(s.substring(comma + 
1).trim)
+                catch
+                    case _: NumberFormatException => throw 
compilerError(s"Invalid max quantifier: $orig")
+
+            if min < 0 || max < 0 || min > max || max == 0 || max > MAX_QTY 
then
+                throw compilerError(s"[$min,$max] quantifiers should be 'max 
>= min, min >= 0, max > 0, max <= $MAX_QTY'.")
+
+        override def exitGroup(ctx: NCMacroDslParser.GroupContext): Unit =
+            given evidence: ParserRuleContext = ctx
+            val grp = stack.pop()
+            // Remove dups.
+            grp.buffer = grp.buffer.distinct
+            checkMaxSyn(grp.buffer)
+            require(grp.isGroup)
+            val prn = stack.top
+            prn.buffer = prn.buffer.flatMap {
+                s => (for (z <- grp.buffer; i <- min to max) yield concat(s, 
s"$z " * i).trim).toSet
+            }
+            // Reset.
+            min = 1
+            max = 1
+
+        override def exitSyn(ctx: NCMacroDslParser.SynContext): Unit =
+            val syn = (
+                if (ctx.TXT() != null) ctx.TXT()
+                else if (ctx.REGEX_TXT() != null) ctx.REGEX_TXT()
+                else ctx.IDL_TXT()
+                ).getText
+
+            val buf = stack.top.buffer
+            require(buf.nonEmpty)
+            for (i <- buf.indices) buf.update(i, concat(buf(i), syn))
+
+        override def exitList(ctx: NCMacroDslParser.ListContext): Unit = if 
ctx.UNDERSCORE() != null then stack.top.buffer += ""
+        override def exitMakro(ctx: NCMacroDslParser.MakroContext): Unit = 
expandedSyns = stack.pop().buffer.map(_.trim).toSet
+
+        /**
+          *
+          * @return
+          */
+        def getExpandedMacro: Set[String] =
+            require(expandedSyns != null)
+            expandedSyns
+
+    /**
+      * Custom error handler.
+      */
+    class CompilerErrorListener(in: String) extends BaseErrorListener:
+        /**
+          *
+          * @param recognizer
+          * @param offendingSymbol
+          * @param line
+          * @param charPos
+          * @param msg
+          * @param e
+          */
+        override def syntaxError(
+            recognizer: Recognizer[_, _],
+            offendingSymbol: scala.Any,
+            line: Int, // 1, 2, ...
+            charPos: Int, // 1, 2, ...
+            msg: String,
+            e: RecognitionException): Unit = throw new 
NCException(mkCompilerError(msg, line, charPos - 1, in))
+
+    /**
+      *
+      * @param line
+      * @param charPos
+      * @param in
+      * @param msg
+      */
+    private def mkCompilerError(
+        msg: String,
+        line: Int, // 1, 2, ...
+        charPos: Int, // 0, 1, 2, ...
+        in: String
+    ): String =
+        val hldr = NCCompilerUtils.mkErrorHolder(in, charPos)
+        val aMsg = NCUtils.decapitalize(msg) match
+            case s: String if s.last == '.' => s
+            case s: String => s + '.'
+        s"Macro compiler error at line $line - $aMsg\n" +
+            s"  |-- ${c("Macro:")} ${hldr.origStr}\n" +
+            s"  +-- ${c("Error:")} ${hldr.ptrStr}"
+
+    /**
+      *
+      * @param in Macro to expand.
+      * @return Expanded macro as a set of finite strings.
+      */
+    def compile(in: String): Set[String] =
+        // ANTLR4 armature.
+        val lexer = new NCMacroDslLexer(CharStreams.fromString(in))
+        val stream = new CommonTokenStream(lexer)
+        val parser = new NCMacroDslParser(stream)
+
+        // Set custom error handlers.
+        lexer.removeErrorListeners()
+        parser.removeErrorListeners()
+        lexer.addErrorListener(new CompilerErrorListener(in))
+        parser.addErrorListener(new CompilerErrorListener(in))
+
+        // State automata.
+        val fsm = new FiniteStateMachine(parser, in)
+        // Parse the input DSL and walk built AST.
+        (new ParseTreeWalker).walk(fsm, parser.makro())
+        // Return the expanded macro.
+        fsm.getExpandedMacro
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroJavaParser.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroJavaParser.java
new file mode 100644
index 0000000..d10aa2d
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroJavaParser.java
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+package org.apache.nlpcraft.common.makro;
+
+import java.util.Set;
+
+/**
+ * Java adapter for macro parser (so that Java reflection could work).
+ */
+public class NCMacroJavaParser implements NCMacroJavaParserTrait {
+    private final NCMacroParser impl = new NCMacroParser();
+
+    /**
+     * Expands given macro DSL string.
+     *
+     * @param s Macro DSL string to expand.
+     * @return Set of macro expansions for a given macro DSL string.
+     */
+    public Set<String> expand(String s) {
+        return impl.expandJava(s);
+    }
+
+    /**
+     * Adds or overrides given macro.
+     *
+     * @param name Macro name (typically an upper case string).
+     *     It must start with '&lt;' and end with '&gt;' symbol.
+     * @param macro Value of the macro (any arbitrary string).
+     * @return {@code true} if an existing macro was overridden, {@code false} 
otherwise.
+     */
+    public boolean addMacro(String name, String macro) {
+        boolean f = impl.hasMacro(name);
+
+        impl.addMacro(name, macro);
+
+        return f;
+    }
+
+    /**
+     * Removes macro.
+     *
+     * @param name Name of the macro to remove.
+     * @return {@code true} if given macro was indeed found and removed, 
{@code false} otherwise.
+     */
+    public boolean removeMacro(String name) {
+        boolean f = impl.hasMacro(name);
+
+        impl.removeMacro(name);
+
+        return f;
+    }
+
+    /**
+     * Tests whether this processor has given macro.
+     *
+     * @param name Name of the macro to test.
+     * @return {@code true} if macro was found, {@code false} otherwise.
+     */
+    public boolean hasMacro(String name) {
+        return impl.hasMacro(name);
+    }
+}
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroJavaParserTrait.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroJavaParserTrait.java
new file mode 100644
index 0000000..58d5452
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroJavaParserTrait.java
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+package org.apache.nlpcraft.common.makro;
+
+import java.util.Set;
+
+/**
+ * Necessary plug for Javadoc to work on mixed Java/Scala project.
+ */
+public interface NCMacroJavaParserTrait {
+    /**
+     * Expands given macro DSL string.
+     *
+     * @param s Macro DSL string to expand.
+     * @return Set of macro expansions for a given macro DSL string.
+     */
+    Set<String> expand(String s);
+
+    /**
+     * Adds or overrides given macro.
+     *
+     * @param name Macro name (typically an upper case string).
+     *     It must start with '&lt;' and end with '&gt;' symbol.
+     * @param macro Value of the macro (any arbitrary string).
+     * @return {@code true} if an existing macro was overridden, {@code false} 
otherwise.
+     */
+    boolean addMacro(String name, String macro);
+
+    /**
+     * Removes macro.
+     *
+     * @param name Name of the macro to remove.
+     * @return {@code true} if given macro was indeed found and removed, 
{@code false} otherwise.
+     */
+    boolean removeMacro(String name);
+
+    /**
+     * Tests whether this processor has given macro.
+     *
+     * @param name Name of the macro to test.
+     * @return {@code true} if macro was found, {@code false} otherwise.
+     */
+    boolean hasMacro(String name);
+}
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroParser.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroParser.scala
new file mode 100644
index 0000000..11c7cc1
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/NCMacroParser.scala
@@ -0,0 +1,197 @@
+/*
+ * 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.common.makro
+
+import org.apache.nlpcraft.common.*
+import org.apache.nlpcraft.common.util.NCUtils
+
+import scala.jdk.CollectionConverters.*
+
+object NCMacroParser:
+    private final val ESC_CHARS = """{}\<>_[]|,"""
+    private final val MACRO_REGEX = s"<[A-Za-z0-9-_]+>".r
+    private final val BROKEN_MACRO_REGEX1 = s"<[A-Za-z0-9-_]+".r
+    private final val BROKEN_MACRO_REGEX2 = s"[A-Za-z0-9-_]+>".r
+
+    /**
+      * Constructor.
+      *
+      * @param macros Set of macros to add.
+      */
+    def apply(macros: List[(String, String)]): NCMacroParser =
+        apply(macros: _*)
+
+    /**
+      * Constructor.
+      *
+      * @param macros Set of macros to add.
+      */
+    def apply(macros: Map[String, String]): NCMacroParser =
+        apply(macros.toSeq: _*)
+
+    /**
+      * Constructor.
+      *
+      * @param macros Set of macros to add.
+      */
+    def apply(macros: (String, String)*): NCMacroParser =
+        val obj = new NCMacroParser
+        macros.foreach(m => obj.addMacro(m._1, m._2))
+        obj
+
+
+/**
+  * Provides generic support for text expansion using macros and options 
groups.
+  *
+  * Syntax:
+  * - all macros should start with '<' and end with '>'.
+  * - '{A|{B}}' denotes either 'A' or 'B'.
+  * - '{A|B|_}' denotes either 'A', or 'B' or nothing ('_').
+  * - '{A}[1,2]' denotes 'A' or 'A A'.
+  * - '{A}[0,1]' denotes 'A' or nothing (just like '{A|_}').
+  * - '\' should be used for escaping any of '{}\_[]|,' special symbols.
+  * - Excessive pairs'{' and '}' are ignored
+  *
+  * Examples:
+  *      "A {B|C}[1,2] D" => "A B D", "A C D", "A B B D", "A C C D"
+  *      "A \{B\|C\} D" => "A {B|C} D"
+  *      "A {B|_} D" => "A D", "A B D"
+  *      "A {_|B|C} {D}[1,2]" => "A D", "A B D", "A C D", "A D D", "A B D D", 
"A C D D"
+  *      "A <MACRO>" => "A ..." based on <MACRO> content.
+  *      "A {<MACRO>|_}" => "A", "A ..." based on <MACRO> content.
+  *
+  * NOTE: Macros cannot be recursive.
+  * NOTE: Macros and '{...}' options groups can be nested.
+  */
+class NCMacroParser:
+    import NCMacroParser.*
+
+    private val macros = new java.util.concurrent.ConcurrentHashMap[String, 
String]().asScala
+
+    /**
+      * Trims all duplicate spaces.
+      *
+      * @param s
+      * @return
+      */
+    private def trimDupSpaces(s: String) = NCUtils.splitTrimFilter(s, " 
").mkString(" ")
+
+    /**
+      *
+      * @param s
+      * @return
+      */
+    private def processEscapes(s: String): String =
+        val len = s.length()
+        val buf = new StringBuilder()
+        var i = 0
+        var isEscape = false
+
+        while (i < len)
+            val ch = s.charAt(i)
+            if ch == '\\' && !isEscape then
+                isEscape = true
+            else
+                if isEscape && !ESC_CHARS.contains(ch) then buf += '\\'
+                buf += ch
+                isEscape = false
+            i += 1
+        buf.toString
+
+    /**
+      * Expand given string.
+      *
+      * @param txt Text to expand.
+      */
+    @throws[NCException]
+    def expand(txt: String): Seq[String] =
+        require(txt != null)
+
+        var s = txt
+
+        // Grab 1st macro match, if any.
+        var m = MACRO_REGEX.findFirstMatchIn(s)
+
+        // Expand macros including nested ones.
+        while (m.isDefined)
+            val ms = m.get.toString()
+            if !macros.keySet.contains(ms) then throw new 
NCException(s"Unknown macro [macro=$ms, txt=$txt]")
+            // Expand all registered macros.
+            for ((k, v) <- macros) s = s.replace(k, v)
+            // Grab another macro match, if any.
+            m = MACRO_REGEX.findFirstMatchIn(s)
+
+        // Check for potentially invalid macros syntax.
+        if BROKEN_MACRO_REGEX1.findFirstIn(s).isDefined || 
BROKEN_MACRO_REGEX2.findFirstIn(s).isDefined then
+            throw new NCException(s"Suspicious or invalid macro in: $txt")
+
+        NCUtils.distinct(NCMacroCompiler.compile(s).toList map trimDupSpaces 
map processEscapes)
+
+    /**
+      * Expand given string.
+      *
+      * @param txt Text to expand.
+      */
+    @throws[NCException]
+    def expandJava(txt: String): java.util.Set[String] =
+        expand(txt).toSet.asJava
+
+    /**
+      * Checks macro name.
+      *
+      * @param name Macro name.
+      */
+    private def checkName(name: String): Unit =
+        if name.head != '<' then throw new NCException(s"Missing macro '<' 
opening: $name")
+        if name.last != '>' then throw new NCException(s"Missing macro '>' 
closing: $name")
+
+    /**
+      * Adds or overrides given macro.
+      *
+      * @param name Macro name (typically an upper case string).
+      *     It must start with '&lt;' and end with '&gt;'.
+      * @param str Value of the macro (any arbitrary string).
+      */
+    @throws[NCException]
+    def addMacro(name: String, str: String): Unit =
+        require(name != null)
+        require(str != null)
+
+        checkName(name)
+        // Check for recursion.
+        if str.contains(name) then throw new NCException(s"Recursion is not 
supported, macro: $name")
+        macros += name -> str
+
+    /**
+      * Removes macro.
+      *
+      * @param name Macro name (typically an upper case string).
+      *      It must start with '<' and end with '>'.
+      */
+    @throws[NCException]
+    def removeMacro(name: String): Unit =
+        require(name != null)
+        macros -= name
+
+    /**
+      * Checks whether or not macro with given name exists or not.
+      *
+      * @param name Name.
+      */
+    def hasMacro(name: String): Boolean = macros.contains(name)
+
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.g4 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.g4
new file mode 100644
index 0000000..c6ee2a1
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.g4
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+grammar NCMacroDsl;
+
+// Parser.
+makro: expr EOF;
+expr
+    : item
+    | expr item
+    ;
+item: syn | group;
+syn : (TXT | REGEX_TXT | IDL_TXT);
+group: LCURLY list RCURLY minMax?;
+list
+    : expr
+    | list VERT expr
+    | list VERT UNDERSCORE
+    | UNDERSCORE VERT list
+    ;
+minMax
+    : minMaxShortcut
+    | MINMAX
+    ;
+minMaxShortcut
+    : QUESTION
+    ;
+
+// Lexer.
+LCURLY: '{';
+RCURLY: '}';
+VERT: '|';
+COMMA: ',';
+UNDERSCORE: '_';
+LBR: '[';
+RBR: ']';
+QUESTION: '?';
+fragment ESC_CHAR: [{}\\_[\]|,/];
+fragment ESC: '\\' ESC_CHAR;
+fragment TXT_CHAR
+    : [~!@#$%^&*?()+._]
+    | [-=<>/\\;:`'",]
+    | 'A'..'Z'
+    | 'a'..'z'
+    | '0'..'9'
+    | '\u0300'..'\u036F'
+    | '\u00A0'..'\u00FF' /* Latin-1 Supplement. */
+    | '\u0100'..'\u017F' /* Latin Extended-A. */
+    | '\u0180'..'\u024F' /* Latin Extended-B. */
+    | '\u1E02'..'\u1EF3' /* Latin Extended Additional. */
+    | '\u0259'..'\u0292' /* IPA Extensions. */
+    | '\u02B0'..'\u02FF' /* Spacing modifier letters. */
+    | '\u203F'..'\u2040'
+    | '\u1F01'..'\u1FFF' /* Greek Extended. */
+    | '\u0400'..'\u04FF' /* Cyrillic. */
+    | '\u200C'..'\u200D'
+    | '\u2070'..'\u218F'
+    | '\u2C00'..'\u2FEF'
+    | '\u3001'..'\uD7FF'
+    | '\uF900'..'\uFDCF'
+    | '\uFDF0'..'\uFFFD'
+    ; // Ignoring ['\u10000-'\uEFFFF].
+REGEX_TXT: '//' .*? '//';
+IDL_TXT: '^^' .*? '^^';
+TXT: (TXT_CHAR | ESC)+;
+MINMAX: '[' [ 0-9,]+ ']';
+WS: [ \r\t\u000C\n]+ -> skip ;
+ERR_CHAR: .;
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.interp
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.interp
new file mode 100644
index 0000000..b85d8dd
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.interp
@@ -0,0 +1,47 @@
+token literal names:
+null
+'{'
+'}'
+'|'
+','
+'_'
+'['
+']'
+'?'
+null
+null
+null
+null
+null
+null
+
+token symbolic names:
+null
+LCURLY
+RCURLY
+VERT
+COMMA
+UNDERSCORE
+LBR
+RBR
+QUESTION
+REGEX_TXT
+IDL_TXT
+TXT
+MINMAX
+WS
+ERR_CHAR
+
+rule names:
+makro
+expr
+item
+syn
+group
+list
+minMax
+minMaxShortcut
+
+
+atn:
+[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 16, 68, 4, 2, 
9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 
4, 9, 9, 9, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 3, 27, 10, 3, 
12, 3, 14, 3, 30, 11, 3, 3, 4, 3, 4, 5, 4, 34, 10, 4, 3, 5, 3, 5, 3, 6, 3, 6, 
3, 6, 3, 6, 5, 6, 42, 10, 6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 49, 10, 7, 3, 
7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 7, 7, 57, 10, 7, 12, 7, 14, 7, 60, 11, 7, 3, 
8, 3, 8, 5, 8, 64, 10, 8, [...]
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.tokens
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.tokens
new file mode 100644
index 0000000..319aa8e
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDsl.tokens
@@ -0,0 +1,22 @@
+LCURLY=1
+RCURLY=2
+VERT=3
+COMMA=4
+UNDERSCORE=5
+LBR=6
+RBR=7
+QUESTION=8
+REGEX_TXT=9
+IDL_TXT=10
+TXT=11
+MINMAX=12
+WS=13
+ERR_CHAR=14
+'{'=1
+'}'=2
+'|'=3
+','=4
+'_'=5
+'['=6
+']'=7
+'?'=8
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslBaseListener.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslBaseListener.java
new file mode 100644
index 0000000..7bd6836
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslBaseListener.java
@@ -0,0 +1,135 @@
+// Generated from C:/Users/Nikita 
Ivanov/Documents/GitHub/incubator-nlpcraft/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4\NCMacroDsl.g4
 by ANTLR 4.9.1
+package org.apache.nlpcraft.common.makro.antlr4;
+
+import org.antlr.v4.runtime.ParserRuleContext;
+import org.antlr.v4.runtime.tree.ErrorNode;
+import org.antlr.v4.runtime.tree.TerminalNode;
+
+/**
+ * This class provides an empty implementation of {@link NCMacroDslListener},
+ * which can be extended to create a listener which only needs to handle a 
subset
+ * of the available methods.
+ */
+public class NCMacroDslBaseListener implements NCMacroDslListener {
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterMakro(NCMacroDslParser.MakroContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitMakro(NCMacroDslParser.MakroContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterExpr(NCMacroDslParser.ExprContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitExpr(NCMacroDslParser.ExprContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterItem(NCMacroDslParser.ItemContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitItem(NCMacroDslParser.ItemContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterSyn(NCMacroDslParser.SynContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitSyn(NCMacroDslParser.SynContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterGroup(NCMacroDslParser.GroupContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitGroup(NCMacroDslParser.GroupContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterList(NCMacroDslParser.ListContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitList(NCMacroDslParser.ListContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterMinMax(NCMacroDslParser.MinMaxContext ctx) { 
}
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitMinMax(NCMacroDslParser.MinMaxContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void 
enterMinMaxShortcut(NCMacroDslParser.MinMaxShortcutContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void 
exitMinMaxShortcut(NCMacroDslParser.MinMaxShortcutContext ctx) { }
+
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void enterEveryRule(ParserRuleContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void exitEveryRule(ParserRuleContext ctx) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void visitTerminal(TerminalNode node) { }
+       /**
+        * {@inheritDoc}
+        *
+        * <p>The default implementation does nothing.</p>
+        */
+       @Override public void visitErrorNode(ErrorNode node) { }
+}
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.interp
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.interp
new file mode 100644
index 0000000..e7fb0fe
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.interp
@@ -0,0 +1,62 @@
+token literal names:
+null
+'{'
+'}'
+'|'
+','
+'_'
+'['
+']'
+'?'
+null
+null
+null
+null
+null
+null
+
+token symbolic names:
+null
+LCURLY
+RCURLY
+VERT
+COMMA
+UNDERSCORE
+LBR
+RBR
+QUESTION
+REGEX_TXT
+IDL_TXT
+TXT
+MINMAX
+WS
+ERR_CHAR
+
+rule names:
+LCURLY
+RCURLY
+VERT
+COMMA
+UNDERSCORE
+LBR
+RBR
+QUESTION
+ESC_CHAR
+ESC
+TXT_CHAR
+REGEX_TXT
+IDL_TXT
+TXT
+MINMAX
+WS
+ERR_CHAR
+
+channel names:
+DEFAULT_TOKEN_CHANNEL
+HIDDEN
+
+mode names:
+DEFAULT_MODE
+
+atn:
+[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 16, 108, 8, 1, 
4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 
9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 
14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 3, 2, 3, 2, 
3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 7, 3, 7, 3, 8, 3, 8, 3, 9, 
3, 9, 3, 10, 3, 10, 3, 11, 3, 11, 3, 11, 3, 12, 5, 12, 60, 10, 12, 3, 13, 3, 
13, 3, 13, 3, 13, 7, 13,  [...]
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.java
new file mode 100644
index 0000000..31e2d67
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.java
@@ -0,0 +1,148 @@
+// Generated from C:/Users/Nikita 
Ivanov/Documents/GitHub/incubator-nlpcraft/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4\NCMacroDsl.g4
 by ANTLR 4.9.1
+package org.apache.nlpcraft.common.makro.antlr4;
+import org.antlr.v4.runtime.Lexer;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.TokenStream;
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.atn.*;
+import org.antlr.v4.runtime.dfa.DFA;
+import org.antlr.v4.runtime.misc.*;
+
+@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
+public class NCMacroDslLexer extends Lexer {
+       static { RuntimeMetaData.checkVersion("4.9.1", 
RuntimeMetaData.VERSION); }
+
+       protected static final DFA[] _decisionToDFA;
+       protected static final PredictionContextCache _sharedContextCache =
+               new PredictionContextCache();
+       public static final int
+               LCURLY=1, RCURLY=2, VERT=3, COMMA=4, UNDERSCORE=5, LBR=6, 
RBR=7, QUESTION=8, 
+               REGEX_TXT=9, IDL_TXT=10, TXT=11, MINMAX=12, WS=13, ERR_CHAR=14;
+       public static String[] channelNames = {
+               "DEFAULT_TOKEN_CHANNEL", "HIDDEN"
+       };
+
+       public static String[] modeNames = {
+               "DEFAULT_MODE"
+       };
+
+       private static String[] makeRuleNames() {
+               return new String[] {
+                       "LCURLY", "RCURLY", "VERT", "COMMA", "UNDERSCORE", 
"LBR", "RBR", "QUESTION", 
+                       "ESC_CHAR", "ESC", "TXT_CHAR", "REGEX_TXT", "IDL_TXT", 
"TXT", "MINMAX", 
+                       "WS", "ERR_CHAR"
+               };
+       }
+       public static final String[] ruleNames = makeRuleNames();
+
+       private static String[] makeLiteralNames() {
+               return new String[] {
+                       null, "'{'", "'}'", "'|'", "','", "'_'", "'['", "']'", 
"'?'"
+               };
+       }
+       private static final String[] _LITERAL_NAMES = makeLiteralNames();
+       private static String[] makeSymbolicNames() {
+               return new String[] {
+                       null, "LCURLY", "RCURLY", "VERT", "COMMA", 
"UNDERSCORE", "LBR", "RBR", 
+                       "QUESTION", "REGEX_TXT", "IDL_TXT", "TXT", "MINMAX", 
"WS", "ERR_CHAR"
+               };
+       }
+       private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
+       public static final Vocabulary VOCABULARY = new 
VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
+
+       /**
+        * @deprecated Use {@link #VOCABULARY} instead.
+        */
+       @Deprecated
+       public static final String[] tokenNames;
+       static {
+               tokenNames = new String[_SYMBOLIC_NAMES.length];
+               for (int i = 0; i < tokenNames.length; i++) {
+                       tokenNames[i] = VOCABULARY.getLiteralName(i);
+                       if (tokenNames[i] == null) {
+                               tokenNames[i] = VOCABULARY.getSymbolicName(i);
+                       }
+
+                       if (tokenNames[i] == null) {
+                               tokenNames[i] = "<INVALID>";
+                       }
+               }
+       }
+
+       @Override
+       @Deprecated
+       public String[] getTokenNames() {
+               return tokenNames;
+       }
+
+       @Override
+
+       public Vocabulary getVocabulary() {
+               return VOCABULARY;
+       }
+
+
+       public NCMacroDslLexer(CharStream input) {
+               super(input);
+               _interp = new 
LexerATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
+       }
+
+       @Override
+       public String getGrammarFileName() { return "NCMacroDsl.g4"; }
+
+       @Override
+       public String[] getRuleNames() { return ruleNames; }
+
+       @Override
+       public String getSerializedATN() { return _serializedATN; }
+
+       @Override
+       public String[] getChannelNames() { return channelNames; }
+
+       @Override
+       public String[] getModeNames() { return modeNames; }
+
+       @Override
+       public ATN getATN() { return _ATN; }
+
+       public static final String _serializedATN =
+               
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\2\20l\b\1\4\2\t\2\4"+
+               
"\3\t\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\4\n\t\n\4\13\t"+
+               
"\13\4\f\t\f\4\r\t\r\4\16\t\16\4\17\t\17\4\20\t\20\4\21\t\21\4\22\t\22"+
+               
"\3\2\3\2\3\3\3\3\3\4\3\4\3\5\3\5\3\6\3\6\3\7\3\7\3\b\3\b\3\t\3\t\3\n\3"+
+               
"\n\3\13\3\13\3\13\3\f\5\f<\n\f\3\r\3\r\3\r\3\r\7\rB\n\r\f\r\16\rE\13\r"+
+               
"\3\r\3\r\3\r\3\16\3\16\3\16\3\16\7\16N\n\16\f\16\16\16Q\13\16\3\16\3\16"+
+               
"\3\16\3\17\3\17\6\17X\n\17\r\17\16\17Y\3\20\3\20\6\20^\n\20\r\20\16\20"+
+               
"_\3\20\3\20\3\21\6\21e\n\21\r\21\16\21f\3\21\3\21\3\22\3\22\4CO\2\23\3"+
+               
"\3\5\4\7\5\t\6\13\7\r\b\17\t\21\n\23\2\25\2\27\2\31\13\33\f\35\r\37\16"+
+               
"!\17#\20\3\2\6\7\2..\61\61]_aa}\177\23\2#\\^^`|\u0080\u0080\u00a2\u0251"+
+               
"\u025b\u0294\u02b2\u0371\u0402\u0501\u1e04\u1ef5\u1f03\u2001\u200e\u200f"+
+               
"\u2041\u2042\u2072\u2191\u2c02\u2ff1\u3003\ud801\uf902\ufdd1\ufdf2\uffff"+
+               
"\5\2\"\"..\62;\5\2\13\f\16\17\"\"\2n\2\3\3\2\2\2\2\5\3\2\2\2\2\7\3\2\2"+
+               
"\2\2\t\3\2\2\2\2\13\3\2\2\2\2\r\3\2\2\2\2\17\3\2\2\2\2\21\3\2\2\2\2\31"+
+               
"\3\2\2\2\2\33\3\2\2\2\2\35\3\2\2\2\2\37\3\2\2\2\2!\3\2\2\2\2#\3\2\2\2"+
+               
"\3%\3\2\2\2\5\'\3\2\2\2\7)\3\2\2\2\t+\3\2\2\2\13-\3\2\2\2\r/\3\2\2\2\17"+
+               
"\61\3\2\2\2\21\63\3\2\2\2\23\65\3\2\2\2\25\67\3\2\2\2\27;\3\2\2\2\31="+
+               
"\3\2\2\2\33I\3\2\2\2\35W\3\2\2\2\37[\3\2\2\2!d\3\2\2\2#j\3\2\2\2%&\7}"+
+               
"\2\2&\4\3\2\2\2\'(\7\177\2\2(\6\3\2\2\2)*\7~\2\2*\b\3\2\2\2+,\7.\2\2,"+
+               
"\n\3\2\2\2-.\7a\2\2.\f\3\2\2\2/\60\7]\2\2\60\16\3\2\2\2\61\62\7_\2\2\62"+
+               
"\20\3\2\2\2\63\64\7A\2\2\64\22\3\2\2\2\65\66\t\2\2\2\66\24\3\2\2\2\67"+
+               
"8\7^\2\289\5\23\n\29\26\3\2\2\2:<\t\3\2\2;:\3\2\2\2<\30\3\2\2\2=>\7\61"+
+               
"\2\2>?\7\61\2\2?C\3\2\2\2@B\13\2\2\2A@\3\2\2\2BE\3\2\2\2CD\3\2\2\2CA\3"+
+               
"\2\2\2DF\3\2\2\2EC\3\2\2\2FG\7\61\2\2GH\7\61\2\2H\32\3\2\2\2IJ\7`\2\2"+
+               
"JK\7`\2\2KO\3\2\2\2LN\13\2\2\2ML\3\2\2\2NQ\3\2\2\2OP\3\2\2\2OM\3\2\2\2"+
+               
"PR\3\2\2\2QO\3\2\2\2RS\7`\2\2ST\7`\2\2T\34\3\2\2\2UX\5\27\f\2VX\5\25\13"+
+               
"\2WU\3\2\2\2WV\3\2\2\2XY\3\2\2\2YW\3\2\2\2YZ\3\2\2\2Z\36\3\2\2\2[]\7]"+
+               
"\2\2\\^\t\4\2\2]\\\3\2\2\2^_\3\2\2\2_]\3\2\2\2_`\3\2\2\2`a\3\2\2\2ab\7"+
+               "_\2\2b 
\3\2\2\2ce\t\5\2\2dc\3\2\2\2ef\3\2\2\2fd\3\2\2\2fg\3\2\2\2gh\3"+
+               
"\2\2\2hi\b\21\2\2i\"\3\2\2\2jk\13\2\2\2k$\3\2\2\2\n\2;COWY_f\3\b\2\2";
+       public static final ATN _ATN =
+               new ATNDeserializer().deserialize(_serializedATN.toCharArray());
+       static {
+               _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
+               for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
+                       _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), 
i);
+               }
+       }
+}
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.tokens
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.tokens
new file mode 100644
index 0000000..319aa8e
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslLexer.tokens
@@ -0,0 +1,22 @@
+LCURLY=1
+RCURLY=2
+VERT=3
+COMMA=4
+UNDERSCORE=5
+LBR=6
+RBR=7
+QUESTION=8
+REGEX_TXT=9
+IDL_TXT=10
+TXT=11
+MINMAX=12
+WS=13
+ERR_CHAR=14
+'{'=1
+'}'=2
+'|'=3
+','=4
+'_'=5
+'['=6
+']'=7
+'?'=8
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslListener.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslListener.java
new file mode 100644
index 0000000..eb0821d
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslListener.java
@@ -0,0 +1,90 @@
+// Generated from C:/Users/Nikita 
Ivanov/Documents/GitHub/incubator-nlpcraft/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4\NCMacroDsl.g4
 by ANTLR 4.9.1
+package org.apache.nlpcraft.common.makro.antlr4;
+import org.antlr.v4.runtime.tree.ParseTreeListener;
+
+/**
+ * This interface defines a complete listener for a parse tree produced by
+ * {@link NCMacroDslParser}.
+ */
+public interface NCMacroDslListener extends ParseTreeListener {
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#makro}.
+        * @param ctx the parse tree
+        */
+       void enterMakro(NCMacroDslParser.MakroContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#makro}.
+        * @param ctx the parse tree
+        */
+       void exitMakro(NCMacroDslParser.MakroContext ctx);
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#expr}.
+        * @param ctx the parse tree
+        */
+       void enterExpr(NCMacroDslParser.ExprContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#expr}.
+        * @param ctx the parse tree
+        */
+       void exitExpr(NCMacroDslParser.ExprContext ctx);
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#item}.
+        * @param ctx the parse tree
+        */
+       void enterItem(NCMacroDslParser.ItemContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#item}.
+        * @param ctx the parse tree
+        */
+       void exitItem(NCMacroDslParser.ItemContext ctx);
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#syn}.
+        * @param ctx the parse tree
+        */
+       void enterSyn(NCMacroDslParser.SynContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#syn}.
+        * @param ctx the parse tree
+        */
+       void exitSyn(NCMacroDslParser.SynContext ctx);
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#group}.
+        * @param ctx the parse tree
+        */
+       void enterGroup(NCMacroDslParser.GroupContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#group}.
+        * @param ctx the parse tree
+        */
+       void exitGroup(NCMacroDslParser.GroupContext ctx);
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#list}.
+        * @param ctx the parse tree
+        */
+       void enterList(NCMacroDslParser.ListContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#list}.
+        * @param ctx the parse tree
+        */
+       void exitList(NCMacroDslParser.ListContext ctx);
+       /**
+        * Enter a parse tree produced by {@link NCMacroDslParser#minMax}.
+        * @param ctx the parse tree
+        */
+       void enterMinMax(NCMacroDslParser.MinMaxContext ctx);
+       /**
+        * Exit a parse tree produced by {@link NCMacroDslParser#minMax}.
+        * @param ctx the parse tree
+        */
+       void exitMinMax(NCMacroDslParser.MinMaxContext ctx);
+       /**
+        * Enter a parse tree produced by {@link 
NCMacroDslParser#minMaxShortcut}.
+        * @param ctx the parse tree
+        */
+       void enterMinMaxShortcut(NCMacroDslParser.MinMaxShortcutContext ctx);
+       /**
+        * Exit a parse tree produced by {@link 
NCMacroDslParser#minMaxShortcut}.
+        * @param ctx the parse tree
+        */
+       void exitMinMaxShortcut(NCMacroDslParser.MinMaxShortcutContext ctx);
+}
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslParser.java
 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslParser.java
new file mode 100644
index 0000000..eef5f94
--- /dev/null
+++ 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4/NCMacroDslParser.java
@@ -0,0 +1,641 @@
+// Generated from C:/Users/Nikita 
Ivanov/Documents/GitHub/incubator-nlpcraft/nlpcraft/src/main/scala/org/apache/nlpcraft/common/makro/antlr4\NCMacroDsl.g4
 by ANTLR 4.9.1
+package org.apache.nlpcraft.common.makro.antlr4;
+import org.antlr.v4.runtime.atn.*;
+import org.antlr.v4.runtime.dfa.DFA;
+import org.antlr.v4.runtime.*;
+import org.antlr.v4.runtime.misc.*;
+import org.antlr.v4.runtime.tree.*;
+import java.util.List;
+import java.util.Iterator;
+import java.util.ArrayList;
+
+@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
+public class NCMacroDslParser extends Parser {
+       static { RuntimeMetaData.checkVersion("4.9.1", 
RuntimeMetaData.VERSION); }
+
+       protected static final DFA[] _decisionToDFA;
+       protected static final PredictionContextCache _sharedContextCache =
+               new PredictionContextCache();
+       public static final int
+               LCURLY=1, RCURLY=2, VERT=3, COMMA=4, UNDERSCORE=5, LBR=6, 
RBR=7, QUESTION=8, 
+               REGEX_TXT=9, IDL_TXT=10, TXT=11, MINMAX=12, WS=13, ERR_CHAR=14;
+       public static final int
+               RULE_makro = 0, RULE_expr = 1, RULE_item = 2, RULE_syn = 3, 
RULE_group = 4, 
+               RULE_list = 5, RULE_minMax = 6, RULE_minMaxShortcut = 7;
+       private static String[] makeRuleNames() {
+               return new String[] {
+                       "makro", "expr", "item", "syn", "group", "list", 
"minMax", "minMaxShortcut"
+               };
+       }
+       public static final String[] ruleNames = makeRuleNames();
+
+       private static String[] makeLiteralNames() {
+               return new String[] {
+                       null, "'{'", "'}'", "'|'", "','", "'_'", "'['", "']'", 
"'?'"
+               };
+       }
+       private static final String[] _LITERAL_NAMES = makeLiteralNames();
+       private static String[] makeSymbolicNames() {
+               return new String[] {
+                       null, "LCURLY", "RCURLY", "VERT", "COMMA", 
"UNDERSCORE", "LBR", "RBR", 
+                       "QUESTION", "REGEX_TXT", "IDL_TXT", "TXT", "MINMAX", 
"WS", "ERR_CHAR"
+               };
+       }
+       private static final String[] _SYMBOLIC_NAMES = makeSymbolicNames();
+       public static final Vocabulary VOCABULARY = new 
VocabularyImpl(_LITERAL_NAMES, _SYMBOLIC_NAMES);
+
+       /**
+        * @deprecated Use {@link #VOCABULARY} instead.
+        */
+       @Deprecated
+       public static final String[] tokenNames;
+       static {
+               tokenNames = new String[_SYMBOLIC_NAMES.length];
+               for (int i = 0; i < tokenNames.length; i++) {
+                       tokenNames[i] = VOCABULARY.getLiteralName(i);
+                       if (tokenNames[i] == null) {
+                               tokenNames[i] = VOCABULARY.getSymbolicName(i);
+                       }
+
+                       if (tokenNames[i] == null) {
+                               tokenNames[i] = "<INVALID>";
+                       }
+               }
+       }
+
+       @Override
+       @Deprecated
+       public String[] getTokenNames() {
+               return tokenNames;
+       }
+
+       @Override
+
+       public Vocabulary getVocabulary() {
+               return VOCABULARY;
+       }
+
+       @Override
+       public String getGrammarFileName() { return "NCMacroDsl.g4"; }
+
+       @Override
+       public String[] getRuleNames() { return ruleNames; }
+
+       @Override
+       public String getSerializedATN() { return _serializedATN; }
+
+       @Override
+       public ATN getATN() { return _ATN; }
+
+       public NCMacroDslParser(TokenStream input) {
+               super(input);
+               _interp = new 
ParserATNSimulator(this,_ATN,_decisionToDFA,_sharedContextCache);
+       }
+
+       public static class MakroContext extends ParserRuleContext {
+               public ExprContext expr() {
+                       return getRuleContext(ExprContext.class,0);
+               }
+               public TerminalNode EOF() { return 
getToken(NCMacroDslParser.EOF, 0); }
+               public MakroContext(ParserRuleContext parent, int 
invokingState) {
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_makro; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterMakro(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitMakro(this);
+               }
+       }
+
+       public final MakroContext makro() throws RecognitionException {
+               MakroContext _localctx = new MakroContext(_ctx, getState());
+               enterRule(_localctx, 0, RULE_makro);
+               try {
+                       enterOuterAlt(_localctx, 1);
+                       {
+                       setState(16);
+                       expr(0);
+                       setState(17);
+                       match(EOF);
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       exitRule();
+               }
+               return _localctx;
+       }
+
+       public static class ExprContext extends ParserRuleContext {
+               public ItemContext item() {
+                       return getRuleContext(ItemContext.class,0);
+               }
+               public ExprContext expr() {
+                       return getRuleContext(ExprContext.class,0);
+               }
+               public ExprContext(ParserRuleContext parent, int invokingState) 
{
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_expr; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterExpr(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitExpr(this);
+               }
+       }
+
+       public final ExprContext expr() throws RecognitionException {
+               return expr(0);
+       }
+
+       private ExprContext expr(int _p) throws RecognitionException {
+               ParserRuleContext _parentctx = _ctx;
+               int _parentState = getState();
+               ExprContext _localctx = new ExprContext(_ctx, _parentState);
+               ExprContext _prevctx = _localctx;
+               int _startState = 2;
+               enterRecursionRule(_localctx, 2, RULE_expr, _p);
+               try {
+                       int _alt;
+                       enterOuterAlt(_localctx, 1);
+                       {
+                       {
+                       setState(20);
+                       item();
+                       }
+                       _ctx.stop = _input.LT(-1);
+                       setState(26);
+                       _errHandler.sync(this);
+                       _alt = getInterpreter().adaptivePredict(_input,0,_ctx);
+                       while ( _alt!=2 && 
_alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+                               if ( _alt==1 ) {
+                                       if ( _parseListeners!=null ) 
triggerExitRuleEvent();
+                                       _prevctx = _localctx;
+                                       {
+                                       {
+                                       _localctx = new ExprContext(_parentctx, 
_parentState);
+                                       pushNewRecursionContext(_localctx, 
_startState, RULE_expr);
+                                       setState(22);
+                                       if (!(precpred(_ctx, 1))) throw new 
FailedPredicateException(this, "precpred(_ctx, 1)");
+                                       setState(23);
+                                       item();
+                                       }
+                                       } 
+                               }
+                               setState(28);
+                               _errHandler.sync(this);
+                               _alt = 
getInterpreter().adaptivePredict(_input,0,_ctx);
+                       }
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       unrollRecursionContexts(_parentctx);
+               }
+               return _localctx;
+       }
+
+       public static class ItemContext extends ParserRuleContext {
+               public SynContext syn() {
+                       return getRuleContext(SynContext.class,0);
+               }
+               public GroupContext group() {
+                       return getRuleContext(GroupContext.class,0);
+               }
+               public ItemContext(ParserRuleContext parent, int invokingState) 
{
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_item; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterItem(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitItem(this);
+               }
+       }
+
+       public final ItemContext item() throws RecognitionException {
+               ItemContext _localctx = new ItemContext(_ctx, getState());
+               enterRule(_localctx, 4, RULE_item);
+               try {
+                       setState(31);
+                       _errHandler.sync(this);
+                       switch (_input.LA(1)) {
+                       case REGEX_TXT:
+                       case IDL_TXT:
+                       case TXT:
+                               enterOuterAlt(_localctx, 1);
+                               {
+                               setState(29);
+                               syn();
+                               }
+                               break;
+                       case LCURLY:
+                               enterOuterAlt(_localctx, 2);
+                               {
+                               setState(30);
+                               group();
+                               }
+                               break;
+                       default:
+                               throw new NoViableAltException(this);
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       exitRule();
+               }
+               return _localctx;
+       }
+
+       public static class SynContext extends ParserRuleContext {
+               public TerminalNode TXT() { return 
getToken(NCMacroDslParser.TXT, 0); }
+               public TerminalNode REGEX_TXT() { return 
getToken(NCMacroDslParser.REGEX_TXT, 0); }
+               public TerminalNode IDL_TXT() { return 
getToken(NCMacroDslParser.IDL_TXT, 0); }
+               public SynContext(ParserRuleContext parent, int invokingState) {
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_syn; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterSyn(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitSyn(this);
+               }
+       }
+
+       public final SynContext syn() throws RecognitionException {
+               SynContext _localctx = new SynContext(_ctx, getState());
+               enterRule(_localctx, 6, RULE_syn);
+               int _la;
+               try {
+                       enterOuterAlt(_localctx, 1);
+                       {
+                       setState(33);
+                       _la = _input.LA(1);
+                       if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & ((1L << 
REGEX_TXT) | (1L << IDL_TXT) | (1L << TXT))) != 0)) ) {
+                       _errHandler.recoverInline(this);
+                       }
+                       else {
+                               if ( _input.LA(1)==Token.EOF ) matchedEOF = 
true;
+                               _errHandler.reportMatch(this);
+                               consume();
+                       }
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       exitRule();
+               }
+               return _localctx;
+       }
+
+       public static class GroupContext extends ParserRuleContext {
+               public TerminalNode LCURLY() { return 
getToken(NCMacroDslParser.LCURLY, 0); }
+               public ListContext list() {
+                       return getRuleContext(ListContext.class,0);
+               }
+               public TerminalNode RCURLY() { return 
getToken(NCMacroDslParser.RCURLY, 0); }
+               public MinMaxContext minMax() {
+                       return getRuleContext(MinMaxContext.class,0);
+               }
+               public GroupContext(ParserRuleContext parent, int 
invokingState) {
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_group; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterGroup(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitGroup(this);
+               }
+       }
+
+       public final GroupContext group() throws RecognitionException {
+               GroupContext _localctx = new GroupContext(_ctx, getState());
+               enterRule(_localctx, 8, RULE_group);
+               try {
+                       enterOuterAlt(_localctx, 1);
+                       {
+                       setState(35);
+                       match(LCURLY);
+                       setState(36);
+                       list(0);
+                       setState(37);
+                       match(RCURLY);
+                       setState(39);
+                       _errHandler.sync(this);
+                       switch ( 
getInterpreter().adaptivePredict(_input,2,_ctx) ) {
+                       case 1:
+                               {
+                               setState(38);
+                               minMax();
+                               }
+                               break;
+                       }
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       exitRule();
+               }
+               return _localctx;
+       }
+
+       public static class ListContext extends ParserRuleContext {
+               public ExprContext expr() {
+                       return getRuleContext(ExprContext.class,0);
+               }
+               public TerminalNode UNDERSCORE() { return 
getToken(NCMacroDslParser.UNDERSCORE, 0); }
+               public TerminalNode VERT() { return 
getToken(NCMacroDslParser.VERT, 0); }
+               public ListContext list() {
+                       return getRuleContext(ListContext.class,0);
+               }
+               public ListContext(ParserRuleContext parent, int invokingState) 
{
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_list; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterList(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitList(this);
+               }
+       }
+
+       public final ListContext list() throws RecognitionException {
+               return list(0);
+       }
+
+       private ListContext list(int _p) throws RecognitionException {
+               ParserRuleContext _parentctx = _ctx;
+               int _parentState = getState();
+               ListContext _localctx = new ListContext(_ctx, _parentState);
+               ListContext _prevctx = _localctx;
+               int _startState = 10;
+               enterRecursionRule(_localctx, 10, RULE_list, _p);
+               try {
+                       int _alt;
+                       enterOuterAlt(_localctx, 1);
+                       {
+                       setState(46);
+                       _errHandler.sync(this);
+                       switch (_input.LA(1)) {
+                       case LCURLY:
+                       case REGEX_TXT:
+                       case IDL_TXT:
+                       case TXT:
+                               {
+                               setState(42);
+                               expr(0);
+                               }
+                               break;
+                       case UNDERSCORE:
+                               {
+                               setState(43);
+                               match(UNDERSCORE);
+                               setState(44);
+                               match(VERT);
+                               setState(45);
+                               list(1);
+                               }
+                               break;
+                       default:
+                               throw new NoViableAltException(this);
+                       }
+                       _ctx.stop = _input.LT(-1);
+                       setState(56);
+                       _errHandler.sync(this);
+                       _alt = getInterpreter().adaptivePredict(_input,5,_ctx);
+                       while ( _alt!=2 && 
_alt!=org.antlr.v4.runtime.atn.ATN.INVALID_ALT_NUMBER ) {
+                               if ( _alt==1 ) {
+                                       if ( _parseListeners!=null ) 
triggerExitRuleEvent();
+                                       _prevctx = _localctx;
+                                       {
+                                       setState(54);
+                                       _errHandler.sync(this);
+                                       switch ( 
getInterpreter().adaptivePredict(_input,4,_ctx) ) {
+                                       case 1:
+                                               {
+                                               _localctx = new 
ListContext(_parentctx, _parentState);
+                                               
pushNewRecursionContext(_localctx, _startState, RULE_list);
+                                               setState(48);
+                                               if (!(precpred(_ctx, 3))) throw 
new FailedPredicateException(this, "precpred(_ctx, 3)");
+                                               setState(49);
+                                               match(VERT);
+                                               setState(50);
+                                               expr(0);
+                                               }
+                                               break;
+                                       case 2:
+                                               {
+                                               _localctx = new 
ListContext(_parentctx, _parentState);
+                                               
pushNewRecursionContext(_localctx, _startState, RULE_list);
+                                               setState(51);
+                                               if (!(precpred(_ctx, 2))) throw 
new FailedPredicateException(this, "precpred(_ctx, 2)");
+                                               setState(52);
+                                               match(VERT);
+                                               setState(53);
+                                               match(UNDERSCORE);
+                                               }
+                                               break;
+                                       }
+                                       } 
+                               }
+                               setState(58);
+                               _errHandler.sync(this);
+                               _alt = 
getInterpreter().adaptivePredict(_input,5,_ctx);
+                       }
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       unrollRecursionContexts(_parentctx);
+               }
+               return _localctx;
+       }
+
+       public static class MinMaxContext extends ParserRuleContext {
+               public MinMaxShortcutContext minMaxShortcut() {
+                       return getRuleContext(MinMaxShortcutContext.class,0);
+               }
+               public TerminalNode MINMAX() { return 
getToken(NCMacroDslParser.MINMAX, 0); }
+               public MinMaxContext(ParserRuleContext parent, int 
invokingState) {
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return RULE_minMax; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterMinMax(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitMinMax(this);
+               }
+       }
+
+       public final MinMaxContext minMax() throws RecognitionException {
+               MinMaxContext _localctx = new MinMaxContext(_ctx, getState());
+               enterRule(_localctx, 12, RULE_minMax);
+               try {
+                       setState(61);
+                       _errHandler.sync(this);
+                       switch (_input.LA(1)) {
+                       case QUESTION:
+                               enterOuterAlt(_localctx, 1);
+                               {
+                               setState(59);
+                               minMaxShortcut();
+                               }
+                               break;
+                       case MINMAX:
+                               enterOuterAlt(_localctx, 2);
+                               {
+                               setState(60);
+                               match(MINMAX);
+                               }
+                               break;
+                       default:
+                               throw new NoViableAltException(this);
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       exitRule();
+               }
+               return _localctx;
+       }
+
+       public static class MinMaxShortcutContext extends ParserRuleContext {
+               public TerminalNode QUESTION() { return 
getToken(NCMacroDslParser.QUESTION, 0); }
+               public MinMaxShortcutContext(ParserRuleContext parent, int 
invokingState) {
+                       super(parent, invokingState);
+               }
+               @Override public int getRuleIndex() { return 
RULE_minMaxShortcut; }
+               @Override
+               public void enterRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).enterMinMaxShortcut(this);
+               }
+               @Override
+               public void exitRule(ParseTreeListener listener) {
+                       if ( listener instanceof NCMacroDslListener ) 
((NCMacroDslListener)listener).exitMinMaxShortcut(this);
+               }
+       }
+
+       public final MinMaxShortcutContext minMaxShortcut() throws 
RecognitionException {
+               MinMaxShortcutContext _localctx = new 
MinMaxShortcutContext(_ctx, getState());
+               enterRule(_localctx, 14, RULE_minMaxShortcut);
+               try {
+                       enterOuterAlt(_localctx, 1);
+                       {
+                       setState(63);
+                       match(QUESTION);
+                       }
+               }
+               catch (RecognitionException re) {
+                       _localctx.exception = re;
+                       _errHandler.reportError(this, re);
+                       _errHandler.recover(this, re);
+               }
+               finally {
+                       exitRule();
+               }
+               return _localctx;
+       }
+
+       public boolean sempred(RuleContext _localctx, int ruleIndex, int 
predIndex) {
+               switch (ruleIndex) {
+               case 1:
+                       return expr_sempred((ExprContext)_localctx, predIndex);
+               case 5:
+                       return list_sempred((ListContext)_localctx, predIndex);
+               }
+               return true;
+       }
+       private boolean expr_sempred(ExprContext _localctx, int predIndex) {
+               switch (predIndex) {
+               case 0:
+                       return precpred(_ctx, 1);
+               }
+               return true;
+       }
+       private boolean list_sempred(ListContext _localctx, int predIndex) {
+               switch (predIndex) {
+               case 1:
+                       return precpred(_ctx, 3);
+               case 2:
+                       return precpred(_ctx, 2);
+               }
+               return true;
+       }
+
+       public static final String _serializedATN =
+               
"\3\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786\u5964\3\20D\4\2\t\2\4\3\t"+
+               
"\3\4\4\t\4\4\5\t\5\4\6\t\6\4\7\t\7\4\b\t\b\4\t\t\t\3\2\3\2\3\2\3\3\3\3"+
+               
"\3\3\3\3\3\3\7\3\33\n\3\f\3\16\3\36\13\3\3\4\3\4\5\4\"\n\4\3\5\3\5\3\6"+
+               
"\3\6\3\6\3\6\5\6*\n\6\3\7\3\7\3\7\3\7\3\7\5\7\61\n\7\3\7\3\7\3\7\3\7\3"+
+               
"\7\3\7\7\79\n\7\f\7\16\7<\13\7\3\b\3\b\5\b@\n\b\3\t\3\t\3\t\2\4\4\f\n"+
+               
"\2\4\6\b\n\f\16\20\2\3\3\2\13\r\2B\2\22\3\2\2\2\4\25\3\2\2\2\6!\3\2\2"+
+               
"\2\b#\3\2\2\2\n%\3\2\2\2\f\60\3\2\2\2\16?\3\2\2\2\20A\3\2\2\2\22\23\5"+
+               
"\4\3\2\23\24\7\2\2\3\24\3\3\2\2\2\25\26\b\3\1\2\26\27\5\6\4\2\27\34\3"+
+               
"\2\2\2\30\31\f\3\2\2\31\33\5\6\4\2\32\30\3\2\2\2\33\36\3\2\2\2\34\32\3"+
+               "\2\2\2\34\35\3\2\2\2\35\5\3\2\2\2\36\34\3\2\2\2\37\"\5\b\5\2 
\"\5\n\6"+
+               "\2!\37\3\2\2\2! 
\3\2\2\2\"\7\3\2\2\2#$\t\2\2\2$\t\3\2\2\2%&\7\3\2\2&\'"+
+               
"\5\f\7\2\')\7\4\2\2(*\5\16\b\2)(\3\2\2\2)*\3\2\2\2*\13\3\2\2\2+,\b\7\1"+
+               
"\2,\61\5\4\3\2-.\7\7\2\2./\7\5\2\2/\61\5\f\7\3\60+\3\2\2\2\60-\3\2\2\2"+
+               
"\61:\3\2\2\2\62\63\f\5\2\2\63\64\7\5\2\2\649\5\4\3\2\65\66\f\4\2\2\66"+
+               
"\67\7\5\2\2\679\7\7\2\28\62\3\2\2\28\65\3\2\2\29<\3\2\2\2:8\3\2\2\2:;"+
+               
"\3\2\2\2;\r\3\2\2\2<:\3\2\2\2=@\5\20\t\2>@\7\16\2\2?=\3\2\2\2?>\3\2\2"+
+               "\2@\17\3\2\2\2AB\7\n\2\2B\21\3\2\2\2\t\34!)\608:?";
+       public static final ATN _ATN =
+               new ATNDeserializer().deserialize(_serializedATN.toCharArray());
+       static {
+               _decisionToDFA = new DFA[_ATN.getNumberOfDecisions()];
+               for (int i = 0; i < _ATN.getNumberOfDecisions(); i++) {
+                       _decisionToDFA[i] = new DFA(_ATN.getDecisionState(i), 
i);
+               }
+       }
+}
\ No newline at end of file
diff --git 
a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala 
b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
index f9d8385..479ebb5 100644
--- a/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
+++ b/nlpcraft/src/main/scala/org/apache/nlpcraft/common/util/NCUtils.scala
@@ -644,4 +644,18 @@ object NCUtils extends LazyLogging:
         if a != null then
             ignoring(classOf[Exception]) {
                 a.close()
-            }
\ No newline at end of file
+            }
+
+    /**
+      *
+      * @param s
+      * @return
+      */
+    def decapitalize(s: String): String = s"${s.head.toLower}${s.tail}"
+
+    /**
+      *
+      * @param s
+      * @return
+      */
+    def capitalize(s: String): String = s"${s.head.toUpper}${s.tail}"
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 25713ab..9f93030 100644
--- a/pom.xml
+++ b/pom.xml
@@ -96,6 +96,7 @@
         <maven.install.plugin.ver>2.5.2</maven.install.plugin.ver>
         <maven.source.plugin.ver>3.0.1</maven.source.plugin.ver>
         <maven.clean.plugin.ver>3.1.0</maven.clean.plugin.ver>
+        <org.antlr4.ver>4.9</org.antlr4.ver>
         <jline.ver>3.20.0</jline.ver>
         <commons.lang3.ver>3.12.0</commons.lang3.ver>
         <scala3.ref.ver>1.0.0</scala3.ref.ver>
@@ -148,6 +149,17 @@
             </dependency>
 
             <!--
+             Other dependencies.
+             ==================
+            -->
+
+            <dependency>
+                <groupId>org.antlr</groupId>
+                <artifactId>antlr4-runtime</artifactId>
+                <version>${org.antlr4.ver}</version>
+            </dependency>
+
+            <!--
              JLine dependencies.
              ==================
             -->

Reply via email to