This is an automated email from the ASF dual-hosted git repository.
sergeykamov 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 91a2fbe Annotations scanner related fixes.
91a2fbe is described below
commit 91a2fbe00fb1d7bd239d9b55d6e01057b6f94c06
Author: Sergey Kamov <[email protected]>
AuthorDate: Sun Jan 30 11:33:37 2022 +0300
Annotations scanner related fixes.
---
.../nlpcraft/internal/antlr4/NCCompilerUtils.scala | 19 +-
.../nlpcraft/internal/impl/NCModelScanner.scala | 482 +++++++++------------
.../impl/scan/NCModelIntentsInvalidArgsSpec.scala | 6 +-
.../scan/NCModelIntentsInvalidIntentsSpec.scala | 124 ++++++
.../impl/scan/NCModelIntentsNestedSpec.scala | 8 +-
.../internal/impl/scan/NCModelIntentsSpec.scala | 2 +-
.../intent/compiler/NCIDLCompilerSpec.scala | 16 +-
7 files changed, 362 insertions(+), 295 deletions(-)
diff --git
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/antlr4/NCCompilerUtils.scala
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/antlr4/NCCompilerUtils.scala
index 540bd0f..e301c2a 100644
---
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/antlr4/NCCompilerUtils.scala
+++
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/antlr4/NCCompilerUtils.scala
@@ -32,14 +32,17 @@ object NCCompilerUtils:
* @return
*/
def mkErrorHolder(in: String, charPos: Int): CompilerErrorHolder =
- val charPos0 = charPos - (in.length - in.stripLeading().length)
val in0 = in.strip()
- val pos = Math.max(0, charPos0)
- val dash = "-" * in0.length
- var ptrStr = s"${dash.substring(0, pos)}^"
- if pos < dash.length - 1 then ptrStr = s"$ptrStr${dash.substring(pos +
1)}"
- else ptrStr = s"$ptrStr${dash.substring(pos + 1)}"
+ if in0.isEmpty || charPos < 0 then CompilerErrorHolder("<empty>",
"<empty>")
+ else
+ val charPos0 = charPos - (in.length - in.stripLeading().length)
+ val pos = Math.max(0, charPos0)
+ val dash = "-" * in0.length
- val origStr = s"${in0.substring(0,
pos)}${in0.charAt(pos)}${in0.substring(pos + 1)}"
- CompilerErrorHolder(ptrStr, origStr)
+ var ptrStr = s"${dash.substring(0, pos)}^"
+ if pos < dash.length - 1 then ptrStr =
s"$ptrStr${dash.substring(pos + 1)}"
+ else ptrStr = s"$ptrStr${dash.substring(pos + 1)}"
+
+ val origStr = s"${in0.substring(0,
pos)}${in0.charAt(pos)}${in0.substring(pos + 1)}"
+ CompilerErrorHolder(ptrStr, origStr)
diff --git
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
index dbc0839..7b37671 100644
---
a/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
+++
b/nlpcraft/src/main/scala/org/apache/nlpcraft/internal/impl/NCModelScanner.scala
@@ -45,11 +45,9 @@ import scala.util.Using
case class NCModelIntent(intent: NCIDLIntent, function: NCIntentMatch =>
NCResult, samples: Seq[Seq[String]])
/**
- * TODO: common comment for annotations usage. Do not apply annotations for
unused private methods and fields. Compiler can drop them.
+ *
*/
object NCModelScanner extends LazyLogging:
- private final val SEPARATORS = Seq('?', ',', '.', '-', '!')
-
private final val CLS_INTENT = classOf[NCIntent]
private final val CLS_INTENT_REF = classOf[NCIntentRef]
private final val CLS_QRY_RES = classOf[NCResult]
@@ -67,6 +65,12 @@ object NCModelScanner extends LazyLogging:
private final val CLS_ENTITY = classOf[NCEntity]
+ private lazy val I = "@NCIntent"
+ private lazy val IT = "@NCIntentTerm"
+ private lazy val IR = "@NCIntentRef"
+ private lazy val S = "@NCIntentSample"
+ private lazy val SR = "@NCIntentSampleRef"
+
private final val COMP_CLS: Set[Class[_]] = Set(
CLS_SCALA_SEQ,
CLS_SCALA_LST,
@@ -75,15 +79,20 @@ object NCModelScanner extends LazyLogging:
CLS_JAVA_OPT
)
- private case class CallbackHolder(method: Method, function: NCIntentMatch
=> NCResult)
+ /**
+ *
+ * @param intent
+ * @param function
+ * @param method
+ */
private case class IntentHolder(intent: NCIDLIntent, function:
NCIntentMatch => NCResult, method: Method)
/**
*
- * @param cls
- * @return
*/
- private def class2Str(cls: Class[_]): String = if cls == null then "null"
else s"'${cls.getSimpleName}'"
+ private object IntentHolder:
+ def apply(cfg: NCModelConfig, intent: NCIDLIntent, obj: AnyRef, mtd:
Method): IntentHolder =
+ new IntentHolder(intent, prepareCallback(cfg, mtd, obj, intent),
mtd)
/**
*
@@ -94,10 +103,17 @@ object NCModelScanner extends LazyLogging:
/**
*
+ * @param iter
+ * @return
+ */
+ private def col2Str(iter: Iterable[_]): String = iter.mkString("(", ",",
")")
+
+ /**
+ *
* @param clazz
* @return
*/
- private def getClassName(clazz: Class[_]): String =
+ private def class2Str(clazz: Class[_]): String =
val cls = clazz.getSimpleName.strip
// Anonymous classes (like `class foo.bar.name$1`) doesn't have simple
names.
if cls.nonEmpty then cls else clazz.getName.reverse.takeWhile(_ !=
'.').reverse
@@ -108,10 +124,9 @@ object NCModelScanner extends LazyLogging:
* @return
*/
private def method2Str(mtd: Method): String =
- val cls = getClassName(mtd.getDeclaringClass)
+ val cls = class2Str(mtd.getDeclaringClass)
val name = mtd.getName
val args = mtd.getParameters.map(_.getType.getSimpleName).mkString(",
")
-
s"$cls#$name($args)"
/**
@@ -120,30 +135,29 @@ object NCModelScanner extends LazyLogging:
* @return
*/
private def field2Str(f: Field): String =
- val cls = getClassName(f.getDeclaringClass)
+ val cls = class2Str(f.getDeclaringClass)
val name = f.getName
-
s"$cls.$name"
/**
*
- * @param mdlId
+ * @param cfg
* @param mtd
- * @param paramClss
+ * @param prmClss
* @param argsList
- * @param ctxFirstParam
+ * @param ctxFirstPrm
* @return
*/
- private def prepareParams(mdlId: String, mtd: Method, paramClss:
Seq[Class[_]], argsList: Seq[util.List[NCEntity]], ctxFirstParam: Boolean):
Seq[AnyRef] =
- paramClss.zip(argsList).zipWithIndex.map { case ((paramCls, argList),
i) =>
- def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
+ private def prepareParams(cfg: NCModelConfig, mtd: Method, prmClss:
Seq[Class[_]], argsList: Seq[util.List[NCEntity]], ctxFirstPrm: Boolean):
Seq[AnyRef] =
+ prmClss.zip(argsList).zipWithIndex.map { case ((paramCls, argList), i)
=>
+ def mkArg(): String = arg2Str(mtd, i, ctxFirstPrm)
+ lazy val z = s"mdlId=${cfg.getId}, type=$paramCls, arg=${mkArg()}"
val entsCnt = argList.size()
// Single entity.
if paramCls == CLS_ENTITY then
- if entsCnt != 1 then
- E(s"Expected single entity (found $entsCnt) in
@NCIntentTerm annotated argument [mdlId=$mdlId, arg=${mkArg()}]")
+ if entsCnt != 1 then E(s"Expected single entity (found
$entsCnt) in $IT annotated argument [$z]")
argList.get(0)
// Array of entities.
@@ -161,89 +175,76 @@ object NCModelScanner extends LazyLogging:
entsCnt match
case 0 => None
case 1 => Option(argList.get(0))
- case _ => E(s"Too many entities ($entsCnt) for
scala.Option[_] @NCIntentTerm annotated argument [mdlId$mdlId, arg=${mkArg()}]")
+ case _ => E(s"Too many entities ($entsCnt) for
'scala.Option[_]' $IT annotated argument [$z]")
else if paramCls == CLS_JAVA_OPT then
entsCnt match
case 0 => util.Optional.empty()
case 1 => util.Optional.of(argList.get(0))
- case _ => E(s"Too many entities ($entsCnt) for
java.util.Optional @NCIntentTerm annotated argument [mdlId$mdlId,
arg=${mkArg()}]")
+ case _ => E(s"Too many entities ($entsCnt) for
'java.util.Optional' $IT annotated argument [$z]")
else
// All allowed arguments types already checked.
- throw new AssertionError(s"Unexpected callback @NCIntentTerm
argument type [mdlId=$mdlId, type=$paramCls, arg=${mkArg()}]")
+ throw new AssertionError(s"Unexpected type for callback's $IT
argument [$z]")
}
/**
*
- * @param mdlId
- * @param method
+ * @param cfg
+ * @param mtd
* @param obj
* @param args
* @return
*/
- private def invoke(mdlId: String, method: Method, obj: Object, args:
scala.Array[AnyRef]): NCResult =
- val methodObj = if Modifier.isStatic(method.getModifiers) then null
else obj
- var flag = method.canAccess(methodObj)
-
+ private def invoke(cfg: NCModelConfig, mtd: Method, obj: AnyRef, args:
scala.Array[AnyRef]): NCResult =
+ val methodObj = if Modifier.isStatic(mtd.getModifiers) then null else
obj
+ var flag = mtd.canAccess(methodObj)
+ lazy val z = s"mdlId=${cfg.getId}, callback=${method2Str(mtd)}"
try
if !flag then
- method.setAccessible(true)
-
+ mtd.setAccessible(true)
flag = true
else
flag = false
-
- method.invoke(methodObj, args: _*).asInstanceOf[NCResult]
+ mtd.invoke(methodObj, args: _*).asInstanceOf[NCResult]
catch
case e: InvocationTargetException =>
e.getTargetException match
case cause: NCIntentSkip => throw cause
case cause: NCRejection => throw cause
case cause: NCException => throw cause
- case cause: Throwable => E(s"Intent callback invocation
error [mdlId=$mdlId, callback=${method2Str(method)}]", cause)
-
- case e: Throwable => E(s"Unexpected intent callback invocation
error [mdlId=$mdlId, callback=${method2Str(method)}]", e)
+ case cause: Throwable => E(s"Intent callback invocation
error [$z]", cause)
+ case e: Throwable => E(s"Unexpected intent callback invocation
error [$z]", e)
finally
if flag then
- try
- method.setAccessible(false)
- catch
- case e: SecurityException => E(s"Access or security error
in intent callback [mdlId=$mdlId, callback=${method2Str(method)}]", e)
+ try mtd.setAccessible(false)
+ catch case e: SecurityException => E(s"Access or security
error in intent callback [$z]", e)
/**
*
- * @param mdlId
+ * @param cfg
* @param field
* @param obj
* @return
*/
- private def getFieldObject(mdlId: String, field: Field, obj: Object):
Object =
+ private def getFieldObject(cfg: NCModelConfig, field: Field, obj: AnyRef):
AnyRef =
lazy val fStr = field2Str(field)
val fieldObj = if Modifier.isStatic(field.getModifiers) then null else
obj
var flag = field.canAccess(fieldObj)
+ lazy val z = s"mdlId=${cfg.getId}, field=$fStr"
val res =
try
if !flag then
field.setAccessible(true)
-
flag = true
else
flag = false
-
field.get(fieldObj)
- catch
- // TODO: text
- case e: Throwable => E(s"Unexpected field access error
[mdlId=$mdlId, field=$fStr]", e)
+ catch case e: Throwable => E(s"Unexpected field access error
[$z]", e)
finally
if flag then
- try
- field.setAccessible(false)
- catch
- // TODO: text
- case e: SecurityException => E(s"Access or security
error in field [mdlId=$mdlId, field=$fStr]", e)
-
- if res == null then
- throw new NCException(s"Value is null for: $fStr") // TODO: text
+ try field.setAccessible(false)
+ catch case e: SecurityException => E(s"Access or security
error in field [$z]", e)
+ if res == null then throw new NCException(s"Field value cannot be
'null' for [$z]")
res
/**
@@ -262,8 +263,8 @@ object NCModelScanner extends LazyLogging:
* @return Methods.
*/
private def getAllMethods(o: AnyRef): Set[Method] =
- val claxx = o.getClass
- (claxx.getDeclaredMethods ++ claxx.getMethods).toSet
+ val cls = o.getClass
+ (cls.getDeclaredMethods ++ cls.getMethods).toSet
/**
* Gets its own fields including private and accessible from parents.
@@ -272,35 +273,62 @@ object NCModelScanner extends LazyLogging:
* @return Fields.
*/
private def getAllFields(o: AnyRef): Set[Field] =
- val claxx = o.getClass
- (claxx.getDeclaredFields ++ claxx.getFields).toSet
+ val cls = o.getClass
+ (cls.getDeclaredFields ++ cls.getFields).toSet
-import org.apache.nlpcraft.internal.impl.NCModelScanner.*
+ /**
+ *
+ * @param cfg
+ * @param mtd
+ * @return
+ */
+ private def scanSamples(cfg: NCModelConfig, mtd: Method): Map[String,
Seq[Seq[String]]] =
+ val smpAnns = mtd.getAnnotationsByType(CLS_SAMPLE)
+ val smpAnnsRef = mtd.getAnnotationsByType(CLS_SAMPLE_REF)
+ lazy val mtdStr = method2Str(mtd)
+ lazy val intAnns = mtd.getAnnotationsByType(CLS_INTENT)
+ lazy val refAnns = mtd.getAnnotationsByType(CLS_INTENT_REF)
+ lazy val samples = mutable.HashMap.empty[String, Seq[Seq[String]]]
-/**
- *
- * @param mdl
- */
-class NCModelScanner(mdl: NCModel) extends LazyLogging:
- require(mdl != null)
+ if smpAnns.nonEmpty || smpAnnsRef.nonEmpty then
+ if intAnns.isEmpty && refAnns.isEmpty then
+ E(s"$S or $SR annotations without corresponding $I or $IR
annotations: $mtdStr")
+ else
+ def read[T](annArr: scala.Array[T], annName: String,
getSamples: T => Seq[String], getSource: Option[T => String]): Seq[Seq[String]]
=
+ for (ann <- annArr.toSeq) yield
+ val samples = getSamples(ann).map(_.strip).filter(s =>
s.nonEmpty && s.head != '#')
+ if samples.isEmpty then
+ getSource match
+ case None => logger.warn(s"$annName annotation
has no samples: $mtdStr")
+ case Some(f) => logger.warn(s"$annName
annotation references '${f(ann)}' file that has no samples: $mtdStr")
+ Seq.empty
+ else
+ samples.filter(_.nonEmpty)
- private final val cfg = mdl.getConfig
- private final val mdlId = cfg.getId
- private final val origin = cfg.getOrigin
+ val seqSeq =
+ read[NCIntentSample](smpAnns, S, _.value.toSeq, None) ++
+ read[NCIntentSampleRef](smpAnnsRef, SR, a =>
NCUtils.readResource(a.value), Option(_.value))
+
+ if NCUtils.containsDups(seqSeq.flatMap(_.toSeq).toList) then
+ logger.warn(s"$S and $SR annotations have duplicates:
$mtdStr")
+
+ val distinct = seqSeq.map(_.distinct).distinct
+ for (ann <- intAnns; intent <-
NCIDLCompiler.compile(ann.value, cfg, mtdStr)) samples += intent.id -> distinct
+ for (ann <- refAnns) samples += ann.value -> distinct
+ else if intAnns.nonEmpty || refAnns.nonEmpty then
+ logger.warn(s"$S or $SR annotations are missing for: $mtdStr")
- private final val intents = mutable.Buffer.empty[IntentHolder]
- private final val intentDecls = mutable.Buffer.empty[NCIDLIntent]
- private final val objs = mutable.Buffer.empty[Object]
- private final val samples = mutable.HashMap.empty[Method, Map[String,
Seq[Seq[String]]]]
+ samples.toMap
/**
*
+ * @param cfg
* @param mtd
* @param argClasses
* @param paramGenTypes
* @param ctxFirstParam
*/
- private def checkTypes(mtd: Method, argClasses: Seq[Class[_]],
paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
+ private def checkTypes(cfg: NCModelConfig, mtd: Method, argClasses:
Seq[Class[_]], paramGenTypes: Seq[Type], ctxFirstParam: Boolean): Unit =
require(argClasses.sizeIs == paramGenTypes.length)
var warned = false
@@ -308,155 +336,95 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
argClasses.zip(paramGenTypes).zipWithIndex.foreach { case ((argClass,
paramGenType), i) =>
def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
+ lazy val z = s"mdlId=${cfg.getId}, type=${class2Str(argClass)},
arg=${mkArg()}"
+
// Entity.
if argClass == CLS_ENTITY then () // No-op.
else if argClass.isArray then
val compType = argClass.getComponentType
-
if compType != CLS_ENTITY then
- E(s"Unexpected array element type for @NCIntentTerm
annotated argument [mdlId=$mdlId, origin=$origin, type=${class2Str(compType)},
arg=${mkArg()}]")
- // Entities collection and optionals.
+ E(s"Unexpected array element type for $IT annotated
argument [$z]")
+ // Entities collection and optionals.
else if COMP_CLS.contains(argClass) then
paramGenType match
case pt: ParameterizedType =>
val actTypes = pt.getActualTypeArguments
val compTypes = if actTypes == null then Seq.empty
else actTypes.toSeq
-
if compTypes.sizeIs != 1 then
- E(s"Unexpected generic types count for
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin,
count=${compTypes.length}, arg=${mkArg()}]")
-
+ E(s"Unexpected generic types count for $IT
annotated argument [count=${compTypes.length}, $z]")
val compType = compTypes.head
-
compType match
// Java, Scala, Groovy.
case _: Class[_] =>
val genClass =
compTypes.head.asInstanceOf[Class[_]]
if genClass != CLS_ENTITY then
- E(s"Unexpected generic type for
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin,
type=${class2Str(genClass)}, arg=${mkArg()}]")
-
+ E(s"Unexpected generic type for $IT
annotated argument [$z]")
// Kotlin.
case _: WildcardType =>
val wildcardType =
compTypes.head.asInstanceOf[WildcardType]
val lowBounds = wildcardType.getLowerBounds
val upBounds = wildcardType.getUpperBounds
if lowBounds.nonEmpty || upBounds.size != 1 ||
upBounds(0) != CLS_ENTITY then
- E(s"Unexpected Kotlin generic type for
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin,
type=${wc2Str(wildcardType)}, arg=${mkArg()}]")
-
- case _ => E(s"Unexpected generic type for
@NCIntentTerm annotated argument [mdlId=$mdlId, origin=$origin,
type=${compType.getTypeName}, arg=${mkArg()}]")
-
+ E(s"Unexpected Kotlin generic type for $IT
annotated argument [$z]")
+ case _ => E(s"Unexpected generic type for $IT
annotated argument [$z]")
case _ =>
// Scala.
if COMP_CLS.exists(_ == paramGenType) then
if !warned then
- warned = true // TODO: text
- logger.warn(s"Method arguments types cannot be
detected and checked: ${method2Str(mtd)}")
+ warned = true
+ logger.warn(s"Unable to detected and
type-check method argument [${method2Str(mtd)}, $z]")
end if
else
- E(s"Unexpected parameter type for @NCIntentTerm
annotated argument [mdlId=$mdlId, origin=$origin,
type=${paramGenType.getTypeName}, arg=${mkArg()}]")
- // Other types.
+ E(s"Unexpected parameter type for $IT annotated
argument [$z]")
else
- E(s"Unexpected parameter type for @NCIntentTerm annotated
argument [mdlId=$mdlId, origin=$origin, type=${class2Str(argClass)},
arg=${mkArg()}]")
+ // Other types.
+ E(s"Unexpected parameter type for $IT annotated argument [$z]")
}
/**
*
+ * @param cfg
* @param mtd
* @param paramCls
* @param limits
* @param ctxFirstParam
*/
- private def checkMinMax(mtd: Method, paramCls: Seq[Class[_]], limits:
Seq[(Int, Int)], ctxFirstParam: Boolean): Unit =
+ private def checkMinMax(cfg: NCModelConfig, mtd: Method, paramCls:
Seq[Class[_]], limits: Seq[(Int, Int)], ctxFirstParam: Boolean): Unit =
require(paramCls.sizeIs == limits.length)
paramCls.zip(limits).zipWithIndex.foreach { case ((cls, (min, max)),
i) =>
def mkArg(): String = arg2Str(mtd, i, ctxFirstParam)
- val p1 = "its @NCIntentTerm annotated argument"
- val p2 = s"[mdlId=$mdlId, origin=$origin, arg=${mkArg()}]"
+ val p1 = "its $IT annotated argument"
+ val p2 = s"mdlId=${cfg.getId}, arg=${mkArg()}"
// Argument is single entity but defined as not single entity.
if cls == CLS_ENTITY && (min != 1 || max != 1) then
- E(s"Intent term must have [1,1] quantifier because $p1 is a
single value $p2")
+ E(s"Intent term must have [1,1] quantifier because $p1 is a
single value [$p2]")
// Argument is not single entity but defined as single entity.
else if cls != CLS_ENTITY && (min == 1 && max == 1) then
- E(s"Intent term has [1,1] quantifier but $p1 is not a single
value $p2")
+ E(s"Intent term has [1,1] quantifier but $p1 is not a single
value [$p2]")
// Argument is optional but defined as not optional.
else if (cls == CLS_SCALA_OPT || cls == CLS_JAVA_OPT) && (min != 0
|| max != 1) then
- E(s"Intent term must have [0,1] quantifier because $p1 is
optional $p2")
+ E(s"Intent term must have [0,1] quantifier because $p1 is
optional [$p2]")
// Argument is not optional but defined as optional.
else if (cls != CLS_SCALA_OPT && cls != CLS_JAVA_OPT) && (min == 0
&& max == 1) then
- E(s"Intent term has [0,1] quantifier but $p1 is not optional
$p2")
+ E(s"Intent term has [0,1] quantifier but $p1 is not optional
[$p2]")
}
/**
*
- * @param intents
- * @param intent
- * @param cb
- * @param mtd
- */
- private def checkBind(intents: mutable.Buffer[IntentHolder], intent:
NCIDLIntent, cb: CallbackHolder, mtd: Method): Unit =
- if intents.exists(i => i._1.id == intent.id && i.method != cb.method)
then
- E(s"The intent cannot be bound to more than one callback
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)},
intentId=${intent.id}]")
-
- private def bindIntent(intent: NCIDLIntent, cb: CallbackHolder, mtd:
Method): Unit =
- if intents.exists(i => i._1.id == intent.id && i.method != cb.method)
then
- E(s"The intent cannot be bound to more than one callback
[mdlId=$mdlId, origin=$origin, class=${getClassName(mtd.getDeclaringClass)},
intentId=${intent.id}]")
- else
- intentDecls += intent
- intents += IntentHolder(intent, cb.function, cb.method)
-
-
- /**
- *
- * @param mtd
- * @param obj
- */
- private def processMethod(mtd: Method, obj: Object): Unit =
- samples += mtd -> scanSamples(mtd)
-
- def existsForOtherMethod(id: String): Boolean =
- intents.find(_.intent.id == id) match
- case Some(i) => i.method != mtd
- case None => false
-
- val anns = mtd.getAnnotationsByType(CLS_INTENT)
-
- if anns.nonEmpty then
- val mtdStr = method2Str(mtd)
-
- for (ann <- anns; intent <- NCIDLCompiler.compile(ann.value, cfg,
mtdStr))
- if intentDecls.exists(_.id == intent.id &&
existsForOtherMethod(intent.id)) then
- E(s"Duplicate intent ID [mdlId=$mdlId, origin=$origin,
callback=$mtdStr, id=${intent.id}]")
- else
- bindIntent(intent, prepareCallback(mtd, obj, intent), mtd)
-
- /**
- *
- * @param mtd
- * @param obj
- */
- private def processMethodRefs(mtd: Method, obj: Object): Unit =
- val anns = mtd.getAnnotationsByType(CLS_INTENT_REF)
-
- for (ann <- anns)
- val refId = ann.value.strip
-
- intentDecls.find(_.id == refId) match
- case Some(intent) => bindIntent(intent, prepareCallback(mtd,
obj, intent), mtd)
- case None => E(s"@NCIntentRef(\"$refId\") references unknown
intent ID [mdlId=$mdlId, origin=$origin, refId=$refId,
callback=${method2Str(mtd)}]")
-
- /**
- *
+ * @param cfg
* @param method
* @param obj
* @param intent
* @return
*/
- private def prepareCallback(method: Method, obj: Object, intent:
NCIDLIntent): CallbackHolder =
+ private def prepareCallback(cfg: NCModelConfig, method: Method, obj:
AnyRef, intent: NCIDLIntent): NCIntentMatch => NCResult =
+ lazy val z = s"mdlId=${cfg.getId}, intentId=${intent.id},
type=${class2Str(method.getReturnType)}, callback=${method2Str(method)}"
+
// Checks method result type.
- if method.getReturnType != CLS_QRY_RES then
- E(s"Unexpected result type for @NCIntent annotated method
[mdlId=$mdlId, intentId=${intent.id}, type=${class2Str(method.getReturnType)},
callback=${method2Str(method)}]")
+ if method.getReturnType != CLS_QRY_RES then E(s"Unexpected result type
for @NCIntent annotated method [$z]")
val allParamTypes = method.getParameterTypes.toSeq
val ctxFirstParam = allParamTypes.nonEmpty && allParamTypes.head ==
CLS_INTENT_MATCH
@@ -472,7 +440,7 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
// Checks entities parameters annotations count.
if tokParamAnns.sizeIs != tokParamTypes.length then
- E(s"Unexpected annotations count for @NCIntent annotated method
[mdlId=$mdlId, intentId=${intent.id}, count=${tokParamAnns.size},
callback=${method2Str(method)}]")
+ E(s"Unexpected annotations count for $I annotated method
[count=${tokParamAnns.size}, $z]")
// Gets terms IDs.
val termIds = tokParamAnns.toList.zipWithIndex.map {
@@ -481,20 +449,17 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
val termAnns = annArr.filter(_.isInstanceOf[NCIntentTerm])
- // Each method arguments (second and later) must have one
NCIntentTerm annotation.
+ // Each method arguments (second and later) must have one
'NCIntentTerm' annotation.
termAnns.length match
case 1 => termAnns.head.asInstanceOf[NCIntentTerm].value
case 0 =>
- if idx == 0 then
- E(s"Missing @NCIntentTerm annotation or wrong type
of the 1st parameter (must be 'NCIntentMatch') for [mdlId=$mdlId,
intentId=${intent.id}, arg=${mkArg()}]")
- else
- E(s"Missing @NCIntentTerm annotation for
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
-
- case _ => E(s"Too many @NCIntentTerm annotations for
[mdlId=$mdlId, intentId=${intent.id}, arg=${mkArg()}]")
- }
+ if idx == 0 then E(s"Missing $IT annotation or wrong
type of the 1st parameter (must be 'NCIntentMatch') for [$z]")
+ else E(s"Missing $IT annotation for [$z]")
+ case _ => E(s"Too many $IT annotations for [$z]")
+ }
if NCUtils.containsDups(termIds) then
- E(s"Duplicate term IDs in @NCIntentTerm annotations [mdlId=$mdlId,
intentId=${intent.id}, dups=${NCUtils.getDups(termIds).mkString(", ")},
callback=${method2Str(method)}]")
+ E(s"Duplicate term IDs in $IT annotations
[dups=${NCUtils.getDups(termIds).mkString(", ")}, $z]")
val terms = intent.terms
@@ -505,128 +470,105 @@ class NCModelScanner(mdl: NCModel) extends LazyLogging:
if invalidIds.nonEmpty then
// Report only the first one for simplicity & clarity.
- E(s"Unknown term ID in @NCIntentTerm annotation [mdlId=$mdlId,
intentId=${intent.id}, termId=${invalidIds.head},
callback=${method2Str(method)}]")
-
- val paramGenTypes =
getSeq(method.getGenericParameterTypes.toIndexedSeq)
+ E(s"Unknown term ID in $IT annotation [termId=${invalidIds.head},
$z]")
// Checks parameters.
- checkTypes(method, tokParamTypes, paramGenTypes, ctxFirstParam)
+ val paramGenTypes =
getSeq(method.getGenericParameterTypes.toIndexedSeq)
+ checkTypes(cfg, method, tokParamTypes, paramGenTypes, ctxFirstParam)
// Checks limits.
val allLimits = terms.map(t => t.id.orNull -> (t.min, t.max)).toMap
+ checkMinMax(cfg, method, tokParamTypes, termIds.map(allLimits),
ctxFirstParam)
- checkMinMax(method, tokParamTypes, termIds.map(allLimits),
ctxFirstParam)
-
- CallbackHolder(
- method,
- (ctx: NCIntentMatch) =>
- val args = mutable.Buffer.empty[AnyRef]
- if ctxFirstParam then args += ctx
- args ++= prepareParams(mdlId, method, tokParamTypes,
termIds.map(ctx.getTermEntities), ctxFirstParam)
-
- invoke(mdlId, method, obj, args.toArray)
- )
+ (ctx: NCIntentMatch) =>
+ val args = mutable.Buffer.empty[AnyRef]
+ if ctxFirstParam then args += ctx
+ args ++= prepareParams(cfg, method, tokParamTypes,
termIds.map(ctx.getTermEntities), ctxFirstParam)
+ invoke(cfg, method, obj, args.toArray)
/**
*
- * @param mtd
* @return
*/
- private def scanSamples(mtd: Method): Map[String, Seq[Seq[String]]] =
- val smpAnns = mtd.getAnnotationsByType(CLS_SAMPLE)
- val smpAnnsRef = mtd.getAnnotationsByType(CLS_SAMPLE_REF)
- lazy val mtdStr = method2Str(mtd)
- lazy val intAnns = mtd.getAnnotationsByType(CLS_INTENT)
- lazy val refAnns = mtd.getAnnotationsByType(CLS_INTENT_REF)
- lazy val samples = mutable.HashMap.empty[String, Seq[Seq[String]]]
-
- if smpAnns.nonEmpty || smpAnnsRef.nonEmpty then
- if intAnns.isEmpty && refAnns.isEmpty then
- E(s"@NCIntentSample or @NCIntentSampleRef annotations without
corresponding @NCIntent or @NCIntentRef annotations: $mtdStr")
- else
- def read[T](annArr: scala.Array[T], annName: String,
getSamples: T => Seq[String], getSource: Option[T => String]): Seq[Seq[String]]
=
- for (ann <- annArr.toSeq) yield
- val samples = getSamples(ann).map(_.strip).filter(s =>
s.nonEmpty && s.head != '#')
-
- if samples.isEmpty then
- getSource match
- case None => logger.warn(s"$annName annotation
has no samples: $mtdStr")
- case Some(f) => logger.warn(s"$annName
annotation references '${f(ann)}' file that has no samples: $mtdStr")
-
- Seq.empty
- else
- samples
- .filter(_.nonEmpty)
-
- val seqSeq =
- read[NCIntentSample](
- smpAnns, "@NCIntentSample", _.value.toSeq, None
- ) ++
- read[NCIntentSampleRef](
- smpAnnsRef, "@NCIntentSampleRef", a =>
NCUtils.readResource(a.value), Option(_.value)
- )
-
- if NCUtils.containsDups(seqSeq.flatMap(_.toSeq).toList) then
- logger.warn(s"@NCIntentSample and @NCIntentSampleRef
annotations have duplicates: $mtdStr")
-
- val distinct = seqSeq.map(_.distinct).distinct
-
- for (ann <- intAnns; intent <-
NCIDLCompiler.compile(ann.value, cfg, mtdStr))
- samples += intent.id -> distinct
-
- for (ann <- refAnns) samples += ann.value -> distinct
- else if intAnns.nonEmpty || refAnns.nonEmpty then
- logger.warn(s"@NCIntentSample or @NCIntentSampleRef annotations
are missing for: $mtdStr")
-
- samples.toMap
-
- /**
- *
- * @return
- */
- def scan(): Seq[NCModelIntent] =
- def processClassHierarchy(claxx: Class[_]): Unit =
- if claxx != null then
- val anns = claxx.getAnnotationsByType(CLS_INTENT)
-
- if anns.nonEmpty then
- val origin = getClassName(claxx)
-
- for (ann <- anns; intent <-
NCIDLCompiler.compile(ann.value, cfg, origin))
- // TODO: duplicate
- intentDecls += intent
-
- processClassHierarchy(claxx.getSuperclass)
- claxx.getInterfaces.foreach(processClassHierarchy)
-
- // 1. First phase scan.
- // - For given object finds references via fields (NCIntentObject).
Scans also each reference recursively and collects them.
- // - For all methods of processed object collects samples
(NCIntentSample, NCIntentSampleRef) and intents (NCIntent)
- def scan(obj: Object): Unit =
+ def scan(mdl: NCModel): Seq[NCModelIntent] =
+ require(mdl != null)
+
+ val cfg = mdl.getConfig
+ lazy val z = s"mdlId=${cfg.getId}"
+ val intents = mutable.Buffer.empty[IntentHolder]
+ val intentDecls = mutable.HashMap.empty[String, NCIDLIntent]
+ val objs = mutable.Buffer.empty[AnyRef]
+ val processed = mutable.HashSet.empty[Class[_]]
+ val samples = mutable.HashMap.empty[Method, Map[String,
Seq[Seq[String]]]]
+
+ def addDecl(intent: NCIDLIntent): Unit =
+ intentDecls.get(intent.id) match
+ case Some(ex) => if ex.idl != intent.idl then E(s"Intent with
given ID already found with different definition [$z, id=${intent.id}]")
+ case None => // No-op.
+ intentDecls += intent.id -> intent
+
+ def addIntent(intent: NCIDLIntent, mtd: Method, obj: AnyRef): Unit =
+ if intents.exists(_.method == mtd) then E(s"The callback cannot
have more one intent [$z, callback=${method2Str(mtd)}]")
+ intents += IntentHolder(cfg, intent, obj, mtd)
+
+ def processClassAnnotations(cls: Class[_]): Unit =
+ if cls != null && processed.add(cls) then
+ for (ann <- cls.getAnnotationsByType(CLS_INTENT); intent <-
NCIDLCompiler.compile(ann.value, cfg, class2Str(cls)))
+ addDecl(intent)
+
+ processClassAnnotations(cls.getSuperclass)
+ cls.getInterfaces.foreach(processClassAnnotations)
+
+ // First phase scan.
+ // For given object finds references via fields (NCIntentObject).
Scans also each reference recursively and collects them.
+ // For all methods of processed object collects samples
(NCIntentSample, NCIntentSampleRef) and intents (NCIntent)
+ def scan(obj: AnyRef): Unit =
objs += obj
+ processClassAnnotations(obj.getClass)
+ val methods = getAllMethods(obj)
+
+ // Collects samples for each method.
+ for (mtd <- methods) samples += mtd -> scanSamples(cfg, mtd)
- processClassHierarchy(obj.getClass)
+ // // Collects intents for each method.
+ for (
+ mtd <- methods;
+ ann <- mtd.getAnnotationsByType(CLS_INTENT);
+ intent <- NCIDLCompiler.compile(ann.value, cfg,
method2Str(mtd))
+ )
+ addDecl(intent)
+ addIntent(intent, mtd, obj)
- for (m <- getAllMethods(obj)) processMethod(m, obj)
- for (f <- getAllFields(obj) if
f.isAnnotationPresent(CLS_INTENT_OBJ)) scan(getFieldObject(mdlId, f, obj))
+ // Scans annotated fields.
+ for (f <- getAllFields(obj) if
f.isAnnotationPresent(CLS_INTENT_OBJ)) scan(getFieldObject(cfg, f, obj))
scan(mdl)
- // 2. For model and all its references scans each method and finds
intents references (NCIntentRef)
- for (o <- objs; m <- getAllMethods(o)) processMethodRefs(m, o)
+ // Second phase. For model and all its references scans each method
and finds intents references (NCIntentRef)
+ for (
+ obj <- objs;
+ mtd <- getAllMethods(obj);
+ ann <- mtd.getAnnotationsByType(CLS_INTENT_REF)
+ )
+ val refId = ann.value.strip
+ val intent = intentDecls.getOrElse(
+ refId,
+ E(s"$IR(\"$refId\") references unknown intent ID [$z,
callback=${method2Str(mtd)}]")
+ )
- // 3. Validation.
- val unusedIntents = intentDecls.filter(i => !intents.exists(_._1.id ==
i.id))
+ addDecl(intent)
+ addIntent(intent, mtd, obj)
- if unusedIntents.nonEmpty then
- logger.warn(s"Intents are unused (have no callback):
[mdlId=$mdlId, origin=$origin,
intentIds=${unusedIntents.map(_.id).mkString("(", ", ", ")")}]")
+ val unusedIds = intentDecls.keys.filter(k =>
!intents.exists(_.intent.id == k))
+ if unusedIds.nonEmpty then
+ logger.warn(s"Intents are unused (have no callback): [$z,
intentIds=${col2Str(unusedIds)}]")
if intents.nonEmpty then
- // Check the uniqueness of intent IDs.
- NCUtils.getDups(intents.map(_._1).toSeq.map(_.id)) match
- case ids if ids.nonEmpty => E(s"Duplicate intent IDs
[mdlId=$mdlId, origin=$origin, ids=${ids.mkString(",")}]")
+ // Check the uniqueness of intent IDs.
+ NCUtils.getDups(intents.map(_.intent.id).toSeq) match
+ case ids if ids.nonEmpty => E(s"Duplicate intent IDs [$z,
ids=${col2Str(ids)}]")
case _ => // No-op.
else
- logger.warn(s"Model has no intent: $mdlId")
+ logger.warn(s"Model has no intent: ${cfg.getId}")
intents.map(i => NCModelIntent(i.intent, i.function,
samples.getOrElse(i.method, Map.empty).getOrElse(i.intent.id, Seq.empty))).toSeq
\ No newline at end of file
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
index 0640627..2ed745e 100644
---
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidArgsSpec.scala
@@ -102,12 +102,12 @@ class NCModelIntentsInvalidArgsSpec:
mkResult0(list)
private def testOk(mdl: NCModel, intentId: String): Unit =
- val i = new NCModelScanner(mdl).scan().find(_.intent.id ==
intentId).get
+ val i = NCModelScanner.scan(mdl).find(_.intent.id == intentId).get
println(s"Test finished [modelClass=${mdl.getClass}, intent=$intentId,
result=${i.function(INTENT_MATCH)}")
private def testRuntimeClassCast(mdl: NCModel, intentId: String): Unit =
- val i = new NCModelScanner(mdl).scan().find(_.intent.id ==
intentId).get
+ val i = NCModelScanner.scan(mdl).find(_.intent.id == intentId).get
try
i.function(INTENT_MATCH)
@@ -123,7 +123,7 @@ class NCModelIntentsInvalidArgsSpec:
private def testScanValidation(mdl: NCModel): Unit =
try
- new NCModelScanner(mdl).scan()
+ NCModelScanner.scan(mdl)
require(false)
catch
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
new file mode 100644
index 0000000..b23dc84
--- /dev/null
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsInvalidIntentsSpec.scala
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nlpcraft.internal.impl.scan
+
+import org.apache.nlpcraft.*
+import org.apache.nlpcraft.internal.impl.NCModelScanner
+import org.apache.nlpcraft.nlp.util.*
+import org.apache.nlpcraft.nlp.util.opennlp.*
+import org.junit.jupiter.api.Test
+
+import java.util
+
+/**
+ * It tests invalid intents definition.
+ */
+class NCModelIntentsInvalidIntentsSpec:
+ private def testError(mdl: NCModel): Unit =
+ try
+ NCModelScanner.scan(mdl)
+
+ require(false)
+ catch
+ case e: NCException =>
+ println("Expected stack trace:")
+ e.printStackTrace(System.out)
+
+ /**
+ * Two intents on one method.
+ */
+ @Test
+ def testError1(): Unit =
+ testError(
+ new NCTestModelAdapter():
+ @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+ @NCIntent("intent=validList2 term(list)~{# == 'x'}[0,10]")
+ def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult =
null
+ )
+
+ /**
+ * Invalid reference.
+ */
+ @Test
+ def testError2(): Unit =
+ testError(
+ new NCTestModelAdapter():
+ @NCIntentRef("missed")
+ def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult =
null
+ )
+
+ /**
+ * Samples without intent.
+ */
+ @Test
+ def testError3(): Unit =
+ testError(
+ new NCTestModelAdapter():
+ @NCIntentSample(Array("sample"))
+ def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult =
null
+ )
+
+ /**
+ * Duplicated intents definitions.
+ */
+ @Test
+ def testError4(): Unit =
+ @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+ @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,11]")
+ class X:
+ val x = 1
+
+ testError(
+ new NCTestModelAdapter():
+ @NCIntentObject
+ val x = new X()
+ )
+
+ /**
+ * Invalid argument type.
+ */
+ @Test
+ def testError5(): Unit =
+ testError(
+ new NCTestModelAdapter():
+ @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+ def x(@NCIntentTerm("list") e: NCEntity): NCResult = null
+ )
+
+ /**
+ * Duplicated samples definitions.
+ */
+ @Test
+ def testWarning1(): Unit =
+ NCModelScanner.scan(
+ new NCTestModelAdapter():
+ @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+ @NCIntentSample(Array("x", "x"))
+ def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult =
null
+ )
+
+ /**
+ * Missed samples.
+ */
+ @Test
+ def testWarning2(): Unit =
+ NCModelScanner.scan(
+ new NCTestModelAdapter():
+ @NCIntent("intent=validList1 term(list)~{# == 'x'}[0,10]")
+ def x(@NCIntentTerm("list") list: List[NCEntity]): NCResult =
null
+ )
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
index c8e2a4c..91e2f09 100644
---
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsNestedSpec.scala
@@ -81,16 +81,16 @@ class NCModelIntentsNestedSpec:
@Test
def test(): Unit =
- require(new NCModelScanner(MDL_VALID1).scan().sizeIs == 4)
- require(new NCModelScanner(MDL_VALID2).scan().sizeIs == 4)
+ require(NCModelScanner.scan(MDL_VALID1).sizeIs == 4)
+ require(NCModelScanner.scan(MDL_VALID2).sizeIs == 4)
@Test
def testNull(): Unit =
try
- new NCModelScanner(MDL_INVALID).scan()
+ NCModelScanner.scan(MDL_INVALID)
require(false)
catch
case e: NCException =>
println("Expected stack trace:")
- e.printStackTrace(System.out)
+ e.printStackTrace(System.out)
\ No newline at end of file
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
index 36ce1e6..e26fe6e 100644
---
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/impl/scan/NCModelIntentsSpec.scala
@@ -34,5 +34,5 @@ class NCModelIntentsSpec:
)
@Test
- def test(): Unit = for (mdl <- mdls) require(new
NCModelScanner(mdl).scan().nonEmpty)
+ def test(): Unit = for (mdl <- mdls)
require(NCModelScanner.scan(mdl).nonEmpty)
diff --git
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCompilerSpec.scala
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCompilerSpec.scala
index b418e26..6214697 100644
---
a/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCompilerSpec.scala
+++
b/nlpcraft/src/test/scala/org/apache/nlpcraft/internal/intent/compiler/NCIDLCompilerSpec.scala
@@ -292,19 +292,17 @@ class NCIDLCompilerSpec:
)
@Test
- def testImport(): Unit =
require(NCIDLCompiler.compile("import('scan/idl.idl')", CFG, "-").size == 1)
+ def testImport(): Unit =
require(NCIDLCompiler.compile("import('scan/idl.idl')", CFG,
"test-origin").size == 1)
@Test
def testEmpty(): Unit =
- def test0(f: () => Unit): Unit =
+ def test0(idl: String): Unit =
try
- f()
-
- require(false)
+ NCIDLCompiler.compile(idl, CFG, "test-origin")
+ require(true)
catch
- case e: NCException => println(s"Expected error: ${e}")
+ case e: NCException => println(s"Unexpected error: $e")
- test0(() => NCIDLCompiler.compile("", CFG, "-"))
- test0(() => NCIDLCompiler.compile(" ", CFG, "-"))
- test0(() => NCIDLCompiler.compile(null, CFG, "-"))
+ test0("")
+ test0(" ")