Skip to content

Commit 0185c7d

Browse files
committed
Add script interpreter engine for schema
1 parent ee310f7 commit 0185c7d

11 files changed

+1435
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.relogiclabs.jschema.internal.engine;
2+
3+
import com.relogiclabs.jschema.type.EValue;
4+
5+
@FunctionalInterface
6+
public interface Evaluator {
7+
EValue evaluate(ScopeContext scope);
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.relogiclabs.jschema.internal.engine;
2+
3+
import com.relogiclabs.jschema.exception.ScriptFunctionException;
4+
import com.relogiclabs.jschema.exception.ScriptVariableException;
5+
import com.relogiclabs.jschema.internal.script.GFunction;
6+
import com.relogiclabs.jschema.internal.script.GReference;
7+
import com.relogiclabs.jschema.tree.RuntimeContext;
8+
import com.relogiclabs.jschema.type.EValue;
9+
import lombok.Getter;
10+
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
14+
import static com.relogiclabs.jschema.internal.library.ScriptLibrary.resolveStatic;
15+
import static com.relogiclabs.jschema.internal.script.GFunction.CONSTRAINT_MARKER;
16+
import static com.relogiclabs.jschema.internal.util.StringHelper.concat;
17+
import static com.relogiclabs.jschema.message.ErrorCode.FUNS02;
18+
import static com.relogiclabs.jschema.message.ErrorCode.FUNS03;
19+
import static com.relogiclabs.jschema.message.ErrorCode.VARD01;
20+
import static org.apache.commons.lang3.StringUtils.substringBefore;
21+
22+
public class ScopeContext {
23+
@Getter private final ScopeContext parent;
24+
private final Map<String, EValue> symbols;
25+
26+
public ScopeContext(ScopeContext parent) {
27+
this.parent = parent;
28+
this.symbols = new HashMap<>();
29+
}
30+
31+
public GReference addVariable(String name, EValue value) {
32+
if(symbols.containsKey(name)) throw new ScriptVariableException(VARD01,
33+
concat("Variable '", name, "' already defined in the scope"));
34+
var reference = new GReference(value);
35+
symbols.put(name, reference);
36+
return reference;
37+
}
38+
39+
public void addFunction(String name, GFunction function) {
40+
if(symbols.containsKey(name)) {
41+
if(name.startsWith(CONSTRAINT_MARKER))
42+
throw failOnDuplicateDefinition(FUNS02, "Constraint", name);
43+
else throw failOnDuplicateDefinition(FUNS03, "Subroutine", name);
44+
}
45+
symbols.put(name, function);
46+
}
47+
48+
private static ScriptFunctionException failOnDuplicateDefinition(String code,
49+
String functionType, String name) {
50+
return new ScriptFunctionException(code, concat(functionType, " function '",
51+
substringBefore(name, '#'), "' with matching parameter(s) already defined"));
52+
}
53+
54+
public EValue resolve(String name) {
55+
var current = this;
56+
do {
57+
var value = current.symbols.get(name);
58+
if(value != null) return value;
59+
current = current.parent;
60+
} while(current != null);
61+
return resolveStatic(name);
62+
}
63+
64+
public RuntimeContext getRuntime() {
65+
var current = this;
66+
while(current.parent != null) current = current.parent;
67+
return current.getRuntime();
68+
}
69+
70+
public void update(String name, EValue value) {
71+
symbols.put(name, value);
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.relogiclabs.jschema.internal.engine;
2+
3+
import com.relogiclabs.jschema.tree.RuntimeContext;
4+
import lombok.Getter;
5+
6+
@Getter
7+
public class ScriptContext extends ScopeContext {
8+
private final RuntimeContext runtime;
9+
10+
public ScriptContext(RuntimeContext runtime) {
11+
super(null);
12+
this.runtime = runtime;
13+
}
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package com.relogiclabs.jschema.internal.engine;
2+
3+
import com.relogiclabs.jschema.exception.InvalidFunctionException;
4+
import com.relogiclabs.jschema.exception.ScriptInvocationException;
5+
import com.relogiclabs.jschema.exception.ScriptOperationException;
6+
import com.relogiclabs.jschema.exception.ScriptRuntimeException;
7+
import com.relogiclabs.jschema.exception.SystemOperationException;
8+
import com.relogiclabs.jschema.internal.script.GRange;
9+
import com.relogiclabs.jschema.type.EArray;
10+
import com.relogiclabs.jschema.type.EInteger;
11+
import com.relogiclabs.jschema.type.EObject;
12+
import com.relogiclabs.jschema.type.EString;
13+
import com.relogiclabs.jschema.type.EValue;
14+
import org.antlr.v4.runtime.Token;
15+
import org.antlr.v4.runtime.tree.TerminalNode;
16+
17+
import static com.relogiclabs.jschema.internal.util.StringHelper.concat;
18+
import static com.relogiclabs.jschema.message.ErrorCode.ASIN01;
19+
import static com.relogiclabs.jschema.message.ErrorCode.CALR01;
20+
import static com.relogiclabs.jschema.message.ErrorCode.FUNS01;
21+
import static com.relogiclabs.jschema.message.ErrorCode.FUNS04;
22+
import static com.relogiclabs.jschema.message.ErrorCode.INDX02;
23+
import static com.relogiclabs.jschema.message.ErrorCode.INDX03;
24+
import static com.relogiclabs.jschema.message.ErrorCode.INDX04;
25+
import static com.relogiclabs.jschema.message.ErrorCode.INDX05;
26+
import static com.relogiclabs.jschema.message.ErrorCode.PRPS02;
27+
import static com.relogiclabs.jschema.message.ErrorCode.RETN01;
28+
import static com.relogiclabs.jschema.message.ErrorCode.RNGS04;
29+
import static com.relogiclabs.jschema.message.ErrorCode.RNGS05;
30+
import static com.relogiclabs.jschema.message.ErrorCode.RNGS06;
31+
import static com.relogiclabs.jschema.message.ErrorCode.RNGS07;
32+
import static com.relogiclabs.jschema.message.ErrorCode.RNGS08;
33+
import static com.relogiclabs.jschema.message.ErrorCode.RNGS09;
34+
import static com.relogiclabs.jschema.message.ErrorCode.TRGT01;
35+
import static com.relogiclabs.jschema.message.ErrorCode.VARD02;
36+
import static com.relogiclabs.jschema.message.MessageFormatter.createOutline;
37+
import static com.relogiclabs.jschema.message.MessageFormatter.formatForSchema;
38+
39+
40+
public final class ScriptErrorHelper {
41+
private ScriptErrorHelper() {
42+
throw new UnsupportedOperationException();
43+
}
44+
45+
static InvalidFunctionException failOnDuplicateParameterName(TerminalNode node) {
46+
return new InvalidFunctionException(formatForSchema(FUNS01,
47+
concat("Duplicate parameter name '", node.getText(), "'"), node.getSymbol()));
48+
}
49+
50+
static ScriptRuntimeException failOnInvalidReturnType(EValue value, Token token) {
51+
return new ScriptRuntimeException(formatForSchema(RETN01,
52+
concat("Invalid return type ", value.getType(), " for constraint function"), token));
53+
}
54+
55+
static ScriptRuntimeException failOnPropertyNotExist(EObject object, String property,
56+
Token token) {
57+
return new ScriptRuntimeException(formatForSchema(PRPS02, concat("Property '",
58+
property, "' not exist in readonly ", object.getType()), token));
59+
}
60+
61+
static RuntimeException failOnIndexOutOfBounds(EString string, EInteger index,
62+
Token token, RuntimeException cause) {
63+
var indexValue = index.getValue();
64+
if(indexValue < 0) return new ScriptRuntimeException(formatForSchema(INDX03,
65+
concat("Invalid negative index ", index, " for string starts at 0"),
66+
token), cause);
67+
var length = string.length();
68+
if(indexValue >= length) return new ScriptRuntimeException(formatForSchema(INDX02,
69+
concat("Index ", index, " out of bounds for string length ", length),
70+
token), cause);
71+
return cause;
72+
}
73+
74+
static RuntimeException failOnIndexOutOfBounds(EArray array, EInteger index,
75+
Token token, RuntimeException cause) {
76+
var indexValue = index.getValue();
77+
if(indexValue < 0) return new ScriptRuntimeException(formatForSchema(INDX05,
78+
concat("Invalid negative index ", index, " for array starts at 0"),
79+
token), cause);
80+
var size = array.size();
81+
if(indexValue >= size) return new ScriptRuntimeException(formatForSchema(INDX04,
82+
concat("Index ", index, " out of bounds for array length ", size),
83+
token), cause);
84+
return cause;
85+
}
86+
87+
static RuntimeException failOnInvalidRangeIndex(EString string, GRange range, Token token,
88+
RuntimeException cause) {
89+
int length = string.length(), start = range.getStart(length), end = range.getEnd(length);
90+
if(start < 0 || start > length) throw new ScriptRuntimeException(formatForSchema(RNGS04,
91+
concat("Range start index ", start, " out of bounds for string length ", length),
92+
token), cause);
93+
if(end < 0 || end > length) return new ScriptRuntimeException(formatForSchema(RNGS05,
94+
concat("Range end index ", end, " out of bounds for string length ", length),
95+
token), cause);
96+
if(start > end) return new ScriptRuntimeException(formatForSchema(RNGS06,
97+
concat("Range start index ", start, " > end index ", end, " for string ",
98+
createOutline(string)), token), cause);
99+
return cause;
100+
}
101+
102+
static RuntimeException failOnInvalidRangeIndex(EArray array, GRange range, Token token,
103+
RuntimeException cause) {
104+
int size = array.size(), start = range.getStart(size), end = range.getEnd(size);
105+
if(start < 0 || start > size) throw new ScriptRuntimeException(formatForSchema(RNGS07,
106+
concat("Range start index ", start, " out of bounds for array length ", size),
107+
token), cause);
108+
if(end < 0 || end > size) return new ScriptRuntimeException(formatForSchema(RNGS08,
109+
concat("Range end index ", end, " out of bounds for array length ", size),
110+
token), cause);
111+
if(start > end) return new ScriptRuntimeException(formatForSchema(RNGS09,
112+
concat("Range start index ", start, " > end index ", end, " for array ",
113+
createOutline(array)), token), cause);
114+
return cause;
115+
}
116+
117+
static ScriptRuntimeException failOnInvalidLValueIncrement(String code, Token token) {
118+
return new ScriptRuntimeException(formatForSchema(code,
119+
"Invalid l-value for increment", token));
120+
}
121+
122+
static ScriptRuntimeException failOnInvalidLValueDecrement(String code, Token token) {
123+
return new ScriptRuntimeException(formatForSchema(code,
124+
"Invalid l-value for decrement", token));
125+
}
126+
127+
static ScriptRuntimeException failOnInvalidLValueAssignment(Token token) {
128+
return new ScriptRuntimeException(formatForSchema(ASIN01,
129+
"Invalid l-value for assignment", token));
130+
}
131+
132+
static ScriptInvocationException failOnTargetNotFound(Token token) {
133+
return new ScriptInvocationException(TRGT01, "Target not found for function '%s'", token);
134+
}
135+
136+
static ScriptInvocationException failOnCallerNotFound(Token token) {
137+
return new ScriptInvocationException(CALR01, "Caller not found for function '%s'", token);
138+
}
139+
140+
static ScriptRuntimeException failOnIdentifierNotFound(Token identifier) {
141+
return new ScriptRuntimeException(formatForSchema(VARD02,
142+
concat("Identifier '", identifier.getText(), "' not found"), identifier));
143+
}
144+
145+
static ScriptRuntimeException failOnFunctionNotFound(String name, int arity, Token token) {
146+
return new ScriptRuntimeException(formatForSchema(FUNS04,
147+
concat("Function '", name, "' with ", arity, " parameter(s) not found"), token));
148+
}
149+
150+
static ScriptRuntimeException failOnRuntime(String code, String message,
151+
Token token, Throwable cause) {
152+
return new ScriptRuntimeException(formatForSchema(code, message, token), cause);
153+
}
154+
155+
static ScriptInvocationException failOnVariadicArgument(String code) {
156+
return new ScriptInvocationException(code,
157+
"Too few argument for invocation of variadic function '%s'");
158+
}
159+
160+
static ScriptInvocationException failOnFixedArgument(String code) {
161+
return new ScriptInvocationException(code,
162+
"Invalid number of arguments for function '%s'");
163+
}
164+
165+
static SystemOperationException failOnSystemException(String code,
166+
Exception exception, Token token) {
167+
return new SystemOperationException(formatForSchema(code, exception.getMessage(),
168+
token), exception);
169+
}
170+
171+
static ScriptRuntimeException failOnOperation(String code, String operationName,
172+
EValue value, Token token, Throwable cause) {
173+
return new ScriptOperationException(formatForSchema(code, concat("Invalid ",
174+
operationName, " operation on type ", value.getType()), token), cause);
175+
}
176+
177+
static ScriptRuntimeException failOnOperation(String code, String operationName,
178+
EValue value, TerminalNode node) {
179+
return failOnOperation(code, operationName, value, node.getSymbol(), null);
180+
}
181+
182+
static ScriptRuntimeException failOnOperation(String code, String operationName,
183+
EValue value1, EValue value2, Token token, Throwable cause) {
184+
return new ScriptOperationException(formatForSchema(code, concat("Invalid ",
185+
operationName, " operation on types ", value1.getType(), " and ",
186+
value2.getType()), token), cause);
187+
}
188+
189+
static ScriptRuntimeException failOnOperation(String code, String operationName,
190+
EValue value1, EValue value2, TerminalNode node) {
191+
return failOnOperation(code, operationName, value1, value2, node.getSymbol(), null);
192+
}
193+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.relogiclabs.jschema.internal.engine;
2+
3+
import com.relogiclabs.jschema.exception.InvalidFunctionException;
4+
import com.relogiclabs.jschema.exception.JsonSchemaException;
5+
import com.relogiclabs.jschema.exception.ScriptCommonException;
6+
import com.relogiclabs.jschema.exception.ScriptInitiatedException;
7+
import com.relogiclabs.jschema.function.FutureFunction;
8+
import com.relogiclabs.jschema.internal.script.GArray;
9+
import com.relogiclabs.jschema.internal.script.GFunction;
10+
import com.relogiclabs.jschema.internal.tree.EFunction;
11+
import com.relogiclabs.jschema.node.JFunction;
12+
import com.relogiclabs.jschema.node.JNode;
13+
import com.relogiclabs.jschema.type.EBoolean;
14+
import com.relogiclabs.jschema.type.EValue;
15+
import lombok.Getter;
16+
import lombok.RequiredArgsConstructor;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
21+
import static com.relogiclabs.jschema.internal.library.ScriptConstant.CALLER_HVAR;
22+
import static com.relogiclabs.jschema.internal.library.ScriptConstant.TARGET_HVAR;
23+
import static com.relogiclabs.jschema.internal.util.CollectionHelper.subList;
24+
import static com.relogiclabs.jschema.internal.util.StringHelper.concat;
25+
import static com.relogiclabs.jschema.message.ErrorCode.FUNC10;
26+
import static com.relogiclabs.jschema.type.EValue.VOID;
27+
28+
@Getter
29+
@RequiredArgsConstructor
30+
public final class ScriptFunction implements EFunction {
31+
private final String name;
32+
private final GFunction function;
33+
34+
@Override
35+
public Object invoke(JFunction caller, Object[] arguments) {
36+
var parameters = function.getParameters();
37+
var scope = new ScopeContext(caller.getRuntime().getScriptContext());
38+
scope.update(CALLER_HVAR, caller);
39+
scope.update(TARGET_HVAR, (EValue) arguments[0]);
40+
int i = 1;
41+
for(var p : parameters) scope.addVariable(p.getName(), (EValue) arguments[i++]);
42+
return function.isFuture()
43+
? (FutureFunction) () -> invoke(function, scope)
44+
: invoke(function, scope);
45+
}
46+
47+
private boolean invoke(GFunction function, ScopeContext scope) {
48+
try {
49+
var result = function.invoke(scope);
50+
if(result == VOID) return true;
51+
if(!(result instanceof EBoolean b)) throw new InvalidFunctionException(FUNC10,
52+
concat("Function '", name, "' must return a boolean value"));
53+
return b.getValue();
54+
} catch(JsonSchemaException | ScriptInitiatedException e) {
55+
throw e;
56+
} catch(ScriptCommonException e) {
57+
scope.getRuntime().getExceptions().fail(e);
58+
return false;
59+
}
60+
}
61+
62+
@Override
63+
public List<Object> prebind(List<? extends EValue> arguments) {
64+
var parameters = function.getParameters();
65+
var minArgSize = function.isVariadic() ? parameters.length - 1 : parameters.length;
66+
if(arguments.size() < minArgSize) return null;
67+
var result = new ArrayList<>(parameters.length);
68+
for(int i = 0; i < parameters.length; i++) {
69+
if(parameters[i].isVariadic()) result.add(new GArray(subList(arguments, i)));
70+
else result.add(arguments.get(i));
71+
}
72+
return result;
73+
}
74+
75+
@Override
76+
public Class<?> getTargetType() {
77+
return JNode.class;
78+
}
79+
80+
@Override
81+
public int getArity() {
82+
return function.isVariadic() ? -1 : function.getParameters().length + 1;
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.relogiclabs.jschema.internal.engine;
2+
3+
public interface ScriptOperation {
4+
String ADDITION = "addition";
5+
String SUBTRACTION = "subtraction";
6+
String MULTIPLICATION = "multiplication";
7+
String DIVISION = "division";
8+
String INDEX = "index";
9+
String RANGE = "range";
10+
String COMPARISON = "comparison";
11+
String PROPERTY = "property access";
12+
String INCREMENT = "increment";
13+
String DECREMENT = "decrement";
14+
String NEGATION = "negation";
15+
}

0 commit comments

Comments
 (0)