getVariable(String identifier) {
+ return Stream.concat(
+ this.frames.stream().map(f -> f.identifierRegistry),
+ Stream.of(this.globalIdentifierRegistry))
+ .filter(registry -> registry.containsKey(identifier))
+ .findFirst()
+ .map(registry -> registry.get(identifier))
+ .filter(Variable.class::isInstance)
+ .map(Variable.class::cast);
}
/**
- * Gets the type of a variable.
- *
- * @param identifierName The identifier of the variable.
- * @return The type of the variable.
+ * Retrieves a function from the function registry by its name.
+ *
+ * @param functionName the name of the function to retrieve
+ * @return the {@link Function} associated with the given name
+ * @throws ParseCancellationException if the function name is not found in the registry,
+ * with a message indicating the undeclared identifier
+ */
+ public Function getFunction(String functionName) {
+ return Optional.ofNullable(this.globalIdentifierRegistry.get(functionName))
+ .filter(Function.class::isInstance)
+ .map(Function.class::cast)
+ .orElseThrow(() -> new ParseCancellationException(UNDECLARED_IDENTIFIER + functionName));
+ }
+
+ /**
+ * Retrieves the list of parameters for a specified function name.
+ *
+ * @param functionName the name of the function whose parameters are to be retrieved
+ * @return the list of parameters associated with the specified function
+ * @throws ParseCancellationException if the function name is not declared in the registry
+ */
+ public ParameterList getFunctionParameters(String functionName) {
+ return Optional.ofNullable(this.globalIdentifierRegistry.get(functionName))
+ .filter(Function.class::isInstance)
+ .map(identifier -> ((Function) identifier).parameters)
+ .orElseThrow(() -> new ParseCancellationException(UNDECLARED_IDENTIFIER + functionName));
+ }
+
+ /**
+ * Retrieves the data type of a given identifier by its name.
+ *
+ * This method first attempts to find the identifier using the {@code getIdentifier} method.
+ * If not found, it tries to resolve it as a function using the {@code getFunction} method.
+ * If the identifier is still not found, a {@link ParseCancellationException} is thrown.
+ *
+ *
+ * @param identifierName the name of the identifier to look up
+ * @return the class type of the identifier's data type
+ * @throws ParseCancellationException if the identifier is not declared
*/
public Class extends EfxDataType> getTypeOfIdentifier(String identifierName) {
- Optional identifier = this.getIdentifier(identifierName);
+ Optional identifier = this.getIdentifier(identifierName)
+ .or(() -> Optional.ofNullable((Identifier) this.getFunction(identifierName)));
if (!identifier.isPresent()) {
throw new ParseCancellationException(UNDECLARED_IDENTIFIER + identifierName);
}
@@ -220,8 +311,8 @@ public Class extends EfxDataType> getTypeOfIdentifier(String identifierName) {
* current scope.
*/
public void pushIdentifierReference(String identifierName) {
- getParameter(identifierName).ifPresentOrElse(parameterValue -> this.push(parameterValue),
- () -> getIdentifier(identifierName).ifPresentOrElse(
+ getParameter(identifierName).ifPresentOrElse(this::push,
+ () -> getVariable(identifierName).ifPresentOrElse(
variable -> this.push(variable.referenceExpression),
() -> {
throw new ParseCancellationException(UNDECLARED_IDENTIFIER + identifierName);
@@ -237,40 +328,54 @@ public void push(ParsedEntity item) {
this.frames.peek().push(item);
}
+ /**
+ * Removes and returns the top element of the call stack, ensuring it matches
+ * the expected type.
+ * This method is thread-safe.
+ *
+ * @param The type of the element expected to be returned, which
+ * must extend {@code ParsedEntity}.
+ * @param expectedType The {@code Class} object representing the expected type
+ * of the element.
+ * @return The top element of the call stack, cast to the specified type.
+ */
public synchronized T pop(Class expectedType) {
return this.frames.peek().pop(expectedType);
}
/**
- * Gets the object at the top of the current stack frame without removing it
- * from the stack.
- *
- * @return The object at the top of the current stack frame.
+ * Retrieves, but does not remove, the top ParsedEntity from the call stack.
+ * This method is thread-safe as it is synchronized.
+ *
+ * @return the top ParsedEntity from the call stack, or {@code null} if the
+ * stack is empty.
*/
public synchronized ParsedEntity peek() {
return this.frames.peek().peek();
}
/**
- * Gets the number of elements in the current stack frame.
- *
- * @return The number of elements in the current stack frame.
+ * Returns the size of the current call stack frame.
+ *
+ * @return the number of elements in the top frame of the call stack.
*/
public int size() {
return this.frames.peek().size();
}
/**
- * Checks if the current stack frame is empty.
- *
- * @return True if the current stack frame is empty.
+ * Checks if the call stack is empty.
+ *
+ * @return {@code true} if the top frame of the call stack is empty,
+ * {@code false} otherwise.
*/
public boolean empty() {
return this.frames.peek().empty();
}
/**
- * Clears the current stack frame.
+ * Clears the current call stack frame by removing all elements from it.
+ * This operation affects only the top frame of the stack.
*/
public void clear() {
this.frames.peek().clear();
diff --git a/src/main/java/eu/europa/ted/efx/model/Context.java b/src/main/java/eu/europa/ted/efx/model/Context.java
index e752d6fa..b24d017d 100644
--- a/src/main/java/eu/europa/ted/efx/model/Context.java
+++ b/src/main/java/eu/europa/ted/efx/model/Context.java
@@ -82,11 +82,11 @@ protected Context(final String symbol, final PathExpression absolutePath) {
this(symbol, absolutePath, absolutePath);
}
- public Boolean isFieldContext() {
+ public boolean isFieldContext() {
return this.getClass().equals(FieldContext.class);
}
- public Boolean isNodeContext() {
+ public boolean isNodeContext() {
return this.getClass().equals(NodeContext.class);
}
diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java b/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java
index c0404122..75d6dd54 100644
--- a/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java
+++ b/src/main/java/eu/europa/ted/efx/model/expressions/Expression.java
@@ -1,6 +1,7 @@
package eu.europa.ted.efx.model.expressions;
import java.lang.reflect.Constructor;
+import java.util.Objects;
import org.antlr.v4.runtime.misc.ParseCancellationException;
@@ -10,7 +11,7 @@ public interface Expression extends ParsedEntity {
public String getScript();
- public Boolean isLiteral();
+ public boolean isLiteral();
static T instantiate(String script, Class type) {
return Expression.instantiate(script, false, type);
@@ -22,7 +23,7 @@ static T from(Expression source, Class returnType) {
static T instantiate(String script, Boolean isLiteral, Class type) {
try {
- if (isLiteral) {
+ if (Boolean.TRUE.equals(isLiteral)) {
Constructor constructor = type.getConstructor(String.class, Boolean.class);
return constructor.newInstance(script, isLiteral);
} else {
@@ -44,7 +45,7 @@ static T empty(Class type) {
public abstract class Impl implements Expression {
private final String script;
- private final Boolean isLiteral;
+ private final boolean isLiteral;
@Override
public String getScript() {
@@ -52,7 +53,7 @@ public String getScript() {
}
@Override
- public Boolean isLiteral() {
+ public boolean isLiteral() {
return this.isLiteral;
}
@@ -71,15 +72,21 @@ public final Boolean isEmpty() {
@Override
public boolean equals(Object obj) {
- if (obj == null) {
- return false;
+ if (this == obj) {
+ return true;
}
- if (Expression.class.isAssignableFrom(obj.getClass())) {
- return this.script.equals(((Expression) obj).getScript());
+ if (!(obj instanceof Expression)) {
+ return false;
}
- return false;
+ Expression other = (Expression) obj;
+ return Objects.equals(script, other.getScript()) && isLiteral == other.isLiteral();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(script, isLiteral);
}
}
}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java b/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java
index dbaf3fe7..1b21095b 100644
--- a/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java
+++ b/src/main/java/eu/europa/ted/efx/model/expressions/TypedExpression.java
@@ -14,16 +14,15 @@ public interface TypedExpression extends Expression {
public Class extends EfxDataType> getDataType();
- public Boolean is(Class extends EfxDataType> dataType);
+ public boolean is(Class extends EfxDataType> dataType);
- public Boolean is(Class extends EfxExpressionType> referenceType, Class extends EfxDataType> dataType);
+ public boolean is(Class extends EfxExpressionType> referenceType, Class extends EfxDataType> dataType);
static Class extends T> getEfxDataType(
Class extends E> clazz, Class extends T> dataType1) {
EfxDataTypeAssociation annotation = clazz.getAnnotation(EfxDataTypeAssociation.class);
if (annotation == null) {
- return EfxDataType.ANY.asSubclass(dataType1); // throw new IllegalArgumentException("Missing
- // @EfxDataTypeAssociation annotation");
+ return EfxDataType.ANY.asSubclass(dataType1); // throw new IllegalArgumentException("Missing @EfxDataTypeAssociation annotation");
}
return annotation.dataType().asSubclass(dataType1);
}
@@ -61,7 +60,7 @@ public static TypedExpressi
}
}
- public static Boolean canConvert(Class extends TypedExpression> from, Class extends TypedExpression> to) {
+ public static boolean canConvert(Class extends TypedExpression> from, Class extends TypedExpression> to) {
var fromExpressionType = from.getAnnotation(EfxExpressionTypeAssociation.class).expressionType();
var fromDataType = from.getAnnotation(EfxDataTypeAssociation.class).dataType();
var toExpressionType = to.getAnnotation(EfxExpressionTypeAssociation.class).expressionType();
@@ -75,12 +74,12 @@ public abstract class Impl extends Expression.Impl implem
private Class extends EfxExpressionType> expressionType;
private Class extends T> dataType;
- public Impl(final String script, Class extends EfxExpressionType> expressionType,
+ protected Impl(final String script, Class extends EfxExpressionType> expressionType,
Class extends T> dataType) {
this(script, false, expressionType, dataType);
}
- public Impl(final String script, final Boolean isLiteral,
+ protected Impl(final String script, final Boolean isLiteral,
Class extends EfxExpressionType> expressionType, Class extends T> dataType) {
super(script, isLiteral);
this.expressionType = expressionType;
@@ -98,12 +97,12 @@ public Class extends T> getDataType() {
}
@Override
- public Boolean is(Class extends EfxDataType> dataType) {
+ public boolean is(Class extends EfxDataType> dataType) {
return dataType.isAssignableFrom(this.dataType);
}
@Override
- public Boolean is(Class extends EfxExpressionType> referenceType, Class extends EfxDataType> dataType) {
+ public boolean is(Class extends EfxExpressionType> referenceType, Class extends EfxDataType> dataType) {
return referenceType.isAssignableFrom(this.expressionType) && dataType.isAssignableFrom(this.dataType);
}
}
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Argument.java b/src/main/java/eu/europa/ted/efx/model/variables/Argument.java
new file mode 100644
index 00000000..0873ed04
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/variables/Argument.java
@@ -0,0 +1,37 @@
+package eu.europa.ted.efx.model.variables;
+
+import java.util.Objects;
+
+import eu.europa.ted.efx.model.expressions.TypedExpression;
+
+public class Argument extends Parameter {
+
+ public final TypedExpression value;
+
+ public Argument(Parameter parameter, TypedExpression value) {
+ super(parameter.name, parameter.referenceExpression);
+ this.value = value;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(value);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Argument other = (Argument) obj;
+ return Objects.equals(value, other.value);
+ }
+}
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/ArgumentList.java b/src/main/java/eu/europa/ted/efx/model/variables/ArgumentList.java
new file mode 100644
index 00000000..e6a451d1
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/variables/ArgumentList.java
@@ -0,0 +1,68 @@
+package eu.europa.ted.efx.model.variables;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.antlr.v4.runtime.misc.ParseCancellationException;
+
+import eu.europa.ted.efx.model.ParsedEntity;
+import eu.europa.ted.efx.model.expressions.TypedExpression;
+
+public class ArgumentList extends LinkedList implements ParsedEntity {
+
+ private static final String TYPE_MISMATCH = "Type mismatch. Expected %s instead of %s.";
+
+ public final String identifier;
+ public final ParameterList parameters;
+
+ public ArgumentList(Function function) {
+ this.identifier = function.name;
+ this.parameters = function.parameters;
+ }
+
+ public boolean addArgument(TypedExpression argument) {
+ int position = this.size(); // Get the current position of the argument
+ if (position >= parameters.size()) {
+ throw new IllegalArgumentException("Too many arguments for the function.");
+ }
+
+ var actualType = argument.getClass();
+ var expectedType = parameters.get(position).getParameterType();
+ if (!TypedExpression.canConvert(actualType, expectedType)) {
+ throw new ParseCancellationException(
+ String.format(TYPE_MISMATCH, TypedExpression.getEfxDataType(expectedType),
+ TypedExpression.getEfxDataType(actualType)));
+ }
+ return this.add(new Argument(parameters.get(position), argument));
+ }
+
+ public List getArguments() {
+ return this.stream().map(argument -> argument.value).collect(Collectors.toList());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!(obj instanceof ArgumentList)) {
+ return false;
+ }
+ ArgumentList other = (ArgumentList) obj;
+ return Objects.equals(identifier, other.identifier) &&
+ Objects.equals(parameters, other.parameters);
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(identifier, parameters);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Function.java b/src/main/java/eu/europa/ted/efx/model/variables/Function.java
new file mode 100644
index 00000000..dc06e74e
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/variables/Function.java
@@ -0,0 +1,17 @@
+package eu.europa.ted.efx.model.variables;
+
+import eu.europa.ted.efx.model.expressions.TypedExpression;
+import eu.europa.ted.efx.model.types.EfxDataType;
+
+public class Function extends Identifier {
+
+ public ParameterList parameters;
+ public TypedExpression expression;
+
+ public Function(String name, Class extends EfxDataType> returnType, ParameterList parameters,
+ TypedExpression expression) {
+ super(name, returnType);
+ this.parameters = parameters;
+ this.expression = expression;
+ }
+}
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/FunctionList.java b/src/main/java/eu/europa/ted/efx/model/variables/FunctionList.java
new file mode 100644
index 00000000..7323022e
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/variables/FunctionList.java
@@ -0,0 +1,11 @@
+package eu.europa.ted.efx.model.variables;
+
+import java.util.LinkedList;
+
+import eu.europa.ted.efx.model.ParsedEntity;
+
+public class FunctionList extends LinkedList implements ParsedEntity {
+
+ public FunctionList() {
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java b/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java
index 22acf0ad..db473e7d 100644
--- a/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java
+++ b/src/main/java/eu/europa/ted/efx/model/variables/Identifier.java
@@ -1,20 +1,55 @@
package eu.europa.ted.efx.model.variables;
+import java.util.Objects;
+
import eu.europa.ted.efx.model.ParsedEntity;
import eu.europa.ted.efx.model.expressions.Expression;
import eu.europa.ted.efx.model.expressions.TypedExpression;
import eu.europa.ted.efx.model.types.EfxDataType;
-public class Identifier implements ParsedEntity {
- final public String name;
- final public Class extends EfxDataType> dataType;
- final public Expression declarationExpression;
- final public TypedExpression referenceExpression;
+/**
+ * Represents an identifier that is declared and used in an EFX expression.
+ * Identifiers typically point to functions, variables and parameters.
+ *
+ * The Identifier class keeps track not only of the name and data type of the identifier,
+ * but also of the expressions used to declare and reference it.
+ *
+ * @see Expression
+ * @see TypedExpression
+ * @see ParsedEntity
+ * @see EfxDataType
+ */
+public abstract class Identifier implements ParsedEntity {
+ public final String name;
+ public final Class extends EfxDataType> dataType;
- public Identifier(String name, Expression declarationExpression, TypedExpression referenceExpression) {
+ /**
+ * Creates an Identifier with the given name and expressions.
+ * The Identifier's data type is inferred from the reference expression.
+ *
+ * @param name The name of the identifier.
+ * @param declarationExpression The expression that should be used to declare the Identifier at runtime.
+ * @param referenceExpression The expression that should be used to reference the identifier.
+ */
+ protected Identifier(String name, Class extends EfxDataType> dataType) {
this.name = name;
- this.dataType = referenceExpression.getDataType();
- this.declarationExpression = declarationExpression;
- this.referenceExpression = referenceExpression;
+ this.dataType = dataType;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, dataType);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Identifier other = (Identifier) obj;
+ return Objects.equals(name, other.name) && Objects.equals(dataType, other.dataType);
}
}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/IdentifierList.java b/src/main/java/eu/europa/ted/efx/model/variables/IdentifierList.java
new file mode 100644
index 00000000..87587ff0
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/variables/IdentifierList.java
@@ -0,0 +1,11 @@
+package eu.europa.ted.efx.model.variables;
+
+import java.util.LinkedList;
+
+import eu.europa.ted.efx.model.ParsedEntity;
+
+public class IdentifierList extends LinkedList implements ParsedEntity {
+
+ public IdentifierList() {
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java b/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java
index 299d480e..67924fe0 100644
--- a/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java
+++ b/src/main/java/eu/europa/ted/efx/model/variables/Parameter.java
@@ -1,14 +1,41 @@
package eu.europa.ted.efx.model.variables;
-import eu.europa.ted.efx.model.expressions.Expression;
+import java.util.Objects;
+
import eu.europa.ted.efx.model.expressions.TypedExpression;
public class Parameter extends Identifier {
- public final TypedExpression parameterValue;
+ public final TypedExpression referenceExpression;
+
+ public Parameter(String parameterName, TypedExpression referenceExpression) {
+ super(parameterName, referenceExpression.getDataType());
+ this.referenceExpression = referenceExpression;
+ }
+
+ public Class extends TypedExpression> getParameterType() {
+ return referenceExpression.getClass();
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = super.hashCode();
+ result = prime * result + Objects.hash(referenceExpression);
+ return result;
+ }
- public Parameter(String parameterName, Expression declarationExpression, TypedExpression referenceExpression, TypedExpression parameterValue) {
- super(parameterName, declarationExpression, referenceExpression);
- this.parameterValue = parameterValue;
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (!super.equals(obj))
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ Parameter other = (Parameter) obj;
+ return Objects.equals(referenceExpression, other.referenceExpression);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/ParameterList.java b/src/main/java/eu/europa/ted/efx/model/variables/ParameterList.java
new file mode 100644
index 00000000..c7c56897
--- /dev/null
+++ b/src/main/java/eu/europa/ted/efx/model/variables/ParameterList.java
@@ -0,0 +1,20 @@
+package eu.europa.ted.efx.model.variables;
+
+import java.util.LinkedList;
+
+import eu.europa.ted.efx.model.ParsedEntity;
+import eu.europa.ted.efx.model.types.EfxDataType;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class ParameterList extends LinkedList implements ParsedEntity {
+
+ public Map> toMap() {
+ var map = new LinkedHashMap>();
+ for (Parameter parameter : this) {
+ map.put(parameter.name, parameter.dataType);
+ }
+ return map;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/model/variables/Variable.java b/src/main/java/eu/europa/ted/efx/model/variables/Variable.java
index 746d18f6..30a2ceaf 100644
--- a/src/main/java/eu/europa/ted/efx/model/variables/Variable.java
+++ b/src/main/java/eu/europa/ted/efx/model/variables/Variable.java
@@ -4,17 +4,15 @@
import eu.europa.ted.efx.model.expressions.TypedExpression;
public class Variable extends Identifier {
- final public TypedExpression initializationExpression;
+ public final Expression declarationExpression;
+ public final TypedExpression initializationExpression;
+ public final TypedExpression referenceExpression;
public Variable(String variableName, Expression declarationExpression, TypedExpression initializationExpression, TypedExpression referenceExpression) {
- super(variableName, declarationExpression, referenceExpression);
+ super(variableName, initializationExpression.getDataType());
+ this.declarationExpression = declarationExpression;
this.initializationExpression = initializationExpression;
+ this.referenceExpression = referenceExpression;
assert referenceExpression.getDataType() == initializationExpression.getDataType();
}
-
- public Variable(Identifier identifier, TypedExpression initializationExpression) {
- super(identifier.name, identifier.declarationExpression, identifier.referenceExpression);
- this.initializationExpression = initializationExpression;
- assert identifier.dataType == initializationExpression.getDataType();
- }
}
\ No newline at end of file
diff --git a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
index 21e5e147..6b1e45b3 100644
--- a/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
+++ b/src/main/java/eu/europa/ted/efx/sdk1/EfxExpressionTranslatorV1.java
@@ -1209,8 +1209,6 @@ private void exitParameterDeclaration(String parameterName, Class extends Type
}
Parameter parameter = new Parameter(parameterName,
- this.script.composeParameterDeclaration(parameterName, parameterType),
- this.script.composeVariableReference(parameterName, parameterType),
this.translateParameter(this.expressionParameters.pop(), parameterType));
this.stack.declareIdentifier(parameter);
}
diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java
index e4a06a87..03eab955 100644
--- a/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java
+++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxExpressionTranslatorV2.java
@@ -57,7 +57,10 @@
import eu.europa.ted.efx.model.expressions.sequence.TimeSequenceExpression;
import eu.europa.ted.efx.model.types.EfxDataType;
import eu.europa.ted.efx.model.types.FieldTypes;
+import eu.europa.ted.efx.model.variables.ArgumentList;
+import eu.europa.ted.efx.model.variables.Function;
import eu.europa.ted.efx.model.variables.Parameter;
+import eu.europa.ted.efx.model.variables.ParameterList;
import eu.europa.ted.efx.model.variables.Variable;
import eu.europa.ted.efx.sdk2.EfxParser.*;
@@ -88,6 +91,9 @@ public class EfxExpressionTranslatorV2 extends EfxBaseListener
EfxLexer.VOCABULARY.getLiteralName(EfxLexer.AttributePrefix).replaceAll("^'|'$", "");
private static final String CODELIST_PREFIX =
EfxLexer.VOCABULARY.getLiteralName(EfxLexer.CodelistPrefix).replaceAll("^'|'$", "");
+
+ private static final String FUNCTION_PREFIX =
+ EfxLexer.VOCABULARY.getLiteralName(EfxLexer.FunctionPrefix).replaceAll("^'|'$", "");
private static final String BEGIN_EXPRESSION_BLOCK = "{";
private static final String END_EXPRESSION_BLOCK = "}";
@@ -1234,6 +1240,77 @@ private void exitSequenceAtIndex(
// #endregion New in EFX-2: Indexers ---------------------------------------
+ // //#region New in EFX-2: Function invocation ------------------------------
+
+
+ @Override
+ public void enterFunctionInvocation(FunctionInvocationContext ctx) {
+ this.stack.push(this.stack.getFunction(getFunctionName(ctx)));
+ }
+
+ @Override
+ public void enterArgumentList(ArgumentListContext ctx) {
+ this.stack.push(new ArgumentList(this.stack.pop(Function.class)));
+ }
+
+ @Override
+ public void exitArgument(ArgumentContext ctx) {
+ var argument = this.stack.pop(TypedExpression.class); // pop the argument
+ var functionArguments = this.stack.pop(ArgumentList.class); // pop the function arguments
+ functionArguments.addArgument(argument); // add the argument to the list of arguments
+ this.stack.push(functionArguments); // push the updated list of arguments
+ }
+
+ @Override
+ public void exitArgumentList(ArgumentListContext ctx) {
+ var arguments = this.stack.pop(ArgumentList.class); // pop the function arguments
+ var countExpectedArguments = arguments.parameters.size();
+ var countPassedArguments = arguments.size();
+ if (countExpectedArguments != countPassedArguments) { // check if the number of passed arguments is correct
+ throw new ParseCancellationException("The function " + arguments.identifier + " expects "
+ + countExpectedArguments + " arguments, but " + countPassedArguments + " were passed.");
+ }
+ this.stack.push(arguments); // push the list of arguments back to the stack
+ }
+
+ @Override
+ public void exitStringFunctionInvocation(StringFunctionInvocationContext ctx) {
+ var parameters = this.stack.pop(ArgumentList.class).getArguments();
+ this.stack.push(this.script.composeFunctionInvocation(getFunctionName(ctx), parameters, StringExpression.class));
+ }
+
+ @Override
+ public void exitNumericFunctionInvocation(NumericFunctionInvocationContext ctx) {
+ var parameters = this.stack.pop(ArgumentList.class).getArguments();
+ this.stack.push(this.script.composeFunctionInvocation(getFunctionName(ctx), parameters, NumericExpression.class));
+ }
+
+ @Override
+ public void exitBooleanFunctionInvocation(BooleanFunctionInvocationContext ctx) {
+ var parameters = this.stack.pop(ArgumentList.class).getArguments();
+ this.stack.push(this.script.composeFunctionInvocation(getFunctionName(ctx), parameters, BooleanExpression.class));
+ }
+
+ @Override
+ public void exitDateFunctionInvocation(DateFunctionInvocationContext ctx) {
+ var parameters = this.stack.pop(ArgumentList.class).getArguments();
+ this.stack.push(this.script.composeFunctionInvocation(getFunctionName(ctx), parameters, DateExpression.class));
+ }
+
+ @Override
+ public void exitTimeFunctionInvocation(TimeFunctionInvocationContext ctx) {
+ var parameters = this.stack.pop(ArgumentList.class).getArguments();
+ this.stack.push(this.script.composeFunctionInvocation(getFunctionName(ctx), parameters, TimeExpression.class));
+ }
+
+ @Override
+ public void exitDurationFunctionInvocation(DurationFunctionInvocationContext ctx) {
+ var parameters = this.stack.pop(ArgumentList.class).getArguments();
+ this.stack.push(this.script.composeFunctionInvocation(getFunctionName(ctx), parameters, DurationExpression.class));
+ }
+
+ // #endregion New in EFX-2: Function invocation -----------------------------
+
// #region Parameter Declarations -------------------------------------------
@@ -1273,8 +1350,6 @@ private void exitParameterDeclaration(String parameterName, Class extends Type
}
Parameter parameter = new Parameter(parameterName,
- this.script.composeParameterDeclaration(parameterName, parameterType),
- this.script.composeVariableReference(parameterName, parameterType),
this.translateParameter(this.expressionParameters.pop(), parameterType));
this.stack.declareIdentifier(parameter);
}
@@ -1626,68 +1701,136 @@ private String getAttributeName(ScalarFromAttributeReferenceContext ctx) {
// #region Variable Names ---------------------------------------------------
- static protected String getVariableName(String efxVariableIdentifier) {
+ protected static String getVariableName(String efxVariableIdentifier) {
return StringUtils.substringAfter(efxVariableIdentifier, VARIABLE_PREFIX);
}
- static private String getVariableName(VariableReferenceContext ctx) {
+ private static String getVariableName(VariableReferenceContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static protected String getVariableName(ContextVariableDeclarationContext ctx) {
+ protected static String getVariableName(ContextVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(StringVariableDeclarationContext ctx) {
+ private static String getVariableName(StringVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(NumericVariableDeclarationContext ctx) {
+ private static String getVariableName(NumericVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(BooleanVariableDeclarationContext ctx) {
+ private static String getVariableName(BooleanVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(DateVariableDeclarationContext ctx) {
+ private static String getVariableName(DateVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(TimeVariableDeclarationContext ctx) {
+ private static String getVariableName(TimeVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(DurationVariableDeclarationContext ctx) {
+ private static String getVariableName(DurationVariableDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(StringParameterDeclarationContext ctx) {
+ protected static String getVariableName(StringParameterDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(NumericParameterDeclarationContext ctx) {
+ protected static String getVariableName(NumericParameterDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(BooleanParameterDeclarationContext ctx) {
+ protected static String getVariableName(BooleanParameterDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(DateParameterDeclarationContext ctx) {
+ protected static String getVariableName(DateParameterDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(TimeParameterDeclarationContext ctx) {
+ protected static String getVariableName(TimeParameterDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(DurationParameterDeclarationContext ctx) {
+ protected static String getVariableName(DurationParameterDeclarationContext ctx) {
return getVariableName(ctx.Variable().getText());
}
// #endregion Variable Names ---------------------------------------------------
+ // #region Function Names ---------------------------------------------------
+
+ protected static String getFunctionName(String functionName) {
+ return StringUtils.substringAfter(functionName, FUNCTION_PREFIX);
+ }
+
+ private static String getFunctionName(StringFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(StringFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(NumericFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(NumericFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(BooleanFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(BooleanFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(DateFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(DateFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(TimeFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(TimeFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(DurationFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(DurationFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(SequenceFromFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(ScalarFromFunctionInvocationContext ctx) {
+ return getFunctionName(ctx.functionInvocation().Function().getText());
+ }
+
+ private static String getFunctionName(FunctionInvocationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ // #endregion Function Names ----------------------------------------------
+
// #endregion Helpers -------------------------------------------------------
// #region Pre-processing ---------------------------------------------------
@@ -1817,6 +1960,21 @@ public void exitScalarFromAttributeReference(ScalarFromAttributeReferenceContext
this.rewriter.insertBefore(ctx.getStart(), "(" + textTypeName + ")");
}
+ @Override
+ public void exitScalarFromFunctionInvocation(ScalarFromFunctionInvocationContext ctx) {
+ if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) {
+ return;
+ }
+
+ String functionName = getFunctionName(ctx);
+ String functionType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(functionName));
+
+ if (functionType != null) {
+ // Insert the type cast
+ this.rewriter.insertBefore(ctx.functionInvocation().Function().getSymbol(), "(" + functionType + ")");
+ }
+ }
+
@Override
public void exitSequenceFromFieldReference(SequenceFromFieldReferenceContext ctx) {
if (!hasParentContextOfType(ctx, LateBoundSequenceContext.class)) {
@@ -1843,6 +2001,21 @@ public void exitSequenceFromAttributeReference(SequenceFromAttributeReferenceCon
this.rewriter.insertBefore(ctx.getStart(), "(" + textTypeName + ")");
}
+ @Override
+ public void exitSequenceFromFunctionInvocation(SequenceFromFunctionInvocationContext ctx) {
+ if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) {
+ return;
+ }
+
+ String functionName = getFunctionName(ctx);
+ String functionType = javaToEfxTypeMap.get(this.stack.getTypeOfIdentifier(functionName));
+
+ if (functionType != null) {
+ // Insert the type cast
+ this.rewriter.insertBefore(ctx.functionInvocation().Function().getSymbol(), "(" + functionType + ")");
+ }
+ }
+
@Override
public void exitVariableReference(VariableReferenceContext ctx) {
if (!hasParentContextOfType(ctx, LateBoundScalarContext.class)) {
@@ -1956,6 +2129,48 @@ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext
// #endregion Parameter declarations --------------------------------------
+ // #region Function declarations -----------------------------------------
+
+ @Override
+ public void exitStringFunctionDeclaration(StringFunctionDeclarationContext ctx) {
+ String functionName = getFunctionName(ctx);
+ this.stack.declareFunction(new Function(functionName, EfxDataType.String.class, new ParameterList(), StringExpression.empty()));
+ }
+
+ @Override
+ public void exitNumericFunctionDeclaration(NumericFunctionDeclarationContext ctx) {
+ String functionName = getFunctionName(ctx);
+ this.stack.declareFunction(new Function(functionName, EfxDataType.Number.class, new ParameterList(), NumericExpression.empty()));
+ }
+
+ @Override
+ public void exitBooleanFunctionDeclaration(BooleanFunctionDeclarationContext ctx) {
+ String functionName = getFunctionName(ctx);
+ this.stack.declareFunction(new Function(functionName, EfxDataType.Boolean.class, new ParameterList(), BooleanExpression.empty()));
+ }
+
+
+ @Override
+ public void exitDateFunctionDeclaration(DateFunctionDeclarationContext ctx) {
+ String functionName = getFunctionName(ctx);
+ this.stack.declareFunction(new Function(functionName, EfxDataType.Date.class, new ParameterList(), DateExpression.empty()));
+ }
+
+
+ @Override
+ public void exitTimeFunctionDeclaration(TimeFunctionDeclarationContext ctx) {
+ String functionName = getFunctionName(ctx);
+ this.stack.declareFunction(new Function(functionName, EfxDataType.Time.class, new ParameterList(), TimeExpression.empty()));
+ }
+
+ @Override
+ public void exitDurationFunctionDeclaration(DurationFunctionDeclarationContext ctx) {
+ String functionName = getFunctionName(ctx);
+ this.stack.declareFunction(new Function(functionName, EfxDataType.Duration.class, new ParameterList(), DurationExpression.empty()));
+ }
+
+ // #endregion Function declarations ---------------------------------------
+
// #region Scope management -----------------------------------------------
@Override
diff --git a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java
index 510852c1..e6ecd605 100644
--- a/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java
+++ b/src/main/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2.java
@@ -6,7 +6,6 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
-
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
@@ -46,20 +45,33 @@
import eu.europa.ted.efx.model.templates.Markup;
import eu.europa.ted.efx.model.types.EfxDataType;
import eu.europa.ted.efx.model.types.FieldTypes;
+import eu.europa.ted.efx.model.variables.Function;
+import eu.europa.ted.efx.model.variables.Identifier;
+import eu.europa.ted.efx.model.variables.Parameter;
+import eu.europa.ted.efx.model.variables.ParameterList;
import eu.europa.ted.efx.model.variables.Variable;
import eu.europa.ted.efx.model.variables.VariableList;
import eu.europa.ted.efx.sdk2.EfxParser.AssetIdContext;
import eu.europa.ted.efx.sdk2.EfxParser.AssetTypeContext;
+import eu.europa.ted.efx.sdk2.EfxParser.BooleanFunctionDeclarationContext;
+import eu.europa.ted.efx.sdk2.EfxParser.BooleanParameterDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.BooleanVariableInitializerContext;
import eu.europa.ted.efx.sdk2.EfxParser.ComputedLabelReferenceContext;
import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationBlockContext;
import eu.europa.ted.efx.sdk2.EfxParser.ContextDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.ContextVariableInitializerContext;
+import eu.europa.ted.efx.sdk2.EfxParser.DateFunctionDeclarationContext;
+import eu.europa.ted.efx.sdk2.EfxParser.DateParameterDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.DateVariableInitializerContext;
+import eu.europa.ted.efx.sdk2.EfxParser.DurationFunctionDeclarationContext;
+import eu.europa.ted.efx.sdk2.EfxParser.DurationParameterDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.DurationVariableInitializerContext;
import eu.europa.ted.efx.sdk2.EfxParser.ExpressionTemplateContext;
+import eu.europa.ted.efx.sdk2.EfxParser.GlobalVariableDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.LabelTemplateContext;
import eu.europa.ted.efx.sdk2.EfxParser.LabelTypeContext;
+import eu.europa.ted.efx.sdk2.EfxParser.NumericFunctionDeclarationContext;
+import eu.europa.ted.efx.sdk2.EfxParser.NumericParameterDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.NumericVariableInitializerContext;
import eu.europa.ted.efx.sdk2.EfxParser.SecondaryTemplateContext;
import eu.europa.ted.efx.sdk2.EfxParser.ShorthandBtLabelReferenceContext;
@@ -70,11 +82,15 @@
import eu.europa.ted.efx.sdk2.EfxParser.ShorthandLabelReferenceFromContextContext;
import eu.europa.ted.efx.sdk2.EfxParser.StandardExpressionBlockContext;
import eu.europa.ted.efx.sdk2.EfxParser.StandardLabelReferenceContext;
+import eu.europa.ted.efx.sdk2.EfxParser.StringFunctionDeclarationContext;
+import eu.europa.ted.efx.sdk2.EfxParser.StringParameterDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.StringVariableInitializerContext;
import eu.europa.ted.efx.sdk2.EfxParser.TemplateFileContext;
import eu.europa.ted.efx.sdk2.EfxParser.TemplateLineContext;
-import eu.europa.ted.efx.sdk2.EfxParser.TemplateVariableListContext;
+import eu.europa.ted.efx.sdk2.EfxParser.TemplateVariableDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.TextTemplateContext;
+import eu.europa.ted.efx.sdk2.EfxParser.TimeFunctionDeclarationContext;
+import eu.europa.ted.efx.sdk2.EfxParser.TimeParameterDeclarationContext;
import eu.europa.ted.efx.sdk2.EfxParser.TimeVariableInitializerContext;
/**
@@ -217,6 +233,83 @@ private String getTranslatedMarkup() {
return sb.toString().trim();
}
+ // #region Global declarationExpressions ---------------------------------------
+
+ @Override
+ public void exitGlobalVariableDeclaration(GlobalVariableDeclarationContext ctx) {
+ var variable = this.stack.pop(Variable.class);
+ this.stack.declareGlobalIdentifier(variable);
+ }
+
+ @Override
+ public void enterStringFunctionDeclaration(StringFunctionDeclarationContext ctx) {
+ this.stack.push(new ParameterList());
+ }
+
+ @Override
+ public void exitStringFunctionDeclaration(StringFunctionDeclarationContext ctx) {
+ this.exitFunctionDeclaration(getFunctionName(ctx), EfxDataType.String.class);
+ }
+
+ @Override
+ public void enterBooleanFunctionDeclaration(BooleanFunctionDeclarationContext ctx) {
+ this.stack.push(new ParameterList());
+ }
+
+ @Override
+ public void exitBooleanFunctionDeclaration(BooleanFunctionDeclarationContext ctx) {
+ this.exitFunctionDeclaration(getFunctionName(ctx), EfxDataType.Boolean.class);
+ }
+
+ @Override
+ public void enterNumericFunctionDeclaration(NumericFunctionDeclarationContext ctx) {
+ this.stack.push(new ParameterList());
+ }
+
+ @Override
+ public void exitNumericFunctionDeclaration(NumericFunctionDeclarationContext ctx) {
+ this.exitFunctionDeclaration(getFunctionName(ctx), EfxDataType.Number.class);
+ }
+
+ @Override
+ public void enterDateFunctionDeclaration(DateFunctionDeclarationContext ctx) {
+ this.stack.push(new ParameterList());
+ }
+
+ @Override
+ public void exitDateFunctionDeclaration(DateFunctionDeclarationContext ctx) {
+ this.exitFunctionDeclaration(getFunctionName(ctx), EfxDataType.Date.class);
+ }
+
+ @Override
+ public void enterTimeFunctionDeclaration(TimeFunctionDeclarationContext ctx) {
+ this.stack.push(new ParameterList());
+ }
+
+ @Override
+ public void exitTimeFunctionDeclaration(TimeFunctionDeclarationContext ctx) {
+ this.exitFunctionDeclaration(getFunctionName(ctx), EfxDataType.Time.class);
+ }
+
+ @Override
+ public void enterDurationFunctionDeclaration(DurationFunctionDeclarationContext ctx) {
+ this.stack.push(new ParameterList());
+ }
+
+ @Override
+ public void exitDurationFunctionDeclaration(DurationFunctionDeclarationContext ctx) {
+ this.exitFunctionDeclaration(getFunctionName(ctx), EfxDataType.Duration.class);
+ }
+
+ private void exitFunctionDeclaration(String functionName, Class extends EfxDataType> returnType) {
+ var expression = this.stack.pop(TypedExpression.class);
+ var parameters = this.stack.pop(ParameterList.class);
+
+ this.stack.declareFunction(new Function(functionName, returnType, parameters, expression));
+ }
+
+ // #endregion Global declarationExpressions ------------------------------------
+
// #region Template File ----------------------------------------------------
@Override
@@ -228,13 +321,24 @@ public void enterTemplateFile(TemplateFileContext ctx) {
public void exitTemplateFile(TemplateFileContext ctx) {
this.blockStack.pop();
+ List globals = new ArrayList<>();
+ for (Identifier identifier : this.stack.getGlobals()) {
+ if (identifier instanceof Variable) {
+ Variable variable = (Variable) identifier;
+ globals.add(this.markup.renderVariableDeclaration(variable.dataType, variable.name, variable.initializationExpression));
+ } else if (identifier instanceof Function) {
+ Function function = (Function) identifier;
+ globals.add(this.markup.renderFunctionDeclaration(function.dataType, function.name, function.parameters.toMap(), function.expression));
+ }
+ }
+
List templateCalls = new ArrayList<>();
List templates = new ArrayList<>();
- for (ContentBlock rootBlock : this.rootBlock.getChildren()) {
- templateCalls.add(rootBlock.renderCallTemplate(markup));
- rootBlock.renderTemplate(markup, templates);
+ for (ContentBlock block : this.rootBlock.getChildren()) {
+ templateCalls.add(block.renderCallTemplate(markup));
+ block.renderTemplate(markup, templates);
}
- Markup file = this.markup.composeOutputFile(templateCalls, templates);
+ Markup file = this.markup.composeOutputFile(globals, templateCalls, templates);
this.stack.push(file);
}
@@ -734,20 +838,26 @@ public void exitDurationVariableInitializer(DurationVariableInitializerContext c
private void exitVariableInitializer(
String variableName, Class extends ScalarExpression> expressionType) {
var expression = this.stack.pop(expressionType);
- VariableList variables = this.stack.pop(VariableList.class);
try {
var variable = new Variable(variableName,
this.script.composeVariableDeclaration(variableName, expression.getClass()),
expression,
this.script.composeVariableReference(variableName, expression.getClass()));
- variables.add(variable);
- this.stack.push(variables);
- this.stack.declareIdentifier(variable);
+ this.stack.push(variable);
} catch (Exception e) {
throw new ParseCancellationException(e);
}
}
+ @Override
+ public void exitTemplateVariableDeclaration(TemplateVariableDeclarationContext arg0) {
+ var variable = this.stack.pop(Variable.class);
+ this.stack.declareIdentifier(variable);
+ VariableList variables = this.stack.pop(VariableList.class);
+ variables.add(variable);
+ this.stack.push(variables);
+ }
+
// #endregion Variable Initializers -----------------------------------------
// #endregion New in EFX-2 --------------------------------------------------
@@ -762,6 +872,49 @@ public void enterContextDeclarationBlock(ContextDeclarationBlockContext arg0) {
// #endregion Context Declaration Blocks {...} ------------------------------
+ // #region Parameter Declarations -------------------------------------------
+
+ @Override
+ public void exitStringParameterDeclaration(StringParameterDeclarationContext ctx) {
+ this.exitParameterDeclaration(getVariableName(ctx), StringExpression.class);
+ }
+
+ @Override
+ public void exitNumericParameterDeclaration(NumericParameterDeclarationContext ctx) {
+ this.exitParameterDeclaration(getVariableName(ctx), NumericExpression.class);
+ }
+
+ @Override
+ public void exitBooleanParameterDeclaration(BooleanParameterDeclarationContext ctx) {
+ this.exitParameterDeclaration(getVariableName(ctx), BooleanExpression.class);
+ }
+
+ @Override
+ public void exitDateParameterDeclaration(DateParameterDeclarationContext ctx) {
+ this.exitParameterDeclaration(getVariableName(ctx), DateExpression.class);
+ }
+
+ @Override
+ public void exitTimeParameterDeclaration(TimeParameterDeclarationContext ctx) {
+ this.exitParameterDeclaration(getVariableName(ctx), TimeExpression.class);
+ }
+
+ @Override
+ public void exitDurationParameterDeclaration(DurationParameterDeclarationContext ctx) {
+ this.exitParameterDeclaration(getVariableName(ctx), DurationExpression.class);
+ }
+
+ private void exitParameterDeclaration(String parameterName, Class extends TypedExpression> parameterType) {
+ Parameter parameter = new Parameter(parameterName,
+ this.script.composeParameterReference(parameterName, parameterType));
+ this.stack.declareIdentifier(parameter);
+ var parameterList = this.stack.pop(ParameterList.class);
+ parameterList.add(parameter);
+ this.stack.push(parameterList);
+ }
+
+ // #endregion Parameter Declarations ----------------------------------------
+
// #region Template lines --------------------------------------------------
@Override
@@ -878,34 +1031,66 @@ private int getIndentLevel(TemplateLineContext ctx) {
return 0;
}
- static private String getVariableName(StringVariableInitializerContext ctx) {
+ // #region Variable Names --------------------------------------------------
+
+ private static String getVariableName(StringVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(NumericVariableInitializerContext ctx) {
+ private static String getVariableName(NumericVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(BooleanVariableInitializerContext ctx) {
+ private static String getVariableName(BooleanVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(DateVariableInitializerContext ctx) {
+ private static String getVariableName(DateVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(TimeVariableInitializerContext ctx) {
+ private static String getVariableName(TimeVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(DurationVariableInitializerContext ctx) {
+ private static String getVariableName(DurationVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
- static private String getVariableName(ContextVariableInitializerContext ctx) {
+ private static String getVariableName(ContextVariableInitializerContext ctx) {
return getVariableName(ctx.Variable().getText());
}
+ // #endregion Variable Names -----------------------------------------------
+
+ // #region Function Names --------------------------------------------------
+
+ private static String getFunctionName(StringFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(BooleanFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(NumericFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(DateFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(TimeFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ private static String getFunctionName(DurationFunctionDeclarationContext ctx) {
+ return getFunctionName(ctx.Function().getText());
+ }
+
+ // #endregion Function Names -----------------------------------------------
+
// #endregion Helpers -------------------------------------------------------
// #region Pre-processing -------------------------------------------------
@@ -929,13 +1114,25 @@ String processTemplate() {
// #region Template Variables ---------------------------------------------
+ @Override
+ public void exitGlobalVariableDeclaration(GlobalVariableDeclarationContext arg0) {
+ var variable = this.stack.pop(Variable.class);
+ this.stack.declareGlobalIdentifier(variable);
+ }
+
+ @Override
+ public void exitTemplateVariableDeclaration(TemplateVariableDeclarationContext arg0) {
+ var variable = this.stack.pop(Variable.class);
+ this.stack.declareIdentifier(variable);
+ }
+
@Override
public void exitContextDeclaration(ContextDeclarationContext ctx) {
- final String filedId = getFieldIdFromChildSimpleFieldReferenceContext(ctx);
- if (filedId != null) {
+ final String fieldId = getFieldIdFromChildSimpleFieldReferenceContext(ctx);
+ if (fieldId != null) {
final ContextVariableInitializerContext initializer = ctx.contextVariableInitializer();
if (initializer != null) {
- var t = FieldTypes.fromString(this.symbols.getTypeOfField(filedId));
+ var t = FieldTypes.fromString(this.symbols.getTypeOfField(fieldId));
this.stack.declareIdentifier(new Variable(getVariableName(initializer), PathExpression.instantiate("", t), PathExpression.instantiate("", t), PathExpression.instantiate("", t)));
}
}
@@ -943,39 +1140,39 @@ public void exitContextDeclaration(ContextDeclarationContext ctx) {
@Override
public void exitStringVariableInitializer(StringVariableInitializerContext ctx) {
- this.stack.declareIdentifier(new Variable(getVariableName(ctx), StringExpression.empty(), StringExpression.empty(), StringExpression.empty()));
+ this.stack.push(new Variable(getVariableName(ctx), StringExpression.empty(), StringExpression.empty(), StringExpression.empty()));
}
@Override
public void exitBooleanVariableInitializer(BooleanVariableInitializerContext ctx) {
- this.stack.declareIdentifier(new Variable(getVariableName(ctx), BooleanExpression.empty(), BooleanExpression.empty(), BooleanExpression.empty()));
+ this.stack.push(new Variable(getVariableName(ctx), BooleanExpression.empty(), BooleanExpression.empty(), BooleanExpression.empty()));
}
@Override
public void exitNumericVariableInitializer(NumericVariableInitializerContext ctx) {
- this.stack.declareIdentifier(new Variable(getVariableName(ctx), NumericExpression.empty(), NumericExpression.empty(), NumericExpression.empty()));
+ this.stack.push(new Variable(getVariableName(ctx), NumericExpression.empty(), NumericExpression.empty(), NumericExpression.empty()));
}
@Override
public void exitDateVariableInitializer(DateVariableInitializerContext ctx) {
- this.stack.declareIdentifier(new Variable(getVariableName(ctx), DateExpression.empty(), DateExpression.empty(), DateExpression.empty()));
+ this.stack.push(new Variable(getVariableName(ctx), DateExpression.empty(), DateExpression.empty(), DateExpression.empty()));
}
@Override
public void exitTimeVariableInitializer(TimeVariableInitializerContext ctx) {
- this.stack.declareIdentifier(new Variable(getVariableName(ctx), TimeExpression.empty(), TimeExpression.empty(), TimeExpression.empty()));
+ this.stack.push(new Variable(getVariableName(ctx), TimeExpression.empty(), TimeExpression.empty(), TimeExpression.empty()));
}
@Override
public void exitDurationVariableInitializer(DurationVariableInitializerContext ctx) {
- this.stack.declareIdentifier(new Variable(getVariableName(ctx), DurationExpression.empty(), DurationExpression.empty(), DurationExpression.empty()));
+ this.stack.push(new Variable(getVariableName(ctx), DurationExpression.empty(), DurationExpression.empty(), DurationExpression.empty()));
}
// #endregion Template Variables ------------------------------------------
// #region Scope management --------------------------------------------
- Stack levels = new Stack();
+ Stack levels = new Stack<>();
@Override
public void enterTemplateLine(TemplateLineContext ctx) {
@@ -1017,13 +1214,10 @@ public void exitTemplateLine(TemplateLineContext ctx) {
throw new ParseCancellationException(START_INDENT_AT_ZERO);
}
this.levels.push(this.levels.peek() + 1);
- } else if (indentChange == 0) {
-
- if (this.levels.isEmpty()) {
+ } else if (indentChange == 0 && this.levels.isEmpty()) {
assert indentLevel == 0 : UNEXPECTED_INDENTATION;
this.levels.push(0);
}
- }
}
// #endregion Scope management --------------------------------------------
diff --git a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
index 3c33cf26..1fb6248b 100644
--- a/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
+++ b/src/main/java/eu/europa/ted/efx/xpath/XPathScriptGenerator.java
@@ -123,6 +123,11 @@ public T composeVariableReference(String variableNam
return Expression.instantiate("$" + variableName, type);
}
+ @Override
+ public T composeParameterReference(String parameterName, Class type) {
+ return Expression.instantiate("$" + parameterName, type);
+ }
+
@Override
public T composeVariableDeclaration(String variableName, Class type) {
return Expression.instantiate("$" + variableName, type);
@@ -548,6 +553,16 @@ public T composeExceptFunction(T listOne, T listT
//#endregion Duration functions ---------------------------------------------
+ @Override
+ public T composeFunctionInvocation(String functionName, List extends TypedExpression> parameters,
+ Class type) {
+ String namespace = translatorOptions.getUserDefinedFunctionNamespace();
+ String qualifiedFunctionName = (namespace != null && !namespace.isEmpty())
+ ? namespace + ":" + functionName
+ : functionName;
+ return Expression.instantiate(qualifiedFunctionName + "(" + parameters.stream().map(p -> p.getScript()).collect(Collectors.joining(", ")) + ")", type);
+ }
+
//#region Helpers -----------------------------------------------------------
@@ -556,7 +571,7 @@ private String quoted(final String text) {
}
private int getWeeksFromDurationLiteral(final String literal) {
- Matcher weeksMatcher = Pattern.compile("(?<=[^0-9])[0-9]+(?=W)").matcher(literal);
+ Matcher weeksMatcher = Pattern.compile("(?<=\\D)\\d+(?=W)").matcher(literal);
return weeksMatcher.find() ? Integer.parseInt(weeksMatcher.group()) : 0;
}
diff --git a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java
index f5f10827..f8bf9af4 100644
--- a/src/test/java/eu/europa/ted/efx/EfxTestsBase.java
+++ b/src/test/java/eu/europa/ted/efx/EfxTestsBase.java
@@ -1,10 +1,16 @@
package eu.europa.ted.efx;
import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import eu.europa.ted.efx.interfaces.TranslatorOptions;
import eu.europa.ted.efx.mock.DependencyFactoryMock;
import eu.europa.ted.efx.model.DecimalFormat;
public abstract class EfxTestsBase {
+
+ protected static final TranslatorOptions DEFAULT_OPTIONS = new EfxTranslatorOptions("udf",
+ DecimalFormat.EFX_DEFAULT);
+
protected abstract String getSdkVersion();
protected void testExpressionTranslationWithContext(final String expectedTranslation,
@@ -24,7 +30,7 @@ protected String translateExpressionWithContext(final String context, final Stri
protected String translateExpression(final String expression, final String... params) {
try {
return EfxTranslator.translateExpression(DependencyFactoryMock.INSTANCE, getSdkVersion(),
- expression, new EfxTranslatorOptions(DecimalFormat.EFX_DEFAULT), params);
+ expression, DEFAULT_OPTIONS, params);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
@@ -33,7 +39,7 @@ protected String translateExpression(final String expression, final String... pa
protected String translateTemplate(final String template) {
try {
return EfxTranslator.translateTemplate(DependencyFactoryMock.INSTANCE, getSdkVersion(),
- template + "\n");
+ template + "\n", DEFAULT_OPTIONS);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
diff --git a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
index 826d6bf3..b6251e8d 100644
--- a/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
+++ b/src/test/java/eu/europa/ted/efx/mock/MarkupGeneratorMock.java
@@ -1,6 +1,8 @@
package eu.europa.ted.efx.mock;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@@ -13,9 +15,27 @@
import eu.europa.ted.efx.model.expressions.scalar.NumericExpression;
import eu.europa.ted.efx.model.expressions.scalar.StringExpression;
import eu.europa.ted.efx.model.templates.Markup;
+import eu.europa.ted.efx.model.types.EfxDataType;
public class MarkupGeneratorMock implements MarkupGenerator {
+ @Override
+ public Markup renderVariableDeclaration(Class extends EfxDataType> dataType, String variableName,
+ Expression initialiser) {
+ return new Markup(String.format("%s:$%s=%s", dataType.getSimpleName(), variableName, initialiser.getScript()));
+ }
+
+ @Override
+ public Markup renderFunctionDeclaration(Class extends EfxDataType> type, String name, Map> parameters,
+ Expression expression) {
+ return new Markup(
+ String.format("%s:%s(%s) -> { %s }", type.getSimpleName(), name,
+ parameters.entrySet().stream()
+ .map(entry -> entry.getValue().getSimpleName() + ":$" + entry.getKey())
+ .collect(Collectors.joining(", ")),
+ expression.getScript()));
+ }
+
@Override
public Markup renderVariableExpression(Expression valueReference) {
return new Markup(String.format("eval(%s)", valueReference.getScript()));
@@ -73,14 +93,20 @@ public Markup renderFragmentInvocation(String name, PathExpression context,
Set> variables) {
return new Markup(String.format("for-each(%s).call(%s(%s))", context.getScript(), name,
variables.stream()
- .map(v -> String.format("%s:%s", v.getLeft(), v.getRight()))
+ .map(v -> String.format("%s:=%s", v.getLeft(), v.getRight()))
.collect(Collectors.joining(", "))));
}
@Override
public Markup composeOutputFile(List body, List templates) {
- return new Markup(String.format("%s\n%s",
+ return this.composeOutputFile(new ArrayList(), body, templates);
+ }
+
+ @Override
+ public Markup composeOutputFile(List globals, List body, List templates) {
+ return new Markup(String.format("%s\n%s\n%s",
+ globals.stream().map(t -> t.script).collect(Collectors.joining("\n")),
templates.stream().map(t -> t.script).collect(Collectors.joining("\n")),
- body.stream().map(t -> t.script).collect(Collectors.joining("\n"))));
+ body.stream().map(t -> t.script).collect(Collectors.joining("\n"))).trim());
}
}
diff --git a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java
index 7ed9036b..5f0a806a 100644
--- a/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java
+++ b/src/test/java/eu/europa/ted/efx/sdk2/EfxTemplateTranslatorV2Test.java
@@ -12,7 +12,34 @@ protected String getSdkVersion() {
return "eforms-sdk-2.0";
}
- /*** Template line ***/
+ // #region Globals ----------------------------------------------------------
+
+ @Test
+ void testGlobals_VariableDeclaration() {
+ assertEquals(
+ lines(
+ "String:$t3='a'",
+ "Number:$n1=12",
+ "String:$t1=$t3",
+ "String:test(String:$p1, Number:$p2) -> { concat($p1, $t1) }",
+ "Number:$n2=$n1 + 1",
+ "String:$t4=udf:test($t3, 22)",
+ "let block01(t2) -> { eval(PathNode/TextField/normalize-space(text())) }",
+ "for-each(/*).call(block01(t2:=udf:test($t3, 99)))"), //
+ translateTemplate(lines(
+ "// comment", //
+ "{text:$t3='a'}// comment",
+ "{number:$n1=12}",
+ "{text:$t1=$t3}",
+ "{text:?test(text:$p1, number:$p2) = concat($p1, $t1)}",
+ "{number:$n2 = $n1 + 1}",
+ "{text:$t4= ?test($t3, 22)}",
+ "{ND-Root, text:$t2=?test($t3, 99)} ${BT-00-Text}")));
+ }
+
+ // #endregion Globals -------------------------------------------------------
+
+ // #region Template line ----------------------------------------------------
@Test
void testTemplateLineNoIdent() {
@@ -176,18 +203,18 @@ void testTemplateLine_VariableScope() {
@Test
void testTemplateLine_ContextVariable() {
assertEquals(
- lines("let block01(ctx, t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", //
- "for-each(.).call(block0101(ctx:$ctx, t:$t, t2:'test'))", //
- "for-each(.).call(block0102(ctx:$ctx, t:$t, t2:'test3')) }", //
- "let block0101(ctx, t, t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", //
- "for-each(.).call(block010101(ctx:$ctx, t:$t, t2:$t2))", //
- "for-each(.).call(block010102(ctx:$ctx, t:$t, t2:$t2)) }", //
- "let block010101(ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", //
- "let block010102(ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", //
- "let block0102(ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", //
- "for-each(/*/PathNode/TextField).call(block01(ctx:., t:./normalize-space(text())))"), //
+ lines("let block01(xyz, ctx, t) -> { #1: eval(for $x in ./normalize-space(text()) return concat($x, $t))", //
+ "for-each(.).call(block0101(xyz:=$xyz, ctx:=$ctx, t:=$t, t2:='test'))", //
+ "for-each(.).call(block0102(xyz:=$xyz, ctx:=$ctx, t:=$t, t2:='test3')) }", //
+ "let block0101(xyz, ctx, t, t2) -> { #1.1: eval(for $y in ./normalize-space(text()) return concat($y, $t, $t2))", //
+ "for-each(.).call(block010101(xyz:=$xyz, ctx:=$ctx, t:=$t, t2:=$t2))", //
+ "for-each(.).call(block010102(xyz:=$xyz, ctx:=$ctx, t:=$t, t2:=$t2)) }", //
+ "let block010101(xyz, ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", //
+ "let block010102(xyz, ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t, $ctx)) }", //
+ "let block0102(xyz, ctx, t, t2) -> { eval(for $z in ./normalize-space(text()) return concat($z, $t2, $ctx)) }", //
+ "for-each(/*/PathNode/TextField).call(block01(xyz:='a', ctx:=., t:=./normalize-space(text())))"), //
translateTemplate(lines(
- "{context:$ctx = BT-00-Text, text:$t = BT-00-Text} ${for text:$x in BT-00-Text return concat($x, $t)}",
+ "{text:$xyz='a', context:$ctx = BT-00-Text, text:$t = BT-00-Text} ${for text:$x in BT-00-Text return concat($x, $t)}",
" {BT-00-Text, text:$t2 = 'test'} ${for text:$y in BT-00-Text return concat($y, $t, $t2)}",
" {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}",
" {BT-00-Text} ${for text:$z in BT-00-Text return concat($z, $t, $ctx)}",
@@ -238,8 +265,9 @@ void testTemplateLine_WithLineBreak() {
translateTemplate("{BT-00-Text} #{field|name|BT-00-Text} \\n"));
}
+ // #endregion Template line -------------------------------------------------
- /*** Labels ***/
+ // #region Labels -----------------------------------------------------------
@Test
void testStandardLabelReference() {
@@ -395,8 +423,9 @@ void testShorthandIndirectLabelReferenceFromContextField_WithNodeContext() {
assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} #value"));
}
+ // #endregion Labels --------------------------------------------------------
- /*** Expression block ***/
+ // #region Expression block -------------------------------------------------
@Test
void testShorthandFieldValueReferenceFromContextField() {
@@ -416,8 +445,9 @@ void testShorthandFieldValueReferenceFromContextField_WithNodeContext() {
assertThrows(ParseCancellationException.class, () -> translateTemplate("{ND-Root} $value"));
}
+ // #endregion Expression block ----------------------------------------------
- /*** Other ***/
+ // #region Other -----------------------------------------------------------
@Test
void testNestedExpression() {
@@ -432,4 +462,6 @@ void testEndOfLineComments() {
"let block01() -> { label(concat('field', '|', 'name', '|', 'BT-00-Text'))text(' blah blah') }\nfor-each(/*).call(block01())",
translateTemplate("{ND-Root} #{name|BT-00-Text} blah blah // comment blah blah"));
}
+
+ // #endregion Other --------------------------------------------------------
}