Skip to content

Commit f376e68

Browse files
authored
Adds STR_LEFT and STR_RIGHT functions (#503)
1 parent d5bddfd commit f376e68

File tree

5 files changed

+293
-45
lines changed

5 files changed

+293
-45
lines changed

docs/references/functions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ Available through the _ExpressionConfiguration.StandardFunctionsDictionary_ cons
3838
| STR_CONTAINS(string, substring) | Returns true if the string contains the substring (case-insensitive) |
3939
| STR_ENDS_WITH(string, substring) | Returns true if the string ends with the substring (case-sensitive) |
4040
| STR_FORMAT(format [,argument, ...]) | Returns a formatted string using the specified format string and arguments, using the configured locale |
41+
| STR_LEFT(value, n) | Returns the first n characters from the left of the given string |
4142
| STR_LENGTH(string) | Returns the length of the string |
4243
| STR_LOWER(value) | Converts the given value to lower case |
4344
| STR_MATCHES(string, pattern) | Returns true if the string matches the RegEx pattern |
45+
| STR_RIGHT(value, n) | Returns the last n characters from the left of the given string |
4446
| STR_STARTS_WITH(string, substring) | Returns true if the string starts with the substring (case-sensitive) |
4547
| STR_SUBSTRING(string, start[, end]) | Returns a substring of the given string, starting at the _start_ index and ending at the _end_ index (the end of the string if not specified) |
4648
| STR_TRIM(string) | Returns the given string with all leading and trailing space removed. |

src/main/java/com/ezylang/evalex/config/ExpressionConfiguration.java

Lines changed: 135 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,109 @@
2121
import com.ezylang.evalex.data.conversion.DefaultEvaluationValueConverter;
2222
import com.ezylang.evalex.data.conversion.EvaluationValueConverterIfc;
2323
import com.ezylang.evalex.functions.FunctionIfc;
24-
import com.ezylang.evalex.functions.basic.*;
25-
import com.ezylang.evalex.functions.datetime.*;
26-
import com.ezylang.evalex.functions.string.*;
27-
import com.ezylang.evalex.functions.trigonometric.*;
24+
import com.ezylang.evalex.functions.basic.AbsFunction;
25+
import com.ezylang.evalex.functions.basic.AverageFunction;
26+
import com.ezylang.evalex.functions.basic.CeilingFunction;
27+
import com.ezylang.evalex.functions.basic.CoalesceFunction;
28+
import com.ezylang.evalex.functions.basic.FactFunction;
29+
import com.ezylang.evalex.functions.basic.FloorFunction;
30+
import com.ezylang.evalex.functions.basic.IfFunction;
31+
import com.ezylang.evalex.functions.basic.Log10Function;
32+
import com.ezylang.evalex.functions.basic.LogFunction;
33+
import com.ezylang.evalex.functions.basic.MaxFunction;
34+
import com.ezylang.evalex.functions.basic.MinFunction;
35+
import com.ezylang.evalex.functions.basic.NotFunction;
36+
import com.ezylang.evalex.functions.basic.RandomFunction;
37+
import com.ezylang.evalex.functions.basic.RoundFunction;
38+
import com.ezylang.evalex.functions.basic.SqrtFunction;
39+
import com.ezylang.evalex.functions.basic.SumFunction;
40+
import com.ezylang.evalex.functions.basic.SwitchFunction;
41+
import com.ezylang.evalex.functions.datetime.DateTimeFormatFunction;
42+
import com.ezylang.evalex.functions.datetime.DateTimeNewFunction;
43+
import com.ezylang.evalex.functions.datetime.DateTimeNowFunction;
44+
import com.ezylang.evalex.functions.datetime.DateTimeParseFunction;
45+
import com.ezylang.evalex.functions.datetime.DateTimeToEpochFunction;
46+
import com.ezylang.evalex.functions.datetime.DateTimeTodayFunction;
47+
import com.ezylang.evalex.functions.datetime.DurationFromMillisFunction;
48+
import com.ezylang.evalex.functions.datetime.DurationNewFunction;
49+
import com.ezylang.evalex.functions.datetime.DurationParseFunction;
50+
import com.ezylang.evalex.functions.datetime.DurationToMillisFunction;
51+
import com.ezylang.evalex.functions.string.StringContains;
52+
import com.ezylang.evalex.functions.string.StringEndsWithFunction;
53+
import com.ezylang.evalex.functions.string.StringFormatFunction;
54+
import com.ezylang.evalex.functions.string.StringLeftFunction;
55+
import com.ezylang.evalex.functions.string.StringLengthFunction;
56+
import com.ezylang.evalex.functions.string.StringLowerFunction;
57+
import com.ezylang.evalex.functions.string.StringMatchesFunction;
58+
import com.ezylang.evalex.functions.string.StringRightFunction;
59+
import com.ezylang.evalex.functions.string.StringStartsWithFunction;
60+
import com.ezylang.evalex.functions.string.StringSubstringFunction;
61+
import com.ezylang.evalex.functions.string.StringTrimFunction;
62+
import com.ezylang.evalex.functions.string.StringUpperFunction;
63+
import com.ezylang.evalex.functions.trigonometric.AcosFunction;
64+
import com.ezylang.evalex.functions.trigonometric.AcosHFunction;
65+
import com.ezylang.evalex.functions.trigonometric.AcosRFunction;
66+
import com.ezylang.evalex.functions.trigonometric.AcotFunction;
67+
import com.ezylang.evalex.functions.trigonometric.AcotHFunction;
68+
import com.ezylang.evalex.functions.trigonometric.AcotRFunction;
69+
import com.ezylang.evalex.functions.trigonometric.AsinFunction;
70+
import com.ezylang.evalex.functions.trigonometric.AsinHFunction;
71+
import com.ezylang.evalex.functions.trigonometric.AsinRFunction;
72+
import com.ezylang.evalex.functions.trigonometric.Atan2Function;
73+
import com.ezylang.evalex.functions.trigonometric.Atan2RFunction;
74+
import com.ezylang.evalex.functions.trigonometric.AtanFunction;
75+
import com.ezylang.evalex.functions.trigonometric.AtanHFunction;
76+
import com.ezylang.evalex.functions.trigonometric.AtanRFunction;
77+
import com.ezylang.evalex.functions.trigonometric.CosFunction;
78+
import com.ezylang.evalex.functions.trigonometric.CosHFunction;
79+
import com.ezylang.evalex.functions.trigonometric.CosRFunction;
80+
import com.ezylang.evalex.functions.trigonometric.CotFunction;
81+
import com.ezylang.evalex.functions.trigonometric.CotHFunction;
82+
import com.ezylang.evalex.functions.trigonometric.CotRFunction;
83+
import com.ezylang.evalex.functions.trigonometric.CscFunction;
84+
import com.ezylang.evalex.functions.trigonometric.CscHFunction;
85+
import com.ezylang.evalex.functions.trigonometric.CscRFunction;
86+
import com.ezylang.evalex.functions.trigonometric.DegFunction;
87+
import com.ezylang.evalex.functions.trigonometric.RadFunction;
88+
import com.ezylang.evalex.functions.trigonometric.SecFunction;
89+
import com.ezylang.evalex.functions.trigonometric.SecHFunction;
90+
import com.ezylang.evalex.functions.trigonometric.SecRFunction;
91+
import com.ezylang.evalex.functions.trigonometric.SinFunction;
92+
import com.ezylang.evalex.functions.trigonometric.SinHFunction;
93+
import com.ezylang.evalex.functions.trigonometric.SinRFunction;
94+
import com.ezylang.evalex.functions.trigonometric.TanFunction;
95+
import com.ezylang.evalex.functions.trigonometric.TanHFunction;
96+
import com.ezylang.evalex.functions.trigonometric.TanRFunction;
2897
import com.ezylang.evalex.operators.OperatorIfc;
29-
import com.ezylang.evalex.operators.arithmetic.*;
30-
import com.ezylang.evalex.operators.booleans.*;
98+
import com.ezylang.evalex.operators.arithmetic.InfixDivisionOperator;
99+
import com.ezylang.evalex.operators.arithmetic.InfixMinusOperator;
100+
import com.ezylang.evalex.operators.arithmetic.InfixModuloOperator;
101+
import com.ezylang.evalex.operators.arithmetic.InfixMultiplicationOperator;
102+
import com.ezylang.evalex.operators.arithmetic.InfixPlusOperator;
103+
import com.ezylang.evalex.operators.arithmetic.InfixPowerOfOperator;
104+
import com.ezylang.evalex.operators.arithmetic.PrefixMinusOperator;
105+
import com.ezylang.evalex.operators.arithmetic.PrefixPlusOperator;
106+
import com.ezylang.evalex.operators.booleans.InfixAndOperator;
107+
import com.ezylang.evalex.operators.booleans.InfixEqualsOperator;
108+
import com.ezylang.evalex.operators.booleans.InfixGreaterEqualsOperator;
109+
import com.ezylang.evalex.operators.booleans.InfixGreaterOperator;
110+
import com.ezylang.evalex.operators.booleans.InfixLessEqualsOperator;
111+
import com.ezylang.evalex.operators.booleans.InfixLessOperator;
112+
import com.ezylang.evalex.operators.booleans.InfixNotEqualsOperator;
113+
import com.ezylang.evalex.operators.booleans.InfixOrOperator;
114+
import com.ezylang.evalex.operators.booleans.PrefixNotOperator;
31115
import java.math.BigDecimal;
32116
import java.math.MathContext;
33117
import java.math.RoundingMode;
34118
import java.time.ZoneId;
35119
import java.time.format.DateTimeFormatter;
36-
import java.util.*;
120+
import java.util.ArrayList;
121+
import java.util.Arrays;
122+
import java.util.Collections;
123+
import java.util.List;
124+
import java.util.Locale;
125+
import java.util.Map;
126+
import java.util.TreeMap;
37127
import java.util.function.Supplier;
38128
import lombok.Builder;
39129
import lombok.Getter;
@@ -183,9 +273,11 @@ public class ExpressionConfiguration {
183273
Map.entry("STR_CONTAINS", new StringContains()),
184274
Map.entry("STR_ENDS_WITH", new StringEndsWithFunction()),
185275
Map.entry("STR_FORMAT", new StringFormatFunction()),
276+
Map.entry("STR_LEFT", new StringLeftFunction()),
186277
Map.entry("STR_LENGTH", new StringLengthFunction()),
187278
Map.entry("STR_LOWER", new StringLowerFunction()),
188279
Map.entry("STR_MATCHES", new StringMatchesFunction()),
280+
Map.entry("STR_RIGHT", new StringRightFunction()),
189281
Map.entry("STR_STARTS_WITH", new StringStartsWithFunction()),
190282
Map.entry("STR_SUBSTRING", new StringSubstringFunction()),
191283
Map.entry("STR_TRIM", new StringTrimFunction()),
@@ -305,17 +397,44 @@ public static ExpressionConfiguration defaultConfiguration() {
305397
return ExpressionConfiguration.builder().build();
306398
}
307399

400+
private static Map<String, EvaluationValue> getStandardConstants() {
401+
402+
Map<String, EvaluationValue> constants = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
403+
404+
constants.put("TRUE", EvaluationValue.TRUE);
405+
constants.put("FALSE", EvaluationValue.FALSE);
406+
constants.put(
407+
"PI",
408+
EvaluationValue.numberValue(
409+
new BigDecimal(
410+
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679")));
411+
constants.put(
412+
"E",
413+
EvaluationValue.numberValue(
414+
new BigDecimal(
415+
"2.71828182845904523536028747135266249775724709369995957496696762772407663")));
416+
constants.put("NULL", EvaluationValue.NULL_VALUE);
417+
418+
constants.put(
419+
"DT_FORMAT_ISO_DATE_TIME",
420+
EvaluationValue.stringValue("yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]['['VV']']"));
421+
constants.put(
422+
"DT_FORMAT_LOCAL_DATE_TIME", EvaluationValue.stringValue("yyyy-MM-dd'T'HH:mm:ss[.SSS]"));
423+
constants.put("DT_FORMAT_LOCAL_DATE", EvaluationValue.stringValue("yyyy-MM-dd"));
424+
425+
return constants;
426+
}
427+
308428
/**
309429
* Adds additional operators to this configuration.
310430
*
311431
* @param operators variable number of arguments with a map entry holding the operator name and
312432
* implementation. <br>
313433
* Example: <code>
314-
* ExpressionConfiguration.defaultConfiguration()
315-
* .withAdditionalOperators(
316-
* Map.entry("++", new PrefixPlusPlusOperator()),
317-
* Map.entry("++", new PostfixPlusPlusOperator()));
318-
* </code>
434+
* ExpressionConfiguration.defaultConfiguration() .withAdditionalOperators(
435+
* Map.entry("++", new PrefixPlusPlusOperator()), Map.entry("++", new
436+
* PostfixPlusPlusOperator()));
437+
* </code>
319438
* @return The modified configuration, to allow chaining of methods.
320439
*/
321440
@SafeVarargs
@@ -332,11 +451,10 @@ public final ExpressionConfiguration withAdditionalOperators(
332451
* @param functions variable number of arguments with a map entry holding the functions name and
333452
* implementation. <br>
334453
* Example: <code>
335-
* ExpressionConfiguration.defaultConfiguration()
336-
* .withAdditionalFunctions(
337-
* Map.entry("save", new SaveFunction()),
338-
* Map.entry("update", new UpdateFunction()));
339-
* </code>
454+
* ExpressionConfiguration.defaultConfiguration() .withAdditionalFunctions(
455+
* Map.entry("save", new SaveFunction()), Map.entry("update", new
456+
* UpdateFunction()));
457+
* </code>
340458
* @return The modified configuration, to allow chaining of methods.
341459
*/
342460
@SafeVarargs
@@ -346,32 +464,4 @@ public final ExpressionConfiguration withAdditionalFunctions(
346464
.forEach(entry -> functionDictionary.addFunction(entry.getKey(), entry.getValue()));
347465
return this;
348466
}
349-
350-
private static Map<String, EvaluationValue> getStandardConstants() {
351-
352-
Map<String, EvaluationValue> constants = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
353-
354-
constants.put("TRUE", EvaluationValue.TRUE);
355-
constants.put("FALSE", EvaluationValue.FALSE);
356-
constants.put(
357-
"PI",
358-
EvaluationValue.numberValue(
359-
new BigDecimal(
360-
"3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679")));
361-
constants.put(
362-
"E",
363-
EvaluationValue.numberValue(
364-
new BigDecimal(
365-
"2.71828182845904523536028747135266249775724709369995957496696762772407663")));
366-
constants.put("NULL", EvaluationValue.NULL_VALUE);
367-
368-
constants.put(
369-
"DT_FORMAT_ISO_DATE_TIME",
370-
EvaluationValue.stringValue("yyyy-MM-dd'T'HH:mm:ss[.SSS][XXX]['['VV']']"));
371-
constants.put(
372-
"DT_FORMAT_LOCAL_DATE_TIME", EvaluationValue.stringValue("yyyy-MM-dd'T'HH:mm:ss[.SSS]"));
373-
constants.put("DT_FORMAT_LOCAL_DATE", EvaluationValue.stringValue("yyyy-MM-dd"));
374-
375-
return constants;
376-
}
377467
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2012-2024 Udo Klimaschewski
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package com.ezylang.evalex.functions.string;
17+
18+
import com.ezylang.evalex.Expression;
19+
import com.ezylang.evalex.data.EvaluationValue;
20+
import com.ezylang.evalex.functions.AbstractFunction;
21+
import com.ezylang.evalex.functions.FunctionParameter;
22+
import com.ezylang.evalex.parser.Token;
23+
24+
/**
25+
* Represents a function that extracts a substring from the left side of a given string. This class
26+
* extends the {@link AbstractFunction} and implements the logic for the `LEFT` string function,
27+
* which returns a specified number of characters from the beginning (left) of the input string.
28+
*
29+
* <p>Two parameters are required for this function:
30+
*
31+
* <ul>
32+
* <li><b>string</b> - The input string from which the substring will be extracted.
33+
* <li><b>length</b> - The number of characters to extract from the left side of the string. If
34+
* the specified length is greater than the string's length, the entire string is returned. If
35+
* the length is negative or zero, an empty string is returned.
36+
* </ul>
37+
*
38+
* <p>Example usage: If the input string is "hello" and the length is 2, the result will be "he".
39+
*/
40+
@FunctionParameter(name = "string")
41+
@FunctionParameter(name = "length")
42+
public class StringLeftFunction extends AbstractFunction {
43+
44+
/**
45+
* Evaluates the `LEFT` string function by extracting a substring from the left side of the given
46+
* string.
47+
*
48+
* @param expression the current expression being evaluated
49+
* @param functionToken the token representing the function being called
50+
* @param parameterValues the parameters passed to the function; expects exactly two parameters: a
51+
* string and a numeric value for length
52+
* @return the substring extracted from the left side of the input string as an {@link
53+
* EvaluationValue}
54+
*/
55+
@Override
56+
public EvaluationValue evaluate(
57+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
58+
String string = parameterValues[0].getStringValue();
59+
int length =
60+
Math.max(0, Math.min(parameterValues[1].getNumberValue().intValue(), string.length()));
61+
String substr = string.substring(0, length);
62+
return expression.convertValue(substr);
63+
}
64+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright 2012-2024 Udo Klimaschewski
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package com.ezylang.evalex.functions.string;
17+
18+
import com.ezylang.evalex.Expression;
19+
import com.ezylang.evalex.data.EvaluationValue;
20+
import com.ezylang.evalex.functions.AbstractFunction;
21+
import com.ezylang.evalex.functions.FunctionParameter;
22+
import com.ezylang.evalex.parser.Token;
23+
24+
/**
25+
* Represents a function that extracts a substring from the right side of a given string. This class
26+
* extends the {@link AbstractFunction} and implements the logic for the `RIGHT` string function,
27+
* which returns a specified number of characters from the end (right) of the input string.
28+
*
29+
* <p>Two parameters are required for this function:
30+
*
31+
* <ul>
32+
* <li><b>string</b> - The input string from which the substring will be extracted.
33+
* <li><b>length</b> - The number of characters to extract from the right side of the string. If
34+
* the specified length is greater than the string's length, the entire string is returned. If
35+
* the length is negative or zero, an empty string is returned.
36+
* </ul>
37+
*
38+
* <p>Example usage: If the input string is "hello" and the length is 2, the result will be "lo".
39+
*/
40+
@FunctionParameter(name = "string")
41+
@FunctionParameter(name = "length")
42+
public class StringRightFunction extends AbstractFunction {
43+
44+
/**
45+
* Evaluates the `RIGHT` string function by extracting a substring from the right side of the
46+
* given string.
47+
*
48+
* @param expression the current expression being evaluated
49+
* @param functionToken the token representing the function being called
50+
* @param parameterValues the parameters passed to the function; expects exactly two parameters: a
51+
* string and a numeric value for length
52+
* @return the substring extracted from the right side of the input string as an {@link
53+
* EvaluationValue}
54+
*/
55+
@Override
56+
public EvaluationValue evaluate(
57+
Expression expression, Token functionToken, EvaluationValue... parameterValues) {
58+
String string = parameterValues[0].getStringValue();
59+
int length =
60+
Math.max(0, Math.min(parameterValues[1].getNumberValue().intValue(), string.length()));
61+
String substr = string.substring(string.length() - length);
62+
return expression.convertValue(substr);
63+
}
64+
}

0 commit comments

Comments
 (0)