Skip to content

Commit a3bc0f1

Browse files
authored
Merge pull request #23 from Instancify/feat/compile
feat: script compiling and one time evaluating
2 parents 4e7a501 + 0af8e02 commit a3bc0f1

7 files changed

Lines changed: 180 additions & 40 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.instancify.scriptify.api.script;
2+
3+
/**
4+
* Defines the structure of a compiled script that can be executed.
5+
*
6+
* @param <T> type of value returned by the script after evaluation
7+
*/
8+
public interface CompiledScript<T> extends AutoCloseable {
9+
10+
/**
11+
* Retrieves the value produced by the script after compilation.
12+
*
13+
* @return the compiled script value
14+
*/
15+
T get();
16+
17+
/**
18+
* Executes the compiled script with the provided arguments.
19+
*
20+
* @param args arguments passed to the script during execution
21+
* @return the result of script execution
22+
*/
23+
T eval(Object... args);
24+
25+
/**
26+
* Closes the compiled script and releases any resources associated with it.
27+
*/
28+
@Override
29+
void close();
30+
}
31+

api/src/main/java/com/instancify/scriptify/api/script/Script.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,34 @@ public interface Script<T> {
6161
void addExtraScript(String script);
6262

6363
/**
64-
* Evaluates and executes this script.
64+
* Compiles the script.
6565
*
66-
* @throws ScriptFunctionException If there's an error during script evaluation
66+
* @param script the script to compile
67+
* @return compiled script object
68+
* @throws ScriptException if there's an error during script compiling
6769
*/
68-
T eval(String script) throws ScriptException;
70+
CompiledScript<T> compile(String script) throws ScriptException;
71+
72+
/**
73+
* One-time evaluation of a script without worrying about closing the context.
74+
*
75+
* @param script the script to evaluate
76+
* @return the evaluation result
77+
* @throws ScriptException if there's an error during script evaluation
78+
*/
79+
T evalOneShot(String script) throws ScriptException;
80+
81+
/**
82+
* Evaluates and executes the script.
83+
*
84+
* @param script the script to evaluate
85+
* @return the evaluation result
86+
* @throws ScriptFunctionException if there's an error during script evaluation
87+
* @deprecated For a one-time evaluation of a script, use the Script#evalOneShot method;
88+
* for pre-compiling a script, use Script#compile.
89+
*/
90+
@Deprecated
91+
default T eval(String script) throws ScriptException {
92+
return this.evalOneShot(script);
93+
}
6994
}

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ java {
1212

1313
allprojects {
1414
group = "com.instancify.scriptify"
15-
version = "1.4.4-SNAPSHOT"
15+
version = "1.5.0-SNAPSHOT"
1616
}
1717

1818
subprojects {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.instancify.scriptify.js.graalvm.script;
2+
3+
import com.instancify.scriptify.api.script.CompiledScript;
4+
import org.graalvm.polyglot.Context;
5+
import org.graalvm.polyglot.Value;
6+
7+
public class JsCompiledScript implements CompiledScript<Value> {
8+
private final Context context;
9+
private final Value value;
10+
11+
public JsCompiledScript(Context context, Value value) {
12+
this.context = context;
13+
this.value = value;
14+
}
15+
16+
@Override
17+
public Value get() {
18+
return value;
19+
}
20+
21+
@Override
22+
public Value eval(Object... args) {
23+
if (!value.canExecute()) {
24+
throw new IllegalStateException("Script is not executable");
25+
}
26+
return value.execute(args);
27+
}
28+
29+
@Override
30+
public void close() {
31+
context.close();
32+
}
33+
}
34+

script-js-graalvm/src/main/java/com/instancify/scriptify/js/graalvm/script/JsScript.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.instancify.scriptify.js.graalvm.script;
22

33
import com.instancify.scriptify.api.exception.ScriptException;
4+
import com.instancify.scriptify.api.script.CompiledScript;
45
import com.instancify.scriptify.api.script.Script;
56
import com.instancify.scriptify.api.script.ScriptObject;
67
import com.instancify.scriptify.api.script.constant.ScriptConstant;
@@ -57,7 +58,7 @@ public void addExtraScript(String script) {
5758
}
5859

5960
@Override
60-
public Value eval(String script) throws ScriptException {
61+
public CompiledScript<Value> compile(String script) throws ScriptException {
6162
Context.Builder builder = Context.newBuilder("js")
6263
.allowHostAccess(HostAccess.newBuilder(HostAccess.ALL)
6364
// Mapping for the ScriptObject class required
@@ -98,11 +99,16 @@ public Value eval(String script) throws ScriptException {
9899
fullScript.append(script);
99100

100101
try {
101-
return context.eval("js", fullScript.toString());
102+
return new JsCompiledScript(context, context.eval("js", fullScript.toString()));
102103
} catch (Exception e) {
103104
throw new ScriptException(e);
104-
} finally {
105-
context.close();
105+
}
106+
}
107+
108+
@Override
109+
public Value evalOneShot(String script) throws ScriptException {
110+
try (CompiledScript<Value> compiled = compile(script)) {
111+
return compiled.get();
106112
}
107113
}
108114
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.instancify.scriptify.js.rhino.script;
2+
3+
import com.instancify.scriptify.api.script.CompiledScript;
4+
import org.mozilla.javascript.Context;
5+
import org.mozilla.javascript.Function;
6+
import org.mozilla.javascript.Script;
7+
import org.mozilla.javascript.ScriptableObject;
8+
9+
public class JsCompiledScript implements CompiledScript<Object> {
10+
11+
private final Context context;
12+
private final ScriptableObject scope;
13+
private final Script compiled;
14+
15+
public JsCompiledScript(Context context, ScriptableObject scope, Script compiled) {
16+
this.context = context;
17+
this.scope = scope;
18+
this.compiled = compiled;
19+
}
20+
21+
@Override
22+
public Object get() {
23+
return compiled.exec(context, scope);
24+
}
25+
26+
@Override
27+
public Object eval(Object... args) {
28+
Object result = compiled.exec(context, scope);
29+
if (result instanceof Function function) {
30+
return function.call(context, scope, scope, args);
31+
}
32+
return result;
33+
}
34+
35+
@Override
36+
public void close() {
37+
context.close();
38+
}
39+
}

script-js-rhino/src/main/java/com/instancify/scriptify/js/rhino/script/JsScript.java

Lines changed: 37 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.instancify.scriptify.js.rhino.script;
22

33
import com.instancify.scriptify.api.exception.ScriptException;
4+
import com.instancify.scriptify.api.script.CompiledScript;
45
import com.instancify.scriptify.api.script.Script;
56
import com.instancify.scriptify.api.script.constant.ScriptConstant;
67
import com.instancify.scriptify.api.script.constant.ScriptConstantManager;
@@ -19,16 +20,11 @@
1920

2021
public class JsScript implements Script<Object> {
2122

22-
private final Context context = Context.enter();
2323
private final ScriptSecurityManager securityManager = new StandardSecurityManager();
2424
private ScriptFunctionManager functionManager = new StandardFunctionManager();
2525
private ScriptConstantManager constantManager = new StandardConstantManager();
2626
private final List<String> extraScript = new ArrayList<>();
2727

28-
public JsScript() {
29-
context.setWrapFactory(new JsWrapFactory());
30-
}
31-
3228
@Override
3329
public ScriptSecurityManager getSecurityManager() {
3430
return securityManager;
@@ -60,36 +56,45 @@ public void addExtraScript(String script) {
6056
}
6157

6258
@Override
63-
public Object eval(String script) throws ScriptException {
64-
ScriptableObject scope = context.initStandardObjects();
65-
66-
// If security mode is enabled, search all exclusions
67-
// and add the classes that were excluded to JsSecurityClassAccessor
68-
if (securityManager.getSecurityMode()) {
69-
context.setClassShutter(new JsSecurityClassAccessor(securityManager.getExcludes()));
70-
}
71-
72-
for (ScriptFunctionDefinition definition : functionManager.getFunctions().values()) {
73-
scope.put(definition.getFunction().getName(), scope, new JsFunction(this, definition));
74-
}
75-
76-
for (ScriptConstant constant : constantManager.getConstants().values()) {
77-
ScriptableObject.putConstProperty(scope, constant.getName(), constant.getValue());
78-
}
79-
80-
// Building full script including extra script code
81-
StringBuilder fullScript = new StringBuilder();
82-
for (String extra : extraScript) {
83-
fullScript.append(extra).append("\n");
84-
}
85-
fullScript.append(script);
86-
59+
public CompiledScript<Object> compile(String script) throws ScriptException {
8760
try {
88-
return context.evaluateString(scope, fullScript.toString(), null, 1, null);
61+
Context context = Context.enter();
62+
context.setWrapFactory(new JsWrapFactory());
63+
64+
ScriptableObject scope = context.initStandardObjects();
65+
66+
// If security mode is enabled, search all exclusions
67+
// and add the classes that were excluded to JsSecurityClassAccessor
68+
if (securityManager.getSecurityMode()) {
69+
context.setClassShutter(new JsSecurityClassAccessor(securityManager.getExcludes()));
70+
}
71+
72+
for (ScriptFunctionDefinition definition : functionManager.getFunctions().values()) {
73+
scope.put(definition.getFunction().getName(), scope, new JsFunction(this, definition));
74+
}
75+
76+
for (ScriptConstant constant : constantManager.getConstants().values()) {
77+
ScriptableObject.putConstProperty(scope, constant.getName(), constant.getValue());
78+
}
79+
80+
// Building full script including extra script code
81+
StringBuilder fullScript = new StringBuilder();
82+
for (String extra : extraScript) {
83+
fullScript.append(extra).append("\n");
84+
}
85+
fullScript.append(script);
86+
87+
var compiled = context.compileString(fullScript.toString(), "script", 1, null);
88+
return new JsCompiledScript(context, scope, compiled);
8989
} catch (Exception e) {
9090
throw new ScriptException(e);
91-
} finally {
92-
context.close();
91+
}
92+
}
93+
94+
@Override
95+
public Object evalOneShot(String script) throws ScriptException {
96+
try (CompiledScript<Object> compiled = compile(script)) {
97+
return compiled.get();
9398
}
9499
}
95100
}

0 commit comments

Comments
 (0)