-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rewrite the Script Engine API #2107
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the following I've added some comments about important parts of the PR. Feel free to take a look at the proposed changes so that we can improve the API!
/** | ||
* A script engine used by Phoenicis | ||
* | ||
* @param <R> The internal script result type | ||
*/ | ||
public interface PhoenicisScriptEngine<R> { | ||
/** | ||
* Sets a global value for the given key (variable) in the script context | ||
* | ||
* @param key The variable name in the script context | ||
* @param value The corresponding value | ||
*/ | ||
void put(String key, Object value); | ||
|
||
/** | ||
* Evaluates the given script and returns its result | ||
* | ||
* @param script The script | ||
* @return The result of the evaluated script | ||
*/ | ||
R evaluate(String script); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A script engine
now provides a blocking method evaluate
which returns the result of the evaluated script in a blocking manner. The result is of a generic type which depends on the concrete script interpreter used underneath (e.g. Value
for Graal)
/** | ||
* A typed script engine used by Phoenicis | ||
* | ||
* @param <T> The result type of the script | ||
*/ | ||
public interface TypedScriptEngine<T> { | ||
/** | ||
* Evaluates the script with the given id and returns its result cast to the given type | ||
* | ||
* @param scriptId The id of the evaluated script | ||
* @return The cast result of the evaluated script | ||
*/ | ||
T evaluate(String scriptId); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class functions as an intermediate class for direct usage. It returns the result of an evaluated script (this time given by the script id) as an application type (e.g. EngineSetting
, Installer
etc.)
/** | ||
* A factory to create new {@link TypedScriptEngine} instances | ||
*/ | ||
public interface TypedScriptEngineFactory { | ||
/** | ||
* Creates a new {@link TypedScriptEngine} instance for the given `type` input | ||
* | ||
* @param type The result type {@link Class} for the created script engine | ||
* @param <T> The result type | ||
* @return The created script engine | ||
*/ | ||
<T> TypedScriptEngine<T> createScriptEngine(final Class<T> type); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class is a factory for TypedScriptEngine
instances. At the end I plan to have a single implementation of the factory class which is able to return TypedScriptEngine
for all application classes, but because the application classes are currently split into multiple modules we currently need to provide multiple TypedScriptEngineFactory
implementations, one for each application class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by "application class"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By application class/type I mean interfaces like Installer
, Engine
, EngineSetting
, Verb
etc.
|
||
void put(String name, Object object, Consumer<Exception> errorCallback); | ||
|
||
void addErrorHandler(Consumer<Exception> errorHandler); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I'm removing the error handlers
contained in the script engine we need to provide them nearer to where they are needed in specific try/catch blocks. I think this is a better approach because it is currently very hard to know what error handlers are called for a script because they are often specified in an entirely different code region.
public class GraalEngineType implements ScriptEngineType<Value> { | ||
@Override | ||
public PhoenicisScriptEngine<Value> createScriptEngine() { | ||
return new PolyglotScriptEngine("js", | ||
Map.of("js.nashorn-compat", "true", | ||
"js.experimental-foreign-object-prototype", "true")); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "graal.js"; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the graal.js
specific implementation of the ScriptEngineType
*/ | ||
public EngineSettingsManager(PhoenicisScriptEngineFactory phoenicisScriptEngineFactory, | ||
ExecutorService executorService) { | ||
public EngineSettingsManager(TypedScriptEngineFactory typedScriptEngineFactory, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class shows an example how I intend to use the script engine
in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it mean that we cannot access e.g. applications and engines via the same script engine?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. We will need to provide a dedicated TypedScriptEngine
implementation for every application type (i.e. Installer
, Engine
, etc)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood. So this is also because we want to keep the APIs in the modules and not centralize them in phoenicis-scripts (we discussed this somewhere else).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So this is also because we want to keep the APIs in the modules and not centralize them in phoenicis-scripts (we discussed this somewhere else).
No the reason why I want to implement a dedicated TypedScriptEngine
class for every application type is because it divides the functionality better. It follows the approach divide and conquer in that we implement independent behavior in different modules/classes. For example what have the processing of Installer
script and Verb
script in common except that they are both executed with graaljs and use the module system? Therefore we divide the corresponding functionality in different classes.
I still plan to move all application types (and ideally also the TypeScriptEngine
implementations) to the phoenicis-scripts
module. I didn't do this yet because I think the main changes of this PR are easier comprehensible if I leave as much of the related refactoring as possible for later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still plan to move all application types
I thought @qparis doesn't like this?
public class EngineSettingScriptEngine implements TypedScriptEngine<EngineSetting> { | ||
private final PhoenicisScriptEngine<Value> scriptEngine; | ||
|
||
public EngineSettingScriptEngine(PhoenicisScriptEngine<Value> scriptEngine) { | ||
super(); | ||
|
||
this.scriptEngine = scriptEngine; | ||
} | ||
|
||
@Override | ||
public EngineSetting evaluate(String scriptId) { | ||
final String script = String.format("include(\"%s\")", scriptId); | ||
|
||
return scriptEngine.evaluate(script).newInstance().as(EngineSetting.class); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here the TypedScriptEngine
for the EngineSetting
application class
Is this something you want to do before alpha-3? |
Ideally yes, but I'm not sure about our schedule for alpha-3. |
I have no clue regarding the schedule. There are some packaging issue which cannot fix. |
I think we will need to wait until @qparis has more time before we can fix those. I'm also currently a bit limited on time I can invest in this PR. Therefore feel free to give the PR an in-depth look so that we have a stable and durable solution when we finally merge the PR! |
This PR is still work in progress and at the current time only meant as a preview for discussion purposes!