Skip to content

Commit b9a9012

Browse files
committed
Finish converting CallbackYield and FlowFunctions
1 parent 1de6f02 commit b9a9012

8 files changed

Lines changed: 231 additions & 84 deletions

File tree

src/main/java/com/laytonsmith/core/CallbackYield.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.laytonsmith.core;
22

3-
import com.laytonsmith.core.constructs.CClosure;
43
import com.laytonsmith.core.constructs.CVoid;
54
import com.laytonsmith.core.constructs.Target;
65
import com.laytonsmith.core.constructs.generics.GenericParameters;
@@ -166,21 +165,14 @@ private StepAction.StepResult<CallbackState> drainNext(Target t, CallbackState s
166165
YieldStep step = yield.steps.poll();
167166
state.currentStep = step;
168167

169-
// Prepare the closure execution
170-
if(step.callable instanceof CClosure closure) {
171-
CClosure.PreparedExecution prep = closure.prepareExecution(step.args);
172-
if(prep == null) {
173-
// Null node closure — result is void
174-
if(step.callback != null) {
175-
step.callback.accept(CVoid.VOID, yield);
176-
}
177-
return drainNext(t, state, env);
178-
}
179-
step.preparedEnv = prep.getEnv();
168+
// Try stack-based execution first (closures, procedures)
169+
Callable.PreparedCallable prep = step.callable.prepareForStack(env, t, step.args);
170+
if(prep != null) {
171+
step.preparedEnv = prep.env();
180172
return new StepAction.StepResult<>(
181-
new StepAction.Evaluate(closure.getNode(), prep.getEnv()), state);
173+
new StepAction.Evaluate(prep.node(), prep.env()), state);
182174
} else {
183-
// Non-closure Callable (e.g. Method, CNativeClosure) — fall back to synchronous
175+
// Sync-only Callable (e.g. CNativeClosure) — execute inline
184176
Mixed result = step.callable.executeCallable(env, t, step.args);
185177
if(step.callback != null) {
186178
step.callback.accept(result, yield);

src/main/java/com/laytonsmith/core/Procedure.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.laytonsmith.core.functions.Function;
2828
import com.laytonsmith.core.functions.FunctionBase;
2929
import com.laytonsmith.core.functions.FunctionList;
30+
import com.laytonsmith.core.natives.interfaces.Callable;
3031
import com.laytonsmith.core.natives.interfaces.Mixed;
3132

3233
import java.util.ArrayList;
@@ -246,6 +247,25 @@ public void definitelyNotConstant() {
246247
possiblyConstant = false;
247248
}
248249

250+
/**
251+
* Prepares this procedure for stack-based execution without re-entering eval().
252+
* Clones the environment, binds arguments, and pushes a stack trace element.
253+
* The caller is responsible for evaluating the returned tree in the returned
254+
* environment, and for popping the stack trace element when done.
255+
*
256+
* @param args The evaluated argument values
257+
* @param callerEnv The caller's environment (will be cloned)
258+
* @param callTarget The target of the procedure call site
259+
* @return The prepared call containing the procedure body tree and environment
260+
*/
261+
public Callable.PreparedCallable prepareCall(List<Mixed> args, Environment callerEnv, Target callTarget) {
262+
Environment env = prepareEnvironment(args, callerEnv, callTarget);
263+
StackTraceManager stManager = env.getEnv(GlobalEnv.class).GetStackTraceManager();
264+
stManager.addStackTraceElement(
265+
new ConfigRuntimeException.StackTraceElement("proc " + name, getTarget()));
266+
return new Callable.PreparedCallable(tree, env);
267+
}
268+
249269
/**
250270
* Clones the environment and assigns procedure arguments (with type checking).
251271
* Used by both {@link #execute} and {@link ProcedureFlow}.

src/main/java/com/laytonsmith/core/constructs/CClosure.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,15 @@ public CClassType getReturnType() {
234234
}
235235
}
236236

237+
@Override
238+
public Callable.PreparedCallable prepareForStack(Environment callerEnv, Target t, Mixed... values) {
239+
PreparedExecution prep = prepareExecution(values);
240+
if(prep == null) {
241+
return null;
242+
}
243+
return new Callable.PreparedCallable(getNode(), prep.getEnv());
244+
}
245+
237246
@Override
238247
public CClosure clone() throws CloneNotSupportedException {
239248
CClosure clone = (CClosure) super.clone();

src/main/java/com/laytonsmith/core/constructs/ProcedureUsage.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public Mixed executeCallable(Environment env, Target t, Mixed... values) throws
8080
return proc.execute(Arrays.asList(values), env, t);
8181
}
8282

83+
@Override
84+
public Callable.PreparedCallable prepareForStack(Environment callerEnv, Target t, Mixed... values) {
85+
return proc.prepareCall(Arrays.asList(values), callerEnv, t);
86+
}
87+
8388
@Override
8489
public Environment getEnv() {
8590
return this.env;

src/main/java/com/laytonsmith/core/functions/CompositeFunction.java

Lines changed: 109 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.laytonsmith.core.functions;
22

33
import com.laytonsmith.PureUtilities.Common.StreamUtils;
4-
import com.laytonsmith.core.MSLog;
4+
import com.laytonsmith.core.FlowFunction;
55
import com.laytonsmith.core.MethodScriptCompiler;
66
import com.laytonsmith.core.NodeModifiers;
77
import com.laytonsmith.core.ParseTree;
88
import com.laytonsmith.core.Prefs;
99
import com.laytonsmith.core.Script;
10+
import com.laytonsmith.core.StepAction;
11+
import com.laytonsmith.core.StepAction.StepResult;
1012
import com.laytonsmith.core.compiler.analysis.ParamDeclaration;
1113
import com.laytonsmith.core.compiler.analysis.ReturnableDeclaration;
1214
import com.laytonsmith.core.compiler.analysis.Scope;
@@ -36,42 +38,25 @@
3638
* exist on a given platform, the function can be automatically provided on that platform.
3739
* This prevents rewrites for straightforward functions.
3840
*/
39-
public abstract class CompositeFunction extends AbstractFunction {
41+
public abstract class CompositeFunction extends AbstractFunction
42+
implements FlowFunction<CompositeFunction.CompositeState> {
4043

4144
private static final Map<Class<? extends CompositeFunction>, ParseTree> CACHED_SCRIPTS = new HashMap<>();
4245

46+
static class CompositeState {
47+
enum Phase { EVAL_ARGS, EVAL_BODY }
48+
Phase phase = Phase.EVAL_ARGS;
49+
ParseTree[] children;
50+
Mixed[] evaluatedArgs;
51+
int argIndex = 0;
52+
IVariableList oldVariables;
53+
}
54+
4355
@Override
4456
public final Mixed exec(Target t, Environment env, GenericParameters generics, Mixed... args) throws ConfigRuntimeException {
45-
ParseTree tree;
46-
// TODO: Ultimately, this is not scalable. We need to compile and cache these scripts at Java compile time,
47-
// not at runtime the first time a function is used. This is an easier first step though.
48-
File debugFile = null;
49-
if(Prefs.DebugMode()) {
50-
debugFile = new File("/NATIVE-MSCRIPT/" + getName());
51-
}
52-
if(!CACHED_SCRIPTS.containsKey(this.getClass())) {
53-
try {
54-
55-
String script = script();
56-
Scope rootScope = new Scope();
57-
rootScope.addDeclaration(new ParamDeclaration("@arguments", CArray.TYPE, null,
58-
new NodeModifiers(),
59-
Target.UNKNOWN));
60-
rootScope.addDeclaration(new ReturnableDeclaration(null, new NodeModifiers(), Target.UNKNOWN));
61-
tree = MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, debugFile, true),
62-
env, env.getEnvClasses(), new StaticAnalysis(rootScope, true))
63-
// the root of the tree is null, so go ahead and pull it up
64-
.getChildAt(0);
65-
} catch (ConfigCompileException | ConfigCompileGroupException ex) {
66-
// This is really bad.
67-
throw new Error(ex);
68-
}
69-
if(cacheCompile()) {
70-
CACHED_SCRIPTS.put(this.getClass(), tree);
71-
}
72-
} else {
73-
tree = CACHED_SCRIPTS.get(this.getClass());
74-
}
57+
// Sync fallback for compile-time optimization (CONSTANT_OFFLINE).
58+
// The FlowFunction path is used during normal interpretation.
59+
ParseTree tree = getOrCompileTree(env);
7560

7661
GlobalEnv gEnv = env.getEnv(GlobalEnv.class);
7762
IVariableList oldVariables = gEnv.GetVarList();
@@ -83,28 +68,105 @@ public final Mixed exec(Target t, Environment env, GenericParameters generics, M
8368
if(gEnv.GetScript() != null) {
8469
ret = gEnv.GetScript().eval(tree, env);
8570
} else {
86-
// This can happen when the environment is not fully setup during tests, in addition to optimization
8771
ret = Script.GenerateScript(null, null, null).eval(tree, env);
8872
}
89-
} catch (ConfigRuntimeException ex) {
90-
if(Prefs.DebugMode()) {
91-
MSLog.GetLogger().e(MSLog.Tags.GENERAL, "Possibly false stacktrace, could be internal error",
92-
ex.getTarget());
93-
}
94-
if(gEnv.GetStackTraceManager().getCurrentStackTrace().isEmpty()) {
95-
ex.setTarget(t);
96-
ConfigRuntimeException.StackTraceElement ste = new ConfigRuntimeException
97-
.StackTraceElement(this.getName(), t);
98-
gEnv.GetStackTraceManager().addStackTraceElement(ste);
99-
}
100-
gEnv.GetStackTraceManager().setCurrentTarget(t);
101-
throw ex;
73+
} finally {
74+
gEnv.SetVarList(oldVariables);
10275
}
103-
gEnv.SetVarList(oldVariables);
10476

10577
return ret;
10678
}
10779

80+
@Override
81+
public StepResult<CompositeState> begin(Target t, ParseTree[] children, Environment env) {
82+
CompositeState state = new CompositeState();
83+
state.children = children;
84+
state.evaluatedArgs = new Mixed[children.length];
85+
if(children.length > 0) {
86+
return new StepResult<>(new StepAction.Evaluate(children[0]), state);
87+
} else {
88+
return evalBody(t, state, env);
89+
}
90+
}
91+
92+
@Override
93+
public StepResult<CompositeState> childCompleted(Target t, CompositeState state, Mixed result, Environment env) {
94+
if(state.phase == CompositeState.Phase.EVAL_ARGS) {
95+
state.evaluatedArgs[state.argIndex] = result;
96+
state.argIndex++;
97+
if(state.argIndex < state.children.length) {
98+
return new StepResult<>(new StepAction.Evaluate(state.children[state.argIndex]), state);
99+
}
100+
return evalBody(t, state, env);
101+
} else {
102+
// Body evaluation complete
103+
return new StepResult<>(new StepAction.Complete(result), state);
104+
}
105+
}
106+
107+
@Override
108+
public StepResult<CompositeState> childInterrupted(Target t, CompositeState state,
109+
StepAction.FlowControl action, Environment env) {
110+
if(state.phase == CompositeState.Phase.EVAL_BODY
111+
&& action.getAction() instanceof ControlFlow.ReturnAction ret) {
112+
return new StepResult<>(new StepAction.Complete(ret.getValue()), state);
113+
}
114+
return null;
115+
}
116+
117+
@Override
118+
public void cleanup(Target t, CompositeState state, Environment env) {
119+
if(state != null && state.oldVariables != null) {
120+
env.getEnv(GlobalEnv.class).SetVarList(state.oldVariables);
121+
}
122+
}
123+
124+
private StepResult<CompositeState> evalBody(Target t, CompositeState state, Environment env) {
125+
state.phase = CompositeState.Phase.EVAL_BODY;
126+
ParseTree tree = getOrCompileTree(env);
127+
128+
GlobalEnv gEnv = env.getEnv(GlobalEnv.class);
129+
state.oldVariables = gEnv.GetVarList();
130+
IVariableList newVariables = new IVariableList(state.oldVariables);
131+
newVariables.set(new IVariable(CArray.TYPE, "@arguments",
132+
new CArray(t, state.evaluatedArgs.length, state.evaluatedArgs), t));
133+
gEnv.SetVarList(newVariables);
134+
135+
return new StepResult<>(new StepAction.Evaluate(tree), state);
136+
}
137+
138+
private ParseTree getOrCompileTree(Environment env) {
139+
if(CACHED_SCRIPTS.containsKey(this.getClass())) {
140+
return CACHED_SCRIPTS.get(this.getClass());
141+
}
142+
// TODO: Ultimately, this is not scalable. We need to compile and cache these scripts at Java compile time,
143+
// not at runtime the first time a function is used. This is an easier first step though.
144+
File debugFile = null;
145+
if(Prefs.DebugMode()) {
146+
debugFile = new File("/NATIVE-MSCRIPT/" + getName());
147+
}
148+
ParseTree tree;
149+
try {
150+
String script = script();
151+
Scope rootScope = new Scope();
152+
rootScope.addDeclaration(new ParamDeclaration("@arguments", CArray.TYPE, null,
153+
new NodeModifiers(),
154+
Target.UNKNOWN));
155+
rootScope.addDeclaration(new ReturnableDeclaration(null, new NodeModifiers(), Target.UNKNOWN));
156+
tree = MethodScriptCompiler.compile(MethodScriptCompiler.lex(script, env, debugFile, true),
157+
env, env.getEnvClasses(), new StaticAnalysis(rootScope, true))
158+
// the root of the tree is null, so go ahead and pull it up
159+
.getChildAt(0);
160+
} catch(ConfigCompileException | ConfigCompileGroupException ex) {
161+
// This is really bad.
162+
throw new Error(ex);
163+
}
164+
if(cacheCompile()) {
165+
CACHED_SCRIPTS.put(this.getClass(), tree);
166+
}
167+
return tree;
168+
}
169+
108170
/**
109171
* The script that will be compiled and run when this function is executed. The value array @arguments will be set
110172
* with the function inputs. Variables set in this script will not leak to the actual script environment, but in

src/main/java/com/laytonsmith/core/functions/ControlFlow.java

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.laytonsmith.annotations.noboilerplate;
1010
import com.laytonsmith.annotations.seealso;
1111
import com.laytonsmith.core.ArgumentValidation;
12+
import com.laytonsmith.core.CallbackYield;
1213
import com.laytonsmith.core.FlowFunction;
1314
import com.laytonsmith.core.MSVersion;
1415
import com.laytonsmith.core.LogLevel;
@@ -56,6 +57,7 @@
5657
import com.laytonsmith.core.constructs.Construct;
5758
import com.laytonsmith.core.constructs.IVariable;
5859
import com.laytonsmith.core.constructs.InstanceofUtil;
60+
import com.laytonsmith.core.constructs.ProcedureUsage;
5961
import com.laytonsmith.core.constructs.Target;
6062
import com.laytonsmith.core.constructs.generics.GenericParameters;
6163
import com.laytonsmith.core.environments.CommandHelperEnvironment;
@@ -3180,7 +3182,7 @@ public FunctionSignatures getSignatures() {
31803182
}
31813183

31823184
@api
3183-
public static class call_proc extends AbstractFunction implements Optimizable {
3185+
public static class call_proc extends CallbackYield implements Optimizable {
31843186

31853187
@Override
31863188
public String getName() {
@@ -3223,17 +3225,18 @@ public Boolean runAsync() {
32233225
}
32243226

32253227
@Override
3226-
public Mixed exec(Target t, Environment env, GenericParameters generics, Mixed... args) throws ConfigRuntimeException {
3228+
protected void execWithYield(Target t, Environment env, Mixed[] args, Yield yield) {
32273229
if(args.length < 1) {
32283230
throw new CREInsufficientArgumentsException("Expecting at least one argument to " + getName(), t);
32293231
}
32303232
Procedure proc = env.getEnv(GlobalEnv.class).GetProcs().get(args[0].val());
3231-
if(proc != null) {
3232-
List<Mixed> vars = new ArrayList<>(Arrays.asList(args));
3233-
vars.remove(0);
3234-
return proc.execute(vars, env, t);
3233+
if(proc == null) {
3234+
throw new CREInvalidProcedureException("Unknown procedure \"" + args[0].val() + "\"", t);
32353235
}
3236-
throw new CREInvalidProcedureException("Unknown procedure \"" + args[0].val() + "\"", t);
3236+
ProcedureUsage procUsage = new ProcedureUsage(proc, env, t);
3237+
Mixed[] procArgs = Arrays.copyOfRange(args, 1, args.length);
3238+
yield.call(procUsage, env, t, procArgs)
3239+
.then((result, y) -> y.done(() -> result));
32373240
}
32383241

32393242
@Override
@@ -3273,7 +3276,7 @@ public ParseTree optimizeDynamic(Target t, Environment env,
32733276
public static class call_proc_array extends call_proc {
32743277

32753278
@Override
3276-
public Mixed exec(Target t, Environment env, GenericParameters generics, Mixed... args) throws ConfigRuntimeException {
3279+
protected void execWithYield(Target t, Environment env, Mixed[] args, Yield yield) {
32773280
CArray ca = ArgumentValidation.getArray(args[1], t, env);
32783281
if(ca.inAssociativeMode()) {
32793282
throw new CRECastException("Expected the array passed to " + getName() + " to be non-associative.", t);
@@ -3284,7 +3287,7 @@ public Mixed exec(Target t, Environment env, GenericParameters generics, Mixed..
32843287
args2[i] = ca.get(i - 1, t, env);
32853288
}
32863289
// TODO: This probably needs to change once generics are added
3287-
return super.exec(t, env, null, args2);
3290+
super.execWithYield(t, env, args2, yield);
32883291
}
32893292

32903293
@Override

0 commit comments

Comments
 (0)