Skip to content

Commit 4e3f3be

Browse files
committed
refactoring of expression api to make multiply arity support
1 parent c372793 commit 4e3f3be

File tree

68 files changed

+425
-394
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+425
-394
lines changed

jcp-tests/jcp-test-maven-action/jcp-test-maven-action-action/src/main/java/com/igormaznitsa/jcp/it/CustomPreprocessorExtension.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
import com.igormaznitsa.jcp.expression.Value;
55
import com.igormaznitsa.jcp.extension.PreprocessorExtension;
66
import java.util.Arrays;
7+
import java.util.Set;
8+
import java.util.List;
79

810
public class CustomPreprocessorExtension implements PreprocessorExtension {
911

12+
private static final Set<Integer> ARITY = Set.of(1);
13+
1014
@Override
1115
public boolean hasAction(int arity) {
1216
return true;
@@ -27,9 +31,9 @@ public boolean processAction(PreprocessorContext context, Value[] parameters) {
2731
}
2832

2933
@Override
30-
public int getUserFunctionArity(String functionName) {
34+
public Set<Integer> getUserFunctionArity(String functionName) {
3135
if (functionName.equals("hellofunc")) {
32-
return 1;
36+
return ARITY;
3337
} else {
3438
throw new IllegalArgumentException("Unexpected user function: " + functionName);
3539
}
@@ -39,9 +43,9 @@ public int getUserFunctionArity(String functionName) {
3943
public Value processUserFunction(
4044
PreprocessorContext context,
4145
String functionName,
42-
Value[] arguments) {
46+
List<Value> arguments) {
4347
if (functionName.equals("hellofunc")) {
44-
return Value.valueOf("Hello " + arguments[0].toString() + "!");
48+
return Value.valueOf("Hello " + arguments.get(0).toString() + "!");
4549
} else {
4650
throw new IllegalArgumentException("Unexpected user function call: " + functionName);
4751
}

jcp/src/main/java/com/igormaznitsa/jcp/InfoHelper.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,16 @@ private static String makeFunctionReference(final AbstractFunction func) {
192192
final String result = func.getResultType().getSignature().toUpperCase(Locale.ROOT);
193193

194194
int variantIndex = 0;
195-
for (ValueType[] signature : func.getAllowedArgumentTypes()) {
195+
for (final List<ValueType> signature : func.getAllowedArgumentTypes()) {
196196
if (variantIndex > 0) {
197197
variants.append(" | ");
198198
}
199199
variants.append(result).append(' ').append(funcName).append(" (");
200-
for (int i = 0; i < signature.length; i++) {
200+
for (int i = 0; i < signature.size(); i++) {
201201
if (i > 0) {
202202
variants.append(',');
203203
}
204-
variants.append(signature[i].getSignature().toUpperCase(Locale.ROOT));
204+
variants.append(signature.get(i).getSignature().toUpperCase(Locale.ROOT));
205205
}
206206
variants.append(')');
207207
variantIndex++;

jcp/src/main/java/com/igormaznitsa/jcp/expression/Expression.java

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@
3030
import java.io.IOException;
3131
import java.lang.reflect.InvocationTargetException;
3232
import java.lang.reflect.Method;
33+
import java.util.ArrayList;
3334
import java.util.Arrays;
35+
import java.util.List;
3436
import java.util.Objects;
3537

3638
/**
@@ -89,12 +91,21 @@ public static Value evalTree(final ExpressionTree tree, final PreprocessorContex
8991
return exp.eval(context);
9092
}
9193

92-
private ExpressionTreeElement evalFunction(final ExpressionTreeElement functionElement,
93-
final PreprocessorContext context) {
94-
final AbstractFunction function = (AbstractFunction) functionElement.getItem();
94+
private ExpressionTreeElement evalFunction(
95+
final ExpressionTreeElement treeElement,
96+
final PreprocessorContext context) {
9597

96-
final int arity = function.getArity();
97-
final Value[] arguments = new Value[arity];
98+
final AbstractFunction functionElement = (AbstractFunction) treeElement.getItem();
99+
final List<ExpressionTreeElement> children = treeElement.extractEffectiveChildren();
100+
101+
if (!functionElement.getArity().contains(children.size())) {
102+
throw context
103+
.makeException(
104+
"Can't find '" + functionElement.getName() + "' for arity " + children.size(), null);
105+
}
106+
107+
final int arity = children.size();
108+
final List<Value> arguments = new ArrayList<>();
98109
final Class<?>[] methodArguments = new Class<?>[arity + 1];
99110
methodArguments[0] = PreprocessorContext.class;
100111

@@ -112,27 +123,27 @@ private ExpressionTreeElement evalFunction(final ExpressionTreeElement functionE
112123

113124
for (int i = 0; i < arity; i++) {
114125
final ExpressionTreeElement item =
115-
this.calculateTreeElement(functionElement.getChildForIndex(i), context);
126+
this.calculateTreeElement(children.get(i), context);
116127

117128
final ExpressionItem itemValue = item.getItem();
118129

119130
if (itemValue instanceof Value) {
120-
arguments[i] = (Value) itemValue;
131+
arguments.add((Value) itemValue);
121132
} else {
122133
throw context.makeException(
123-
"[Expression]Wrong argument type detected for the '" + function.getName() +
134+
"[Expression]Wrong argument type detected for the '" + functionElement.getName() +
124135
"' function", null);
125136
}
126137
}
127138

128-
final ValueType[][] allowedSignatures = function.getAllowedArgumentTypes();
129-
ValueType[] allowed = null;
130-
for (final ValueType[] current : allowedSignatures) {
139+
final List<List<ValueType>> allowedSignatures = functionElement.getAllowedArgumentTypes();
140+
List<ValueType> allowed = null;
141+
for (final List<ValueType> current : allowedSignatures) {
131142
boolean allCompatible = true;
132143

133144
int thatIndex = 0;
134145
for (final ValueType type : current) {
135-
if (!type.isCompatible(arguments[thatIndex].getType())) {
146+
if (!type.isCompatible(arguments.get(thatIndex).getType())) {
136147
allCompatible = false;
137148
break;
138149
}
@@ -150,11 +161,12 @@ private ExpressionTreeElement evalFunction(final ExpressionTreeElement functionE
150161

151162
if (allowed == null) {
152163
throw context.makeException(
153-
"[Expression]Unsupported argument detected for '" + function.getName() + '\'', null);
164+
"[Expression]Unsupported argument detected for '" + functionElement.getName() + '\'',
165+
null);
154166
}
155167

156-
if (function instanceof FunctionDefinedByUser) {
157-
final FunctionDefinedByUser userFunction = (FunctionDefinedByUser) function;
168+
if (functionElement instanceof FunctionDefinedByUser) {
169+
final FunctionDefinedByUser userFunction = (FunctionDefinedByUser) functionElement;
158170
try {
159171
return new ExpressionTreeElement(userFunction.execute(context, arguments), stack, sources);
160172
} catch (Exception unexpected) {
@@ -164,15 +176,16 @@ private ExpressionTreeElement evalFunction(final ExpressionTreeElement functionE
164176
}
165177
} else {
166178
try {
167-
final Method method = function.getClass().getMethod(signature.toString(), methodArguments);
179+
final Method method =
180+
functionElement.getClass().getMethod(signature.toString(), methodArguments);
168181

169182
final Object[] callArgs = new Object[arity + 1];
170183
callArgs[0] = context;
171-
System.arraycopy(arguments, 0, callArgs, 1, arity);
184+
System.arraycopy(arguments.toArray(), 0, callArgs, 1, arity);
172185

173-
final Value result = (Value) method.invoke(function, callArgs);
186+
final Value result = (Value) method.invoke(functionElement, callArgs);
174187

175-
if (!result.getType().isCompatible(function.getResultType())) {
188+
if (!result.getType().isCompatible(functionElement.getResultType())) {
176189
throw context.makeException("[Expression]Unsupported function result detected [" +
177190
result.getType().getSignature() + ']', null);
178191
}
@@ -189,7 +202,7 @@ private ExpressionTreeElement evalFunction(final ExpressionTreeElement functionE
189202
}
190203
throw context.makeException(
191204
"[Expression]Can't execute a function method to process data [" +
192-
function.getClass().getName() + '.' + signature + ']', unexpected);
205+
functionElement.getClass().getName() + '.' + signature + ']', unexpected);
193206
}
194207
}
195208
}
@@ -323,7 +336,7 @@ private ExpressionTreeElement calculateTreeElement(final ExpressionTreeElement e
323336
}
324337
break;
325338
case FUNCTION: {
326-
treeElement = evalFunction(element, context);
339+
treeElement = this.evalFunction(element, context);
327340
}
328341
break;
329342
}

jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionParser.java

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.ArrayList;
3939
import java.util.List;
4040
import java.util.Locale;
41+
import java.util.stream.Collectors;
4142

4243
/**
4344
* This class is a parser allows to parse an expression and make a tree as the output
@@ -205,21 +206,23 @@ public ExpressionItem readExpression(
205206
* @return an expression tree containing parsed function arguments
206207
* @throws IOException it will be thrown if there is any problem to read chars
207208
*/
208-
private ExpressionTree readFunction(final AbstractFunction function, final PushbackReader reader,
209-
final PreprocessorContext context,
210-
final FilePositionInfo[] includeStack, final String sources)
209+
private ExpressionTree readFunction(
210+
final AbstractFunction function,
211+
final PushbackReader reader,
212+
final PreprocessorContext context,
213+
final FilePositionInfo[] includeStack,
214+
final String sources)
211215
throws IOException {
212216
final ExpressionItem expectedBracket = nextItem(reader, context);
213217
if (expectedBracket == null) {
214218
throw context
215219
.makeException("Detected function without params [" + function.getName() + ']', null);
216220
}
217221

218-
final int arity = function.getArity();
222+
final int maxArity = function.getArity().stream().mapToInt(x -> x).max().orElse(0);
219223

220224
ExpressionTree functionTree;
221-
222-
if (arity == 0) {
225+
if (maxArity == 0) {
223226
final ExpressionTree subExpression = new ExpressionTree(includeStack, sources);
224227
final ExpressionItem lastItem =
225228
readFunctionArgument(reader, subExpression, context, includeStack, sources);
@@ -236,9 +239,8 @@ private ExpressionTree readFunction(final AbstractFunction function, final Pushb
236239
functionTree.addItem(function);
237240
}
238241
} else {
239-
240-
final List<ExpressionTree> arguments = new ArrayList<>(arity);
241-
for (int i = 0; i < function.getArity(); i++) {
242+
final List<ExpressionTree> arguments = new ArrayList<>();
243+
for (int i = 0; i < maxArity; i++) {
242244
final ExpressionTree subExpression = new ExpressionTree(includeStack, sources);
243245
final ExpressionItem lastItem =
244246
readFunctionArgument(reader, subExpression, context, includeStack, sources);
@@ -250,18 +252,19 @@ private ExpressionTree readFunction(final AbstractFunction function, final Pushb
250252
arguments.add(subExpression);
251253
} else {
252254
throw context
253-
.makeException("Wrong argument for function [" + function.getName() + ']', null);
255+
.makeException("Error argument for function '" + function.getName() + '\'', null);
254256
}
255257
}
256258

257259
functionTree = new ExpressionTree(includeStack, sources);
258260
functionTree.addItem(function);
259261
ExpressionTreeElement functionTreeElement = functionTree.getRoot();
260262

261-
if (arguments.size() != functionTreeElement.getArity()) {
263+
if (!functionTreeElement.getAllowedArities().contains(arguments.size())) {
262264
throw context.makeException(
263-
"Wrong argument number detected '" + function.getName() + "', must be " +
264-
function.getArity() + " argument(s)", null);
265+
"Wrong argument number detected for '" + function.getName() + "', expected " +
266+
function.getArity().stream().map(Object::toString)
267+
.collect(Collectors.joining(";")) + " argument(s)", null);
265268
}
266269

267270
functionTreeElement.fillArguments(arguments);

jcp/src/main/java/com/igormaznitsa/jcp/expression/ExpressionTreeElement.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import java.util.Arrays;
3030
import java.util.List;
3131
import java.util.Objects;
32+
import java.util.Set;
33+
import java.util.stream.Collectors;
3234

3335
/**
3436
* The class describes a wrapper around an expression item to be saved into an expression tree
@@ -75,10 +77,18 @@ public class ExpressionTreeElement {
7577
*/
7678
private int nextChildSlot = 0;
7779

80+
private static final Set<Integer> ZERO_ARITY = Set.of(0);
81+
7882
private ExpressionTreeElement() {
7983
this.sourceString = "";
8084
this.includeStack = new FilePositionInfo[0];
8185
}
86+
/**
87+
* Set of allowed arities.
88+
*
89+
* @since 7.3.0
90+
*/
91+
private Set<Integer> allowedArities = Set.of();
8292

8393
/**
8494
* The constructor
@@ -97,18 +107,45 @@ private ExpressionTreeElement() {
97107
this.includeStack, null);
98108
}
99109

100-
int arity = 0;
110+
final int arity;
101111
if (item.getExpressionItemType() == ExpressionItemType.OPERATOR) {
102112
arity = ((AbstractOperator) item).getArity();
113+
this.allowedArities = Set.of(arity);
103114
} else if (item.getExpressionItemType() == ExpressionItemType.FUNCTION) {
104-
arity = ((AbstractFunction) item).getArity();
115+
final AbstractFunction functionItem = (AbstractFunction) item;
116+
this.allowedArities = functionItem.getArity();
117+
arity = this.allowedArities.stream().mapToInt(x -> x).max().orElse(0);
118+
} else {
119+
arity = 0;
120+
this.allowedArities = ZERO_ARITY;
105121
}
106122
priority = item.getExpressionItemPriority().getPriority();
107123
this.savedItem = item;
108124
childElements = arity == 0 ? EMPTY : new ExpressionTreeElement[arity];
109125
Arrays.fill(this.childElements, EMPTY_SLOT);
110126
}
111127

128+
/**
129+
* Get effective child slots.
130+
*
131+
* @return list of non-empty child slots.
132+
* @since 7.3.0
133+
*/
134+
public List<ExpressionTreeElement> extractEffectiveChildren() {
135+
return Arrays.stream(this.childElements).takeWhile(x -> x != EMPTY_SLOT)
136+
.collect(Collectors.toUnmodifiableList());
137+
}
138+
139+
/**
140+
* Variants of allowed arities by the expression tree element
141+
*
142+
* @return allowed artiy numbers as set
143+
* @since 7.3.0
144+
*/
145+
public Set<Integer> getAllowedArities() {
146+
return this.allowedArities;
147+
}
148+
112149
/**
113150
* Allows to check that the element is EMPTY_SLOT
114151
*
@@ -166,7 +203,7 @@ public ExpressionTreeElement getParent() {
166203
* @return the priority
167204
*/
168205
public int getPriority() {
169-
return priority;
206+
return this.priority;
170207
}
171208

172209
/**

jcp/src/main/java/com/igormaznitsa/jcp/expression/functions/AbstractFunction.java

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import java.util.Comparator;
3939
import java.util.List;
4040
import java.util.Map;
41+
import java.util.Set;
4142
import java.util.concurrent.atomic.AtomicLong;
4243
import java.util.concurrent.atomic.AtomicReference;
4344
import java.util.stream.Collectors;
@@ -49,6 +50,12 @@
4950
*/
5051
public abstract class AbstractFunction implements ExpressionItem {
5152

53+
public static final Set<Integer> ARITY_0 = Set.of(0);
54+
public static final Set<Integer> ARITY_1 = Set.of(1);
55+
public static final Set<Integer> ARITY_1_2 = Set.of(1, 2);
56+
public static final Set<Integer> ARITY_2 = Set.of(2);
57+
public static final Set<Integer> ARITY_3 = Set.of(3);
58+
5259
/**
5360
* The string contains the prefix for all executing methods of functions
5461
*/
@@ -164,19 +171,20 @@ public static AbstractFunction findForName(final String functionName) {
164171
public abstract String getReference();
165172

166173
/**
167-
* Get the function arity
174+
* Get the function arities
168175
*
169-
* @return the function arity (zero or greater)
176+
* @return all allowed arities for the function
177+
* @since 7.3.0
170178
*/
171-
public abstract int getArity();
179+
public abstract Set<Integer> getArity();
172180

173181
/**
174182
* Get arrays of supported argument types
175183
*
176-
* @return the array of argument type combinations allowed by the function
184+
* @return list of argument type combinations allowed by the function
177185
* handler, must not be null
178186
*/
179-
public abstract ValueType[][] getAllowedArgumentTypes();
187+
public abstract List<List<ValueType>> getAllowedArgumentTypes();
180188

181189
/**
182190
* Get the result type

0 commit comments

Comments
 (0)