11package com .laytonsmith .core .functions ;
22
33import com .laytonsmith .PureUtilities .Common .StreamUtils ;
4- import com .laytonsmith .core .MSLog ;
4+ import com .laytonsmith .core .FlowFunction ;
55import com .laytonsmith .core .MethodScriptCompiler ;
66import com .laytonsmith .core .NodeModifiers ;
77import com .laytonsmith .core .ParseTree ;
88import com .laytonsmith .core .Prefs ;
99import com .laytonsmith .core .Script ;
10+ import com .laytonsmith .core .StepAction ;
11+ import com .laytonsmith .core .StepAction .StepResult ;
1012import com .laytonsmith .core .compiler .analysis .ParamDeclaration ;
1113import com .laytonsmith .core .compiler .analysis .ReturnableDeclaration ;
1214import com .laytonsmith .core .compiler .analysis .Scope ;
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
0 commit comments