diff options
author | UbitUmarov | 2018-02-23 14:52:34 +0000 |
---|---|---|
committer | UbitUmarov | 2018-02-23 14:52:34 +0000 |
commit | 2129d941acbc5f83aca4dc4c30a62d3226888136 (patch) | |
tree | e12f2391978c923ef780f558ee5a32d857ee9124 /OpenSim/Region/ScriptEngine/YEngine/MMRScriptCodeGen.cs | |
parent | Merge branch 'master' into httptests (diff) | |
download | opensim-SC-2129d941acbc5f83aca4dc4c30a62d3226888136.zip opensim-SC-2129d941acbc5f83aca4dc4c30a62d3226888136.tar.gz opensim-SC-2129d941acbc5f83aca4dc4c30a62d3226888136.tar.bz2 opensim-SC-2129d941acbc5f83aca4dc4c30a62d3226888136.tar.xz |
rename XMREngine as Yengine (still not all done), big mess source formating changes, move state files to proper folder, fix a source file locking on errors, more changes for cross platform including from Mike,... yes yes i know a messy commit
Diffstat (limited to '')
-rw-r--r-- | OpenSim/Region/ScriptEngine/YEngine/MMRScriptCodeGen.cs | 7170 |
1 files changed, 7170 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/YEngine/MMRScriptCodeGen.cs b/OpenSim/Region/ScriptEngine/YEngine/MMRScriptCodeGen.cs new file mode 100644 index 0000000..a480263 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/YEngine/MMRScriptCodeGen.cs | |||
@@ -0,0 +1,7170 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using OpenSim.Region.ScriptEngine.Shared.ScriptBase; | ||
29 | using OpenSim.Region.ScriptEngine.Yengine; | ||
30 | using System; | ||
31 | using System.Collections.Generic; | ||
32 | using System.IO; | ||
33 | using System.Reflection; | ||
34 | using System.Reflection.Emit; | ||
35 | using System.Runtime.Serialization; | ||
36 | using System.Text; | ||
37 | using System.Threading; | ||
38 | |||
39 | using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; | ||
40 | using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; | ||
41 | using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
42 | using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; | ||
43 | using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; | ||
44 | using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
45 | using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; | ||
46 | |||
47 | /** | ||
48 | * @brief translate a reduced script token into corresponding CIL code. | ||
49 | * The single script token contains a tokenized and textured version of the whole script file. | ||
50 | */ | ||
51 | |||
52 | namespace OpenSim.Region.ScriptEngine.Yengine | ||
53 | { | ||
54 | public interface IScriptCodeGen | ||
55 | { | ||
56 | ScriptMyILGen ilGen | ||
57 | { | ||
58 | get; | ||
59 | } // the output instruction stream | ||
60 | void ErrorMsg(Token token, string message); | ||
61 | void PushDefaultValue(TokenType type); | ||
62 | void PushXMRInst(); | ||
63 | } | ||
64 | |||
65 | public class ScriptCodeGen: IScriptCodeGen | ||
66 | { | ||
67 | private static readonly bool DEBUG_STACKCAPRES = false; | ||
68 | private static readonly bool DEBUG_TRYSTMT = false; | ||
69 | |||
70 | public static readonly string OBJECT_CODE_MAGIC = "XMRObjectCode"; | ||
71 | // reserve positive version values for original xmr | ||
72 | public static int COMPILED_VERSION_VALUE = -1; // decremented when compiler or object file changes | ||
73 | |||
74 | public static readonly int CALL_FRAME_MEMUSE = 64; | ||
75 | public static readonly int STRING_LEN_TO_MEMUSE = 2; | ||
76 | |||
77 | public static Type xmrInstSuperType = null; // typeof whatever is actually malloc'd for script instances | ||
78 | // - must inherit from XMRInstAbstract | ||
79 | |||
80 | /* | ||
81 | * Static tables that there only needs to be one copy of for all. | ||
82 | */ | ||
83 | private static VarDict legalEventHandlers = CreateLegalEventHandlers(); | ||
84 | private static CompValu[] zeroCompValus = new CompValu[0]; | ||
85 | private static TokenType[] zeroArgs = new TokenType[0]; | ||
86 | private static TokenTypeBool tokenTypeBool = new TokenTypeBool(null); | ||
87 | private static TokenTypeExc tokenTypeExc = new TokenTypeExc(null); | ||
88 | private static TokenTypeFloat tokenTypeFlt = new TokenTypeFloat(null); | ||
89 | private static TokenTypeInt tokenTypeInt = new TokenTypeInt(null); | ||
90 | private static TokenTypeObject tokenTypeObj = new TokenTypeObject(null); | ||
91 | private static TokenTypeRot tokenTypeRot = new TokenTypeRot(null); | ||
92 | private static TokenTypeStr tokenTypeStr = new TokenTypeStr(null); | ||
93 | private static TokenTypeVec tokenTypeVec = new TokenTypeVec(null); | ||
94 | private static Type[] instanceTypeArg = new Type[] { typeof(XMRInstAbstract) }; | ||
95 | private static string[] instanceNameArg = new string[] { "$xmrthis" }; | ||
96 | |||
97 | private static ConstructorInfo lslFloatConstructorInfo = typeof(LSL_Float).GetConstructor(new Type[] { typeof(double) }); | ||
98 | private static ConstructorInfo lslIntegerConstructorInfo = typeof(LSL_Integer).GetConstructor(new Type[] { typeof(int) }); | ||
99 | private static ConstructorInfo lslListConstructorInfo = typeof(LSL_List).GetConstructor(new Type[] { typeof(object[]) }); | ||
100 | public static ConstructorInfo lslRotationConstructorInfo = typeof(LSL_Rotation).GetConstructor(new Type[] { typeof(double), typeof(double), typeof(double), typeof(double) }); | ||
101 | private static ConstructorInfo lslStringConstructorInfo = typeof(LSL_String).GetConstructor(new Type[] { typeof(string) }); | ||
102 | public static ConstructorInfo lslVectorConstructorInfo = typeof(LSL_Vector).GetConstructor(new Type[] { typeof(double), typeof(double), typeof(double) }); | ||
103 | private static ConstructorInfo scriptBadCallNoExceptionConstructorInfo = typeof(ScriptBadCallNoException).GetConstructor(new Type[] { typeof(int) }); | ||
104 | private static ConstructorInfo scriptChangeStateExceptionConstructorInfo = typeof(ScriptChangeStateException).GetConstructor(new Type[] { typeof(int) }); | ||
105 | private static ConstructorInfo scriptRestoreCatchExceptionConstructorInfo = typeof(ScriptRestoreCatchException).GetConstructor(new Type[] { typeof(Exception) }); | ||
106 | private static ConstructorInfo scriptUndefinedStateExceptionConstructorInfo = typeof(ScriptUndefinedStateException).GetConstructor(new Type[] { typeof(string) }); | ||
107 | private static ConstructorInfo sdtClassConstructorInfo = typeof(XMRSDTypeClObj).GetConstructor(new Type[] { typeof(XMRInstAbstract), typeof(int) }); | ||
108 | private static ConstructorInfo xmrArrayConstructorInfo = typeof(XMR_Array).GetConstructor(new Type[] { typeof(XMRInstAbstract) }); | ||
109 | private static FieldInfo callModeFieldInfo = typeof(XMRInstAbstract).GetField("callMode"); | ||
110 | private static FieldInfo doGblInitFieldInfo = typeof(XMRInstAbstract).GetField("doGblInit"); | ||
111 | private static FieldInfo ehArgsFieldInfo = typeof(XMRInstAbstract).GetField("ehArgs"); | ||
112 | private static FieldInfo rotationXFieldInfo = typeof(LSL_Rotation).GetField("x"); | ||
113 | private static FieldInfo rotationYFieldInfo = typeof(LSL_Rotation).GetField("y"); | ||
114 | private static FieldInfo rotationZFieldInfo = typeof(LSL_Rotation).GetField("z"); | ||
115 | private static FieldInfo rotationSFieldInfo = typeof(LSL_Rotation).GetField("s"); | ||
116 | private static FieldInfo sdtXMRInstFieldInfo = typeof(XMRSDTypeClObj).GetField("xmrInst"); | ||
117 | private static FieldInfo stackLeftFieldInfo = typeof(XMRInstAbstract).GetField("m_StackLeft"); | ||
118 | private static FieldInfo vectorXFieldInfo = typeof(LSL_Vector).GetField("x"); | ||
119 | private static FieldInfo vectorYFieldInfo = typeof(LSL_Vector).GetField("y"); | ||
120 | private static FieldInfo vectorZFieldInfo = typeof(LSL_Vector).GetField("z"); | ||
121 | |||
122 | private static MethodInfo arrayClearMethodInfo = typeof(XMR_Array).GetMethod("__pub_clear", new Type[] { }); | ||
123 | private static MethodInfo arrayCountMethodInfo = typeof(XMR_Array).GetMethod("__pub_count", new Type[] { }); | ||
124 | private static MethodInfo arrayIndexMethodInfo = typeof(XMR_Array).GetMethod("__pub_index", new Type[] { typeof(int) }); | ||
125 | private static MethodInfo arrayValueMethodInfo = typeof(XMR_Array).GetMethod("__pub_value", new Type[] { typeof(int) }); | ||
126 | private static MethodInfo checkRunStackMethInfo = typeof(XMRInstAbstract).GetMethod("CheckRunStack", new Type[] { }); | ||
127 | private static MethodInfo checkRunQuickMethInfo = typeof(XMRInstAbstract).GetMethod("CheckRunQuick", new Type[] { }); | ||
128 | private static MethodInfo ehArgUnwrapFloat = GetStaticMethod(typeof(TypeCast), "EHArgUnwrapFloat", new Type[] { typeof(object) }); | ||
129 | private static MethodInfo ehArgUnwrapInteger = GetStaticMethod(typeof(TypeCast), "EHArgUnwrapInteger", new Type[] { typeof(object) }); | ||
130 | private static MethodInfo ehArgUnwrapRotation = GetStaticMethod(typeof(TypeCast), "EHArgUnwrapRotation", new Type[] { typeof(object) }); | ||
131 | private static MethodInfo ehArgUnwrapString = GetStaticMethod(typeof(TypeCast), "EHArgUnwrapString", new Type[] { typeof(object) }); | ||
132 | private static MethodInfo ehArgUnwrapVector = GetStaticMethod(typeof(TypeCast), "EHArgUnwrapVector", new Type[] { typeof(object) }); | ||
133 | private static MethodInfo xmrArrPubIndexMethod = typeof(XMR_Array).GetMethod("__pub_index", new Type[] { typeof(int) }); | ||
134 | private static MethodInfo xmrArrPubValueMethod = typeof(XMR_Array).GetMethod("__pub_value", new Type[] { typeof(int) }); | ||
135 | private static MethodInfo captureStackFrameMethodInfo = typeof(XMRInstAbstract).GetMethod("CaptureStackFrame", new Type[] { typeof(string), typeof(int), typeof(int) }); | ||
136 | private static MethodInfo restoreStackFrameMethodInfo = typeof(XMRInstAbstract).GetMethod("RestoreStackFrame", new Type[] { typeof(string), typeof(int).MakeByRefType() }); | ||
137 | private static MethodInfo stringCompareMethodInfo = GetStaticMethod(typeof(String), "Compare", new Type[] { typeof(string), typeof(string), typeof(StringComparison) }); | ||
138 | private static MethodInfo stringConcat2MethodInfo = GetStaticMethod(typeof(String), "Concat", new Type[] { typeof(string), typeof(string) }); | ||
139 | private static MethodInfo stringConcat3MethodInfo = GetStaticMethod(typeof(String), "Concat", new Type[] { typeof(string), typeof(string), typeof(string) }); | ||
140 | private static MethodInfo stringConcat4MethodInfo = GetStaticMethod(typeof(String), "Concat", new Type[] { typeof(string), typeof(string), typeof(string), typeof(string) }); | ||
141 | private static MethodInfo lslRotationNegateMethodInfo = GetStaticMethod(typeof(ScriptCodeGen), | ||
142 | "LSLRotationNegate", | ||
143 | new Type[] { typeof(LSL_Rotation) }); | ||
144 | private static MethodInfo lslVectorNegateMethodInfo = GetStaticMethod(typeof(ScriptCodeGen), | ||
145 | "LSLVectorNegate", | ||
146 | new Type[] { typeof(LSL_Vector) }); | ||
147 | private static MethodInfo scriptRestoreCatchExceptionUnwrap = GetStaticMethod(typeof(ScriptRestoreCatchException), "Unwrap", new Type[] { typeof(Exception) }); | ||
148 | private static MethodInfo thrownExceptionWrapMethodInfo = GetStaticMethod(typeof(ScriptThrownException), "Wrap", new Type[] { typeof(object) }); | ||
149 | |||
150 | private static MethodInfo catchExcToStrMethodInfo = GetStaticMethod(typeof(ScriptCodeGen), | ||
151 | "CatchExcToStr", | ||
152 | new Type[] { typeof(Exception) }); | ||
153 | |||
154 | private static MethodInfo consoleWriteMethodInfo = GetStaticMethod(typeof(ScriptCodeGen), "ConsoleWrite", new Type[] { typeof(object) }); | ||
155 | public static void ConsoleWrite(object o) | ||
156 | { | ||
157 | if(o == null) | ||
158 | o = "<<null>>"; | ||
159 | Console.Write(o.ToString()); | ||
160 | } | ||
161 | |||
162 | public static bool CodeGen(TokenScript tokenScript, BinaryWriter objFileWriter, string sourceHash) | ||
163 | { | ||
164 | /* | ||
165 | * Run compiler such that it has a 'this' context for convenience. | ||
166 | */ | ||
167 | ScriptCodeGen scg = new ScriptCodeGen(tokenScript, objFileWriter, sourceHash); | ||
168 | |||
169 | /* | ||
170 | * Return pointer to resultant script object code. | ||
171 | */ | ||
172 | return !scg.youveAnError; | ||
173 | } | ||
174 | |||
175 | /* | ||
176 | * There is one set of these variables for each script being compiled. | ||
177 | */ | ||
178 | private bool mightGetHere = false; | ||
179 | private bool youveAnError = false; | ||
180 | private BreakContTarg curBreakTarg = null; | ||
181 | private BreakContTarg curContTarg = null; | ||
182 | private int lastErrorLine = 0; | ||
183 | private int nStates = 0; | ||
184 | private string sourceHash; | ||
185 | private string lastErrorFile = ""; | ||
186 | private string[] stateNames; | ||
187 | private XMRInstArSizes glblSizes = new XMRInstArSizes(); | ||
188 | private Token errorMessageToken = null; | ||
189 | private TokenDeclVar curDeclFunc = null; | ||
190 | private TokenStmtBlock curStmtBlock = null; | ||
191 | private BinaryWriter objFileWriter = null; | ||
192 | private TokenScript tokenScript = null; | ||
193 | public int tempCompValuNum = 0; | ||
194 | private TokenDeclSDTypeClass currentSDTClass = null; | ||
195 | |||
196 | private Dictionary<string, int> stateIndices = null; | ||
197 | |||
198 | // These get cleared at beginning of every function definition | ||
199 | private ScriptMyLocal instancePointer; // holds XMRInstanceSuperType pointer | ||
200 | private ScriptMyLabel retLabel = null; // where to jump to exit function | ||
201 | private ScriptMyLocal retValue = null; | ||
202 | private ScriptMyLocal actCallNo = null; // for the active try/catch/finally stack or the big one outside them all | ||
203 | private LinkedList<CallLabel> actCallLabels = new LinkedList<CallLabel>(); // for the active try/catch/finally stack or the big one outside them all | ||
204 | private LinkedList<CallLabel> allCallLabels = new LinkedList<CallLabel>(); // this holds each and every one for all stacks in total | ||
205 | public CallLabel openCallLabel = null; // only one call label can be open at a time | ||
206 | // - the call label is open from the time of CallPre() until corresponding CallPost() | ||
207 | // - so no non-trivial pushes/pops etc allowed between a CallPre() and a CallPost() | ||
208 | |||
209 | private ScriptMyILGen _ilGen; | ||
210 | public ScriptMyILGen ilGen | ||
211 | { | ||
212 | get | ||
213 | { | ||
214 | return _ilGen; | ||
215 | } | ||
216 | } | ||
217 | |||
218 | private ScriptCodeGen(TokenScript tokenScript, BinaryWriter objFileWriter, string sourceHash) | ||
219 | { | ||
220 | this.tokenScript = tokenScript; | ||
221 | this.objFileWriter = objFileWriter; | ||
222 | this.sourceHash = sourceHash; | ||
223 | |||
224 | try | ||
225 | { | ||
226 | PerformCompilation(); | ||
227 | } | ||
228 | catch | ||
229 | { | ||
230 | // if we've an error, just punt on any exception | ||
231 | // it's probably just a null reference from something | ||
232 | // not being filled in etc. | ||
233 | if(!youveAnError) | ||
234 | throw; | ||
235 | } | ||
236 | finally | ||
237 | { | ||
238 | objFileWriter = null; | ||
239 | } | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * @brief Convert 'tokenScript' to 'objFileWriter' format. | ||
244 | * 'tokenScript' is a parsed/reduced abstract syntax tree of the script source file | ||
245 | * 'objFileWriter' is a serialized form of the CIL code that we generate | ||
246 | */ | ||
247 | private void PerformCompilation() | ||
248 | { | ||
249 | /* | ||
250 | * errorMessageToken is used only when the given token doesn't have a | ||
251 | * output delegate associated with it such as for backend API functions | ||
252 | * that only have one copy for the whole system. It is kept up-to-date | ||
253 | * approximately but is rarely needed so going to assume it doesn't have | ||
254 | * to be exact. | ||
255 | */ | ||
256 | errorMessageToken = tokenScript; | ||
257 | |||
258 | /* | ||
259 | * Set up dictionary to translate state names to their index number. | ||
260 | */ | ||
261 | stateIndices = new Dictionary<string, int>(); | ||
262 | |||
263 | /* | ||
264 | * Assign each state its own unique index. | ||
265 | * The default state gets 0. | ||
266 | */ | ||
267 | nStates = 0; | ||
268 | tokenScript.defaultState.body.index = nStates++; | ||
269 | stateIndices.Add("default", 0); | ||
270 | foreach(KeyValuePair<string, TokenDeclState> kvp in tokenScript.states) | ||
271 | { | ||
272 | TokenDeclState declState = kvp.Value; | ||
273 | declState.body.index = nStates++; | ||
274 | stateIndices.Add(declState.name.val, declState.body.index); | ||
275 | } | ||
276 | |||
277 | /* | ||
278 | * Make up an array that translates state indices to state name strings. | ||
279 | */ | ||
280 | stateNames = new string[nStates]; | ||
281 | stateNames[0] = "default"; | ||
282 | foreach(KeyValuePair<string, TokenDeclState> kvp in tokenScript.states) | ||
283 | { | ||
284 | TokenDeclState declState = kvp.Value; | ||
285 | stateNames[declState.body.index] = declState.name.val; | ||
286 | } | ||
287 | |||
288 | /* | ||
289 | * Make sure we have delegates for all script-defined functions and methods, | ||
290 | * creating anonymous ones if needed. Note that this includes all property | ||
291 | * getter and setter methods. | ||
292 | */ | ||
293 | foreach(TokenDeclVar declFunc in tokenScript.variablesStack) | ||
294 | { | ||
295 | if(declFunc.retType != null) | ||
296 | { | ||
297 | declFunc.GetDelType(); | ||
298 | } | ||
299 | } | ||
300 | while(true) | ||
301 | { | ||
302 | bool itIsAGoodDayToDie = true; | ||
303 | try | ||
304 | { | ||
305 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
306 | { | ||
307 | itIsAGoodDayToDie = false; | ||
308 | if(sdType is TokenDeclSDTypeClass) | ||
309 | { | ||
310 | TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; | ||
311 | foreach(TokenDeclVar declFunc in sdtClass.members) | ||
312 | { | ||
313 | if(declFunc.retType != null) | ||
314 | { | ||
315 | declFunc.GetDelType(); | ||
316 | if(declFunc.funcNameSig.val.StartsWith("$ctor(")) | ||
317 | { | ||
318 | // this is for the "$new()" static method that we create below. | ||
319 | // See GenerateStmtNewobj() etc. | ||
320 | new TokenTypeSDTypeDelegate(declFunc, sdtClass.MakeRefToken(declFunc), | ||
321 | declFunc.argDecl.types, tokenScript); | ||
322 | } | ||
323 | } | ||
324 | } | ||
325 | } | ||
326 | if(sdType is TokenDeclSDTypeInterface) | ||
327 | { | ||
328 | TokenDeclSDTypeInterface sdtIFace = (TokenDeclSDTypeInterface)sdType; | ||
329 | foreach(TokenDeclVar declFunc in sdtIFace.methsNProps) | ||
330 | { | ||
331 | if(declFunc.retType != null) | ||
332 | { | ||
333 | declFunc.GetDelType(); | ||
334 | } | ||
335 | } | ||
336 | } | ||
337 | itIsAGoodDayToDie = true; | ||
338 | } | ||
339 | break; | ||
340 | } | ||
341 | catch(InvalidOperationException) | ||
342 | { | ||
343 | if(!itIsAGoodDayToDie) | ||
344 | throw; | ||
345 | // fetching the delegate created an anonymous entry in tokenScript.sdSrcTypesValues | ||
346 | // which made the foreach statement puque, so start over... | ||
347 | } | ||
348 | } | ||
349 | |||
350 | /* | ||
351 | * No more types can be defined or we won't be able to write them to the object file. | ||
352 | */ | ||
353 | tokenScript.sdSrcTypesSeal(); | ||
354 | |||
355 | /* | ||
356 | * Assign all global variables a slot in its corresponding XMRInstance.gbl<Type>s[] array. | ||
357 | * Global variables are simply elements of those arrays at runtime, thus we don't need to create | ||
358 | * an unique class for each script, we can just use XMRInstance as is for all. | ||
359 | */ | ||
360 | foreach(TokenDeclVar declVar in tokenScript.variablesStack) | ||
361 | { | ||
362 | |||
363 | /* | ||
364 | * Omit 'constant' variables as they are coded inline so don't need a slot. | ||
365 | */ | ||
366 | if(declVar.constant) | ||
367 | continue; | ||
368 | |||
369 | /* | ||
370 | * Do functions later. | ||
371 | */ | ||
372 | if(declVar.retType != null) | ||
373 | continue; | ||
374 | |||
375 | /* | ||
376 | * Create entry in the value array for the variable or property. | ||
377 | */ | ||
378 | declVar.location = new CompValuGlobalVar(declVar, glblSizes); | ||
379 | } | ||
380 | |||
381 | /* | ||
382 | * Likewise for any static fields in script-defined classes. | ||
383 | * They can be referenced anywhere by <typename>.<fieldname>, see | ||
384 | * GenerateFromLValSField(). | ||
385 | */ | ||
386 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
387 | { | ||
388 | if(!(sdType is TokenDeclSDTypeClass)) | ||
389 | continue; | ||
390 | TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; | ||
391 | |||
392 | foreach(TokenDeclVar declVar in sdtClass.members) | ||
393 | { | ||
394 | |||
395 | /* | ||
396 | * Omit 'constant' variables as they are coded inline so don't need a slot. | ||
397 | */ | ||
398 | if(declVar.constant) | ||
399 | continue; | ||
400 | |||
401 | /* | ||
402 | * Do methods later. | ||
403 | */ | ||
404 | if(declVar.retType != null) | ||
405 | continue; | ||
406 | |||
407 | /* | ||
408 | * Ignore non-static fields for now. | ||
409 | * They get assigned below. | ||
410 | */ | ||
411 | if((declVar.sdtFlags & ScriptReduce.SDT_STATIC) == 0) | ||
412 | continue; | ||
413 | |||
414 | /* | ||
415 | * Create entry in the value array for the static field or static property. | ||
416 | */ | ||
417 | declVar.location = new CompValuGlobalVar(declVar, glblSizes); | ||
418 | } | ||
419 | } | ||
420 | |||
421 | /* | ||
422 | * Assign slots for all interface method prototypes. | ||
423 | * These indices are used to index the array of delegates that holds a class' implementation of an | ||
424 | * interface. | ||
425 | * Properties do not get a slot because they aren't called as such. But their corresponding | ||
426 | * <name>$get() and <name>$set(<type>) methods are in the table and they each get a slot. | ||
427 | */ | ||
428 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
429 | { | ||
430 | if(!(sdType is TokenDeclSDTypeInterface)) | ||
431 | continue; | ||
432 | TokenDeclSDTypeInterface sdtIFace = (TokenDeclSDTypeInterface)sdType; | ||
433 | int vti = 0; | ||
434 | foreach(TokenDeclVar im in sdtIFace.methsNProps) | ||
435 | { | ||
436 | if((im.getProp == null) && (im.setProp == null)) | ||
437 | { | ||
438 | im.vTableIndex = vti++; | ||
439 | } | ||
440 | } | ||
441 | } | ||
442 | |||
443 | /* | ||
444 | * Assign slots for all instance fields and virtual methods of script-defined classes. | ||
445 | */ | ||
446 | int maxExtends = tokenScript.sdSrcTypesCount; | ||
447 | bool didOne; | ||
448 | do | ||
449 | { | ||
450 | didOne = false; | ||
451 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
452 | { | ||
453 | if(!(sdType is TokenDeclSDTypeClass)) | ||
454 | continue; | ||
455 | TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; | ||
456 | if(sdtClass.slotsAssigned) | ||
457 | continue; | ||
458 | |||
459 | /* | ||
460 | * If this class extends another, the extended class has to already | ||
461 | * be set up, because our slots add on to the end of the extended class. | ||
462 | */ | ||
463 | TokenDeclSDTypeClass extends = sdtClass.extends; | ||
464 | if(extends != null) | ||
465 | { | ||
466 | if(!extends.slotsAssigned) | ||
467 | continue; | ||
468 | sdtClass.instSizes = extends.instSizes; | ||
469 | sdtClass.numVirtFuncs = extends.numVirtFuncs; | ||
470 | sdtClass.numInterfaces = extends.numInterfaces; | ||
471 | |||
472 | int n = maxExtends; | ||
473 | for(TokenDeclSDTypeClass ex = extends; ex != null; ex = ex.extends) | ||
474 | { | ||
475 | if(--n < 0) | ||
476 | break; | ||
477 | } | ||
478 | if(n < 0) | ||
479 | { | ||
480 | ErrorMsg(sdtClass, "loop in extended classes"); | ||
481 | sdtClass.slotsAssigned = true; | ||
482 | continue; | ||
483 | } | ||
484 | } | ||
485 | |||
486 | /* | ||
487 | * Extended class's slots all assigned, assign our instance fields | ||
488 | * slots in the XMRSDTypeClObj arrays. | ||
489 | */ | ||
490 | foreach(TokenDeclVar declVar in sdtClass.members) | ||
491 | { | ||
492 | if(declVar.retType != null) | ||
493 | continue; | ||
494 | if(declVar.constant) | ||
495 | continue; | ||
496 | if((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) | ||
497 | continue; | ||
498 | if((declVar.getProp == null) && (declVar.setProp == null)) | ||
499 | { | ||
500 | declVar.type.AssignVarSlot(declVar, sdtClass.instSizes); | ||
501 | } | ||
502 | } | ||
503 | |||
504 | /* | ||
505 | * ... and assign virtual method vtable slots. | ||
506 | * | ||
507 | * - : error if any overridden method, doesn't need a slot | ||
508 | * abstract : error if any overridden method, alloc new slot but leave it empty | ||
509 | * new : ignore any overridden method, doesn't need a slot | ||
510 | * new abstract : ignore any overridden method, alloc new slot but leave it empty | ||
511 | * override : must have overridden abstract/virtual, use old slot | ||
512 | * override abstract : must have overridden abstract, use old slot but it is still empty | ||
513 | * static : error if any overridden method, doesn't need a slot | ||
514 | * static new : ignore any overridden method, doesn't need a slot | ||
515 | * virtual : error if any overridden method, alloc new slot and fill it in | ||
516 | * virtual new : ignore any overridden method, alloc new slot and fill it in | ||
517 | */ | ||
518 | foreach(TokenDeclVar declFunc in sdtClass.members) | ||
519 | { | ||
520 | if(declFunc.retType == null) | ||
521 | continue; | ||
522 | curDeclFunc = declFunc; | ||
523 | |||
524 | /* | ||
525 | * See if there is a method in an extended class that this method overshadows. | ||
526 | * If so, check for various conflicts. | ||
527 | * In any case, SDT_NEW on our method means to ignore any overshadowed method. | ||
528 | */ | ||
529 | string declLongName = sdtClass.longName.val + "." + declFunc.funcNameSig.val; | ||
530 | uint declFlags = declFunc.sdtFlags; | ||
531 | TokenDeclVar overridden = null; | ||
532 | if((declFlags & ScriptReduce.SDT_NEW) == 0) | ||
533 | { | ||
534 | for(TokenDeclSDTypeClass sdtd = extends; sdtd != null; sdtd = sdtd.extends) | ||
535 | { | ||
536 | overridden = FindExactWithRet(sdtd.members, declFunc.name, declFunc.retType, declFunc.argDecl.types); | ||
537 | if(overridden != null) | ||
538 | break; | ||
539 | } | ||
540 | } | ||
541 | if(overridden != null) | ||
542 | do | ||
543 | { | ||
544 | string overLongName = overridden.sdtClass.longName.val; | ||
545 | uint overFlags = overridden.sdtFlags; | ||
546 | |||
547 | /* | ||
548 | * See if overridden method allows itself to be overridden. | ||
549 | */ | ||
550 | if((overFlags & ScriptReduce.SDT_ABSTRACT) != 0) | ||
551 | { | ||
552 | if((declFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_OVERRIDE)) == 0) | ||
553 | { | ||
554 | ErrorMsg(declFunc, declLongName + " overshadows abstract " + overLongName + " but is not marked abstract, new or override"); | ||
555 | break; | ||
556 | } | ||
557 | } | ||
558 | else if((overFlags & ScriptReduce.SDT_FINAL) != 0) | ||
559 | { | ||
560 | ErrorMsg(declFunc, declLongName + " overshadows final " + overLongName + " but is not marked new"); | ||
561 | } | ||
562 | else if((overFlags & (ScriptReduce.SDT_OVERRIDE | ScriptReduce.SDT_VIRTUAL)) != 0) | ||
563 | { | ||
564 | if((declFlags & (ScriptReduce.SDT_NEW | ScriptReduce.SDT_OVERRIDE)) == 0) | ||
565 | { | ||
566 | ErrorMsg(declFunc, declLongName + " overshadows virtual " + overLongName + " but is not marked new or override"); | ||
567 | break; | ||
568 | } | ||
569 | } | ||
570 | else | ||
571 | { | ||
572 | ErrorMsg(declFunc, declLongName + " overshadows non-virtual " + overLongName + " but is not marked new"); | ||
573 | break; | ||
574 | } | ||
575 | |||
576 | /* | ||
577 | * See if our method is capable of overriding the other method. | ||
578 | */ | ||
579 | if((declFlags & ScriptReduce.SDT_ABSTRACT) != 0) | ||
580 | { | ||
581 | if((overFlags & ScriptReduce.SDT_ABSTRACT) == 0) | ||
582 | { | ||
583 | ErrorMsg(declFunc, declLongName + " abstract overshadows non-abstract " + overLongName + " but is not marked new"); | ||
584 | break; | ||
585 | } | ||
586 | } | ||
587 | else if((declFlags & ScriptReduce.SDT_OVERRIDE) != 0) | ||
588 | { | ||
589 | if((overFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_OVERRIDE | ScriptReduce.SDT_VIRTUAL)) == 0) | ||
590 | { | ||
591 | ErrorMsg(declFunc, declLongName + " override overshadows non-abstract/non-virtual " + overLongName); | ||
592 | break; | ||
593 | } | ||
594 | } | ||
595 | else | ||
596 | { | ||
597 | ErrorMsg(declFunc, declLongName + " overshadows " + overLongName + " but is not marked new"); | ||
598 | break; | ||
599 | } | ||
600 | } while(false); | ||
601 | |||
602 | /* | ||
603 | * Now we can assign it a vtable slot if it needs one (ie, it is virtual). | ||
604 | */ | ||
605 | declFunc.vTableIndex = -1; | ||
606 | if(overridden != null) | ||
607 | { | ||
608 | declFunc.vTableIndex = overridden.vTableIndex; | ||
609 | } | ||
610 | else if((declFlags & ScriptReduce.SDT_OVERRIDE) != 0) | ||
611 | { | ||
612 | ErrorMsg(declFunc, declLongName + " marked override but nothing matching found that it overrides"); | ||
613 | } | ||
614 | if((declFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_VIRTUAL)) != 0) | ||
615 | { | ||
616 | declFunc.vTableIndex = sdtClass.numVirtFuncs++; | ||
617 | } | ||
618 | } | ||
619 | curDeclFunc = null; | ||
620 | |||
621 | /* | ||
622 | * ... and assign implemented interface slots. | ||
623 | * Note that our implementations of a given interface is completely independent of any | ||
624 | * rootward class's implementation of that same interface. | ||
625 | */ | ||
626 | int nIFaces = sdtClass.numInterfaces + sdtClass.implements.Count; | ||
627 | sdtClass.iFaces = new TokenDeclSDTypeInterface[nIFaces]; | ||
628 | sdtClass.iImplFunc = new TokenDeclVar[nIFaces][]; | ||
629 | for(int i = 0; i < sdtClass.numInterfaces; i++) | ||
630 | { | ||
631 | sdtClass.iFaces[i] = extends.iFaces[i]; | ||
632 | sdtClass.iImplFunc[i] = extends.iImplFunc[i]; | ||
633 | } | ||
634 | |||
635 | foreach(TokenDeclSDTypeInterface intf in sdtClass.implements) | ||
636 | { | ||
637 | int i = sdtClass.numInterfaces++; | ||
638 | sdtClass.iFaces[i] = intf; | ||
639 | sdtClass.intfIndices.Add(intf.longName.val, i); | ||
640 | int nMeths = 0; | ||
641 | foreach(TokenDeclVar m in intf.methsNProps) | ||
642 | { | ||
643 | if((m.getProp == null) && (m.setProp == null)) | ||
644 | nMeths++; | ||
645 | } | ||
646 | sdtClass.iImplFunc[i] = new TokenDeclVar[nMeths]; | ||
647 | } | ||
648 | |||
649 | foreach(TokenDeclVar classMeth in sdtClass.members) | ||
650 | { | ||
651 | if(classMeth.retType == null) | ||
652 | continue; | ||
653 | curDeclFunc = classMeth; | ||
654 | for(TokenIntfImpl intfImpl = classMeth.implements; intfImpl != null; intfImpl = (TokenIntfImpl)intfImpl.nextToken) | ||
655 | { | ||
656 | |||
657 | /* | ||
658 | * One of the class methods implements an interface method. | ||
659 | * Try to find the interface method that is implemented and verify its signature. | ||
660 | */ | ||
661 | TokenDeclSDTypeInterface intfType = intfImpl.intfType.decl; | ||
662 | TokenDeclVar intfMeth = FindExactWithRet(intfType.methsNProps, intfImpl.methName, classMeth.retType, classMeth.argDecl.types); | ||
663 | if(intfMeth == null) | ||
664 | { | ||
665 | ErrorMsg(intfImpl, "interface does not define method " + intfImpl.methName.val + classMeth.argDecl.GetArgSig()); | ||
666 | continue; | ||
667 | } | ||
668 | |||
669 | /* | ||
670 | * See if this class was declared to implement that interface. | ||
671 | */ | ||
672 | bool found = false; | ||
673 | foreach(TokenDeclSDTypeInterface intf in sdtClass.implements) | ||
674 | { | ||
675 | if(intf == intfType) | ||
676 | { | ||
677 | found = true; | ||
678 | break; | ||
679 | } | ||
680 | } | ||
681 | if(!found) | ||
682 | { | ||
683 | ErrorMsg(intfImpl, "class not declared to implement " + intfType.longName.val); | ||
684 | continue; | ||
685 | } | ||
686 | |||
687 | /* | ||
688 | * Get index in iFaces[] and iImplFunc[] arrays. | ||
689 | * Start scanning from the end in case one of our rootward classes also implements the interface. | ||
690 | * We should always be successful because we know by now that this class implements the interface. | ||
691 | */ | ||
692 | int i; | ||
693 | for(i = sdtClass.numInterfaces; --i >= 0;) | ||
694 | { | ||
695 | if(sdtClass.iFaces[i] == intfType) | ||
696 | break; | ||
697 | } | ||
698 | |||
699 | /* | ||
700 | * Now remember which of the class methods implements that interface method. | ||
701 | */ | ||
702 | int j = intfMeth.vTableIndex; | ||
703 | if(sdtClass.iImplFunc[i][j] != null) | ||
704 | { | ||
705 | ErrorMsg(intfImpl, "also implemented by " + sdtClass.iImplFunc[i][j].funcNameSig.val); | ||
706 | continue; | ||
707 | } | ||
708 | sdtClass.iImplFunc[i][j] = classMeth; | ||
709 | } | ||
710 | } | ||
711 | curDeclFunc = null; | ||
712 | |||
713 | /* | ||
714 | * Now make sure this class implements all methods for all declared interfaces. | ||
715 | */ | ||
716 | for(int i = sdtClass.numInterfaces - sdtClass.implements.Count; i < sdtClass.numInterfaces; i++) | ||
717 | { | ||
718 | TokenDeclVar[] implementations = sdtClass.iImplFunc[i]; | ||
719 | for(int j = implementations.Length; --j >= 0;) | ||
720 | { | ||
721 | if(implementations[j] == null) | ||
722 | { | ||
723 | TokenDeclSDTypeInterface intf = sdtClass.iFaces[i]; | ||
724 | TokenDeclVar meth = null; | ||
725 | foreach(TokenDeclVar im in intf.methsNProps) | ||
726 | { | ||
727 | if(im.vTableIndex == j) | ||
728 | { | ||
729 | meth = im; | ||
730 | break; | ||
731 | } | ||
732 | } | ||
733 | ErrorMsg(sdtClass, "does not implement " + intf.longName.val + "." + meth.funcNameSig.val); | ||
734 | } | ||
735 | } | ||
736 | } | ||
737 | |||
738 | /* | ||
739 | * All slots for this class have been assigned. | ||
740 | */ | ||
741 | sdtClass.slotsAssigned = true; | ||
742 | didOne = true; | ||
743 | } | ||
744 | } while(didOne); | ||
745 | |||
746 | /* | ||
747 | * Compute final values for all variables/fields declared as 'constant'. | ||
748 | * Note that there may be forward references. | ||
749 | */ | ||
750 | do | ||
751 | { | ||
752 | didOne = false; | ||
753 | foreach(TokenDeclVar tdv in tokenScript.variablesStack) | ||
754 | { | ||
755 | if(tdv.constant && !(tdv.init is TokenRValConst)) | ||
756 | { | ||
757 | tdv.init = tdv.init.TryComputeConstant(LookupInitConstants, ref didOne); | ||
758 | } | ||
759 | } | ||
760 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
761 | { | ||
762 | if(!(sdType is TokenDeclSDTypeClass)) | ||
763 | continue; | ||
764 | currentSDTClass = (TokenDeclSDTypeClass)sdType; | ||
765 | foreach(TokenDeclVar tdv in currentSDTClass.members) | ||
766 | { | ||
767 | if(tdv.constant && !(tdv.init is TokenRValConst)) | ||
768 | { | ||
769 | tdv.init = tdv.init.TryComputeConstant(LookupInitConstants, ref didOne); | ||
770 | } | ||
771 | } | ||
772 | } | ||
773 | currentSDTClass = null; | ||
774 | } while(didOne); | ||
775 | |||
776 | /* | ||
777 | * Now we should be able to assign all those constants their type and location. | ||
778 | */ | ||
779 | foreach(TokenDeclVar tdv in tokenScript.variablesStack) | ||
780 | { | ||
781 | if(tdv.constant) | ||
782 | { | ||
783 | if(tdv.init is TokenRValConst) | ||
784 | { | ||
785 | TokenRValConst rvc = (TokenRValConst)tdv.init; | ||
786 | tdv.type = rvc.tokType; | ||
787 | tdv.location = rvc.GetCompValu(); | ||
788 | } | ||
789 | else | ||
790 | { | ||
791 | ErrorMsg(tdv, "value is not constant"); | ||
792 | } | ||
793 | } | ||
794 | } | ||
795 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
796 | { | ||
797 | if(!(sdType is TokenDeclSDTypeClass)) | ||
798 | continue; | ||
799 | currentSDTClass = (TokenDeclSDTypeClass)sdType; | ||
800 | foreach(TokenDeclVar tdv in currentSDTClass.members) | ||
801 | { | ||
802 | if(tdv.constant) | ||
803 | { | ||
804 | if(tdv.init is TokenRValConst) | ||
805 | { | ||
806 | TokenRValConst rvc = (TokenRValConst)tdv.init; | ||
807 | tdv.type = rvc.tokType; | ||
808 | tdv.location = rvc.GetCompValu(); | ||
809 | } | ||
810 | else | ||
811 | { | ||
812 | ErrorMsg(tdv, "value is not constant"); | ||
813 | } | ||
814 | } | ||
815 | } | ||
816 | } | ||
817 | currentSDTClass = null; | ||
818 | |||
819 | /* | ||
820 | * For all classes that define all the methods needed for the class, ie, they aren't abstract, | ||
821 | * define a static class.$new() method with same args as the $ctor(s). This will allow the | ||
822 | * class to be instantiated via the new operator. | ||
823 | */ | ||
824 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
825 | { | ||
826 | if(!(sdType is TokenDeclSDTypeClass)) | ||
827 | continue; | ||
828 | TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; | ||
829 | |||
830 | /* | ||
831 | * See if the class as it stands would be able to fill every slot of its vtable. | ||
832 | */ | ||
833 | bool[] filled = new bool[sdtClass.numVirtFuncs]; | ||
834 | int numFilled = 0; | ||
835 | for(TokenDeclSDTypeClass sdtc = sdtClass; sdtc != null; sdtc = sdtc.extends) | ||
836 | { | ||
837 | foreach(TokenDeclVar tdf in sdtc.members) | ||
838 | { | ||
839 | if((tdf.retType != null) && (tdf.vTableIndex >= 0) && ((tdf.sdtFlags & ScriptReduce.SDT_ABSTRACT) == 0)) | ||
840 | { | ||
841 | if(!filled[tdf.vTableIndex]) | ||
842 | { | ||
843 | filled[tdf.vTableIndex] = true; | ||
844 | numFilled++; | ||
845 | } | ||
846 | } | ||
847 | } | ||
848 | } | ||
849 | |||
850 | /* | ||
851 | * If so, define a static class.$new() method for every constructor defined for the class. | ||
852 | * Give it the same access (private/protected/public) as the script declared for the constructor. | ||
853 | * Note that the reducer made sure there is at least a default constructor for every class. | ||
854 | */ | ||
855 | if(numFilled >= sdtClass.numVirtFuncs) | ||
856 | { | ||
857 | List<TokenDeclVar> newobjDeclFuncs = new List<TokenDeclVar>(); | ||
858 | foreach(TokenDeclVar ctorDeclFunc in sdtClass.members) | ||
859 | { | ||
860 | if((ctorDeclFunc.funcNameSig != null) && ctorDeclFunc.funcNameSig.val.StartsWith("$ctor(")) | ||
861 | { | ||
862 | TokenDeclVar newobjDeclFunc = DefineNewobjFunc(ctorDeclFunc); | ||
863 | newobjDeclFuncs.Add(newobjDeclFunc); | ||
864 | } | ||
865 | } | ||
866 | foreach(TokenDeclVar newobjDeclFunc in newobjDeclFuncs) | ||
867 | { | ||
868 | sdtClass.members.AddEntry(newobjDeclFunc); | ||
869 | } | ||
870 | } | ||
871 | } | ||
872 | |||
873 | /* | ||
874 | * Write fixed portion of object file. | ||
875 | */ | ||
876 | objFileWriter.Write(OBJECT_CODE_MAGIC.ToCharArray()); | ||
877 | objFileWriter.Write(COMPILED_VERSION_VALUE); | ||
878 | objFileWriter.Write(sourceHash); | ||
879 | glblSizes.WriteToFile(objFileWriter); | ||
880 | |||
881 | objFileWriter.Write(nStates); | ||
882 | for(int i = 0; i < nStates; i++) | ||
883 | { | ||
884 | objFileWriter.Write(stateNames[i]); | ||
885 | } | ||
886 | |||
887 | /* | ||
888 | * For debugging, we also write out global variable array slot assignments. | ||
889 | */ | ||
890 | foreach(TokenDeclVar declVar in tokenScript.variablesStack) | ||
891 | { | ||
892 | if(declVar.retType == null) | ||
893 | { | ||
894 | WriteOutGblAssignment("", declVar); | ||
895 | } | ||
896 | } | ||
897 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
898 | { | ||
899 | if(!(sdType is TokenDeclSDTypeClass)) | ||
900 | continue; | ||
901 | TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; | ||
902 | foreach(TokenDeclVar declVar in sdtClass.members) | ||
903 | { | ||
904 | if((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) | ||
905 | { | ||
906 | WriteOutGblAssignment(sdtClass.longName.val + ".", declVar); | ||
907 | } | ||
908 | } | ||
909 | } | ||
910 | objFileWriter.Write(""); | ||
911 | |||
912 | /* | ||
913 | * Write out script-defined types. | ||
914 | */ | ||
915 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
916 | { | ||
917 | objFileWriter.Write(sdType.longName.val); | ||
918 | sdType.WriteToFile(objFileWriter); | ||
919 | } | ||
920 | objFileWriter.Write(""); | ||
921 | |||
922 | /* | ||
923 | * Output function headers then bodies. | ||
924 | * Do all headers first in case bodies do forward references. | ||
925 | * Do both global functions, script-defined class static methods and | ||
926 | * script-defined instance methods, as we handle the differences | ||
927 | * during compilation of the functions/methods themselves. | ||
928 | */ | ||
929 | for(int pass = 0; pass < 2; pass++) | ||
930 | { | ||
931 | foreach(TokenDeclVar declFunc in tokenScript.variablesStack) | ||
932 | { | ||
933 | if(declFunc.retType != null) | ||
934 | { | ||
935 | if(pass == 0) | ||
936 | GenerateMethodHeader(declFunc); | ||
937 | else | ||
938 | GenerateMethodBody(declFunc); | ||
939 | } | ||
940 | } | ||
941 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
942 | { | ||
943 | if(sdType is TokenDeclSDTypeClass) | ||
944 | { | ||
945 | TokenDeclSDTypeClass sdtClass = (TokenDeclSDTypeClass)sdType; | ||
946 | foreach(TokenDeclVar declFunc in sdtClass.members) | ||
947 | { | ||
948 | if((declFunc.retType != null) && ((declFunc.sdtFlags & ScriptReduce.SDT_ABSTRACT) == 0)) | ||
949 | { | ||
950 | if(pass == 0) | ||
951 | GenerateMethodHeader(declFunc); | ||
952 | else | ||
953 | GenerateMethodBody(declFunc); | ||
954 | } | ||
955 | } | ||
956 | } | ||
957 | } | ||
958 | } | ||
959 | |||
960 | /* | ||
961 | * Output default state event handler functions. | ||
962 | * Each event handler is a private static method named 'default <eventname>'. | ||
963 | * Splice in a default state_entry() handler if none defined so we can init global vars. | ||
964 | */ | ||
965 | TokenDeclVar defaultStateEntry = null; | ||
966 | for(defaultStateEntry = tokenScript.defaultState.body.eventFuncs; | ||
967 | defaultStateEntry != null; | ||
968 | defaultStateEntry = (TokenDeclVar)defaultStateEntry.nextToken) | ||
969 | { | ||
970 | if(defaultStateEntry.funcNameSig.val == "state_entry()") | ||
971 | break; | ||
972 | } | ||
973 | if(defaultStateEntry == null) | ||
974 | { | ||
975 | defaultStateEntry = new TokenDeclVar(tokenScript.defaultState.body, null, tokenScript); | ||
976 | defaultStateEntry.name = new TokenName(tokenScript.defaultState.body, "state_entry"); | ||
977 | defaultStateEntry.retType = new TokenTypeVoid(tokenScript.defaultState.body); | ||
978 | defaultStateEntry.argDecl = new TokenArgDecl(tokenScript.defaultState.body); | ||
979 | defaultStateEntry.body = new TokenStmtBlock(tokenScript.defaultState.body); | ||
980 | defaultStateEntry.body.function = defaultStateEntry; | ||
981 | |||
982 | defaultStateEntry.nextToken = tokenScript.defaultState.body.eventFuncs; | ||
983 | tokenScript.defaultState.body.eventFuncs = defaultStateEntry; | ||
984 | } | ||
985 | GenerateStateEventHandlers("default", tokenScript.defaultState.body); | ||
986 | |||
987 | /* | ||
988 | * Output script-defined state event handler methods. | ||
989 | * Each event handler is a private static method named <statename> <eventname> | ||
990 | */ | ||
991 | foreach(KeyValuePair<string, TokenDeclState> kvp in tokenScript.states) | ||
992 | { | ||
993 | TokenDeclState declState = kvp.Value; | ||
994 | GenerateStateEventHandlers(declState.name.val, declState.body); | ||
995 | } | ||
996 | |||
997 | ScriptObjWriter.TheEnd(objFileWriter); | ||
998 | } | ||
999 | |||
1000 | /** | ||
1001 | * @brief Write out what slot was assigned for a global or sdtclass static variable. | ||
1002 | * Constants, functions, instance fields, methods, properties do not have slots in the global variables arrays. | ||
1003 | */ | ||
1004 | private void WriteOutGblAssignment(string pfx, TokenDeclVar declVar) | ||
1005 | { | ||
1006 | if(!declVar.constant && (declVar.retType == null) && (declVar.getProp == null) && (declVar.setProp == null)) | ||
1007 | { | ||
1008 | objFileWriter.Write(pfx + declVar.name.val); // string | ||
1009 | objFileWriter.Write(declVar.vTableArray.Name); // string | ||
1010 | objFileWriter.Write(declVar.vTableIndex); // int | ||
1011 | } | ||
1012 | } | ||
1013 | |||
1014 | /** | ||
1015 | * @brief generate event handler code | ||
1016 | * Writes out a function definition for each state handler | ||
1017 | * named <statename> <eventname> | ||
1018 | * | ||
1019 | * However, each has just 'XMRInstance __sw' as its single argument | ||
1020 | * and each of its user-visible argments is extracted from __sw.ehArgs[]. | ||
1021 | * | ||
1022 | * So we end up generating something like this: | ||
1023 | * | ||
1024 | * private static void <statename> <eventname>(XMRInstance __sw) | ||
1025 | * { | ||
1026 | * <typeArg0> <nameArg0> = (<typeArg0>)__sw.ehArgs[0]; | ||
1027 | * <typeArg1> <nameArg1> = (<typeArg1>)__sw.ehArgs[1]; | ||
1028 | * | ||
1029 | * ... script code ... | ||
1030 | * } | ||
1031 | * | ||
1032 | * The continuations code assumes there will be no references to ehArgs[] | ||
1033 | * after the first call to CheckRun() as CheckRun() makes no attempt to | ||
1034 | * serialize the ehArgs[] array, as doing so would be redundant. Any values | ||
1035 | * from ehArgs[] that are being used will be in local stack variables and | ||
1036 | * thus preserved that way. | ||
1037 | */ | ||
1038 | private void GenerateStateEventHandlers(string statename, TokenStateBody body) | ||
1039 | { | ||
1040 | Dictionary<string, TokenDeclVar> statehandlers = new Dictionary<string, TokenDeclVar>(); | ||
1041 | for(Token t = body.eventFuncs; t != null; t = t.nextToken) | ||
1042 | { | ||
1043 | TokenDeclVar tdv = (TokenDeclVar)t; | ||
1044 | string eventname = tdv.GetSimpleName(); | ||
1045 | if(statehandlers.ContainsKey(eventname)) | ||
1046 | { | ||
1047 | ErrorMsg(tdv, "event handler " + eventname + " already defined for state " + statename); | ||
1048 | } | ||
1049 | else | ||
1050 | { | ||
1051 | statehandlers.Add(eventname, tdv); | ||
1052 | GenerateEventHandler(statename, tdv); | ||
1053 | } | ||
1054 | } | ||
1055 | } | ||
1056 | |||
1057 | private void GenerateEventHandler(string statename, TokenDeclVar declFunc) | ||
1058 | { | ||
1059 | string eventname = declFunc.GetSimpleName(); | ||
1060 | TokenArgDecl argDecl = declFunc.argDecl; | ||
1061 | |||
1062 | /* | ||
1063 | * Make sure event handler name is valid and that number and type of arguments is correct. | ||
1064 | * Apparently some scripts exist with fewer than correct number of args in their declaration | ||
1065 | * so allow for that. It is ok because the handlers are called with the arguments in an | ||
1066 | * object[] array, and we just won't access the missing argments in the vector. But the | ||
1067 | * specified types must match one of the prototypes in legalEventHandlers. | ||
1068 | */ | ||
1069 | TokenDeclVar protoDeclFunc = legalEventHandlers.FindExact(eventname, argDecl.types); | ||
1070 | if(protoDeclFunc == null) | ||
1071 | { | ||
1072 | ErrorMsg(declFunc, "unknown event handler " + eventname + argDecl.GetArgSig()); | ||
1073 | return; | ||
1074 | } | ||
1075 | |||
1076 | /* | ||
1077 | * Output function header. | ||
1078 | * They just have the XMRInstAbstract pointer as the one argument. | ||
1079 | */ | ||
1080 | string functionName = statename + " " + eventname; | ||
1081 | _ilGen = new ScriptObjWriter(tokenScript, | ||
1082 | functionName, | ||
1083 | typeof(void), | ||
1084 | instanceTypeArg, | ||
1085 | instanceNameArg, | ||
1086 | objFileWriter); | ||
1087 | StartFunctionBody(declFunc); | ||
1088 | |||
1089 | /* | ||
1090 | * Create a temp to hold XMRInstanceSuperType version of arg 0. | ||
1091 | */ | ||
1092 | instancePointer = ilGen.DeclareLocal(xmrInstSuperType, "__xmrinst"); | ||
1093 | ilGen.Emit(declFunc, OpCodes.Ldarg_0); | ||
1094 | ilGen.Emit(declFunc, OpCodes.Castclass, xmrInstSuperType); | ||
1095 | ilGen.Emit(declFunc, OpCodes.Stloc, instancePointer); | ||
1096 | |||
1097 | /* | ||
1098 | * Output args as variable definitions and initialize each from __sw.ehArgs[]. | ||
1099 | * If the script writer goofed, the typecast will complain. | ||
1100 | */ | ||
1101 | int nArgs = argDecl.vars.Length; | ||
1102 | for(int i = 0; i < nArgs; i++) | ||
1103 | { | ||
1104 | |||
1105 | /* | ||
1106 | * Say that the argument variable is going to be located in a local var. | ||
1107 | */ | ||
1108 | TokenDeclVar argVar = argDecl.vars[i]; | ||
1109 | TokenType argTokType = argVar.type; | ||
1110 | CompValuLocalVar local = new CompValuLocalVar(argTokType, argVar.name.val, this); | ||
1111 | argVar.location = local; | ||
1112 | |||
1113 | /* | ||
1114 | * Copy from the ehArgs[i] element to the temp var. | ||
1115 | * Cast as needed, there is a lot of craziness like OpenMetaverse.Quaternion. | ||
1116 | */ | ||
1117 | local.PopPre(this, argVar.name); | ||
1118 | PushXMRInst(); // instance | ||
1119 | ilGen.Emit(declFunc, OpCodes.Ldfld, ehArgsFieldInfo); // instance.ehArgs (array of objects) | ||
1120 | ilGen.Emit(declFunc, OpCodes.Ldc_I4, i); // array index = i | ||
1121 | ilGen.Emit(declFunc, OpCodes.Ldelem, typeof(object)); // select the argument we want | ||
1122 | TokenType stkTokType = tokenTypeObj; // stack has a type 'object' on it now | ||
1123 | Type argSysType = argTokType.ToSysType(); // this is the type the script expects | ||
1124 | if(argSysType == typeof(double)) | ||
1125 | { // LSL_Float/double -> double | ||
1126 | ilGen.Emit(declFunc, OpCodes.Call, ehArgUnwrapFloat); | ||
1127 | stkTokType = tokenTypeFlt; // stack has a type 'double' on it now | ||
1128 | } | ||
1129 | if(argSysType == typeof(int)) | ||
1130 | { // LSL_Integer/int -> int | ||
1131 | ilGen.Emit(declFunc, OpCodes.Call, ehArgUnwrapInteger); | ||
1132 | stkTokType = tokenTypeInt; // stack has a type 'int' on it now | ||
1133 | } | ||
1134 | if(argSysType == typeof(LSL_List)) | ||
1135 | { // LSL_List -> LSL_List | ||
1136 | TypeCast.CastTopOfStack(this, argVar.name, stkTokType, argTokType, true); | ||
1137 | stkTokType = argTokType; // stack has a type 'LSL_List' on it now | ||
1138 | } | ||
1139 | if(argSysType == typeof(LSL_Rotation)) | ||
1140 | { // OpenMetaverse.Quaternion/LSL_Rotation -> LSL_Rotation | ||
1141 | ilGen.Emit(declFunc, OpCodes.Call, ehArgUnwrapRotation); | ||
1142 | stkTokType = tokenTypeRot; // stack has a type 'LSL_Rotation' on it now | ||
1143 | } | ||
1144 | if(argSysType == typeof(string)) | ||
1145 | { // LSL_Key/LSL_String/string -> string | ||
1146 | ilGen.Emit(declFunc, OpCodes.Call, ehArgUnwrapString); | ||
1147 | stkTokType = tokenTypeStr; // stack has a type 'string' on it now | ||
1148 | } | ||
1149 | if(argSysType == typeof(LSL_Vector)) | ||
1150 | { // OpenMetaverse.Vector3/LSL_Vector -> LSL_Vector | ||
1151 | ilGen.Emit(declFunc, OpCodes.Call, ehArgUnwrapVector); | ||
1152 | stkTokType = tokenTypeVec; // stack has a type 'LSL_Vector' on it now | ||
1153 | } | ||
1154 | local.PopPost(this, argVar.name, stkTokType); // pop stack type into argtype | ||
1155 | } | ||
1156 | |||
1157 | /* | ||
1158 | * Output code for the statements and clean up. | ||
1159 | */ | ||
1160 | GenerateFuncBody(); | ||
1161 | } | ||
1162 | |||
1163 | /** | ||
1164 | * @brief generate header for an arbitrary script-defined global function. | ||
1165 | * @param declFunc = function being defined | ||
1166 | */ | ||
1167 | private void GenerateMethodHeader(TokenDeclVar declFunc) | ||
1168 | { | ||
1169 | curDeclFunc = declFunc; | ||
1170 | |||
1171 | /* | ||
1172 | * Make up array of all argument types as seen by the code generator. | ||
1173 | * We splice in XMRInstanceSuperType or XMRSDTypeClObj for the first | ||
1174 | * arg as the function itself is static, followed by script-visible | ||
1175 | * arg types. | ||
1176 | */ | ||
1177 | TokenArgDecl argDecl = declFunc.argDecl; | ||
1178 | int nArgs = argDecl.vars.Length; | ||
1179 | Type[] argTypes = new Type[nArgs + 1]; | ||
1180 | string[] argNames = new string[nArgs + 1]; | ||
1181 | if(IsSDTInstMethod()) | ||
1182 | { | ||
1183 | argTypes[0] = typeof(XMRSDTypeClObj); | ||
1184 | argNames[0] = "$sdtthis"; | ||
1185 | } | ||
1186 | else | ||
1187 | { | ||
1188 | argTypes[0] = xmrInstSuperType; | ||
1189 | argNames[0] = "$xmrthis"; | ||
1190 | } | ||
1191 | for(int i = 0; i < nArgs; i++) | ||
1192 | { | ||
1193 | argTypes[i + 1] = argDecl.vars[i].type.ToSysType(); | ||
1194 | argNames[i + 1] = argDecl.vars[i].name.val; | ||
1195 | } | ||
1196 | |||
1197 | /* | ||
1198 | * Set up entrypoint. | ||
1199 | */ | ||
1200 | string objCodeName = declFunc.GetObjCodeName(); | ||
1201 | declFunc.ilGen = new ScriptObjWriter(tokenScript, | ||
1202 | objCodeName, | ||
1203 | declFunc.retType.ToSysType(), | ||
1204 | argTypes, | ||
1205 | argNames, | ||
1206 | objFileWriter); | ||
1207 | |||
1208 | /* | ||
1209 | * This says how to generate a call to the function and to get a delegate. | ||
1210 | */ | ||
1211 | declFunc.location = new CompValuGlobalMeth(declFunc); | ||
1212 | |||
1213 | curDeclFunc = null; | ||
1214 | } | ||
1215 | |||
1216 | /** | ||
1217 | * @brief generate code for an arbitrary script-defined function. | ||
1218 | * @param name = name of the function | ||
1219 | * @param argDecl = argument declarations | ||
1220 | * @param body = function's code body | ||
1221 | */ | ||
1222 | private void GenerateMethodBody(TokenDeclVar declFunc) | ||
1223 | { | ||
1224 | /* | ||
1225 | * Set up code generator for the function's contents. | ||
1226 | */ | ||
1227 | _ilGen = declFunc.ilGen; | ||
1228 | StartFunctionBody(declFunc); | ||
1229 | |||
1230 | /* | ||
1231 | * Create a temp to hold XMRInstanceSuperType version of arg 0. | ||
1232 | * For most functions, arg 0 is already XMRInstanceSuperType. | ||
1233 | * But for script-defined class instance methods, arg 0 holds | ||
1234 | * the XMRSDTypeClObj pointer and so we read the XMRInstAbstract | ||
1235 | * pointer from its XMRSDTypeClObj.xmrInst field then cast it to | ||
1236 | * XMRInstanceSuperType. | ||
1237 | */ | ||
1238 | if(IsSDTInstMethod()) | ||
1239 | { | ||
1240 | instancePointer = ilGen.DeclareLocal(xmrInstSuperType, "__xmrinst"); | ||
1241 | ilGen.Emit(declFunc, OpCodes.Ldarg_0); | ||
1242 | ilGen.Emit(declFunc, OpCodes.Ldfld, sdtXMRInstFieldInfo); | ||
1243 | ilGen.Emit(declFunc, OpCodes.Castclass, xmrInstSuperType); | ||
1244 | ilGen.Emit(declFunc, OpCodes.Stloc, instancePointer); | ||
1245 | } | ||
1246 | |||
1247 | /* | ||
1248 | * Define location of all script-level arguments so script body can access them. | ||
1249 | * The argument indices need to have +1 added to them because XMRInstance or | ||
1250 | * XMRSDTypeClObj is spliced in at arg 0. | ||
1251 | */ | ||
1252 | TokenArgDecl argDecl = declFunc.argDecl; | ||
1253 | int nArgs = argDecl.vars.Length; | ||
1254 | for(int i = 0; i < nArgs; i++) | ||
1255 | { | ||
1256 | TokenDeclVar argVar = argDecl.vars[i]; | ||
1257 | argVar.location = new CompValuArg(argVar.type, i + 1); | ||
1258 | } | ||
1259 | |||
1260 | /* | ||
1261 | * Output code for the statements and clean up. | ||
1262 | */ | ||
1263 | GenerateFuncBody(); | ||
1264 | } | ||
1265 | |||
1266 | private void StartFunctionBody(TokenDeclVar declFunc) | ||
1267 | { | ||
1268 | /* | ||
1269 | * Start current function being processed. | ||
1270 | * Set 'mightGetHere' as the code at the top is always executed. | ||
1271 | */ | ||
1272 | instancePointer = null; | ||
1273 | mightGetHere = true; | ||
1274 | curBreakTarg = null; | ||
1275 | curContTarg = null; | ||
1276 | curDeclFunc = declFunc; | ||
1277 | |||
1278 | /* | ||
1279 | * Start generating code. | ||
1280 | */ | ||
1281 | ((ScriptObjWriter)ilGen).BegMethod(); | ||
1282 | } | ||
1283 | |||
1284 | /** | ||
1285 | * @brief Define function for a script-defined type's <typename>.$new(<argsig>) method. | ||
1286 | * See GenerateStmtNewobj() for more info. | ||
1287 | */ | ||
1288 | private TokenDeclVar DefineNewobjFunc(TokenDeclVar ctorDeclFunc) | ||
1289 | { | ||
1290 | /* | ||
1291 | * Set up 'static classname $new(params-same-as-ctor) { }'. | ||
1292 | */ | ||
1293 | TokenDeclVar newobjDeclFunc = new TokenDeclVar(ctorDeclFunc, null, tokenScript); | ||
1294 | newobjDeclFunc.name = new TokenName(newobjDeclFunc, "$new"); | ||
1295 | newobjDeclFunc.retType = ctorDeclFunc.sdtClass.MakeRefToken(newobjDeclFunc); | ||
1296 | newobjDeclFunc.argDecl = ctorDeclFunc.argDecl; | ||
1297 | newobjDeclFunc.sdtClass = ctorDeclFunc.sdtClass; | ||
1298 | newobjDeclFunc.sdtFlags = ScriptReduce.SDT_STATIC | ctorDeclFunc.sdtFlags; | ||
1299 | |||
1300 | /* | ||
1301 | * Declare local variable named '$objptr' in a frame just under | ||
1302 | * what the '$new(...)' function's arguments are declared in. | ||
1303 | */ | ||
1304 | TokenDeclVar objptrVar = new TokenDeclVar(newobjDeclFunc, newobjDeclFunc, tokenScript); | ||
1305 | objptrVar.type = newobjDeclFunc.retType; | ||
1306 | objptrVar.name = new TokenName(newobjDeclFunc, "$objptr"); | ||
1307 | VarDict newFrame = new VarDict(false); | ||
1308 | newFrame.outerVarDict = ctorDeclFunc.argDecl.varDict; | ||
1309 | newFrame.AddEntry(objptrVar); | ||
1310 | |||
1311 | /* | ||
1312 | * Set up '$objptr.$ctor' | ||
1313 | */ | ||
1314 | TokenLValName objptrLValName = new TokenLValName(objptrVar.name, newFrame); | ||
1315 | // ref a var by giving its name | ||
1316 | TokenLValIField objptrDotCtor = new TokenLValIField(newobjDeclFunc); // an instance member reference | ||
1317 | objptrDotCtor.baseRVal = objptrLValName; // '$objptr' | ||
1318 | objptrDotCtor.fieldName = ctorDeclFunc.name; // '.' '$ctor' | ||
1319 | |||
1320 | /* | ||
1321 | * Set up '$objptr.$ctor(arglist)' call for use in the '$new(...)' body. | ||
1322 | * Copy the arglist from the constructor declaration so triviality | ||
1323 | * processing will pick the correct overloaded constructor. | ||
1324 | */ | ||
1325 | TokenRValCall callCtorRVal = new TokenRValCall(newobjDeclFunc); // doing a call of some sort | ||
1326 | callCtorRVal.meth = objptrDotCtor; // calling $objptr.$ctor() | ||
1327 | TokenDeclVar[] argList = newobjDeclFunc.argDecl.vars; // get args $new() was declared with | ||
1328 | callCtorRVal.nArgs = argList.Length; // ...that is nArgs we are passing to $objptr.$ctor() | ||
1329 | for(int i = argList.Length; --i >= 0;) | ||
1330 | { | ||
1331 | TokenDeclVar arg = argList[i]; // find out about one of the args | ||
1332 | TokenLValName argLValName = new TokenLValName(arg.name, ctorDeclFunc.argDecl.varDict); | ||
1333 | // pass arg of that name to $objptr.$ctor() | ||
1334 | argLValName.nextToken = callCtorRVal.args; // link to list of args passed to $objptr.$ctor() | ||
1335 | callCtorRVal.args = argLValName; | ||
1336 | } | ||
1337 | |||
1338 | /* | ||
1339 | * Set up a funky call to the constructor for the code body. | ||
1340 | * This will let code generator know there is some craziness. | ||
1341 | * See GenerateStmtNewobj(). | ||
1342 | * | ||
1343 | * This is in essence: | ||
1344 | * { | ||
1345 | * classname $objptr = newobj (classname); | ||
1346 | * $objptr.$ctor (...); | ||
1347 | * return $objptr; | ||
1348 | * } | ||
1349 | */ | ||
1350 | TokenStmtNewobj newobjStmtBody = new TokenStmtNewobj(ctorDeclFunc); | ||
1351 | newobjStmtBody.objptrVar = objptrVar; | ||
1352 | newobjStmtBody.rValCall = callCtorRVal; | ||
1353 | TokenStmtBlock newobjBody = new TokenStmtBlock(ctorDeclFunc); | ||
1354 | newobjBody.statements = newobjStmtBody; | ||
1355 | |||
1356 | /* | ||
1357 | * Link that code as the body of the function. | ||
1358 | */ | ||
1359 | newobjDeclFunc.body = newobjBody; | ||
1360 | |||
1361 | /* | ||
1362 | * Say the function calls '$objptr.$ctor(arglist)' so we will inherit ctor's triviality. | ||
1363 | */ | ||
1364 | newobjDeclFunc.unknownTrivialityCalls.AddLast(callCtorRVal); | ||
1365 | return newobjDeclFunc; | ||
1366 | } | ||
1367 | |||
1368 | private class TokenStmtNewobj: TokenStmt | ||
1369 | { | ||
1370 | public TokenDeclVar objptrVar; | ||
1371 | public TokenRValCall rValCall; | ||
1372 | public TokenStmtNewobj(Token original) : base(original) { } | ||
1373 | } | ||
1374 | |||
1375 | /** | ||
1376 | * @brief Output function body (either event handler or script-defined method). | ||
1377 | */ | ||
1378 | private void GenerateFuncBody() | ||
1379 | { | ||
1380 | /* | ||
1381 | * We want to know if the function's code is trivial, ie, | ||
1382 | * if it doesn't have anything that might be an infinite | ||
1383 | * loop and that is doesn't call anything that might have | ||
1384 | * an infinite loop. If it is, we don't need any CheckRun() | ||
1385 | * stuff or any of the frame save/restore stuff. | ||
1386 | */ | ||
1387 | bool isTrivial = curDeclFunc.IsFuncTrivial(this); | ||
1388 | |||
1389 | /* | ||
1390 | * Clear list of all call labels. | ||
1391 | * A call label is inserted just before every call that can possibly | ||
1392 | * call CheckRun(), including any direct calls to CheckRun(). | ||
1393 | * Then, when restoring stack, we can just switch to this label to | ||
1394 | * resume at the correct spot. | ||
1395 | */ | ||
1396 | actCallLabels.Clear(); | ||
1397 | allCallLabels.Clear(); | ||
1398 | openCallLabel = null; | ||
1399 | |||
1400 | /* | ||
1401 | * Alloc stack space for local vars. | ||
1402 | */ | ||
1403 | int stackframesize = AllocLocalVarStackSpace(); | ||
1404 | |||
1405 | /* | ||
1406 | * Include argument variables in stack space for this frame. | ||
1407 | */ | ||
1408 | foreach(TokenType tokType in curDeclFunc.argDecl.types) | ||
1409 | { | ||
1410 | stackframesize += LocalVarStackSize(tokType); | ||
1411 | } | ||
1412 | |||
1413 | /* | ||
1414 | * Any return statements inside function body jump to this label | ||
1415 | * after putting return value in __retval. | ||
1416 | */ | ||
1417 | retLabel = ilGen.DefineLabel("__retlbl"); | ||
1418 | retValue = null; | ||
1419 | if(!(curDeclFunc.retType is TokenTypeVoid)) | ||
1420 | { | ||
1421 | retValue = ilGen.DeclareLocal(curDeclFunc.retType.ToSysType(), "__retval"); | ||
1422 | } | ||
1423 | |||
1424 | /* | ||
1425 | * Output: | ||
1426 | * int __mainCallNo = -1; | ||
1427 | * instance.m_StackLeft -= stackframesize; | ||
1428 | * try { | ||
1429 | * if (instance.callMode != CallMode_NORMAL) goto __cmRestore; | ||
1430 | */ | ||
1431 | actCallNo = null; | ||
1432 | ScriptMyLabel cmRestore = null; | ||
1433 | if(!isTrivial) | ||
1434 | { | ||
1435 | actCallNo = ilGen.DeclareLocal(typeof(int), "__mainCallNo"); | ||
1436 | SetCallNo(curDeclFunc, actCallNo, -1); | ||
1437 | PushXMRInst(); | ||
1438 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1439 | ilGen.Emit(curDeclFunc, OpCodes.Ldfld, stackLeftFieldInfo); | ||
1440 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, stackframesize); | ||
1441 | ilGen.Emit(curDeclFunc, OpCodes.Sub); | ||
1442 | ilGen.Emit(curDeclFunc, OpCodes.Stfld, stackLeftFieldInfo); | ||
1443 | cmRestore = ilGen.DefineLabel("__cmRestore"); | ||
1444 | ilGen.BeginExceptionBlock(); | ||
1445 | PushXMRInst(); | ||
1446 | ilGen.Emit(curDeclFunc, OpCodes.Ldfld, ScriptCodeGen.callModeFieldInfo); | ||
1447 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_NORMAL); | ||
1448 | ilGen.Emit(curDeclFunc, OpCodes.Bne_Un, cmRestore); | ||
1449 | } | ||
1450 | |||
1451 | /* | ||
1452 | * Splice in the code optimizer for the body of the function. | ||
1453 | */ | ||
1454 | ScriptCollector collector = new ScriptCollector((ScriptObjWriter)ilGen); | ||
1455 | _ilGen = collector; | ||
1456 | |||
1457 | /* | ||
1458 | * If this is the default state_entry() handler, output code to set all global | ||
1459 | * variables to their initial values. Note that every script must have a | ||
1460 | * default state_entry() handler, we provide one if the script doesn't explicitly | ||
1461 | * define one. | ||
1462 | */ | ||
1463 | string methname = ilGen.methName; | ||
1464 | if(methname == "default state_entry") | ||
1465 | { | ||
1466 | |||
1467 | // if (!doGblInit) goto skipGblInit; | ||
1468 | ScriptMyLabel skipGblInitLabel = ilGen.DefineLabel("__skipGblInit"); | ||
1469 | PushXMRInst(); // instance | ||
1470 | ilGen.Emit(curDeclFunc, OpCodes.Ldfld, doGblInitFieldInfo); // instance.doGblInit | ||
1471 | ilGen.Emit(curDeclFunc, OpCodes.Brfalse, skipGblInitLabel); | ||
1472 | |||
1473 | // $globalvarinit(); | ||
1474 | TokenDeclVar gviFunc = tokenScript.globalVarInit; | ||
1475 | if(gviFunc.body.statements != null) | ||
1476 | { | ||
1477 | gviFunc.location.CallPre(this, gviFunc); | ||
1478 | gviFunc.location.CallPost(this, gviFunc); | ||
1479 | } | ||
1480 | |||
1481 | // various $staticfieldinit(); | ||
1482 | foreach(TokenDeclSDType sdType in tokenScript.sdSrcTypesValues) | ||
1483 | { | ||
1484 | if(sdType is TokenDeclSDTypeClass) | ||
1485 | { | ||
1486 | TokenDeclVar sfiFunc = ((TokenDeclSDTypeClass)sdType).staticFieldInit; | ||
1487 | if((sfiFunc != null) && (sfiFunc.body.statements != null)) | ||
1488 | { | ||
1489 | sfiFunc.location.CallPre(this, sfiFunc); | ||
1490 | sfiFunc.location.CallPost(this, sfiFunc); | ||
1491 | } | ||
1492 | } | ||
1493 | } | ||
1494 | |||
1495 | // doGblInit = 0; | ||
1496 | PushXMRInst(); // instance | ||
1497 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4_0); | ||
1498 | ilGen.Emit(curDeclFunc, OpCodes.Stfld, doGblInitFieldInfo); // instance.doGblInit | ||
1499 | |||
1500 | //skipGblInit: | ||
1501 | ilGen.MarkLabel(skipGblInitLabel); | ||
1502 | } | ||
1503 | |||
1504 | /* | ||
1505 | * If this is a script-defined type constructor, call the base constructor and call | ||
1506 | * this class's $instfieldinit() method to initialize instance fields. | ||
1507 | */ | ||
1508 | if((curDeclFunc.sdtClass != null) && curDeclFunc.funcNameSig.val.StartsWith("$ctor(")) | ||
1509 | { | ||
1510 | if(curDeclFunc.baseCtorCall != null) | ||
1511 | { | ||
1512 | GenerateFromRValCall(curDeclFunc.baseCtorCall); | ||
1513 | } | ||
1514 | TokenDeclVar ifiFunc = ((TokenDeclSDTypeClass)curDeclFunc.sdtClass).instFieldInit; | ||
1515 | if(ifiFunc.body.statements != null) | ||
1516 | { | ||
1517 | CompValu thisCompValu = new CompValuArg(ifiFunc.sdtClass.MakeRefToken(ifiFunc), 0); | ||
1518 | CompValu ifiFuncLocn = new CompValuInstMember(ifiFunc, thisCompValu, true); | ||
1519 | ifiFuncLocn.CallPre(this, ifiFunc); | ||
1520 | ifiFuncLocn.CallPost(this, ifiFunc); | ||
1521 | } | ||
1522 | } | ||
1523 | |||
1524 | /* | ||
1525 | * See if time to suspend in case they are doing a loop with recursion. | ||
1526 | */ | ||
1527 | if(!isTrivial) | ||
1528 | EmitCallCheckRun(curDeclFunc, true); | ||
1529 | |||
1530 | /* | ||
1531 | * Output code body. | ||
1532 | */ | ||
1533 | GenerateStmtBlock(curDeclFunc.body); | ||
1534 | |||
1535 | /* | ||
1536 | * If code falls through to this point, means they are missing | ||
1537 | * a return statement. And that is legal only if the function | ||
1538 | * returns 'void'. | ||
1539 | */ | ||
1540 | if(mightGetHere) | ||
1541 | { | ||
1542 | if(!(curDeclFunc.retType is TokenTypeVoid)) | ||
1543 | { | ||
1544 | ErrorMsg(curDeclFunc.body, "missing final return statement"); | ||
1545 | } | ||
1546 | ilGen.Emit(curDeclFunc, OpCodes.Leave, retLabel); | ||
1547 | } | ||
1548 | |||
1549 | /* | ||
1550 | * End of the code to be optimized. | ||
1551 | * Do optimizations then write it all out to object file. | ||
1552 | * After this, all code gets written directly to object file. | ||
1553 | * Optimization must be completed before we scan the allCallLabels | ||
1554 | * list below to look for active locals and temps. | ||
1555 | */ | ||
1556 | collector.Optimize(); | ||
1557 | _ilGen = collector.WriteOutAll(); | ||
1558 | collector = null; | ||
1559 | |||
1560 | /* | ||
1561 | * Output code to restore stack frame from stream. | ||
1562 | * It jumps back to the call labels within the function body. | ||
1563 | */ | ||
1564 | List<ScriptMyLocal> activeTemps = null; | ||
1565 | if(!isTrivial) | ||
1566 | { | ||
1567 | |||
1568 | /* | ||
1569 | * Build list of locals and temps active at all the call labels. | ||
1570 | */ | ||
1571 | activeTemps = new List<ScriptMyLocal>(); | ||
1572 | foreach(CallLabel cl in allCallLabels) | ||
1573 | { | ||
1574 | foreach(ScriptMyLocal lcl in cl.callLabel.whereAmI.localsReadBeforeWritten) | ||
1575 | { | ||
1576 | if(!activeTemps.Contains(lcl)) | ||
1577 | { | ||
1578 | activeTemps.Add(lcl); | ||
1579 | } | ||
1580 | } | ||
1581 | } | ||
1582 | |||
1583 | /* | ||
1584 | * Output code to restore the args, locals and temps then jump to | ||
1585 | * the call label that we were interrupted at. | ||
1586 | */ | ||
1587 | ilGen.MarkLabel(cmRestore); | ||
1588 | GenerateFrameRestoreCode(activeTemps); | ||
1589 | } | ||
1590 | |||
1591 | /* | ||
1592 | * Output epilog that saves stack frame state if CallMode_SAVE. | ||
1593 | * | ||
1594 | * finally { | ||
1595 | * instance.m_StackLeft += stackframesize; | ||
1596 | * if (instance.callMode != CallMode_SAVE) goto __endFin; | ||
1597 | * GenerateFrameCaptureCode(); | ||
1598 | * __endFin: | ||
1599 | * } | ||
1600 | */ | ||
1601 | ScriptMyLabel endFin = null; | ||
1602 | if(!isTrivial) | ||
1603 | { | ||
1604 | ilGen.BeginFinallyBlock(); | ||
1605 | PushXMRInst(); | ||
1606 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1607 | ilGen.Emit(curDeclFunc, OpCodes.Ldfld, stackLeftFieldInfo); | ||
1608 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, stackframesize); | ||
1609 | ilGen.Emit(curDeclFunc, OpCodes.Add); | ||
1610 | ilGen.Emit(curDeclFunc, OpCodes.Stfld, stackLeftFieldInfo); | ||
1611 | endFin = ilGen.DefineLabel("__endFin"); | ||
1612 | PushXMRInst(); | ||
1613 | ilGen.Emit(curDeclFunc, OpCodes.Ldfld, callModeFieldInfo); | ||
1614 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_SAVE); | ||
1615 | ilGen.Emit(curDeclFunc, OpCodes.Bne_Un, endFin); | ||
1616 | GenerateFrameCaptureCode(activeTemps); | ||
1617 | ilGen.MarkLabel(endFin); | ||
1618 | ilGen.Emit(curDeclFunc, OpCodes.Endfinally); | ||
1619 | ilGen.EndExceptionBlock(); | ||
1620 | } | ||
1621 | |||
1622 | /* | ||
1623 | * Output the 'real' return opcode. | ||
1624 | */ | ||
1625 | ilGen.MarkLabel(retLabel); | ||
1626 | if(!(curDeclFunc.retType is TokenTypeVoid)) | ||
1627 | { | ||
1628 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, retValue); | ||
1629 | } | ||
1630 | ilGen.Emit(curDeclFunc, OpCodes.Ret); | ||
1631 | retLabel = null; | ||
1632 | retValue = null; | ||
1633 | |||
1634 | /* | ||
1635 | * No more instructions for this method. | ||
1636 | */ | ||
1637 | ((ScriptObjWriter)ilGen).EndMethod(); | ||
1638 | _ilGen = null; | ||
1639 | |||
1640 | /* | ||
1641 | * Not generating function code any more. | ||
1642 | */ | ||
1643 | curBreakTarg = null; | ||
1644 | curContTarg = null; | ||
1645 | curDeclFunc = null; | ||
1646 | } | ||
1647 | |||
1648 | /** | ||
1649 | * @brief Allocate stack space for all local variables, regardless of | ||
1650 | * which { } statement block they are actually defined in. | ||
1651 | * @returns approximate stack frame size | ||
1652 | */ | ||
1653 | private int AllocLocalVarStackSpace() | ||
1654 | { | ||
1655 | int stackframesize = 64; // RIP, RBX, RBP, R12..R15, one extra | ||
1656 | foreach(TokenDeclVar localVar in curDeclFunc.localVars) | ||
1657 | { | ||
1658 | |||
1659 | /* | ||
1660 | * Skip all 'constant' vars as they were handled by the reducer. | ||
1661 | */ | ||
1662 | if(localVar.constant) | ||
1663 | continue; | ||
1664 | |||
1665 | /* | ||
1666 | * Get a stack location for the local variable. | ||
1667 | */ | ||
1668 | localVar.location = new CompValuLocalVar(localVar.type, localVar.name.val, this); | ||
1669 | |||
1670 | /* | ||
1671 | * Stack size for the local variable. | ||
1672 | */ | ||
1673 | stackframesize += LocalVarStackSize(localVar.type); | ||
1674 | } | ||
1675 | return stackframesize; | ||
1676 | } | ||
1677 | |||
1678 | private static int LocalVarStackSize(TokenType tokType) | ||
1679 | { | ||
1680 | Type sysType = tokType.ToSysType(); | ||
1681 | return sysType.IsValueType ? System.Runtime.InteropServices.Marshal.SizeOf(sysType) : 8; | ||
1682 | } | ||
1683 | |||
1684 | /** | ||
1685 | * @brief Generate code to write all arguments and locals to the capture stack frame. | ||
1686 | * This includes temp variables. | ||
1687 | * We only need to save what is active at the point of callLabels through because | ||
1688 | * those are the only points we will jump to on restore. This saves us from saving | ||
1689 | * all the little temp vars we create. | ||
1690 | * @param activeTemps = list of locals and temps that we care about, ie, which | ||
1691 | * ones get restored by GenerateFrameRestoreCode(). | ||
1692 | */ | ||
1693 | private void GenerateFrameCaptureCode(List<ScriptMyLocal> activeTemps) | ||
1694 | { | ||
1695 | /* | ||
1696 | * Compute total number of slots we need to save stuff. | ||
1697 | * Assume we need to save all call arguments. | ||
1698 | */ | ||
1699 | int nSaves = curDeclFunc.argDecl.vars.Length + activeTemps.Count; | ||
1700 | |||
1701 | /* | ||
1702 | * Output code to allocate a stack frame object with an object array. | ||
1703 | * This also pushes the stack frame object on the instance.stackFrames list. | ||
1704 | * It returns a pointer to the object array it allocated. | ||
1705 | */ | ||
1706 | PushXMRInst(); | ||
1707 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, ilGen.methName); | ||
1708 | GetCallNo(curDeclFunc, actCallNo); | ||
1709 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, nSaves); | ||
1710 | ilGen.Emit(curDeclFunc, OpCodes.Call, captureStackFrameMethodInfo); | ||
1711 | |||
1712 | if(DEBUG_STACKCAPRES) | ||
1713 | { | ||
1714 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, ilGen.methName + "*: capture mainCallNo="); | ||
1715 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1716 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, actCallNo); | ||
1717 | ilGen.Emit(curDeclFunc, OpCodes.Box, typeof(int)); | ||
1718 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1719 | } | ||
1720 | |||
1721 | /* | ||
1722 | * Copy arg values to object array, boxing as needed. | ||
1723 | */ | ||
1724 | int i = 0; | ||
1725 | foreach(TokenDeclVar argVar in curDeclFunc.argDecl.varDict) | ||
1726 | { | ||
1727 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1728 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, i); | ||
1729 | argVar.location.PushVal(this, argVar.name, tokenTypeObj); | ||
1730 | if(DEBUG_STACKCAPRES) | ||
1731 | { | ||
1732 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, "\n arg:" + argVar.name.val + "="); | ||
1733 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1734 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1735 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1736 | } | ||
1737 | ilGen.Emit(curDeclFunc, OpCodes.Stelem_Ref); | ||
1738 | i++; | ||
1739 | } | ||
1740 | |||
1741 | /* | ||
1742 | * Copy local and temp values to object array, boxing as needed. | ||
1743 | */ | ||
1744 | foreach(ScriptMyLocal lcl in activeTemps) | ||
1745 | { | ||
1746 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1747 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, i++); | ||
1748 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, lcl); | ||
1749 | Type t = lcl.type; | ||
1750 | if(t == typeof(HeapTrackerList)) | ||
1751 | { | ||
1752 | t = HeapTrackerList.GenPush(curDeclFunc, ilGen); | ||
1753 | } | ||
1754 | if(t == typeof(HeapTrackerObject)) | ||
1755 | { | ||
1756 | t = HeapTrackerObject.GenPush(curDeclFunc, ilGen); | ||
1757 | } | ||
1758 | if(t == typeof(HeapTrackerString)) | ||
1759 | { | ||
1760 | t = HeapTrackerString.GenPush(curDeclFunc, ilGen); | ||
1761 | } | ||
1762 | if(t.IsValueType) | ||
1763 | { | ||
1764 | ilGen.Emit(curDeclFunc, OpCodes.Box, t); | ||
1765 | } | ||
1766 | if(DEBUG_STACKCAPRES) | ||
1767 | { | ||
1768 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, "\n lcl:" + lcl.name + "="); | ||
1769 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1770 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1771 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1772 | } | ||
1773 | ilGen.Emit(curDeclFunc, OpCodes.Stelem_Ref); | ||
1774 | } | ||
1775 | if(DEBUG_STACKCAPRES) | ||
1776 | { | ||
1777 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, "\n"); | ||
1778 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1779 | } | ||
1780 | |||
1781 | ilGen.Emit(curDeclFunc, OpCodes.Pop); | ||
1782 | } | ||
1783 | |||
1784 | /** | ||
1785 | * @brief Generate code to restore all arguments and locals from the restore stack frame. | ||
1786 | * This includes temp variables. | ||
1787 | */ | ||
1788 | private void GenerateFrameRestoreCode(List<ScriptMyLocal> activeTemps) | ||
1789 | { | ||
1790 | ScriptMyLocal objArray = ilGen.DeclareLocal(typeof(object[]), "__restObjArray"); | ||
1791 | |||
1792 | /* | ||
1793 | * Output code to pop stack frame from instance.stackFrames. | ||
1794 | * It returns a pointer to the object array that contains values to be restored. | ||
1795 | */ | ||
1796 | PushXMRInst(); | ||
1797 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, ilGen.methName); | ||
1798 | ilGen.Emit(curDeclFunc, OpCodes.Ldloca, actCallNo); // __mainCallNo | ||
1799 | ilGen.Emit(curDeclFunc, OpCodes.Call, restoreStackFrameMethodInfo); | ||
1800 | ilGen.Emit(curDeclFunc, OpCodes.Stloc, objArray); | ||
1801 | if(DEBUG_STACKCAPRES) | ||
1802 | { | ||
1803 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, ilGen.methName + "*: restore mainCallNo="); | ||
1804 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1805 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, actCallNo); | ||
1806 | ilGen.Emit(curDeclFunc, OpCodes.Box, typeof(int)); | ||
1807 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1808 | } | ||
1809 | |||
1810 | /* | ||
1811 | * Restore argument values from object array, unboxing as needed. | ||
1812 | * Although the caller has restored them to what it called us with, it's possible that this | ||
1813 | * function has modified them since, so we need to do our own restore. | ||
1814 | */ | ||
1815 | int i = 0; | ||
1816 | foreach(TokenDeclVar argVar in curDeclFunc.argDecl.varDict) | ||
1817 | { | ||
1818 | CompValu argLoc = argVar.location; | ||
1819 | argLoc.PopPre(this, argVar.name); | ||
1820 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, objArray); | ||
1821 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, i); | ||
1822 | ilGen.Emit(curDeclFunc, OpCodes.Ldelem_Ref); | ||
1823 | if(DEBUG_STACKCAPRES) | ||
1824 | { | ||
1825 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, "\n arg:" + argVar.name.val + "="); | ||
1826 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1827 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1828 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1829 | } | ||
1830 | TypeCast.CastTopOfStack(this, argVar.name, tokenTypeObj, argLoc.type, true); | ||
1831 | argLoc.PopPost(this, argVar.name); | ||
1832 | i++; | ||
1833 | } | ||
1834 | |||
1835 | /* | ||
1836 | * Restore local and temp values from object array, unboxing as needed. | ||
1837 | */ | ||
1838 | foreach(ScriptMyLocal lcl in activeTemps) | ||
1839 | { | ||
1840 | Type t = lcl.type; | ||
1841 | Type u = t; | ||
1842 | if(t == typeof(HeapTrackerList)) | ||
1843 | u = typeof(LSL_List); | ||
1844 | if(t == typeof(HeapTrackerObject)) | ||
1845 | u = typeof(object); | ||
1846 | if(t == typeof(HeapTrackerString)) | ||
1847 | u = typeof(string); | ||
1848 | if(u != t) | ||
1849 | { | ||
1850 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, lcl); | ||
1851 | } | ||
1852 | ilGen.Emit(curDeclFunc, OpCodes.Ldloc, objArray); | ||
1853 | ilGen.Emit(curDeclFunc, OpCodes.Ldc_I4, i++); | ||
1854 | ilGen.Emit(curDeclFunc, OpCodes.Ldelem_Ref); | ||
1855 | if(DEBUG_STACKCAPRES) | ||
1856 | { | ||
1857 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, "\n lcl:" + lcl.name + "="); | ||
1858 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1859 | ilGen.Emit(curDeclFunc, OpCodes.Dup); | ||
1860 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1861 | } | ||
1862 | if(u.IsValueType) | ||
1863 | { | ||
1864 | ilGen.Emit(curDeclFunc, OpCodes.Unbox_Any, u); | ||
1865 | } | ||
1866 | else if(u != typeof(object)) | ||
1867 | { | ||
1868 | ilGen.Emit(curDeclFunc, OpCodes.Castclass, u); | ||
1869 | } | ||
1870 | if(u != t) | ||
1871 | { | ||
1872 | if(t == typeof(HeapTrackerList)) | ||
1873 | HeapTrackerList.GenPop(curDeclFunc, ilGen); | ||
1874 | if(t == typeof(HeapTrackerObject)) | ||
1875 | HeapTrackerObject.GenPop(curDeclFunc, ilGen); | ||
1876 | if(t == typeof(HeapTrackerString)) | ||
1877 | HeapTrackerString.GenPop(curDeclFunc, ilGen); | ||
1878 | } | ||
1879 | else | ||
1880 | { | ||
1881 | ilGen.Emit(curDeclFunc, OpCodes.Stloc, lcl); | ||
1882 | } | ||
1883 | } | ||
1884 | if(DEBUG_STACKCAPRES) | ||
1885 | { | ||
1886 | ilGen.Emit(curDeclFunc, OpCodes.Ldstr, "\n"); | ||
1887 | ilGen.Emit(curDeclFunc, OpCodes.Call, consoleWriteMethodInfo); | ||
1888 | } | ||
1889 | |||
1890 | OutputCallNoSwitchStmt(); | ||
1891 | } | ||
1892 | |||
1893 | /** | ||
1894 | * @brief Output a switch statement with a case for each possible | ||
1895 | * value of whatever callNo is currently active, either | ||
1896 | * __mainCallNo or one of the try/catch/finally's callNos. | ||
1897 | * | ||
1898 | * switch (callNo) { | ||
1899 | * case 0: goto __call_0; | ||
1900 | * case 1: goto __call_1; | ||
1901 | * ... | ||
1902 | * } | ||
1903 | * throw new ScriptBadCallNoException (callNo); | ||
1904 | */ | ||
1905 | private void OutputCallNoSwitchStmt() | ||
1906 | { | ||
1907 | ScriptMyLabel[] callLabels = new ScriptMyLabel[actCallLabels.Count]; | ||
1908 | foreach(CallLabel cl in actCallLabels) | ||
1909 | { | ||
1910 | callLabels[cl.index] = cl.callLabel; | ||
1911 | } | ||
1912 | GetCallNo(curDeclFunc, actCallNo); | ||
1913 | ilGen.Emit(curDeclFunc, OpCodes.Switch, callLabels); | ||
1914 | |||
1915 | GetCallNo(curDeclFunc, actCallNo); | ||
1916 | ilGen.Emit(curDeclFunc, OpCodes.Newobj, scriptBadCallNoExceptionConstructorInfo); | ||
1917 | ilGen.Emit(curDeclFunc, OpCodes.Throw); | ||
1918 | } | ||
1919 | |||
1920 | /** | ||
1921 | * @brief There is one of these per call that can possibly call CheckRun(), | ||
1922 | * including direct calls to CheckRun(). | ||
1923 | * They mark points that the stack capture/restore code will save & restore to. | ||
1924 | * All object-code level local vars active at the call label's point will | ||
1925 | * be saved & restored. | ||
1926 | * | ||
1927 | * callNo = 5; | ||
1928 | * __call_5: | ||
1929 | * push call arguments from temps | ||
1930 | * call SomethingThatCallsCheckRun() | ||
1931 | * | ||
1932 | * If SomethingThatCallsCheckRun() actually calls CheckRun(), our restore code | ||
1933 | * will restore our args, locals & temps, then jump to __call_5, which will then | ||
1934 | * call SomethingThatCallsCheckRun() again, which will restore its stuff likewise. | ||
1935 | * When eventually the actual CheckRun() call is restored, it will turn off restore | ||
1936 | * mode (by changing callMode from CallMode_RESTORE to CallMode_NORMAL) and return, | ||
1937 | * allowing the code to run normally from that point. | ||
1938 | */ | ||
1939 | public class CallLabel | ||
1940 | { | ||
1941 | public int index; // sequential integer, starting at 0, within actCallLabels | ||
1942 | // - used for the switch statement | ||
1943 | public ScriptMyLabel callLabel; // the actual label token | ||
1944 | |||
1945 | public CallLabel(ScriptCodeGen scg, Token errorAt) | ||
1946 | { | ||
1947 | if(scg.openCallLabel != null) | ||
1948 | throw new Exception("call label already open"); | ||
1949 | |||
1950 | if(!scg.curDeclFunc.IsFuncTrivial(scg)) | ||
1951 | { | ||
1952 | this.index = scg.actCallLabels.Count; | ||
1953 | string name = "__call_" + index + "_" + scg.allCallLabels.Count; | ||
1954 | |||
1955 | /* | ||
1956 | * Make sure eval stack is empty because the frame capture/restore | ||
1957 | * code expects such (restore switch stmt has an empty stack). | ||
1958 | */ | ||
1959 | int depth = ((ScriptCollector)scg.ilGen).stackDepth.Count; | ||
1960 | if(depth > 0) | ||
1961 | { | ||
1962 | // maybe need to call Trivialize() | ||
1963 | throw new Exception("call label stack depth " + depth + " at " + errorAt.SrcLoc); | ||
1964 | } | ||
1965 | |||
1966 | /* | ||
1967 | * Eval stack is empty so the restore code can handle it. | ||
1968 | */ | ||
1969 | this.index = scg.actCallLabels.Count; | ||
1970 | scg.actCallLabels.AddLast(this); | ||
1971 | scg.allCallLabels.AddLast(this); | ||
1972 | this.callLabel = scg.ilGen.DefineLabel(name); | ||
1973 | scg.SetCallNo(errorAt, scg.actCallNo, this.index); | ||
1974 | scg.ilGen.MarkLabel(this.callLabel); | ||
1975 | } | ||
1976 | |||
1977 | scg.openCallLabel = this; | ||
1978 | } | ||
1979 | }; | ||
1980 | |||
1981 | /** | ||
1982 | * @brief generate code for an arbitrary statement. | ||
1983 | */ | ||
1984 | private void GenerateStmt(TokenStmt stmt) | ||
1985 | { | ||
1986 | errorMessageToken = stmt; | ||
1987 | if(stmt is TokenDeclVar) | ||
1988 | { | ||
1989 | GenerateDeclVar((TokenDeclVar)stmt); | ||
1990 | return; | ||
1991 | } | ||
1992 | if(stmt is TokenStmtBlock) | ||
1993 | { | ||
1994 | GenerateStmtBlock((TokenStmtBlock)stmt); | ||
1995 | return; | ||
1996 | } | ||
1997 | if(stmt is TokenStmtBreak) | ||
1998 | { | ||
1999 | GenerateStmtBreak((TokenStmtBreak)stmt); | ||
2000 | return; | ||
2001 | } | ||
2002 | if(stmt is TokenStmtCont) | ||
2003 | { | ||
2004 | GenerateStmtCont((TokenStmtCont)stmt); | ||
2005 | return; | ||
2006 | } | ||
2007 | if(stmt is TokenStmtDo) | ||
2008 | { | ||
2009 | GenerateStmtDo((TokenStmtDo)stmt); | ||
2010 | return; | ||
2011 | } | ||
2012 | if(stmt is TokenStmtFor) | ||
2013 | { | ||
2014 | GenerateStmtFor((TokenStmtFor)stmt); | ||
2015 | return; | ||
2016 | } | ||
2017 | if(stmt is TokenStmtForEach) | ||
2018 | { | ||
2019 | GenerateStmtForEach((TokenStmtForEach)stmt); | ||
2020 | return; | ||
2021 | } | ||
2022 | if(stmt is TokenStmtIf) | ||
2023 | { | ||
2024 | GenerateStmtIf((TokenStmtIf)stmt); | ||
2025 | return; | ||
2026 | } | ||
2027 | if(stmt is TokenStmtJump) | ||
2028 | { | ||
2029 | GenerateStmtJump((TokenStmtJump)stmt); | ||
2030 | return; | ||
2031 | } | ||
2032 | if(stmt is TokenStmtLabel) | ||
2033 | { | ||
2034 | GenerateStmtLabel((TokenStmtLabel)stmt); | ||
2035 | return; | ||
2036 | } | ||
2037 | if(stmt is TokenStmtNewobj) | ||
2038 | { | ||
2039 | GenerateStmtNewobj((TokenStmtNewobj)stmt); | ||
2040 | return; | ||
2041 | } | ||
2042 | if(stmt is TokenStmtNull) | ||
2043 | { | ||
2044 | return; | ||
2045 | } | ||
2046 | if(stmt is TokenStmtRet) | ||
2047 | { | ||
2048 | GenerateStmtRet((TokenStmtRet)stmt); | ||
2049 | return; | ||
2050 | } | ||
2051 | if(stmt is TokenStmtRVal) | ||
2052 | { | ||
2053 | GenerateStmtRVal((TokenStmtRVal)stmt); | ||
2054 | return; | ||
2055 | } | ||
2056 | if(stmt is TokenStmtState) | ||
2057 | { | ||
2058 | GenerateStmtState((TokenStmtState)stmt); | ||
2059 | return; | ||
2060 | } | ||
2061 | if(stmt is TokenStmtSwitch) | ||
2062 | { | ||
2063 | GenerateStmtSwitch((TokenStmtSwitch)stmt); | ||
2064 | return; | ||
2065 | } | ||
2066 | if(stmt is TokenStmtThrow) | ||
2067 | { | ||
2068 | GenerateStmtThrow((TokenStmtThrow)stmt); | ||
2069 | return; | ||
2070 | } | ||
2071 | if(stmt is TokenStmtTry) | ||
2072 | { | ||
2073 | GenerateStmtTry((TokenStmtTry)stmt); | ||
2074 | return; | ||
2075 | } | ||
2076 | if(stmt is TokenStmtVarIniDef) | ||
2077 | { | ||
2078 | GenerateStmtVarIniDef((TokenStmtVarIniDef)stmt); | ||
2079 | return; | ||
2080 | } | ||
2081 | if(stmt is TokenStmtWhile) | ||
2082 | { | ||
2083 | GenerateStmtWhile((TokenStmtWhile)stmt); | ||
2084 | return; | ||
2085 | } | ||
2086 | throw new Exception("unknown TokenStmt type " + stmt.GetType().ToString()); | ||
2087 | } | ||
2088 | |||
2089 | /** | ||
2090 | * @brief generate statement block (ie, with braces) | ||
2091 | */ | ||
2092 | private void GenerateStmtBlock(TokenStmtBlock stmtBlock) | ||
2093 | { | ||
2094 | if(!mightGetHere) | ||
2095 | return; | ||
2096 | |||
2097 | /* | ||
2098 | * Push new current statement block pointer for anyone who cares. | ||
2099 | */ | ||
2100 | TokenStmtBlock oldStmtBlock = curStmtBlock; | ||
2101 | curStmtBlock = stmtBlock; | ||
2102 | |||
2103 | /* | ||
2104 | * Output the statements that make up the block. | ||
2105 | */ | ||
2106 | for(Token t = stmtBlock.statements; t != null; t = t.nextToken) | ||
2107 | { | ||
2108 | GenerateStmt((TokenStmt)t); | ||
2109 | } | ||
2110 | |||
2111 | /* | ||
2112 | * Pop the current statement block. | ||
2113 | */ | ||
2114 | curStmtBlock = oldStmtBlock; | ||
2115 | } | ||
2116 | |||
2117 | /** | ||
2118 | * @brief output code for a 'break' statement | ||
2119 | */ | ||
2120 | private void GenerateStmtBreak(TokenStmtBreak breakStmt) | ||
2121 | { | ||
2122 | if(!mightGetHere) | ||
2123 | return; | ||
2124 | |||
2125 | /* | ||
2126 | * Make sure we are in a breakable situation. | ||
2127 | */ | ||
2128 | if(curBreakTarg == null) | ||
2129 | { | ||
2130 | ErrorMsg(breakStmt, "not in a breakable situation"); | ||
2131 | return; | ||
2132 | } | ||
2133 | |||
2134 | /* | ||
2135 | * Tell anyone who cares that the break target was actually used. | ||
2136 | */ | ||
2137 | curBreakTarg.used = true; | ||
2138 | |||
2139 | /* | ||
2140 | * Output the instructions. | ||
2141 | */ | ||
2142 | EmitJumpCode(curBreakTarg.label, curBreakTarg.block, breakStmt); | ||
2143 | } | ||
2144 | |||
2145 | /** | ||
2146 | * @brief output code for a 'continue' statement | ||
2147 | */ | ||
2148 | private void GenerateStmtCont(TokenStmtCont contStmt) | ||
2149 | { | ||
2150 | if(!mightGetHere) | ||
2151 | return; | ||
2152 | |||
2153 | /* | ||
2154 | * Make sure we are in a contable situation. | ||
2155 | */ | ||
2156 | if(curContTarg == null) | ||
2157 | { | ||
2158 | ErrorMsg(contStmt, "not in a continueable situation"); | ||
2159 | return; | ||
2160 | } | ||
2161 | |||
2162 | /* | ||
2163 | * Tell anyone who cares that the continue target was actually used. | ||
2164 | */ | ||
2165 | curContTarg.used = true; | ||
2166 | |||
2167 | /* | ||
2168 | * Output the instructions. | ||
2169 | */ | ||
2170 | EmitJumpCode(curContTarg.label, curContTarg.block, contStmt); | ||
2171 | } | ||
2172 | |||
2173 | /** | ||
2174 | * @brief output code for a 'do' statement | ||
2175 | */ | ||
2176 | private void GenerateStmtDo(TokenStmtDo doStmt) | ||
2177 | { | ||
2178 | if(!mightGetHere) | ||
2179 | return; | ||
2180 | |||
2181 | BreakContTarg oldBreakTarg = curBreakTarg; | ||
2182 | BreakContTarg oldContTarg = curContTarg; | ||
2183 | ScriptMyLabel loopLabel = ilGen.DefineLabel("doloop_" + doStmt.Unique); | ||
2184 | |||
2185 | curBreakTarg = new BreakContTarg(this, "dobreak_" + doStmt.Unique); | ||
2186 | curContTarg = new BreakContTarg(this, "docont_" + doStmt.Unique); | ||
2187 | |||
2188 | ilGen.MarkLabel(loopLabel); | ||
2189 | GenerateStmt(doStmt.bodyStmt); | ||
2190 | if(curContTarg.used) | ||
2191 | { | ||
2192 | ilGen.MarkLabel(curContTarg.label); | ||
2193 | mightGetHere = true; | ||
2194 | } | ||
2195 | |||
2196 | if(mightGetHere) | ||
2197 | { | ||
2198 | EmitCallCheckRun(doStmt, false); | ||
2199 | CompValu testRVal = GenerateFromRVal(doStmt.testRVal); | ||
2200 | if(IsConstBoolExprTrue(testRVal)) | ||
2201 | { | ||
2202 | |||
2203 | /* | ||
2204 | * Unconditional looping, unconditional branch and | ||
2205 | * say we never fall through to next statement. | ||
2206 | */ | ||
2207 | ilGen.Emit(doStmt, OpCodes.Br, loopLabel); | ||
2208 | mightGetHere = false; | ||
2209 | } | ||
2210 | else | ||
2211 | { | ||
2212 | |||
2213 | /* | ||
2214 | * Conditional looping, test and brach back to top of loop. | ||
2215 | */ | ||
2216 | testRVal.PushVal(this, doStmt.testRVal, tokenTypeBool); | ||
2217 | ilGen.Emit(doStmt, OpCodes.Brtrue, loopLabel); | ||
2218 | } | ||
2219 | } | ||
2220 | |||
2221 | /* | ||
2222 | * If 'break' statement was used, output target label. | ||
2223 | * And assume that since a 'break' statement was used, it's possible for the code to get here. | ||
2224 | */ | ||
2225 | if(curBreakTarg.used) | ||
2226 | { | ||
2227 | ilGen.MarkLabel(curBreakTarg.label); | ||
2228 | mightGetHere = true; | ||
2229 | } | ||
2230 | |||
2231 | curBreakTarg = oldBreakTarg; | ||
2232 | curContTarg = oldContTarg; | ||
2233 | } | ||
2234 | |||
2235 | /** | ||
2236 | * @brief output code for a 'for' statement | ||
2237 | */ | ||
2238 | private void GenerateStmtFor(TokenStmtFor forStmt) | ||
2239 | { | ||
2240 | if(!mightGetHere) | ||
2241 | return; | ||
2242 | |||
2243 | BreakContTarg oldBreakTarg = curBreakTarg; | ||
2244 | BreakContTarg oldContTarg = curContTarg; | ||
2245 | ScriptMyLabel loopLabel = ilGen.DefineLabel("forloop_" + forStmt.Unique); | ||
2246 | |||
2247 | curBreakTarg = new BreakContTarg(this, "forbreak_" + forStmt.Unique); | ||
2248 | curContTarg = new BreakContTarg(this, "forcont_" + forStmt.Unique); | ||
2249 | |||
2250 | if(forStmt.initStmt != null) | ||
2251 | { | ||
2252 | GenerateStmt(forStmt.initStmt); | ||
2253 | } | ||
2254 | ilGen.MarkLabel(loopLabel); | ||
2255 | |||
2256 | /* | ||
2257 | * See if we have a test expression that is other than a constant TRUE. | ||
2258 | * If so, test it and conditionally branch to end if false. | ||
2259 | */ | ||
2260 | if(forStmt.testRVal != null) | ||
2261 | { | ||
2262 | CompValu testRVal = GenerateFromRVal(forStmt.testRVal); | ||
2263 | if(!IsConstBoolExprTrue(testRVal)) | ||
2264 | { | ||
2265 | testRVal.PushVal(this, forStmt.testRVal, tokenTypeBool); | ||
2266 | ilGen.Emit(forStmt, OpCodes.Brfalse, curBreakTarg.label); | ||
2267 | curBreakTarg.used = true; | ||
2268 | } | ||
2269 | } | ||
2270 | |||
2271 | /* | ||
2272 | * Output loop body. | ||
2273 | */ | ||
2274 | GenerateStmt(forStmt.bodyStmt); | ||
2275 | |||
2276 | /* | ||
2277 | * Here's where a 'continue' statement jumps to. | ||
2278 | */ | ||
2279 | if(curContTarg.used) | ||
2280 | { | ||
2281 | ilGen.MarkLabel(curContTarg.label); | ||
2282 | mightGetHere = true; | ||
2283 | } | ||
2284 | |||
2285 | if(mightGetHere) | ||
2286 | { | ||
2287 | |||
2288 | /* | ||
2289 | * After checking for excessive CPU time, output increment statement, if any. | ||
2290 | */ | ||
2291 | EmitCallCheckRun(forStmt, false); | ||
2292 | if(forStmt.incrRVal != null) | ||
2293 | { | ||
2294 | GenerateFromRVal(forStmt.incrRVal); | ||
2295 | } | ||
2296 | |||
2297 | /* | ||
2298 | * Unconditional branch back to beginning of loop. | ||
2299 | */ | ||
2300 | ilGen.Emit(forStmt, OpCodes.Br, loopLabel); | ||
2301 | } | ||
2302 | |||
2303 | /* | ||
2304 | * If test needs label, output label for it to jump to. | ||
2305 | * Otherwise, clear mightGetHere as we know loop never | ||
2306 | * falls out the bottom. | ||
2307 | */ | ||
2308 | mightGetHere = curBreakTarg.used; | ||
2309 | if(mightGetHere) | ||
2310 | { | ||
2311 | ilGen.MarkLabel(curBreakTarg.label); | ||
2312 | } | ||
2313 | |||
2314 | curBreakTarg = oldBreakTarg; | ||
2315 | curContTarg = oldContTarg; | ||
2316 | } | ||
2317 | |||
2318 | private void GenerateStmtForEach(TokenStmtForEach forEachStmt) | ||
2319 | { | ||
2320 | if(!mightGetHere) | ||
2321 | return; | ||
2322 | |||
2323 | BreakContTarg oldBreakTarg = curBreakTarg; | ||
2324 | BreakContTarg oldContTarg = curContTarg; | ||
2325 | CompValu keyLVal = null; | ||
2326 | CompValu valLVal = null; | ||
2327 | CompValu arrayRVal = GenerateFromRVal(forEachStmt.arrayRVal); | ||
2328 | |||
2329 | if(forEachStmt.keyLVal != null) | ||
2330 | { | ||
2331 | keyLVal = GenerateFromLVal(forEachStmt.keyLVal); | ||
2332 | if(!(keyLVal.type is TokenTypeObject)) | ||
2333 | { | ||
2334 | ErrorMsg(forEachStmt.arrayRVal, "must be object"); | ||
2335 | } | ||
2336 | } | ||
2337 | if(forEachStmt.valLVal != null) | ||
2338 | { | ||
2339 | valLVal = GenerateFromLVal(forEachStmt.valLVal); | ||
2340 | if(!(valLVal.type is TokenTypeObject)) | ||
2341 | { | ||
2342 | ErrorMsg(forEachStmt.arrayRVal, "must be object"); | ||
2343 | } | ||
2344 | } | ||
2345 | if(!(arrayRVal.type is TokenTypeArray)) | ||
2346 | { | ||
2347 | ErrorMsg(forEachStmt.arrayRVal, "must be an array"); | ||
2348 | } | ||
2349 | |||
2350 | curBreakTarg = new BreakContTarg(this, "foreachbreak_" + forEachStmt.Unique); | ||
2351 | curContTarg = new BreakContTarg(this, "foreachcont_" + forEachStmt.Unique); | ||
2352 | |||
2353 | CompValuTemp indexVar = new CompValuTemp(new TokenTypeInt(forEachStmt), this); | ||
2354 | ScriptMyLabel loopLabel = ilGen.DefineLabel("foreachloop_" + forEachStmt.Unique); | ||
2355 | |||
2356 | // indexVar = 0 | ||
2357 | ilGen.Emit(forEachStmt, OpCodes.Ldc_I4_0); | ||
2358 | indexVar.Pop(this, forEachStmt); | ||
2359 | |||
2360 | ilGen.MarkLabel(loopLabel); | ||
2361 | |||
2362 | // key = array.__pub_index (indexVar); | ||
2363 | // if (key == null) goto curBreakTarg; | ||
2364 | if(keyLVal != null) | ||
2365 | { | ||
2366 | keyLVal.PopPre(this, forEachStmt.keyLVal); | ||
2367 | arrayRVal.PushVal(this, forEachStmt.arrayRVal); | ||
2368 | indexVar.PushVal(this, forEachStmt); | ||
2369 | ilGen.Emit(forEachStmt, OpCodes.Call, xmrArrPubIndexMethod); | ||
2370 | keyLVal.PopPost(this, forEachStmt.keyLVal); | ||
2371 | keyLVal.PushVal(this, forEachStmt.keyLVal); | ||
2372 | ilGen.Emit(forEachStmt, OpCodes.Brfalse, curBreakTarg.label); | ||
2373 | curBreakTarg.used = true; | ||
2374 | } | ||
2375 | |||
2376 | // val = array._pub_value (indexVar); | ||
2377 | // if (val == null) goto curBreakTarg; | ||
2378 | if(valLVal != null) | ||
2379 | { | ||
2380 | valLVal.PopPre(this, forEachStmt.valLVal); | ||
2381 | arrayRVal.PushVal(this, forEachStmt.arrayRVal); | ||
2382 | indexVar.PushVal(this, forEachStmt); | ||
2383 | ilGen.Emit(forEachStmt, OpCodes.Call, xmrArrPubValueMethod); | ||
2384 | valLVal.PopPost(this, forEachStmt.valLVal); | ||
2385 | if(keyLVal == null) | ||
2386 | { | ||
2387 | valLVal.PushVal(this, forEachStmt.valLVal); | ||
2388 | ilGen.Emit(forEachStmt, OpCodes.Brfalse, curBreakTarg.label); | ||
2389 | curBreakTarg.used = true; | ||
2390 | } | ||
2391 | } | ||
2392 | |||
2393 | // indexVar ++; | ||
2394 | indexVar.PushVal(this, forEachStmt); | ||
2395 | ilGen.Emit(forEachStmt, OpCodes.Ldc_I4_1); | ||
2396 | ilGen.Emit(forEachStmt, OpCodes.Add); | ||
2397 | indexVar.Pop(this, forEachStmt); | ||
2398 | |||
2399 | // body statement | ||
2400 | GenerateStmt(forEachStmt.bodyStmt); | ||
2401 | |||
2402 | // continue label | ||
2403 | if(curContTarg.used) | ||
2404 | { | ||
2405 | ilGen.MarkLabel(curContTarg.label); | ||
2406 | mightGetHere = true; | ||
2407 | } | ||
2408 | |||
2409 | // call CheckRun() | ||
2410 | if(mightGetHere) | ||
2411 | { | ||
2412 | EmitCallCheckRun(forEachStmt, false); | ||
2413 | ilGen.Emit(forEachStmt, OpCodes.Br, loopLabel); | ||
2414 | } | ||
2415 | |||
2416 | // break label | ||
2417 | ilGen.MarkLabel(curBreakTarg.label); | ||
2418 | mightGetHere = true; | ||
2419 | |||
2420 | curBreakTarg = oldBreakTarg; | ||
2421 | curContTarg = oldContTarg; | ||
2422 | } | ||
2423 | |||
2424 | /** | ||
2425 | * @brief output code for an 'if' statement | ||
2426 | * Braces are necessary because what may be one statement for trueStmt or elseStmt in | ||
2427 | * the script may translate to more than one statement in the resultant C# code. | ||
2428 | */ | ||
2429 | private void GenerateStmtIf(TokenStmtIf ifStmt) | ||
2430 | { | ||
2431 | if(!mightGetHere) | ||
2432 | return; | ||
2433 | |||
2434 | bool constVal; | ||
2435 | |||
2436 | /* | ||
2437 | * Test condition and see if constant test expression. | ||
2438 | */ | ||
2439 | CompValu testRVal = GenerateFromRVal(ifStmt.testRVal); | ||
2440 | if(IsConstBoolExpr(testRVal, out constVal)) | ||
2441 | { | ||
2442 | |||
2443 | /* | ||
2444 | * Constant, output just either the true or else part. | ||
2445 | */ | ||
2446 | if(constVal) | ||
2447 | { | ||
2448 | GenerateStmt(ifStmt.trueStmt); | ||
2449 | } | ||
2450 | else if(ifStmt.elseStmt != null) | ||
2451 | { | ||
2452 | GenerateStmt(ifStmt.elseStmt); | ||
2453 | } | ||
2454 | } | ||
2455 | else if(ifStmt.elseStmt == null) | ||
2456 | { | ||
2457 | |||
2458 | /* | ||
2459 | * This is an 'if' statement without an 'else' clause. | ||
2460 | */ | ||
2461 | testRVal.PushVal(this, ifStmt.testRVal, tokenTypeBool); | ||
2462 | ScriptMyLabel doneLabel = ilGen.DefineLabel("ifdone_" + ifStmt.Unique); | ||
2463 | ilGen.Emit(ifStmt, OpCodes.Brfalse, doneLabel); // brfalse doneLabel | ||
2464 | GenerateStmt(ifStmt.trueStmt); // generate true body code | ||
2465 | ilGen.MarkLabel(doneLabel); | ||
2466 | mightGetHere = true; // there's always a possibility of getting here | ||
2467 | } | ||
2468 | else | ||
2469 | { | ||
2470 | |||
2471 | /* | ||
2472 | * This is an 'if' statement with an 'else' clause. | ||
2473 | */ | ||
2474 | testRVal.PushVal(this, ifStmt.testRVal, tokenTypeBool); | ||
2475 | ScriptMyLabel elseLabel = ilGen.DefineLabel("ifelse_" + ifStmt.Unique); | ||
2476 | ilGen.Emit(ifStmt, OpCodes.Brfalse, elseLabel); // brfalse elseLabel | ||
2477 | GenerateStmt(ifStmt.trueStmt); // generate true body code | ||
2478 | bool trueMightGetHere = mightGetHere; // save whether or not true falls through | ||
2479 | ScriptMyLabel doneLabel = ilGen.DefineLabel("ifdone_" + ifStmt.Unique); | ||
2480 | ilGen.Emit(ifStmt, OpCodes.Br, doneLabel); // branch to done | ||
2481 | ilGen.MarkLabel(elseLabel); // beginning of else code | ||
2482 | mightGetHere = true; // the top of the else might be executed | ||
2483 | GenerateStmt(ifStmt.elseStmt); // output else code | ||
2484 | ilGen.MarkLabel(doneLabel); // where end of true clause code branches to | ||
2485 | mightGetHere |= trueMightGetHere; // gets this far if either true or else falls through | ||
2486 | } | ||
2487 | } | ||
2488 | |||
2489 | /** | ||
2490 | * @brief output code for a 'jump' statement | ||
2491 | */ | ||
2492 | private void GenerateStmtJump(TokenStmtJump jumpStmt) | ||
2493 | { | ||
2494 | if(!mightGetHere) | ||
2495 | return; | ||
2496 | |||
2497 | /* | ||
2498 | * Make sure the target label is defined somewhere in the function. | ||
2499 | */ | ||
2500 | TokenStmtLabel stmtLabel; | ||
2501 | if(!curDeclFunc.labels.TryGetValue(jumpStmt.label.val, out stmtLabel)) | ||
2502 | { | ||
2503 | ErrorMsg(jumpStmt, "undefined label " + jumpStmt.label.val); | ||
2504 | return; | ||
2505 | } | ||
2506 | if(!stmtLabel.labelTagged) | ||
2507 | { | ||
2508 | stmtLabel.labelStruct = ilGen.DefineLabel("jump_" + stmtLabel.name.val); | ||
2509 | stmtLabel.labelTagged = true; | ||
2510 | } | ||
2511 | |||
2512 | /* | ||
2513 | * Emit instructions to do the jump. | ||
2514 | */ | ||
2515 | EmitJumpCode(stmtLabel.labelStruct, stmtLabel.block, jumpStmt); | ||
2516 | } | ||
2517 | |||
2518 | /** | ||
2519 | * @brief Emit code to jump to a label | ||
2520 | * @param target = label being jumped to | ||
2521 | * @param targetsBlock = { ... } the label is defined in | ||
2522 | */ | ||
2523 | private void EmitJumpCode(ScriptMyLabel target, TokenStmtBlock targetsBlock, Token errorAt) | ||
2524 | { | ||
2525 | /* | ||
2526 | * Jumps never fall through. | ||
2527 | */ | ||
2528 | mightGetHere = false; | ||
2529 | |||
2530 | /* | ||
2531 | * Find which block the target label is in. Must be in this or an outer block, | ||
2532 | * no laterals allowed. And if we exit a try/catch block, use Leave instead of Br. | ||
2533 | * | ||
2534 | * jump lateral; | ||
2535 | * { | ||
2536 | * @lateral; | ||
2537 | * } | ||
2538 | */ | ||
2539 | bool useLeave = false; | ||
2540 | TokenStmtBlock stmtBlock; | ||
2541 | Stack<TokenStmtTry> finallyBlocksCalled = new Stack<TokenStmtTry>(); | ||
2542 | for(stmtBlock = curStmtBlock; stmtBlock != targetsBlock; stmtBlock = stmtBlock.outerStmtBlock) | ||
2543 | { | ||
2544 | if(stmtBlock == null) | ||
2545 | { | ||
2546 | ErrorMsg(errorAt, "no lateral jumps allowed"); | ||
2547 | return; | ||
2548 | } | ||
2549 | if(stmtBlock.isFinally) | ||
2550 | { | ||
2551 | ErrorMsg(errorAt, "cannot jump out of finally"); | ||
2552 | return; | ||
2553 | } | ||
2554 | if(stmtBlock.isTry || stmtBlock.isCatch) | ||
2555 | useLeave = true; | ||
2556 | if((stmtBlock.tryStmt != null) && (stmtBlock.tryStmt.finallyStmt != null)) | ||
2557 | { | ||
2558 | finallyBlocksCalled.Push(stmtBlock.tryStmt); | ||
2559 | } | ||
2560 | } | ||
2561 | |||
2562 | /* | ||
2563 | * If popping through more than one finally block, we have to break it down for the stack | ||
2564 | * capture and restore code, one finally block at a time. | ||
2565 | * | ||
2566 | * try { | ||
2567 | * try { | ||
2568 | * try { | ||
2569 | * jump exit; | ||
2570 | * } finally { | ||
2571 | * llOwnerSay ("exiting inner"); | ||
2572 | * } | ||
2573 | * } finally { | ||
2574 | * llOwnerSay ("exiting middle"); | ||
2575 | * } | ||
2576 | * } finally { | ||
2577 | * llOwnerSay ("exiting outer"); | ||
2578 | * } | ||
2579 | * @exit; | ||
2580 | * | ||
2581 | * try { | ||
2582 | * try { | ||
2583 | * try { | ||
2584 | * jump intr2_exit; <<< gets its own tryNo call label so inner try knows where to restore to | ||
2585 | * } finally { | ||
2586 | * llOwnerSay ("exiting inner"); | ||
2587 | * } | ||
2588 | * jump outtry2; | ||
2589 | * @intr2_exit; jump intr1_exit; <<< gets its own tryNo call label so middle try knows where to restore to | ||
2590 | * @outtry2; | ||
2591 | * } finally { | ||
2592 | * llOwnerSay ("exiting middle"); | ||
2593 | * } | ||
2594 | * jump outtry1; | ||
2595 | * @intr1_exit: jump exit; <<< gets its own tryNo call label so outer try knows where to restore to | ||
2596 | * @outtry1; | ||
2597 | * } finally { | ||
2598 | * llOwnerSay ("exiting outer"); | ||
2599 | * } | ||
2600 | * @exit; | ||
2601 | */ | ||
2602 | int level = 0; | ||
2603 | while(finallyBlocksCalled.Count > 1) | ||
2604 | { | ||
2605 | TokenStmtTry finallyBlock = finallyBlocksCalled.Pop(); | ||
2606 | string intername = "intr" + (++level) + "_" + target.name; | ||
2607 | IntermediateLeave iLeave; | ||
2608 | if(!finallyBlock.iLeaves.TryGetValue(intername, out iLeave)) | ||
2609 | { | ||
2610 | iLeave = new IntermediateLeave(); | ||
2611 | iLeave.jumpIntoLabel = ilGen.DefineLabel(intername); | ||
2612 | iLeave.jumpAwayLabel = target; | ||
2613 | finallyBlock.iLeaves.Add(intername, iLeave); | ||
2614 | } | ||
2615 | target = iLeave.jumpIntoLabel; | ||
2616 | } | ||
2617 | |||
2618 | /* | ||
2619 | * Finally output the branch/leave opcode. | ||
2620 | * If using Leave, prefix with a call label in case the corresponding finally block | ||
2621 | * calls CheckRun() and that CheckRun() captures the stack, it will have a point to | ||
2622 | * restore to that will properly jump back into the finally block. | ||
2623 | */ | ||
2624 | if(useLeave) | ||
2625 | { | ||
2626 | new CallLabel(this, errorAt); | ||
2627 | ilGen.Emit(errorAt, OpCodes.Leave, target); | ||
2628 | openCallLabel = null; | ||
2629 | } | ||
2630 | else | ||
2631 | { | ||
2632 | ilGen.Emit(errorAt, OpCodes.Br, target); | ||
2633 | } | ||
2634 | } | ||
2635 | |||
2636 | /** | ||
2637 | * @brief output code for a jump target label statement. | ||
2638 | * If there are any backward jumps to the label, do a CheckRun() also. | ||
2639 | */ | ||
2640 | private void GenerateStmtLabel(TokenStmtLabel labelStmt) | ||
2641 | { | ||
2642 | if(!labelStmt.labelTagged) | ||
2643 | { | ||
2644 | labelStmt.labelStruct = ilGen.DefineLabel("jump_" + labelStmt.name.val); | ||
2645 | labelStmt.labelTagged = true; | ||
2646 | } | ||
2647 | ilGen.MarkLabel(labelStmt.labelStruct); | ||
2648 | if(labelStmt.hasBkwdRefs) | ||
2649 | { | ||
2650 | EmitCallCheckRun(labelStmt, false); | ||
2651 | } | ||
2652 | |||
2653 | /* | ||
2654 | * We are going to say that the label falls through. | ||
2655 | * It would be nice if we could analyze all referencing | ||
2656 | * goto's to see if all of them are not used but we are | ||
2657 | * going to assume that if the script writer put a label | ||
2658 | * somewhere, it is probably going to be used. | ||
2659 | */ | ||
2660 | mightGetHere = true; | ||
2661 | } | ||
2662 | |||
2663 | /** | ||
2664 | * @brief Generate code for a script-defined type's <typename>.$new(<argsig>) method. | ||
2665 | * It is used to malloc the object and initialize it. | ||
2666 | * It is defined as a script-defined type static method, so the object level | ||
2667 | * method gets the XMRInstance pointer passed as arg 0, and the method is | ||
2668 | * supposed to return the allocated and constructed XMRSDTypeClObj | ||
2669 | * object pointer. | ||
2670 | */ | ||
2671 | private void GenerateStmtNewobj(TokenStmtNewobj newobjStmt) | ||
2672 | { | ||
2673 | /* | ||
2674 | * First off, malloc a new empty XMRSDTypeClObj object | ||
2675 | * then call the XMRSDTypeClObj()-level constructor. | ||
2676 | * Store the result in local var $objptr. | ||
2677 | */ | ||
2678 | newobjStmt.objptrVar.location.PopPre(this, newobjStmt); | ||
2679 | ilGen.Emit(newobjStmt, OpCodes.Ldarg_0); | ||
2680 | ilGen.Emit(newobjStmt, OpCodes.Ldc_I4, curDeclFunc.sdtClass.sdTypeIndex); | ||
2681 | ilGen.Emit(newobjStmt, OpCodes.Newobj, sdtClassConstructorInfo); | ||
2682 | newobjStmt.objptrVar.location.PopPost(this, newobjStmt); | ||
2683 | |||
2684 | /* | ||
2685 | * Now call the script-level constructor. | ||
2686 | * Pass the object pointer in $objptr as it's 'this' argument. | ||
2687 | * The rest of the args are the script-visible args and are just copied from $new() call. | ||
2688 | */ | ||
2689 | GenerateFromRValCall(newobjStmt.rValCall); | ||
2690 | |||
2691 | /* | ||
2692 | * Put object pointer in retval so it gets returned to caller. | ||
2693 | */ | ||
2694 | newobjStmt.objptrVar.location.PushVal(this, newobjStmt); | ||
2695 | ilGen.Emit(newobjStmt, OpCodes.Stloc, retValue); | ||
2696 | |||
2697 | /* | ||
2698 | * Exit the function like a return statement. | ||
2699 | * And thus we don't fall through. | ||
2700 | */ | ||
2701 | ilGen.Emit(newobjStmt, OpCodes.Leave, retLabel); | ||
2702 | mightGetHere = false; | ||
2703 | } | ||
2704 | |||
2705 | /** | ||
2706 | * @brief output code for a return statement. | ||
2707 | * @param retStmt = return statement token, including return value if any | ||
2708 | */ | ||
2709 | private void GenerateStmtRet(TokenStmtRet retStmt) | ||
2710 | { | ||
2711 | if(!mightGetHere) | ||
2712 | return; | ||
2713 | |||
2714 | for(TokenStmtBlock stmtBlock = curStmtBlock; stmtBlock != null; stmtBlock = stmtBlock.outerStmtBlock) | ||
2715 | { | ||
2716 | if(stmtBlock.isFinally) | ||
2717 | { | ||
2718 | ErrorMsg(retStmt, "cannot return out of finally"); | ||
2719 | return; | ||
2720 | } | ||
2721 | } | ||
2722 | |||
2723 | if(curDeclFunc.retType is TokenTypeVoid) | ||
2724 | { | ||
2725 | if(retStmt.rVal != null) | ||
2726 | { | ||
2727 | ErrorMsg(retStmt, "function returns void, no value allowed"); | ||
2728 | return; | ||
2729 | } | ||
2730 | } | ||
2731 | else | ||
2732 | { | ||
2733 | if(retStmt.rVal == null) | ||
2734 | { | ||
2735 | ErrorMsg(retStmt, "function requires return value type " + curDeclFunc.retType.ToString()); | ||
2736 | return; | ||
2737 | } | ||
2738 | CompValu rVal = GenerateFromRVal(retStmt.rVal); | ||
2739 | rVal.PushVal(this, retStmt.rVal, curDeclFunc.retType); | ||
2740 | ilGen.Emit(retStmt, OpCodes.Stloc, retValue); | ||
2741 | } | ||
2742 | |||
2743 | /* | ||
2744 | * Use a OpCodes.Leave instruction to break out of any try { } blocks. | ||
2745 | * All Leave's inside script-defined try { } need call labels (see GenerateStmtTry()). | ||
2746 | */ | ||
2747 | bool brokeOutOfTry = false; | ||
2748 | for(TokenStmtBlock stmtBlock = curStmtBlock; stmtBlock != null; stmtBlock = stmtBlock.outerStmtBlock) | ||
2749 | { | ||
2750 | if(stmtBlock.isTry) | ||
2751 | { | ||
2752 | brokeOutOfTry = true; | ||
2753 | break; | ||
2754 | } | ||
2755 | } | ||
2756 | if(brokeOutOfTry) | ||
2757 | new CallLabel(this, retStmt); | ||
2758 | ilGen.Emit(retStmt, OpCodes.Leave, retLabel); | ||
2759 | if(brokeOutOfTry) | ||
2760 | openCallLabel = null; | ||
2761 | |||
2762 | /* | ||
2763 | * 'return' statements never fall through. | ||
2764 | */ | ||
2765 | mightGetHere = false; | ||
2766 | } | ||
2767 | |||
2768 | /** | ||
2769 | * @brief the statement is just an expression, most likely an assignment or a ++ or -- thing. | ||
2770 | */ | ||
2771 | private void GenerateStmtRVal(TokenStmtRVal rValStmt) | ||
2772 | { | ||
2773 | if(!mightGetHere) | ||
2774 | return; | ||
2775 | |||
2776 | GenerateFromRVal(rValStmt.rVal); | ||
2777 | } | ||
2778 | |||
2779 | /** | ||
2780 | * @brief generate code for a 'state' statement that transitions state. | ||
2781 | * It sets the new state by throwing a ScriptChangeStateException. | ||
2782 | */ | ||
2783 | private void GenerateStmtState(TokenStmtState stateStmt) | ||
2784 | { | ||
2785 | if(!mightGetHere) | ||
2786 | return; | ||
2787 | |||
2788 | int index = 0; // 'default' state | ||
2789 | |||
2790 | /* | ||
2791 | * Set new state value by throwing an exception. | ||
2792 | * These exceptions aren't catchable by script-level try { } catch { }. | ||
2793 | */ | ||
2794 | if((stateStmt.state != null) && !stateIndices.TryGetValue(stateStmt.state.val, out index)) | ||
2795 | { | ||
2796 | // The moron XEngine compiles scripts that reference undefined states. | ||
2797 | // So rather than produce a compile-time error, we'll throw an exception at runtime. | ||
2798 | // ErrorMsg (stateStmt, "undefined state " + stateStmt.state.val); | ||
2799 | |||
2800 | // throw new UndefinedStateException (stateStmt.state.val); | ||
2801 | ilGen.Emit(stateStmt, OpCodes.Ldstr, stateStmt.state.val); | ||
2802 | ilGen.Emit(stateStmt, OpCodes.Newobj, scriptUndefinedStateExceptionConstructorInfo); | ||
2803 | } | ||
2804 | else | ||
2805 | { | ||
2806 | ilGen.Emit(stateStmt, OpCodes.Ldc_I4, index); // new state's index | ||
2807 | ilGen.Emit(stateStmt, OpCodes.Newobj, scriptChangeStateExceptionConstructorInfo); | ||
2808 | } | ||
2809 | ilGen.Emit(stateStmt, OpCodes.Throw); | ||
2810 | |||
2811 | /* | ||
2812 | * 'state' statements never fall through. | ||
2813 | */ | ||
2814 | mightGetHere = false; | ||
2815 | } | ||
2816 | |||
2817 | /** | ||
2818 | * @brief output code for a 'switch' statement | ||
2819 | */ | ||
2820 | private void GenerateStmtSwitch(TokenStmtSwitch switchStmt) | ||
2821 | { | ||
2822 | if(!mightGetHere) | ||
2823 | return; | ||
2824 | |||
2825 | /* | ||
2826 | * Output code to calculate index. | ||
2827 | */ | ||
2828 | CompValu testRVal = GenerateFromRVal(switchStmt.testRVal); | ||
2829 | |||
2830 | /* | ||
2831 | * Generate code based on string or integer index. | ||
2832 | */ | ||
2833 | if((testRVal.type is TokenTypeKey) || (testRVal.type is TokenTypeStr)) | ||
2834 | { | ||
2835 | GenerateStmtSwitchStr(testRVal, switchStmt); | ||
2836 | } | ||
2837 | else | ||
2838 | { | ||
2839 | GenerateStmtSwitchInt(testRVal, switchStmt); | ||
2840 | } | ||
2841 | } | ||
2842 | |||
2843 | private void GenerateStmtSwitchInt(CompValu testRVal, TokenStmtSwitch switchStmt) | ||
2844 | { | ||
2845 | testRVal.PushVal(this, switchStmt.testRVal, tokenTypeInt); | ||
2846 | |||
2847 | BreakContTarg oldBreakTarg = curBreakTarg; | ||
2848 | ScriptMyLabel defaultLabel = null; | ||
2849 | TokenSwitchCase sortedCases = null; | ||
2850 | TokenSwitchCase defaultCase = null; | ||
2851 | |||
2852 | curBreakTarg = new BreakContTarg(this, "switchbreak_" + switchStmt.Unique); | ||
2853 | |||
2854 | /* | ||
2855 | * Build list of cases sorted by ascending values. | ||
2856 | * There should not be any overlapping of values. | ||
2857 | */ | ||
2858 | for(TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) | ||
2859 | { | ||
2860 | thisCase.label = ilGen.DefineLabel("case_" + thisCase.Unique); | ||
2861 | |||
2862 | /* | ||
2863 | * The default case if any, goes in its own separate slot. | ||
2864 | */ | ||
2865 | if(thisCase.rVal1 == null) | ||
2866 | { | ||
2867 | if(defaultCase != null) | ||
2868 | { | ||
2869 | ErrorMsg(thisCase, "only one default case allowed"); | ||
2870 | ErrorMsg(defaultCase, "...prior default case"); | ||
2871 | return; | ||
2872 | } | ||
2873 | defaultCase = thisCase; | ||
2874 | defaultLabel = thisCase.label; | ||
2875 | continue; | ||
2876 | } | ||
2877 | |||
2878 | /* | ||
2879 | * Evaluate case operands, they must be compile-time integer constants. | ||
2880 | */ | ||
2881 | CompValu rVal = GenerateFromRVal(thisCase.rVal1); | ||
2882 | if(!IsConstIntExpr(rVal, out thisCase.val1)) | ||
2883 | { | ||
2884 | ErrorMsg(thisCase.rVal1, "must be compile-time char or integer constant"); | ||
2885 | return; | ||
2886 | } | ||
2887 | thisCase.val2 = thisCase.val1; | ||
2888 | if(thisCase.rVal2 != null) | ||
2889 | { | ||
2890 | rVal = GenerateFromRVal(thisCase.rVal2); | ||
2891 | if(!IsConstIntExpr(rVal, out thisCase.val2)) | ||
2892 | { | ||
2893 | ErrorMsg(thisCase.rVal2, "must be compile-time char or integer constant"); | ||
2894 | return; | ||
2895 | } | ||
2896 | } | ||
2897 | if(thisCase.val2 < thisCase.val1) | ||
2898 | { | ||
2899 | ErrorMsg(thisCase.rVal2, "must be .ge. first value for the case"); | ||
2900 | return; | ||
2901 | } | ||
2902 | |||
2903 | /* | ||
2904 | * Insert into list, sorted by value. | ||
2905 | * Note that both limits are inclusive. | ||
2906 | */ | ||
2907 | TokenSwitchCase lastCase = null; | ||
2908 | TokenSwitchCase nextCase; | ||
2909 | for(nextCase = sortedCases; nextCase != null; nextCase = nextCase.nextSortedCase) | ||
2910 | { | ||
2911 | if(nextCase.val1 > thisCase.val2) | ||
2912 | break; | ||
2913 | if(nextCase.val2 >= thisCase.val1) | ||
2914 | { | ||
2915 | ErrorMsg(thisCase, "value used by previous case"); | ||
2916 | ErrorMsg(nextCase, "...previous case"); | ||
2917 | return; | ||
2918 | } | ||
2919 | lastCase = nextCase; | ||
2920 | } | ||
2921 | thisCase.nextSortedCase = nextCase; | ||
2922 | if(lastCase == null) | ||
2923 | { | ||
2924 | sortedCases = thisCase; | ||
2925 | } | ||
2926 | else | ||
2927 | { | ||
2928 | lastCase.nextSortedCase = thisCase; | ||
2929 | } | ||
2930 | } | ||
2931 | |||
2932 | if(defaultLabel == null) | ||
2933 | { | ||
2934 | defaultLabel = ilGen.DefineLabel("default_" + switchStmt.Unique); | ||
2935 | } | ||
2936 | |||
2937 | /* | ||
2938 | * Output code to jump to the case statement's labels based on integer index on stack. | ||
2939 | * Note that each case still has the integer index on stack when jumped to. | ||
2940 | */ | ||
2941 | int offset = 0; | ||
2942 | for(TokenSwitchCase thisCase = sortedCases; thisCase != null;) | ||
2943 | { | ||
2944 | |||
2945 | /* | ||
2946 | * Scan through list of cases to find the maximum number of cases who's numvalues-to-case ratio | ||
2947 | * is from 0.5 to 2.0. If such a group is found, use a CIL switch for them. If not, just use a | ||
2948 | * compare-and-branch for the current case. | ||
2949 | */ | ||
2950 | int numCases = 0; | ||
2951 | int numFound = 0; | ||
2952 | int lowValue = thisCase.val1; | ||
2953 | int numValues = 0; | ||
2954 | for(TokenSwitchCase scanCase = thisCase; scanCase != null; scanCase = scanCase.nextSortedCase) | ||
2955 | { | ||
2956 | int nVals = scanCase.val2 - thisCase.val1 + 1; | ||
2957 | double ratio = (double)nVals / (double)(++numCases); | ||
2958 | if((ratio >= 0.5) && (ratio <= 2.0)) | ||
2959 | { | ||
2960 | numFound = numCases; | ||
2961 | numValues = nVals; | ||
2962 | } | ||
2963 | } | ||
2964 | if(numFound > 1) | ||
2965 | { | ||
2966 | |||
2967 | /* | ||
2968 | * There is a group of case's, starting with thisCase, that fall within our criteria, ie, | ||
2969 | * that have a nice density of meaningful jumps. | ||
2970 | * | ||
2971 | * So first generate an array of jumps to the default label (explicit or implicit). | ||
2972 | */ | ||
2973 | ScriptMyLabel[] labels = new ScriptMyLabel[numValues]; | ||
2974 | for(int i = 0; i < numValues; i++) | ||
2975 | { | ||
2976 | labels[i] = defaultLabel; | ||
2977 | } | ||
2978 | |||
2979 | /* | ||
2980 | * Next, for each case in that group, fill in the corresponding array entries to jump to | ||
2981 | * that case's label. | ||
2982 | */ | ||
2983 | do | ||
2984 | { | ||
2985 | for(int i = thisCase.val1; i <= thisCase.val2; i++) | ||
2986 | { | ||
2987 | labels[i - lowValue] = thisCase.label; | ||
2988 | } | ||
2989 | thisCase = thisCase.nextSortedCase; | ||
2990 | } while(--numFound > 0); | ||
2991 | |||
2992 | /* | ||
2993 | * Subtract the low value and do the computed jump. | ||
2994 | * The OpCodes.Switch falls through if out of range (unsigned compare). | ||
2995 | */ | ||
2996 | if(offset != lowValue) | ||
2997 | { | ||
2998 | ilGen.Emit(switchStmt, OpCodes.Ldc_I4, lowValue - offset); | ||
2999 | ilGen.Emit(switchStmt, OpCodes.Sub); | ||
3000 | offset = lowValue; | ||
3001 | } | ||
3002 | ilGen.Emit(switchStmt, OpCodes.Dup); | ||
3003 | ilGen.Emit(switchStmt, OpCodes.Switch, labels); | ||
3004 | } | ||
3005 | else | ||
3006 | { | ||
3007 | |||
3008 | /* | ||
3009 | * It's not economical to do with a computed jump, so output a subtract/compare/branch | ||
3010 | * for thisCase. | ||
3011 | */ | ||
3012 | if(lowValue == thisCase.val2) | ||
3013 | { | ||
3014 | ilGen.Emit(switchStmt, OpCodes.Dup); | ||
3015 | ilGen.Emit(switchStmt, OpCodes.Ldc_I4, lowValue - offset); | ||
3016 | ilGen.Emit(switchStmt, OpCodes.Beq, thisCase.label); | ||
3017 | } | ||
3018 | else | ||
3019 | { | ||
3020 | if(offset != lowValue) | ||
3021 | { | ||
3022 | ilGen.Emit(switchStmt, OpCodes.Ldc_I4, lowValue - offset); | ||
3023 | ilGen.Emit(switchStmt, OpCodes.Sub); | ||
3024 | offset = lowValue; | ||
3025 | } | ||
3026 | ilGen.Emit(switchStmt, OpCodes.Dup); | ||
3027 | ilGen.Emit(switchStmt, OpCodes.Ldc_I4, thisCase.val2 - offset); | ||
3028 | ilGen.Emit(switchStmt, OpCodes.Ble_Un, thisCase.label); | ||
3029 | } | ||
3030 | thisCase = thisCase.nextSortedCase; | ||
3031 | } | ||
3032 | } | ||
3033 | ilGen.Emit(switchStmt, OpCodes.Br, defaultLabel); | ||
3034 | |||
3035 | /* | ||
3036 | * Output code for the cases themselves, in the order given by the programmer, | ||
3037 | * so they fall through as programmer wants. This includes the default case, if any. | ||
3038 | * | ||
3039 | * Each label is jumped to with the index still on the stack. So pop it off in case | ||
3040 | * the case body does a goto outside the switch or a return. If the case body might | ||
3041 | * fall through to the next case or the bottom of the switch, push a zero so the stack | ||
3042 | * matches in all cases. | ||
3043 | */ | ||
3044 | for(TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) | ||
3045 | { | ||
3046 | ilGen.MarkLabel(thisCase.label); // the branch comes here | ||
3047 | ilGen.Emit(thisCase, OpCodes.Pop); // pop the integer index off stack | ||
3048 | mightGetHere = true; // it's possible to get here | ||
3049 | for(TokenStmt stmt = thisCase.stmts; stmt != null; stmt = (TokenStmt)(stmt.nextToken)) | ||
3050 | { | ||
3051 | GenerateStmt(stmt); // output the case/explicit default body | ||
3052 | } | ||
3053 | if(mightGetHere) | ||
3054 | { | ||
3055 | ilGen.Emit(thisCase, OpCodes.Ldc_I4_0); | ||
3056 | // in case we fall through, push a dummy integer index | ||
3057 | } | ||
3058 | } | ||
3059 | |||
3060 | /* | ||
3061 | * If no explicit default case, output the default label here. | ||
3062 | */ | ||
3063 | if(defaultCase == null) | ||
3064 | { | ||
3065 | ilGen.MarkLabel(defaultLabel); | ||
3066 | mightGetHere = true; | ||
3067 | } | ||
3068 | |||
3069 | /* | ||
3070 | * If the last case of the switch falls through out the bottom, | ||
3071 | * we have to pop the index still on the stack. | ||
3072 | */ | ||
3073 | if(mightGetHere) | ||
3074 | { | ||
3075 | ilGen.Emit(switchStmt, OpCodes.Pop); | ||
3076 | } | ||
3077 | |||
3078 | /* | ||
3079 | * Output the 'break' statement target label. | ||
3080 | * Note that the integer index is not on the stack at this point. | ||
3081 | */ | ||
3082 | if(curBreakTarg.used) | ||
3083 | { | ||
3084 | ilGen.MarkLabel(curBreakTarg.label); | ||
3085 | mightGetHere = true; | ||
3086 | } | ||
3087 | |||
3088 | curBreakTarg = oldBreakTarg; | ||
3089 | } | ||
3090 | |||
3091 | private void GenerateStmtSwitchStr(CompValu testRVal, TokenStmtSwitch switchStmt) | ||
3092 | { | ||
3093 | BreakContTarg oldBreakTarg = curBreakTarg; | ||
3094 | ScriptMyLabel defaultLabel = null; | ||
3095 | TokenSwitchCase caseTreeTop = null; | ||
3096 | TokenSwitchCase defaultCase = null; | ||
3097 | |||
3098 | curBreakTarg = new BreakContTarg(this, "switchbreak_" + switchStmt.Unique); | ||
3099 | |||
3100 | /* | ||
3101 | * Make sure value is in a temp so we don't compute it more than once. | ||
3102 | */ | ||
3103 | if(!(testRVal is CompValuTemp)) | ||
3104 | { | ||
3105 | CompValuTemp temp = new CompValuTemp(testRVal.type, this); | ||
3106 | testRVal.PushVal(this, switchStmt); | ||
3107 | temp.Pop(this, switchStmt); | ||
3108 | testRVal = temp; | ||
3109 | } | ||
3110 | |||
3111 | /* | ||
3112 | * Build tree of cases. | ||
3113 | * There should not be any overlapping of values. | ||
3114 | */ | ||
3115 | for(TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) | ||
3116 | { | ||
3117 | thisCase.label = ilGen.DefineLabel("case"); | ||
3118 | |||
3119 | /* | ||
3120 | * The default case if any, goes in its own separate slot. | ||
3121 | */ | ||
3122 | if(thisCase.rVal1 == null) | ||
3123 | { | ||
3124 | if(defaultCase != null) | ||
3125 | { | ||
3126 | ErrorMsg(thisCase, "only one default case allowed"); | ||
3127 | ErrorMsg(defaultCase, "...prior default case"); | ||
3128 | return; | ||
3129 | } | ||
3130 | defaultCase = thisCase; | ||
3131 | defaultLabel = thisCase.label; | ||
3132 | continue; | ||
3133 | } | ||
3134 | |||
3135 | /* | ||
3136 | * Evaluate case operands, they must be compile-time string constants. | ||
3137 | */ | ||
3138 | CompValu rVal = GenerateFromRVal(thisCase.rVal1); | ||
3139 | if(!IsConstStrExpr(rVal, out thisCase.str1)) | ||
3140 | { | ||
3141 | ErrorMsg(thisCase.rVal1, "must be compile-time string constant"); | ||
3142 | continue; | ||
3143 | } | ||
3144 | thisCase.str2 = thisCase.str1; | ||
3145 | if(thisCase.rVal2 != null) | ||
3146 | { | ||
3147 | rVal = GenerateFromRVal(thisCase.rVal2); | ||
3148 | if(!IsConstStrExpr(rVal, out thisCase.str2)) | ||
3149 | { | ||
3150 | ErrorMsg(thisCase.rVal2, "must be compile-time string constant"); | ||
3151 | continue; | ||
3152 | } | ||
3153 | } | ||
3154 | if(String.Compare(thisCase.str2, thisCase.str1, StringComparison.Ordinal) < 0) | ||
3155 | { | ||
3156 | ErrorMsg(thisCase.rVal2, "must be .ge. first value for the case"); | ||
3157 | continue; | ||
3158 | } | ||
3159 | |||
3160 | /* | ||
3161 | * Insert into list, sorted by value. | ||
3162 | * Note that both limits are inclusive. | ||
3163 | */ | ||
3164 | caseTreeTop = InsertCaseInTree(caseTreeTop, thisCase); | ||
3165 | } | ||
3166 | |||
3167 | /* | ||
3168 | * Balance tree so we end up generating code that does O(log2 n) comparisons. | ||
3169 | */ | ||
3170 | caseTreeTop = BalanceTree(caseTreeTop); | ||
3171 | |||
3172 | /* | ||
3173 | * Output compare and branch instructions in a tree-like fashion so we do O(log2 n) comparisons. | ||
3174 | */ | ||
3175 | if(defaultLabel == null) | ||
3176 | { | ||
3177 | defaultLabel = ilGen.DefineLabel("default"); | ||
3178 | } | ||
3179 | OutputStrCase(testRVal, caseTreeTop, defaultLabel); | ||
3180 | |||
3181 | /* | ||
3182 | * Output code for the cases themselves, in the order given by the programmer, | ||
3183 | * so they fall through as programmer wants. This includes the default case, if any. | ||
3184 | */ | ||
3185 | for(TokenSwitchCase thisCase = switchStmt.cases; thisCase != null; thisCase = thisCase.nextCase) | ||
3186 | { | ||
3187 | ilGen.MarkLabel(thisCase.label); // the branch comes here | ||
3188 | mightGetHere = true; // it's possible to get here | ||
3189 | for(TokenStmt stmt = thisCase.stmts; stmt != null; stmt = (TokenStmt)(stmt.nextToken)) | ||
3190 | { | ||
3191 | GenerateStmt(stmt); // output the case/explicit default body | ||
3192 | } | ||
3193 | } | ||
3194 | |||
3195 | /* | ||
3196 | * If no explicit default case, output the default label here. | ||
3197 | */ | ||
3198 | if(defaultCase == null) | ||
3199 | { | ||
3200 | ilGen.MarkLabel(defaultLabel); | ||
3201 | mightGetHere = true; | ||
3202 | } | ||
3203 | |||
3204 | /* | ||
3205 | * Output the 'break' statement target label. | ||
3206 | */ | ||
3207 | if(curBreakTarg.used) | ||
3208 | { | ||
3209 | ilGen.MarkLabel(curBreakTarg.label); | ||
3210 | mightGetHere = true; | ||
3211 | } | ||
3212 | |||
3213 | curBreakTarg = oldBreakTarg; | ||
3214 | } | ||
3215 | |||
3216 | /** | ||
3217 | * @brief Insert a case in a tree of cases | ||
3218 | * @param r = root of existing cases to insert into | ||
3219 | * @param n = new case being inserted | ||
3220 | * @returns new root with new case inserted | ||
3221 | */ | ||
3222 | private TokenSwitchCase InsertCaseInTree(TokenSwitchCase r, TokenSwitchCase n) | ||
3223 | { | ||
3224 | if(r == null) | ||
3225 | return n; | ||
3226 | |||
3227 | TokenSwitchCase t = r; | ||
3228 | while(true) | ||
3229 | { | ||
3230 | if(String.Compare(n.str2, t.str1, StringComparison.Ordinal) < 0) | ||
3231 | { | ||
3232 | if(t.lowerCase == null) | ||
3233 | { | ||
3234 | t.lowerCase = n; | ||
3235 | break; | ||
3236 | } | ||
3237 | t = t.lowerCase; | ||
3238 | continue; | ||
3239 | } | ||
3240 | if(String.Compare(n.str1, t.str2, StringComparison.Ordinal) > 0) | ||
3241 | { | ||
3242 | if(t.higherCase == null) | ||
3243 | { | ||
3244 | t.higherCase = n; | ||
3245 | break; | ||
3246 | } | ||
3247 | t = t.higherCase; | ||
3248 | continue; | ||
3249 | } | ||
3250 | ErrorMsg(n, "duplicate case"); | ||
3251 | ErrorMsg(r, "...duplicate of"); | ||
3252 | break; | ||
3253 | } | ||
3254 | return r; | ||
3255 | } | ||
3256 | |||
3257 | /** | ||
3258 | * @brief Balance a tree so left & right halves contain same number within +-1 | ||
3259 | * @param r = root of tree to balance | ||
3260 | * @returns new root | ||
3261 | */ | ||
3262 | private static TokenSwitchCase BalanceTree(TokenSwitchCase r) | ||
3263 | { | ||
3264 | if(r == null) | ||
3265 | return r; | ||
3266 | |||
3267 | int lc = CountTree(r.lowerCase); | ||
3268 | int hc = CountTree(r.higherCase); | ||
3269 | TokenSwitchCase n, x; | ||
3270 | |||
3271 | /* | ||
3272 | * If lower side is heavy, move highest nodes from lower side to | ||
3273 | * higher side until balanced. | ||
3274 | */ | ||
3275 | while(lc > hc + 1) | ||
3276 | { | ||
3277 | x = ExtractHighest(r.lowerCase, out n); | ||
3278 | n.lowerCase = x; | ||
3279 | n.higherCase = r; | ||
3280 | r.lowerCase = null; | ||
3281 | r = n; | ||
3282 | lc--; | ||
3283 | hc++; | ||
3284 | } | ||
3285 | |||
3286 | /* | ||
3287 | * If higher side is heavy, move lowest nodes from higher side to | ||
3288 | * lower side until balanced. | ||
3289 | */ | ||
3290 | while(hc > lc + 1) | ||
3291 | { | ||
3292 | x = ExtractLowest(r.higherCase, out n); | ||
3293 | n.higherCase = x; | ||
3294 | n.lowerCase = r; | ||
3295 | r.higherCase = null; | ||
3296 | r = n; | ||
3297 | lc++; | ||
3298 | hc--; | ||
3299 | } | ||
3300 | |||
3301 | /* | ||
3302 | * Now balance each side because they can be lopsided individually. | ||
3303 | */ | ||
3304 | r.lowerCase = BalanceTree(r.lowerCase); | ||
3305 | r.higherCase = BalanceTree(r.higherCase); | ||
3306 | return r; | ||
3307 | } | ||
3308 | |||
3309 | /** | ||
3310 | * @brief Get number of nodes in a tree | ||
3311 | * @param n = root of tree to count | ||
3312 | * @returns number of nodes including root | ||
3313 | */ | ||
3314 | private static int CountTree(TokenSwitchCase n) | ||
3315 | { | ||
3316 | if(n == null) | ||
3317 | return 0; | ||
3318 | return 1 + CountTree(n.lowerCase) + CountTree(n.higherCase); | ||
3319 | } | ||
3320 | |||
3321 | // Extract highest node from a tree | ||
3322 | // @param r = root of tree to extract highest from | ||
3323 | // @returns new root after node has been extracted | ||
3324 | // n = node that was extracted from tree | ||
3325 | private static TokenSwitchCase ExtractHighest(TokenSwitchCase r, out TokenSwitchCase n) | ||
3326 | { | ||
3327 | if(r.higherCase == null) | ||
3328 | { | ||
3329 | n = r; | ||
3330 | return r.lowerCase; | ||
3331 | } | ||
3332 | r.higherCase = ExtractHighest(r.higherCase, out n); | ||
3333 | return r; | ||
3334 | } | ||
3335 | |||
3336 | // Extract lowest node from a tree | ||
3337 | // @param r = root of tree to extract lowest from | ||
3338 | // @returns new root after node has been extracted | ||
3339 | // n = node that was extracted from tree | ||
3340 | private static TokenSwitchCase ExtractLowest(TokenSwitchCase r, out TokenSwitchCase n) | ||
3341 | { | ||
3342 | if(r.lowerCase == null) | ||
3343 | { | ||
3344 | n = r; | ||
3345 | return r.higherCase; | ||
3346 | } | ||
3347 | r.lowerCase = ExtractLowest(r.lowerCase, out n); | ||
3348 | return r; | ||
3349 | } | ||
3350 | |||
3351 | /** | ||
3352 | * Output code for string-style case of a switch/case to jump to the script code associated with the case. | ||
3353 | * @param testRVal = value being switched on | ||
3354 | * @param thisCase = case that the code is being output for | ||
3355 | * @param defaultLabel = where the default clause is (or past all cases if none) | ||
3356 | * Note: | ||
3357 | * Outputs code for this case and the lowerCase and higherCases if any. | ||
3358 | * If no lowerCase or higherCase, outputs a br to defaultLabel so this code never falls through. | ||
3359 | */ | ||
3360 | private void OutputStrCase(CompValu testRVal, TokenSwitchCase thisCase, ScriptMyLabel defaultLabel) | ||
3361 | { | ||
3362 | /* | ||
3363 | * If nothing lower on tree and there is a single case value, | ||
3364 | * just do one compare for equality. | ||
3365 | */ | ||
3366 | if((thisCase.lowerCase == null) && (thisCase.higherCase == null) && (thisCase.str1 == thisCase.str2)) | ||
3367 | { | ||
3368 | testRVal.PushVal(this, thisCase, tokenTypeStr); | ||
3369 | ilGen.Emit(thisCase, OpCodes.Ldstr, thisCase.str1); | ||
3370 | ilGen.Emit(thisCase, OpCodes.Ldc_I4, (int)StringComparison.Ordinal); | ||
3371 | ilGen.Emit(thisCase, OpCodes.Call, stringCompareMethodInfo); | ||
3372 | ilGen.Emit(thisCase, OpCodes.Brfalse, thisCase.label); | ||
3373 | ilGen.Emit(thisCase, OpCodes.Br, defaultLabel); | ||
3374 | return; | ||
3375 | } | ||
3376 | |||
3377 | /* | ||
3378 | * Determine where to jump if switch value is lower than lower case value. | ||
3379 | */ | ||
3380 | ScriptMyLabel lowerLabel = defaultLabel; | ||
3381 | if(thisCase.lowerCase != null) | ||
3382 | { | ||
3383 | lowerLabel = ilGen.DefineLabel("lower"); | ||
3384 | } | ||
3385 | |||
3386 | /* | ||
3387 | * If single case value, put comparison result in this temp. | ||
3388 | */ | ||
3389 | CompValuTemp cmpv1 = null; | ||
3390 | if(thisCase.str1 == thisCase.str2) | ||
3391 | { | ||
3392 | cmpv1 = new CompValuTemp(tokenTypeInt, this); | ||
3393 | } | ||
3394 | |||
3395 | /* | ||
3396 | * If switch value .lt. lower case value, jump to lower label. | ||
3397 | * Maybe save comparison result in a temp. | ||
3398 | */ | ||
3399 | testRVal.PushVal(this, thisCase, tokenTypeStr); | ||
3400 | ilGen.Emit(thisCase, OpCodes.Ldstr, thisCase.str1); | ||
3401 | ilGen.Emit(thisCase, OpCodes.Ldc_I4, (int)StringComparison.Ordinal); | ||
3402 | ilGen.Emit(thisCase, OpCodes.Call, stringCompareMethodInfo); | ||
3403 | if(cmpv1 != null) | ||
3404 | { | ||
3405 | ilGen.Emit(thisCase, OpCodes.Dup); | ||
3406 | cmpv1.Pop(this, thisCase); | ||
3407 | } | ||
3408 | ilGen.Emit(thisCase, OpCodes.Ldc_I4_0); | ||
3409 | ilGen.Emit(thisCase, OpCodes.Blt, lowerLabel); | ||
3410 | |||
3411 | /* | ||
3412 | * If switch value .le. higher case value, jump to case code. | ||
3413 | * Maybe get comparison from the temp. | ||
3414 | */ | ||
3415 | if(cmpv1 == null) | ||
3416 | { | ||
3417 | testRVal.PushVal(this, thisCase, tokenTypeStr); | ||
3418 | ilGen.Emit(thisCase, OpCodes.Ldstr, thisCase.str2); | ||
3419 | ilGen.Emit(thisCase, OpCodes.Ldc_I4, (int)StringComparison.Ordinal); | ||
3420 | ilGen.Emit(thisCase, OpCodes.Call, stringCompareMethodInfo); | ||
3421 | } | ||
3422 | else | ||
3423 | { | ||
3424 | cmpv1.PushVal(this, thisCase); | ||
3425 | } | ||
3426 | ilGen.Emit(thisCase, OpCodes.Ldc_I4_0); | ||
3427 | ilGen.Emit(thisCase, OpCodes.Ble, thisCase.label); | ||
3428 | |||
3429 | /* | ||
3430 | * Output code for higher comparison if any. | ||
3431 | */ | ||
3432 | if(thisCase.higherCase == null) | ||
3433 | { | ||
3434 | ilGen.Emit(thisCase, OpCodes.Br, defaultLabel); | ||
3435 | } | ||
3436 | else | ||
3437 | { | ||
3438 | OutputStrCase(testRVal, thisCase.higherCase, defaultLabel); | ||
3439 | } | ||
3440 | |||
3441 | /* | ||
3442 | * Output code for lower comparison if any. | ||
3443 | */ | ||
3444 | if(thisCase.lowerCase != null) | ||
3445 | { | ||
3446 | ilGen.MarkLabel(lowerLabel); | ||
3447 | OutputStrCase(testRVal, thisCase.lowerCase, defaultLabel); | ||
3448 | } | ||
3449 | } | ||
3450 | |||
3451 | /** | ||
3452 | * @brief output code for a throw statement. | ||
3453 | * @param throwStmt = throw statement token, including value to be thrown | ||
3454 | */ | ||
3455 | private void GenerateStmtThrow(TokenStmtThrow throwStmt) | ||
3456 | { | ||
3457 | if(!mightGetHere) | ||
3458 | return; | ||
3459 | |||
3460 | /* | ||
3461 | * 'throw' statements never fall through. | ||
3462 | */ | ||
3463 | mightGetHere = false; | ||
3464 | |||
3465 | /* | ||
3466 | * Output code for either a throw or a rethrow. | ||
3467 | */ | ||
3468 | if(throwStmt.rVal == null) | ||
3469 | { | ||
3470 | for(TokenStmtBlock blk = curStmtBlock; blk != null; blk = blk.outerStmtBlock) | ||
3471 | { | ||
3472 | if(curStmtBlock.isCatch) | ||
3473 | { | ||
3474 | ilGen.Emit(throwStmt, OpCodes.Rethrow); | ||
3475 | return; | ||
3476 | } | ||
3477 | } | ||
3478 | ErrorMsg(throwStmt, "rethrow allowed only in catch clause"); | ||
3479 | } | ||
3480 | else | ||
3481 | { | ||
3482 | CompValu rVal = GenerateFromRVal(throwStmt.rVal); | ||
3483 | rVal.PushVal(this, throwStmt.rVal, tokenTypeObj); | ||
3484 | ilGen.Emit(throwStmt, OpCodes.Call, thrownExceptionWrapMethodInfo); | ||
3485 | ilGen.Emit(throwStmt, OpCodes.Throw); | ||
3486 | } | ||
3487 | } | ||
3488 | |||
3489 | /** | ||
3490 | * @brief output code for a try/catch/finally block | ||
3491 | */ | ||
3492 | private void GenerateStmtTry(TokenStmtTry tryStmt) | ||
3493 | { | ||
3494 | if(!mightGetHere) | ||
3495 | return; | ||
3496 | |||
3497 | /* | ||
3498 | * Reducer should make sure we have exactly one of catch or finally. | ||
3499 | */ | ||
3500 | if((tryStmt.catchStmt == null) && (tryStmt.finallyStmt == null)) | ||
3501 | { | ||
3502 | throw new Exception("must have a catch or a finally on try"); | ||
3503 | } | ||
3504 | if((tryStmt.catchStmt != null) && (tryStmt.finallyStmt != null)) | ||
3505 | { | ||
3506 | throw new Exception("can't have both catch and finally on same try"); | ||
3507 | } | ||
3508 | |||
3509 | /* | ||
3510 | * Stack the call labels. | ||
3511 | * Try blocks have their own series of call labels. | ||
3512 | */ | ||
3513 | ScriptMyLocal saveCallNo = actCallNo; | ||
3514 | LinkedList<CallLabel> saveCallLabels = actCallLabels; | ||
3515 | |||
3516 | /* | ||
3517 | * Generate code for either try { } catch { } or try { } finally { }. | ||
3518 | */ | ||
3519 | if(tryStmt.catchStmt != null) | ||
3520 | GenerateStmtTryCatch(tryStmt); | ||
3521 | if(tryStmt.finallyStmt != null) | ||
3522 | GenerateStmtTryFinally(tryStmt); | ||
3523 | |||
3524 | /* | ||
3525 | * Restore call labels. | ||
3526 | */ | ||
3527 | actCallNo = saveCallNo; | ||
3528 | actCallLabels = saveCallLabels; | ||
3529 | } | ||
3530 | |||
3531 | |||
3532 | /** | ||
3533 | * @brief output code for a try/catch block | ||
3534 | * | ||
3535 | * int __tryCallNo = -1; // call number within try { } subblock | ||
3536 | * int __catCallNo = -1; // call number within catch { } subblock | ||
3537 | * Exception __catThrown = null; // caught exception | ||
3538 | * <oldCallLabel>: // the outside world jumps here to restore us no matter ... | ||
3539 | * try { // ... where we actually were inside of try/catch | ||
3540 | * if (__tryCallNo >= 0) goto tryCallSw; // maybe go do restore | ||
3541 | * <try body using __tryCallNo> // execute script-defined code | ||
3542 | * // ...stack capture WILL run catch { } subblock | ||
3543 | * leave tryEnd; // exits | ||
3544 | * tryThrow:<tryCallLabel>: | ||
3545 | * throw new ScriptRestoreCatchException(__catThrown); // catch { } was running, jump to its beginning | ||
3546 | * tryCallSw: // restoring... | ||
3547 | * switch (__tryCallNo) back up into <try body> // not catching, jump back inside try | ||
3548 | * } catch (Exception exc) { | ||
3549 | * exc = ScriptRestoreCatchException.Unwrap(exc); // unwrap possible ScriptRestoreCatchException | ||
3550 | * if (exc == null) goto catchRetro; // rethrow if IXMRUncatchable (eg, StackCaptureException) | ||
3551 | * __catThrown = exc; // save what was thrown so restoring try { } will throw it again | ||
3552 | * catchVar = exc; // set up script-visible variable | ||
3553 | * __tryCallNo = tryThrow:<tryCallLabel> | ||
3554 | * if (__catCallNo >= 0) goto catchCallSw; // if restoring, go check below | ||
3555 | * <catch body using __catCallNo> // normal, execute script-defined code | ||
3556 | * leave tryEnd; // all done, exit catch { } | ||
3557 | * catchRetro: | ||
3558 | * rethrow; | ||
3559 | * catchCallSw: | ||
3560 | * switch (__catCallNo) back up into <catch body> // restart catch { } code wherever it was | ||
3561 | * } | ||
3562 | * tryEnd: | ||
3563 | */ | ||
3564 | private void GenerateStmtTryCatch(TokenStmtTry tryStmt) | ||
3565 | { | ||
3566 | CompValuTemp tryCallNo = new CompValuTemp(tokenTypeInt, this); | ||
3567 | CompValuTemp catCallNo = new CompValuTemp(tokenTypeInt, this); | ||
3568 | CompValuTemp catThrown = new CompValuTemp(tokenTypeExc, this); | ||
3569 | |||
3570 | ScriptMyLabel tryCallSw = ilGen.DefineLabel("__tryCallSw_" + tryStmt.Unique); | ||
3571 | ScriptMyLabel catchRetro = ilGen.DefineLabel("__catchRetro_" + tryStmt.Unique); | ||
3572 | ScriptMyLabel catchCallSw = ilGen.DefineLabel("__catchCallSw_" + tryStmt.Unique); | ||
3573 | ScriptMyLabel tryEnd = ilGen.DefineLabel("__tryEnd_" + tryStmt.Unique); | ||
3574 | |||
3575 | SetCallNo(tryStmt, tryCallNo, -1); | ||
3576 | SetCallNo(tryStmt, catCallNo, -1); | ||
3577 | ilGen.Emit(tryStmt, OpCodes.Ldnull); | ||
3578 | catThrown.Pop(this, tryStmt); | ||
3579 | |||
3580 | new CallLabel(this, tryStmt); // <oldcalllabel>: | ||
3581 | ilGen.BeginExceptionBlock(); // try { | ||
3582 | openCallLabel = null; | ||
3583 | if(DEBUG_TRYSTMT) | ||
3584 | { | ||
3585 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "enter try*: " + tryStmt.line + " callMode="); | ||
3586 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3587 | PushXMRInst(); | ||
3588 | ilGen.Emit(tryStmt, OpCodes.Ldfld, callModeFieldInfo); | ||
3589 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3590 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3591 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " tryCallNo="); | ||
3592 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3593 | tryCallNo.PushVal(this, tryStmt); | ||
3594 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3595 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3596 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " catThrown.IsNull="); | ||
3597 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3598 | catThrown.PushVal(this, tryStmt); | ||
3599 | ilGen.Emit(tryStmt, OpCodes.Ldnull); | ||
3600 | ilGen.Emit(tryStmt, OpCodes.Ceq); | ||
3601 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3602 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3603 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " catCallNo="); | ||
3604 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3605 | catCallNo.PushVal(this, tryStmt); | ||
3606 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3607 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3608 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "\n"); | ||
3609 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3610 | } | ||
3611 | |||
3612 | GetCallNo(tryStmt, tryCallNo); // if (__tryCallNo >= 0) goto tryCallSw; | ||
3613 | ilGen.Emit(tryStmt, OpCodes.Ldc_I4_0); | ||
3614 | ilGen.Emit(tryStmt, OpCodes.Bge, tryCallSw); | ||
3615 | |||
3616 | actCallNo = tryCallNo.localBuilder; // set up __tryCallNo for call labels | ||
3617 | actCallLabels = new LinkedList<CallLabel>(); | ||
3618 | |||
3619 | GenerateStmtBlock(tryStmt.tryStmt); // output the try block statement subblock | ||
3620 | |||
3621 | bool tryBlockFallsOutBottom = mightGetHere; | ||
3622 | if(tryBlockFallsOutBottom) | ||
3623 | { | ||
3624 | new CallLabel(this, tryStmt); // <tryCallLabel>: | ||
3625 | ilGen.Emit(tryStmt, OpCodes.Leave, tryEnd); // leave tryEnd; | ||
3626 | openCallLabel = null; | ||
3627 | } | ||
3628 | |||
3629 | CallLabel tryThrow = new CallLabel(this, tryStmt); // tryThrow:<tryCallLabel>: | ||
3630 | if(DEBUG_TRYSTMT) | ||
3631 | { | ||
3632 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "tryThrow*: " + tryStmt.line + " catThrown="); | ||
3633 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3634 | catThrown.PushVal(this, tryStmt); | ||
3635 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3636 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "\n"); | ||
3637 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3638 | } | ||
3639 | catThrown.PushVal(this, tryStmt); // throw new ScriptRestoreCatchException (__catThrown); | ||
3640 | ilGen.Emit(tryStmt, OpCodes.Newobj, scriptRestoreCatchExceptionConstructorInfo); | ||
3641 | ilGen.Emit(tryStmt, OpCodes.Throw); | ||
3642 | openCallLabel = null; | ||
3643 | |||
3644 | ilGen.MarkLabel(tryCallSw); // tryCallSw: | ||
3645 | if(DEBUG_TRYSTMT) | ||
3646 | { | ||
3647 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "tryCallSw*: " + tryStmt.line + " tryCallNo="); | ||
3648 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3649 | tryCallNo.PushVal(this, tryStmt); | ||
3650 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3651 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3652 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "\n"); | ||
3653 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3654 | } | ||
3655 | OutputCallNoSwitchStmt(); // switch (tryCallNo) ... | ||
3656 | |||
3657 | CompValuLocalVar catchVarLocExc = null; | ||
3658 | CompValuTemp catchVarLocStr = null; | ||
3659 | |||
3660 | if(tryStmt.catchVar.type.ToSysType() == typeof(Exception)) | ||
3661 | { | ||
3662 | catchVarLocExc = new CompValuLocalVar(tryStmt.catchVar.type, tryStmt.catchVar.name.val, this); | ||
3663 | } | ||
3664 | else if(tryStmt.catchVar.type.ToSysType() == typeof(String)) | ||
3665 | { | ||
3666 | catchVarLocStr = new CompValuTemp(tryStmt.catchVar.type, this); | ||
3667 | } | ||
3668 | |||
3669 | ScriptMyLocal excLocal = ilGen.DeclareLocal(typeof(String), "catchstr_" + tryStmt.Unique); | ||
3670 | |||
3671 | ilGen.BeginCatchBlock(typeof(Exception)); // start of the catch block that can catch any exception | ||
3672 | if(DEBUG_TRYSTMT) | ||
3673 | { | ||
3674 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldstr, "enter catch*: " + tryStmt.line + " callMode="); | ||
3675 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3676 | PushXMRInst(); | ||
3677 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldfld, callModeFieldInfo); | ||
3678 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Box, typeof(int)); | ||
3679 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3680 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldstr, " catCallNo="); | ||
3681 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3682 | catCallNo.PushVal(this, tryStmt); | ||
3683 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Box, typeof(int)); | ||
3684 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3685 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldstr, " exc="); | ||
3686 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3687 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Dup); | ||
3688 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3689 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldstr, "\n"); | ||
3690 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3691 | } | ||
3692 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, scriptRestoreCatchExceptionUnwrap); | ||
3693 | // exc = ScriptRestoreCatchException.Unwrap (exc); | ||
3694 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Dup); // rethrow if IXMRUncatchable (eg, StackCaptureException) | ||
3695 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Brfalse, catchRetro); | ||
3696 | if(tryStmt.catchVar.type.ToSysType() == typeof(Exception)) | ||
3697 | { | ||
3698 | tryStmt.catchVar.location = catchVarLocExc; | ||
3699 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Dup); | ||
3700 | catThrown.Pop(this, tryStmt); // store exception object in catThrown | ||
3701 | catchVarLocExc.Pop(this, tryStmt.catchVar.name); // also store in script-visible variable | ||
3702 | } | ||
3703 | else if(tryStmt.catchVar.type.ToSysType() == typeof(String)) | ||
3704 | { | ||
3705 | tryStmt.catchVar.location = catchVarLocStr; | ||
3706 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Dup); | ||
3707 | catThrown.Pop(this, tryStmt); // store exception object in catThrown | ||
3708 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Call, catchExcToStrMethodInfo); | ||
3709 | |||
3710 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Stloc, excLocal); | ||
3711 | catchVarLocStr.PopPre(this, tryStmt.catchVar.name); | ||
3712 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldloc, excLocal); | ||
3713 | catchVarLocStr.PopPost(this, tryStmt.catchVar.name, tokenTypeStr); | ||
3714 | } | ||
3715 | else | ||
3716 | { | ||
3717 | throw new Exception("bad catch var type " + tryStmt.catchVar.type.ToString()); | ||
3718 | } | ||
3719 | |||
3720 | SetCallNo(tryStmt, tryCallNo, tryThrow.index); // __tryCallNo = tryThrow so it knows to do 'throw catThrown' on restore | ||
3721 | |||
3722 | GetCallNo(tryStmt, catCallNo); // if (__catCallNo >= 0) goto catchCallSw; | ||
3723 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Ldc_I4_0); | ||
3724 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Bge, catchCallSw); | ||
3725 | |||
3726 | actCallNo = catCallNo.localBuilder; // set up __catCallNo for call labels | ||
3727 | actCallLabels.Clear(); | ||
3728 | mightGetHere = true; // if we can get to the 'try' assume we can get to the 'catch' | ||
3729 | GenerateStmtBlock(tryStmt.catchStmt); // output catch clause statement subblock | ||
3730 | |||
3731 | if(mightGetHere) | ||
3732 | { | ||
3733 | new CallLabel(this, tryStmt.catchStmt); | ||
3734 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Leave, tryEnd); | ||
3735 | openCallLabel = null; | ||
3736 | } | ||
3737 | |||
3738 | ilGen.MarkLabel(catchRetro); // not a script-visible exception, rethrow it | ||
3739 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Pop); | ||
3740 | ilGen.Emit(tryStmt.catchStmt, OpCodes.Rethrow); | ||
3741 | |||
3742 | ilGen.MarkLabel(catchCallSw); | ||
3743 | OutputCallNoSwitchStmt(); // restoring, jump back inside script-defined body | ||
3744 | |||
3745 | ilGen.EndExceptionBlock(); | ||
3746 | ilGen.MarkLabel(tryEnd); | ||
3747 | |||
3748 | mightGetHere |= tryBlockFallsOutBottom; // also get here if try body falls out bottom | ||
3749 | } | ||
3750 | |||
3751 | /** | ||
3752 | * @brief output code for a try/finally block | ||
3753 | * | ||
3754 | * This is such a mess because there is hidden state for the finally { } that we have to recreate. | ||
3755 | * The finally { } can be entered either via an exception being thrown in the try { } or a leave | ||
3756 | * being executed in the try { } whose target is outside the try { } finally { }. | ||
3757 | * | ||
3758 | * For the thrown exception case, we slip in a try { } catch { } wrapper around the original try { } | ||
3759 | * body. This will sense any thrown exception that would execute the finally { }. Then we have our | ||
3760 | * try { } throw the exception on restore which gets the finally { } called and on its way again. | ||
3761 | * | ||
3762 | * For the leave case, we prefix all leave instructions with a call label and we explicitly chain | ||
3763 | * all leaves through each try { } that has an associated finally { } that the leave would unwind | ||
3764 | * through. This gets each try { } to simply jump to the correct leave instruction which immediately | ||
3765 | * invokes the corresponding finally { } and then chains to the next leave instruction on out until | ||
3766 | * it gets to its target. | ||
3767 | * | ||
3768 | * int __finCallNo = -1; // call number within finally { } subblock | ||
3769 | * int __tryCallNo = -1; // call number within try { } subblock | ||
3770 | * Exception __catThrown = null; // caught exception | ||
3771 | * <oldCallLabel>: // the outside world jumps here to restore us no matter ... | ||
3772 | * try { // ... where we actually were inside of try/finally | ||
3773 | * try { | ||
3774 | * if (__tryCallNo >= 0) goto tryCallSw; // maybe go do restore | ||
3775 | * <try body using __tryCallNo> // execute script-defined code | ||
3776 | * // ...stack capture WILL run catch/finally { } subblock | ||
3777 | * leave tryEnd; // executes finally { } subblock and exits | ||
3778 | * tryThrow:<tryCallLabel>: | ||
3779 | * throw new ScriptRestoreCatchException(__catThrown); // catch { } was running, jump to its beginning | ||
3780 | * tryCallSw: // restoring... | ||
3781 | * switch (__tryCallNo) back up into <try body> // jump back inside try, ... | ||
3782 | * // ... maybe to a leave if we were doing finally { } subblock | ||
3783 | * } catch (Exception exc) { // in case we're getting to finally { } via a thrown exception: | ||
3784 | * exc = ScriptRestoreCatchException.Unwrap(exc); // unwrap possible ScriptRestoreCatchException | ||
3785 | * if (callMode == CallMode_SAVE) goto catchRetro; // don't touch anything if capturing stack | ||
3786 | * __catThrown = exc; // save exception so try { } can throw it on restore | ||
3787 | * __tryCallNo = tryThrow:<tryCallLabel>; // tell try { } to throw it on restore | ||
3788 | * catchRetro: | ||
3789 | * rethrow; // in any case, go on to finally { } subblock now | ||
3790 | * } | ||
3791 | * } finally { | ||
3792 | * if (callMode == CallMode_SAVE) goto finEnd; // don't touch anything if capturing stack | ||
3793 | * if (__finCallNo >= 0) goto finCallSw; // maybe go do restore | ||
3794 | * <finally body using __finCallNo> // normal, execute script-defined code | ||
3795 | * finEnd: | ||
3796 | * endfinally // jump to leave/throw target or next outer finally { } | ||
3797 | * finCallSw: | ||
3798 | * switch (__finCallNo) back up into <finally body> // restoring, restart finally { } code wherever it was | ||
3799 | * } | ||
3800 | * tryEnd: | ||
3801 | */ | ||
3802 | private void GenerateStmtTryFinally(TokenStmtTry tryStmt) | ||
3803 | { | ||
3804 | CompValuTemp finCallNo = new CompValuTemp(tokenTypeInt, this); | ||
3805 | CompValuTemp tryCallNo = new CompValuTemp(tokenTypeInt, this); | ||
3806 | CompValuTemp catThrown = new CompValuTemp(tokenTypeExc, this); | ||
3807 | |||
3808 | ScriptMyLabel tryCallSw = ilGen.DefineLabel("__tryCallSw_" + tryStmt.Unique); | ||
3809 | ScriptMyLabel catchRetro = ilGen.DefineLabel("__catchRetro_" + tryStmt.Unique); | ||
3810 | ScriptMyLabel finCallSw = ilGen.DefineLabel("__finCallSw_" + tryStmt.Unique); | ||
3811 | BreakContTarg finEnd = new BreakContTarg(this, "__finEnd_" + tryStmt.Unique); | ||
3812 | ScriptMyLabel tryEnd = ilGen.DefineLabel("__tryEnd_" + tryStmt.Unique); | ||
3813 | |||
3814 | SetCallNo(tryStmt, finCallNo, -1); | ||
3815 | SetCallNo(tryStmt, tryCallNo, -1); | ||
3816 | ilGen.Emit(tryStmt, OpCodes.Ldnull); | ||
3817 | catThrown.Pop(this, tryStmt); | ||
3818 | |||
3819 | new CallLabel(this, tryStmt); // <oldcalllabel>: | ||
3820 | ilGen.BeginExceptionBlock(); // try { | ||
3821 | ilGen.BeginExceptionBlock(); // try { | ||
3822 | openCallLabel = null; | ||
3823 | if(DEBUG_TRYSTMT) | ||
3824 | { | ||
3825 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "enter try*: " + tryStmt.line + " callMode="); | ||
3826 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3827 | PushXMRInst(); | ||
3828 | ilGen.Emit(tryStmt, OpCodes.Ldfld, callModeFieldInfo); | ||
3829 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3830 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3831 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " tryCallNo="); | ||
3832 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3833 | tryCallNo.PushVal(this, tryStmt); | ||
3834 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3835 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3836 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " finCallNo="); | ||
3837 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3838 | finCallNo.PushVal(this, tryStmt); | ||
3839 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3840 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3841 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " catThrown.IsNull="); | ||
3842 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3843 | catThrown.PushVal(this, tryStmt); | ||
3844 | ilGen.Emit(tryStmt, OpCodes.Ldnull); | ||
3845 | ilGen.Emit(tryStmt, OpCodes.Ceq); | ||
3846 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3847 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3848 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "\n"); | ||
3849 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3850 | } | ||
3851 | |||
3852 | GetCallNo(tryStmt, tryCallNo); // if (__tryCallNo >= 0) goto tryCallSw; | ||
3853 | ilGen.Emit(tryStmt, OpCodes.Ldc_I4_0); | ||
3854 | ilGen.Emit(tryStmt, OpCodes.Bge, tryCallSw); | ||
3855 | |||
3856 | actCallNo = tryCallNo.localBuilder; // set up __tryCallNo for call labels | ||
3857 | actCallLabels = new LinkedList<CallLabel>(); | ||
3858 | |||
3859 | GenerateStmtBlock(tryStmt.tryStmt); // output the try block statement subblock | ||
3860 | |||
3861 | if(mightGetHere) | ||
3862 | { | ||
3863 | new CallLabel(this, tryStmt); // <newCallLabel>: | ||
3864 | ilGen.Emit(tryStmt, OpCodes.Leave, tryEnd); // leave tryEnd; | ||
3865 | openCallLabel = null; | ||
3866 | } | ||
3867 | |||
3868 | foreach(IntermediateLeave iLeave in tryStmt.iLeaves.Values) | ||
3869 | { | ||
3870 | ilGen.MarkLabel(iLeave.jumpIntoLabel); // intr2_exit: | ||
3871 | new CallLabel(this, tryStmt); // tryCallNo = n; | ||
3872 | ilGen.Emit(tryStmt, OpCodes.Leave, iLeave.jumpAwayLabel); // __callNo_n_: leave int1_exit; | ||
3873 | openCallLabel = null; | ||
3874 | } | ||
3875 | |||
3876 | CallLabel tryThrow = new CallLabel(this, tryStmt); // tryThrow:<tryCallLabel>: | ||
3877 | if(DEBUG_TRYSTMT) | ||
3878 | { | ||
3879 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "tryThrow*: " + tryStmt.line + " catThrown="); | ||
3880 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3881 | catThrown.PushVal(this, tryStmt); | ||
3882 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3883 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "\n"); | ||
3884 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3885 | } | ||
3886 | catThrown.PushVal(this, tryStmt); // throw new ScriptRestoreCatchException (__catThrown); | ||
3887 | ilGen.Emit(tryStmt, OpCodes.Newobj, scriptRestoreCatchExceptionConstructorInfo); | ||
3888 | ilGen.Emit(tryStmt, OpCodes.Throw); | ||
3889 | openCallLabel = null; | ||
3890 | |||
3891 | ilGen.MarkLabel(tryCallSw); // tryCallSw: | ||
3892 | OutputCallNoSwitchStmt(); // switch (tryCallNo) ... | ||
3893 | // } | ||
3894 | |||
3895 | ilGen.BeginCatchBlock(typeof(Exception)); // start of the catch block that can catch any exception | ||
3896 | if(DEBUG_TRYSTMT) | ||
3897 | { | ||
3898 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "enter catch*: " + tryStmt.line + " callMode="); | ||
3899 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3900 | PushXMRInst(); | ||
3901 | ilGen.Emit(tryStmt, OpCodes.Ldfld, callModeFieldInfo); | ||
3902 | ilGen.Emit(tryStmt, OpCodes.Box, typeof(int)); | ||
3903 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3904 | ilGen.Emit(tryStmt, OpCodes.Ldstr, " exc="); | ||
3905 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3906 | ilGen.Emit(tryStmt, OpCodes.Dup); | ||
3907 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3908 | ilGen.Emit(tryStmt, OpCodes.Ldstr, "\n"); | ||
3909 | ilGen.Emit(tryStmt, OpCodes.Call, consoleWriteMethodInfo); | ||
3910 | } | ||
3911 | ilGen.Emit(tryStmt, OpCodes.Call, scriptRestoreCatchExceptionUnwrap); // exc = ScriptRestoreCatchException.Unwrap (exc); | ||
3912 | PushXMRInst(); // if (callMode == CallMode_SAVE) goto catchRetro; | ||
3913 | ilGen.Emit(tryStmt, OpCodes.Ldfld, callModeFieldInfo); | ||
3914 | ilGen.Emit(tryStmt, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_SAVE); | ||
3915 | ilGen.Emit(tryStmt, OpCodes.Beq, catchRetro); | ||
3916 | |||
3917 | catThrown.Pop(this, tryStmt); // __catThrown = exc; | ||
3918 | SetCallNo(tryStmt, tryCallNo, tryThrow.index); // __tryCallNo = tryThrow:<tryCallLabel>; | ||
3919 | ilGen.Emit(tryStmt, OpCodes.Rethrow); | ||
3920 | |||
3921 | ilGen.MarkLabel(catchRetro); // catchRetro: | ||
3922 | ilGen.Emit(tryStmt, OpCodes.Pop); | ||
3923 | ilGen.Emit(tryStmt, OpCodes.Rethrow); // rethrow; | ||
3924 | |||
3925 | ilGen.EndExceptionBlock(); // } | ||
3926 | |||
3927 | ilGen.BeginFinallyBlock(); // start of the finally block | ||
3928 | |||
3929 | PushXMRInst(); // if (callMode == CallMode_SAVE) goto finEnd; | ||
3930 | ilGen.Emit(tryStmt, OpCodes.Ldfld, callModeFieldInfo); | ||
3931 | ilGen.Emit(tryStmt, OpCodes.Ldc_I4, XMRInstAbstract.CallMode_SAVE); | ||
3932 | ilGen.Emit(tryStmt, OpCodes.Beq, finEnd.label); | ||
3933 | |||
3934 | GetCallNo(tryStmt, finCallNo); // if (__finCallNo >= 0) goto finCallSw; | ||
3935 | ilGen.Emit(tryStmt, OpCodes.Ldc_I4_0); | ||
3936 | ilGen.Emit(tryStmt, OpCodes.Bge, finCallSw); | ||
3937 | |||
3938 | actCallNo = finCallNo.localBuilder; // set up __finCallNo for call labels | ||
3939 | actCallLabels.Clear(); | ||
3940 | mightGetHere = true; // if we can get to the 'try' assume we can get to the 'finally' | ||
3941 | GenerateStmtBlock(tryStmt.finallyStmt); // output finally clause statement subblock | ||
3942 | |||
3943 | ilGen.MarkLabel(finEnd.label); // finEnd: | ||
3944 | ilGen.Emit(tryStmt, OpCodes.Endfinally); // return out to next finally { } or catch { } or leave target | ||
3945 | |||
3946 | ilGen.MarkLabel(finCallSw); // restore mode, switch (finCallNo) ... | ||
3947 | OutputCallNoSwitchStmt(); | ||
3948 | |||
3949 | ilGen.EndExceptionBlock(); | ||
3950 | ilGen.MarkLabel(tryEnd); | ||
3951 | |||
3952 | mightGetHere |= finEnd.used; // get here if finally body falls through or has a break statement | ||
3953 | } | ||
3954 | |||
3955 | /** | ||
3956 | * @brief Generate code to initialize a variable to its default value. | ||
3957 | */ | ||
3958 | private void GenerateStmtVarIniDef(TokenStmtVarIniDef varIniDefStmt) | ||
3959 | { | ||
3960 | if(!mightGetHere) | ||
3961 | return; | ||
3962 | |||
3963 | CompValu left = GenerateFromLVal(varIniDefStmt.var); | ||
3964 | left.PopPre(this, varIniDefStmt); | ||
3965 | PushDefaultValue(left.type); | ||
3966 | left.PopPost(this, varIniDefStmt); | ||
3967 | } | ||
3968 | |||
3969 | /** | ||
3970 | * @brief generate code for a 'while' statement including the loop body. | ||
3971 | */ | ||
3972 | private void GenerateStmtWhile(TokenStmtWhile whileStmt) | ||
3973 | { | ||
3974 | if(!mightGetHere) | ||
3975 | return; | ||
3976 | |||
3977 | BreakContTarg oldBreakTarg = curBreakTarg; | ||
3978 | BreakContTarg oldContTarg = curContTarg; | ||
3979 | ScriptMyLabel loopLabel = ilGen.DefineLabel("whileloop_" + whileStmt.Unique); | ||
3980 | |||
3981 | curBreakTarg = new BreakContTarg(this, "whilebreak_" + whileStmt.Unique); | ||
3982 | curContTarg = new BreakContTarg(this, "whilecont_" + whileStmt.Unique); | ||
3983 | |||
3984 | ilGen.MarkLabel(loopLabel); // loop: | ||
3985 | CompValu testRVal = GenerateFromRVal(whileStmt.testRVal); // testRVal = while test expression | ||
3986 | if(!IsConstBoolExprTrue(testRVal)) | ||
3987 | { | ||
3988 | testRVal.PushVal(this, whileStmt.testRVal, tokenTypeBool); // if (!testRVal) | ||
3989 | ilGen.Emit(whileStmt, OpCodes.Brfalse, curBreakTarg.label); // goto break | ||
3990 | curBreakTarg.used = true; | ||
3991 | } | ||
3992 | GenerateStmt(whileStmt.bodyStmt); // while body statement | ||
3993 | if(curContTarg.used) | ||
3994 | { | ||
3995 | ilGen.MarkLabel(curContTarg.label); // cont: | ||
3996 | mightGetHere = true; | ||
3997 | } | ||
3998 | if(mightGetHere) | ||
3999 | { | ||
4000 | EmitCallCheckRun(whileStmt, false); // __sw.CheckRun() | ||
4001 | ilGen.Emit(whileStmt, OpCodes.Br, loopLabel); // goto loop | ||
4002 | } | ||
4003 | mightGetHere = curBreakTarg.used; | ||
4004 | if(mightGetHere) | ||
4005 | { | ||
4006 | ilGen.MarkLabel(curBreakTarg.label); // done: | ||
4007 | } | ||
4008 | |||
4009 | curBreakTarg = oldBreakTarg; | ||
4010 | curContTarg = oldContTarg; | ||
4011 | } | ||
4012 | |||
4013 | /** | ||
4014 | * @brief process a local variable declaration statement, possibly with initialization expression. | ||
4015 | * Note that the function header processing allocated stack space (CompValuTemp) for the | ||
4016 | * variable and now all we do is write its initialization value. | ||
4017 | */ | ||
4018 | private void GenerateDeclVar(TokenDeclVar declVar) | ||
4019 | { | ||
4020 | /* | ||
4021 | * Script gave us an initialization value, so just store init value in var like an assignment statement. | ||
4022 | * If no init given, set it to its default value. | ||
4023 | */ | ||
4024 | CompValu local = declVar.location; | ||
4025 | if(declVar.init != null) | ||
4026 | { | ||
4027 | CompValu rVal = GenerateFromRVal(declVar.init, local.GetArgTypes()); | ||
4028 | local.PopPre(this, declVar); | ||
4029 | rVal.PushVal(this, declVar.init, declVar.type); | ||
4030 | local.PopPost(this, declVar); | ||
4031 | } | ||
4032 | else | ||
4033 | { | ||
4034 | local.PopPre(this, declVar); | ||
4035 | PushDefaultValue(declVar.type); | ||
4036 | local.PopPost(this, declVar); | ||
4037 | } | ||
4038 | } | ||
4039 | |||
4040 | /** | ||
4041 | * @brief Get the type and location of an L-value (eg, variable) | ||
4042 | * @param lVal = L-value expression to evaluate | ||
4043 | * @param argsig = null: it's a field/property | ||
4044 | * else: select overload method that fits these arg types | ||
4045 | */ | ||
4046 | private CompValu GenerateFromLVal(TokenLVal lVal) | ||
4047 | { | ||
4048 | return GenerateFromLVal(lVal, null); | ||
4049 | } | ||
4050 | private CompValu GenerateFromLVal(TokenLVal lVal, TokenType[] argsig) | ||
4051 | { | ||
4052 | if(lVal is TokenLValArEle) | ||
4053 | return GenerateFromLValArEle((TokenLValArEle)lVal); | ||
4054 | if(lVal is TokenLValBaseField) | ||
4055 | return GenerateFromLValBaseField((TokenLValBaseField)lVal, argsig); | ||
4056 | if(lVal is TokenLValIField) | ||
4057 | return GenerateFromLValIField((TokenLValIField)lVal, argsig); | ||
4058 | if(lVal is TokenLValName) | ||
4059 | return GenerateFromLValName((TokenLValName)lVal, argsig); | ||
4060 | if(lVal is TokenLValSField) | ||
4061 | return GenerateFromLValSField((TokenLValSField)lVal, argsig); | ||
4062 | throw new Exception("bad lval class"); | ||
4063 | } | ||
4064 | |||
4065 | /** | ||
4066 | * @brief we have an L-value token that is an element within an array. | ||
4067 | * @returns a CompValu giving the type and location of the element of the array. | ||
4068 | */ | ||
4069 | private CompValu GenerateFromLValArEle(TokenLValArEle lVal) | ||
4070 | { | ||
4071 | CompValu subCompValu; | ||
4072 | |||
4073 | /* | ||
4074 | * Compute location of array itself. | ||
4075 | */ | ||
4076 | CompValu baseCompValu = GenerateFromRVal(lVal.baseRVal); | ||
4077 | |||
4078 | /* | ||
4079 | * Maybe it is a fixed array access. | ||
4080 | */ | ||
4081 | string basetypestring = baseCompValu.type.ToString(); | ||
4082 | if(basetypestring.EndsWith("]")) | ||
4083 | { | ||
4084 | TokenRVal subRVal = lVal.subRVal; | ||
4085 | int nSubs = 1; | ||
4086 | if(subRVal is TokenRValList) | ||
4087 | { | ||
4088 | nSubs = ((TokenRValList)subRVal).nItems; | ||
4089 | subRVal = ((TokenRValList)subRVal).rVal; | ||
4090 | } | ||
4091 | |||
4092 | int rank = basetypestring.IndexOf(']') - basetypestring.IndexOf('['); | ||
4093 | if(nSubs != rank) | ||
4094 | { | ||
4095 | ErrorMsg(lVal.baseRVal, "expect " + rank + " subscript" + ((rank == 1) ? "" : "s") + " but have " + nSubs); | ||
4096 | } | ||
4097 | CompValu[] subCompValus = new CompValu[rank]; | ||
4098 | int i; | ||
4099 | for(i = 0; (subRVal != null) && (i < rank); i++) | ||
4100 | { | ||
4101 | subCompValus[i] = GenerateFromRVal(subRVal); | ||
4102 | subRVal = (TokenRVal)subRVal.nextToken; | ||
4103 | } | ||
4104 | while(i < rank) | ||
4105 | subCompValus[i++] = new CompValuInteger(new TokenTypeInt(lVal.subRVal), 0); | ||
4106 | return new CompValuFixArEl(this, baseCompValu, subCompValus); | ||
4107 | } | ||
4108 | |||
4109 | /* | ||
4110 | * Maybe it is accessing the $idxprop property of a script-defined class. | ||
4111 | */ | ||
4112 | if(baseCompValu.type is TokenTypeSDTypeClass) | ||
4113 | { | ||
4114 | TokenName name = new TokenName(lVal, "$idxprop"); | ||
4115 | TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseCompValu.type; | ||
4116 | TokenDeclSDTypeClass sdtDecl = sdtType.decl; | ||
4117 | TokenDeclVar idxProp = FindThisMember(sdtDecl, name, null); | ||
4118 | if(idxProp == null) | ||
4119 | { | ||
4120 | ErrorMsg(lVal, "no index property in class " + sdtDecl.longName.val); | ||
4121 | return new CompValuVoid(lVal); | ||
4122 | } | ||
4123 | if((idxProp.sdtFlags & ScriptReduce.SDT_STATIC) != 0) | ||
4124 | { | ||
4125 | ErrorMsg(lVal, "non-static reference to static member " + idxProp.name.val); | ||
4126 | return new CompValuVoid(idxProp); | ||
4127 | } | ||
4128 | CheckAccess(idxProp, name); | ||
4129 | |||
4130 | TokenType[] argTypes = IdxPropArgTypes(idxProp); | ||
4131 | CompValu[] compValus = IdxPropCompValus(lVal, argTypes.Length); | ||
4132 | return new CompValuIdxProp(idxProp, baseCompValu, argTypes, compValus); | ||
4133 | |||
4134 | } | ||
4135 | |||
4136 | /* | ||
4137 | * Maybe they are accessing $idxprop property of a script-defined interface. | ||
4138 | */ | ||
4139 | if(baseCompValu.type is TokenTypeSDTypeInterface) | ||
4140 | { | ||
4141 | TokenName name = new TokenName(lVal, "$idxprop"); | ||
4142 | TokenTypeSDTypeInterface sdtType = (TokenTypeSDTypeInterface)baseCompValu.type; | ||
4143 | TokenDeclVar idxProp = FindInterfaceMember(sdtType, name, null, ref baseCompValu); | ||
4144 | if(idxProp == null) | ||
4145 | { | ||
4146 | ErrorMsg(lVal, "no index property defined for interface " + sdtType.decl.longName.val); | ||
4147 | return baseCompValu; | ||
4148 | } | ||
4149 | |||
4150 | TokenType[] argTypes = IdxPropArgTypes(idxProp); | ||
4151 | CompValu[] compValus = IdxPropCompValus(lVal, argTypes.Length); | ||
4152 | return new CompValuIdxProp(idxProp, baseCompValu, argTypes, compValus); | ||
4153 | } | ||
4154 | |||
4155 | /* | ||
4156 | * Maybe it is extracting a character from a string. | ||
4157 | */ | ||
4158 | if((baseCompValu.type is TokenTypeKey) || (baseCompValu.type is TokenTypeStr)) | ||
4159 | { | ||
4160 | subCompValu = GenerateFromRVal(lVal.subRVal); | ||
4161 | return new CompValuStrChr(new TokenTypeChar(lVal), baseCompValu, subCompValu); | ||
4162 | } | ||
4163 | |||
4164 | /* | ||
4165 | * Maybe it is extracting an element from a list. | ||
4166 | */ | ||
4167 | if(baseCompValu.type is TokenTypeList) | ||
4168 | { | ||
4169 | subCompValu = GenerateFromRVal(lVal.subRVal); | ||
4170 | return new CompValuListEl(new TokenTypeObject(lVal), baseCompValu, subCompValu); | ||
4171 | } | ||
4172 | |||
4173 | /* | ||
4174 | * Access should be to XMR_Array otherwise. | ||
4175 | */ | ||
4176 | if(!(baseCompValu.type is TokenTypeArray)) | ||
4177 | { | ||
4178 | ErrorMsg(lVal, "taking subscript of non-array"); | ||
4179 | return baseCompValu; | ||
4180 | } | ||
4181 | subCompValu = GenerateFromRVal(lVal.subRVal); | ||
4182 | return new CompValuArEle(new TokenTypeObject(lVal), baseCompValu, subCompValu); | ||
4183 | } | ||
4184 | |||
4185 | /** | ||
4186 | * @brief Get number and type of arguments required by an index property. | ||
4187 | */ | ||
4188 | private static TokenType[] IdxPropArgTypes(TokenDeclVar idxProp) | ||
4189 | { | ||
4190 | TokenType[] argTypes; | ||
4191 | if(idxProp.getProp != null) | ||
4192 | { | ||
4193 | int nArgs = idxProp.getProp.argDecl.varDict.Count; | ||
4194 | argTypes = new TokenType[nArgs]; | ||
4195 | foreach(TokenDeclVar var in idxProp.getProp.argDecl.varDict) | ||
4196 | { | ||
4197 | argTypes[var.vTableIndex] = var.type; | ||
4198 | } | ||
4199 | } | ||
4200 | else | ||
4201 | { | ||
4202 | int nArgs = idxProp.setProp.argDecl.varDict.Count - 1; | ||
4203 | argTypes = new TokenType[nArgs]; | ||
4204 | foreach(TokenDeclVar var in idxProp.setProp.argDecl.varDict) | ||
4205 | { | ||
4206 | if(var.vTableIndex < nArgs) | ||
4207 | { | ||
4208 | argTypes[var.vTableIndex] = var.type; | ||
4209 | } | ||
4210 | } | ||
4211 | } | ||
4212 | return argTypes; | ||
4213 | } | ||
4214 | |||
4215 | /** | ||
4216 | * @brief Get number and computed value of index property arguments. | ||
4217 | * @param lVal = list of arguments | ||
4218 | * @param nArgs = number of arguments required | ||
4219 | * @returns null: argument count mismatch | ||
4220 | * else: array of index property argument values | ||
4221 | */ | ||
4222 | private CompValu[] IdxPropCompValus(TokenLValArEle lVal, int nArgs) | ||
4223 | { | ||
4224 | TokenRVal subRVal = lVal.subRVal; | ||
4225 | int nSubs = 1; | ||
4226 | if(subRVal is TokenRValList) | ||
4227 | { | ||
4228 | nSubs = ((TokenRValList)subRVal).nItems; | ||
4229 | subRVal = ((TokenRValList)subRVal).rVal; | ||
4230 | } | ||
4231 | |||
4232 | if(nSubs != nArgs) | ||
4233 | { | ||
4234 | ErrorMsg(lVal, "index property requires " + nArgs + " subscript(s)"); | ||
4235 | return null; | ||
4236 | } | ||
4237 | |||
4238 | CompValu[] subCompValus = new CompValu[nArgs]; | ||
4239 | for(int i = 0; i < nArgs; i++) | ||
4240 | { | ||
4241 | subCompValus[i] = GenerateFromRVal(subRVal); | ||
4242 | subRVal = (TokenRVal)subRVal.nextToken; | ||
4243 | } | ||
4244 | return subCompValus; | ||
4245 | } | ||
4246 | |||
4247 | /** | ||
4248 | * @brief using 'base' within a script-defined instance method to refer to an instance field/method | ||
4249 | * of the class being extended. | ||
4250 | */ | ||
4251 | private CompValu GenerateFromLValBaseField(TokenLValBaseField baseField, TokenType[] argsig) | ||
4252 | { | ||
4253 | string fieldName = baseField.fieldName.val; | ||
4254 | |||
4255 | TokenDeclSDType sdtDecl = curDeclFunc.sdtClass; | ||
4256 | if((sdtDecl == null) || ((curDeclFunc.sdtFlags & ScriptReduce.SDT_STATIC) != 0)) | ||
4257 | { | ||
4258 | ErrorMsg(baseField, "cannot use 'base' outside instance method body"); | ||
4259 | return new CompValuVoid(baseField); | ||
4260 | } | ||
4261 | if(!IsSDTInstMethod()) | ||
4262 | { | ||
4263 | ErrorMsg(baseField, "cannot access instance member of base class from static method"); | ||
4264 | return new CompValuVoid(baseField); | ||
4265 | } | ||
4266 | |||
4267 | TokenDeclVar declVar = FindThisMember(sdtDecl.extends, baseField.fieldName, argsig); | ||
4268 | if(declVar != null) | ||
4269 | { | ||
4270 | CheckAccess(declVar, baseField.fieldName); | ||
4271 | TokenType baseType = declVar.sdtClass.MakeRefToken(baseField); | ||
4272 | CompValu basePtr = new CompValuArg(baseType, 0); | ||
4273 | return AccessInstanceMember(declVar, basePtr, baseField, true); | ||
4274 | } | ||
4275 | |||
4276 | ErrorMsg(baseField, "no member " + fieldName + ArgSigString(argsig) + " rootward of " + sdtDecl.longName.val); | ||
4277 | return new CompValuVoid(baseField); | ||
4278 | } | ||
4279 | |||
4280 | /** | ||
4281 | * @brief We have an L-value token that is an instance field/method within a struct. | ||
4282 | * @returns a CompValu giving the type and location of the field/method in the struct. | ||
4283 | */ | ||
4284 | private CompValu GenerateFromLValIField(TokenLValIField lVal, TokenType[] argsig) | ||
4285 | { | ||
4286 | CompValu baseRVal = GenerateFromRVal(lVal.baseRVal); | ||
4287 | string fieldName = lVal.fieldName.val + ArgSigString(argsig); | ||
4288 | |||
4289 | /* | ||
4290 | * Maybe they are accessing an instance field, method or property of a script-defined class. | ||
4291 | */ | ||
4292 | if(baseRVal.type is TokenTypeSDTypeClass) | ||
4293 | { | ||
4294 | TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)baseRVal.type; | ||
4295 | TokenDeclSDTypeClass sdtDecl = sdtType.decl; | ||
4296 | TokenDeclVar declVar = FindThisMember(sdtDecl, lVal.fieldName, argsig); | ||
4297 | if(declVar != null) | ||
4298 | { | ||
4299 | CheckAccess(declVar, lVal.fieldName); | ||
4300 | return AccessInstanceMember(declVar, baseRVal, lVal, false); | ||
4301 | } | ||
4302 | ErrorMsg(lVal.fieldName, "no member " + fieldName + " in class " + sdtDecl.longName.val); | ||
4303 | return new CompValuVoid(lVal.fieldName); | ||
4304 | } | ||
4305 | |||
4306 | /* | ||
4307 | * Maybe they are accessing a method or property of a script-defined interface. | ||
4308 | */ | ||
4309 | if(baseRVal.type is TokenTypeSDTypeInterface) | ||
4310 | { | ||
4311 | TokenTypeSDTypeInterface sdtType = (TokenTypeSDTypeInterface)baseRVal.type; | ||
4312 | TokenDeclVar declVar = FindInterfaceMember(sdtType, lVal.fieldName, argsig, ref baseRVal); | ||
4313 | if(declVar != null) | ||
4314 | { | ||
4315 | return new CompValuIntfMember(declVar, baseRVal); | ||
4316 | } | ||
4317 | ErrorMsg(lVal.fieldName, "no member " + fieldName + " in interface " + sdtType.decl.longName.val); | ||
4318 | return new CompValuVoid(lVal.fieldName); | ||
4319 | } | ||
4320 | |||
4321 | /* | ||
4322 | * Since we only have a few built-in types with fields, just pound them out. | ||
4323 | */ | ||
4324 | if(baseRVal.type is TokenTypeArray) | ||
4325 | { | ||
4326 | |||
4327 | // no arguments, no parentheses, just the field name, returning integer | ||
4328 | // but internally, it is a call to a method() | ||
4329 | if(fieldName == "count") | ||
4330 | { | ||
4331 | return new CompValuIntInstROProp(tokenTypeInt, baseRVal, arrayCountMethodInfo); | ||
4332 | } | ||
4333 | |||
4334 | // no arguments but with the parentheses, returning void | ||
4335 | if(fieldName == "clear()") | ||
4336 | { | ||
4337 | return new CompValuIntInstMeth(XMR_Array.clearDelegate, baseRVal, arrayClearMethodInfo); | ||
4338 | } | ||
4339 | |||
4340 | // single integer argument, returning an object | ||
4341 | if(fieldName == "index(integer)") | ||
4342 | { | ||
4343 | return new CompValuIntInstMeth(XMR_Array.indexDelegate, baseRVal, arrayIndexMethodInfo); | ||
4344 | } | ||
4345 | if(fieldName == "value(integer)") | ||
4346 | { | ||
4347 | return new CompValuIntInstMeth(XMR_Array.valueDelegate, baseRVal, arrayValueMethodInfo); | ||
4348 | } | ||
4349 | } | ||
4350 | if(baseRVal.type is TokenTypeRot) | ||
4351 | { | ||
4352 | FieldInfo fi = null; | ||
4353 | if(fieldName == "x") | ||
4354 | fi = rotationXFieldInfo; | ||
4355 | if(fieldName == "y") | ||
4356 | fi = rotationYFieldInfo; | ||
4357 | if(fieldName == "z") | ||
4358 | fi = rotationZFieldInfo; | ||
4359 | if(fieldName == "s") | ||
4360 | fi = rotationSFieldInfo; | ||
4361 | if(fi != null) | ||
4362 | { | ||
4363 | return new CompValuField(new TokenTypeFloat(lVal), baseRVal, fi); | ||
4364 | } | ||
4365 | } | ||
4366 | if(baseRVal.type is TokenTypeVec) | ||
4367 | { | ||
4368 | FieldInfo fi = null; | ||
4369 | if(fieldName == "x") | ||
4370 | fi = vectorXFieldInfo; | ||
4371 | if(fieldName == "y") | ||
4372 | fi = vectorYFieldInfo; | ||
4373 | if(fieldName == "z") | ||
4374 | fi = vectorZFieldInfo; | ||
4375 | if(fi != null) | ||
4376 | { | ||
4377 | return new CompValuField(new TokenTypeFloat(lVal), baseRVal, fi); | ||
4378 | } | ||
4379 | } | ||
4380 | |||
4381 | ErrorMsg(lVal, "type " + baseRVal.type.ToString() + " does not define member " + fieldName); | ||
4382 | return baseRVal; | ||
4383 | } | ||
4384 | |||
4385 | /** | ||
4386 | * @brief We have an L-value token that is a function, method or variable name. | ||
4387 | * @param lVal = name we are looking for | ||
4388 | * @param argsig = null: just look for name as a variable | ||
4389 | * else: look for name as a function/method being called with the given argument types | ||
4390 | * eg, "(string,integer,list)" | ||
4391 | * @returns a CompValu giving the type and location of the function, method or variable. | ||
4392 | */ | ||
4393 | private CompValu GenerateFromLValName(TokenLValName lVal, TokenType[] argsig) | ||
4394 | { | ||
4395 | /* | ||
4396 | * Look in variable stack then look for built-in constants and functions. | ||
4397 | */ | ||
4398 | TokenDeclVar var = FindNamedVar(lVal, argsig); | ||
4399 | if(var == null) | ||
4400 | { | ||
4401 | ErrorMsg(lVal, "undefined constant/function/variable " + lVal.name.val + ArgSigString(argsig)); | ||
4402 | return new CompValuVoid(lVal); | ||
4403 | } | ||
4404 | |||
4405 | /* | ||
4406 | * Maybe it has an implied 'this.' on the front. | ||
4407 | */ | ||
4408 | if((var.sdtClass != null) && ((var.sdtFlags & ScriptReduce.SDT_STATIC) == 0)) | ||
4409 | { | ||
4410 | |||
4411 | if(!IsSDTInstMethod()) | ||
4412 | { | ||
4413 | ErrorMsg(lVal, "cannot access instance member of class from static method"); | ||
4414 | return new CompValuVoid(lVal); | ||
4415 | } | ||
4416 | |||
4417 | /* | ||
4418 | * Don't allow something such as: | ||
4419 | * | ||
4420 | * class A { | ||
4421 | * integer I; | ||
4422 | * class B { | ||
4423 | * Print () | ||
4424 | * { | ||
4425 | * llOwnerSay ("I=" + (string)I); <- access to I not allowed inside class B. | ||
4426 | * explicit reference required as we don't | ||
4427 | * have a valid reference to class A. | ||
4428 | * } | ||
4429 | * } | ||
4430 | * } | ||
4431 | * | ||
4432 | * But do allow something such as: | ||
4433 | * | ||
4434 | * class A { | ||
4435 | * integer I; | ||
4436 | * } | ||
4437 | * class B : A { | ||
4438 | * Print () | ||
4439 | * { | ||
4440 | * llOwnerSay ("I=" + (string)I); | ||
4441 | * } | ||
4442 | * } | ||
4443 | */ | ||
4444 | for(TokenDeclSDType c = curDeclFunc.sdtClass; c != var.sdtClass; c = c.extends) | ||
4445 | { | ||
4446 | if(c == null) | ||
4447 | { | ||
4448 | // our arg0 points to an instance of curDeclFunc.sdtClass, not var.sdtClass | ||
4449 | ErrorMsg(lVal, "cannot access instance member of outer class with implied 'this'"); | ||
4450 | break; | ||
4451 | } | ||
4452 | } | ||
4453 | |||
4454 | CompValu thisCompValu = new CompValuArg(var.sdtClass.MakeRefToken(lVal), 0); | ||
4455 | return AccessInstanceMember(var, thisCompValu, lVal, false); | ||
4456 | } | ||
4457 | |||
4458 | /* | ||
4459 | * It's a local variable, static field, global, constant, etc. | ||
4460 | */ | ||
4461 | return var.location; | ||
4462 | } | ||
4463 | |||
4464 | /** | ||
4465 | * @brief Access a script-defined type's instance member | ||
4466 | * @param declVar = which member (field,method,property) to access | ||
4467 | * @param basePtr = points to particular object instance | ||
4468 | * @param ignoreVirt = true: access declVar's method directly; else: maybe use vTable | ||
4469 | * @returns where the field/method/property is located | ||
4470 | */ | ||
4471 | private CompValu AccessInstanceMember(TokenDeclVar declVar, CompValu basePtr, Token errorAt, bool ignoreVirt) | ||
4472 | { | ||
4473 | if((declVar.sdtFlags & ScriptReduce.SDT_STATIC) != 0) | ||
4474 | { | ||
4475 | ErrorMsg(errorAt, "non-static reference to static member " + declVar.name.val); | ||
4476 | return new CompValuVoid(declVar); | ||
4477 | } | ||
4478 | return new CompValuInstMember(declVar, basePtr, ignoreVirt); | ||
4479 | } | ||
4480 | |||
4481 | /** | ||
4482 | * @brief we have an L-value token that is a static member within a struct. | ||
4483 | * @returns a CompValu giving the type and location of the member in the struct. | ||
4484 | */ | ||
4485 | private CompValu GenerateFromLValSField(TokenLValSField lVal, TokenType[] argsig) | ||
4486 | { | ||
4487 | TokenType stType = lVal.baseType; | ||
4488 | string fieldName = lVal.fieldName.val + ArgSigString(argsig); | ||
4489 | |||
4490 | /* | ||
4491 | * Maybe they are accessing a static member of a script-defined class. | ||
4492 | */ | ||
4493 | if(stType is TokenTypeSDTypeClass) | ||
4494 | { | ||
4495 | TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)stType; | ||
4496 | TokenDeclVar declVar = FindThisMember(sdtType.decl, lVal.fieldName, argsig); | ||
4497 | if(declVar != null) | ||
4498 | { | ||
4499 | CheckAccess(declVar, lVal.fieldName); | ||
4500 | if((declVar.sdtFlags & ScriptReduce.SDT_STATIC) == 0) | ||
4501 | { | ||
4502 | ErrorMsg(lVal.fieldName, "static reference to non-static member " + fieldName); | ||
4503 | return new CompValuVoid(lVal.fieldName); | ||
4504 | } | ||
4505 | return declVar.location; | ||
4506 | } | ||
4507 | } | ||
4508 | |||
4509 | ErrorMsg(lVal.fieldName, "no member " + fieldName + " in " + stType.ToString()); | ||
4510 | return new CompValuVoid(lVal.fieldName); | ||
4511 | } | ||
4512 | |||
4513 | /** | ||
4514 | * @brief generate code from an RVal expression and return its type and where the result is stored. | ||
4515 | * For anything that has side-effects, statements are generated that perform the computation then | ||
4516 | * the result it put in a temp var and the temp var name is returned. | ||
4517 | * For anything without side-effects, they are returned as an equivalent sequence of Emits. | ||
4518 | * @param rVal = rVal token to be evaluated | ||
4519 | * @param argsig = null: not being used in an function/method context | ||
4520 | * else: string giving argument types, eg, "(string,integer,list,vector)" | ||
4521 | * that can be used to select among overloaded methods | ||
4522 | * @returns resultant type and location | ||
4523 | */ | ||
4524 | private CompValu GenerateFromRVal(TokenRVal rVal) | ||
4525 | { | ||
4526 | return GenerateFromRVal(rVal, null); | ||
4527 | } | ||
4528 | private CompValu GenerateFromRVal(TokenRVal rVal, TokenType[] argsig) | ||
4529 | { | ||
4530 | errorMessageToken = rVal; | ||
4531 | |||
4532 | /* | ||
4533 | * Maybe the expression can be converted to a constant. | ||
4534 | */ | ||
4535 | bool didOne; | ||
4536 | do | ||
4537 | { | ||
4538 | didOne = false; | ||
4539 | rVal = rVal.TryComputeConstant(LookupBodyConstants, ref didOne); | ||
4540 | } while(didOne); | ||
4541 | |||
4542 | /* | ||
4543 | * Generate code for the computation and return resulting type and location. | ||
4544 | */ | ||
4545 | CompValu cVal = null; | ||
4546 | if(rVal is TokenRValAsnPost) | ||
4547 | cVal = GenerateFromRValAsnPost((TokenRValAsnPost)rVal); | ||
4548 | if(rVal is TokenRValAsnPre) | ||
4549 | cVal = GenerateFromRValAsnPre((TokenRValAsnPre)rVal); | ||
4550 | if(rVal is TokenRValCall) | ||
4551 | cVal = GenerateFromRValCall((TokenRValCall)rVal); | ||
4552 | if(rVal is TokenRValCast) | ||
4553 | cVal = GenerateFromRValCast((TokenRValCast)rVal); | ||
4554 | if(rVal is TokenRValCondExpr) | ||
4555 | cVal = GenerateFromRValCondExpr((TokenRValCondExpr)rVal); | ||
4556 | if(rVal is TokenRValConst) | ||
4557 | cVal = GenerateFromRValConst((TokenRValConst)rVal); | ||
4558 | if(rVal is TokenRValInitDef) | ||
4559 | cVal = GenerateFromRValInitDef((TokenRValInitDef)rVal); | ||
4560 | if(rVal is TokenRValIsType) | ||
4561 | cVal = GenerateFromRValIsType((TokenRValIsType)rVal); | ||
4562 | if(rVal is TokenRValList) | ||
4563 | cVal = GenerateFromRValList((TokenRValList)rVal); | ||
4564 | if(rVal is TokenRValNewArIni) | ||
4565 | cVal = GenerateFromRValNewArIni((TokenRValNewArIni)rVal); | ||
4566 | if(rVal is TokenRValOpBin) | ||
4567 | cVal = GenerateFromRValOpBin((TokenRValOpBin)rVal); | ||
4568 | if(rVal is TokenRValOpUn) | ||
4569 | cVal = GenerateFromRValOpUn((TokenRValOpUn)rVal); | ||
4570 | if(rVal is TokenRValParen) | ||
4571 | cVal = GenerateFromRValParen((TokenRValParen)rVal); | ||
4572 | if(rVal is TokenRValRot) | ||
4573 | cVal = GenerateFromRValRot((TokenRValRot)rVal); | ||
4574 | if(rVal is TokenRValThis) | ||
4575 | cVal = GenerateFromRValThis((TokenRValThis)rVal); | ||
4576 | if(rVal is TokenRValUndef) | ||
4577 | cVal = GenerateFromRValUndef((TokenRValUndef)rVal); | ||
4578 | if(rVal is TokenRValVec) | ||
4579 | cVal = GenerateFromRValVec((TokenRValVec)rVal); | ||
4580 | if(rVal is TokenLVal) | ||
4581 | cVal = GenerateFromLVal((TokenLVal)rVal, argsig); | ||
4582 | |||
4583 | if(cVal == null) | ||
4584 | throw new Exception("bad rval class " + rVal.GetType().ToString()); | ||
4585 | |||
4586 | /* | ||
4587 | * Sanity check. | ||
4588 | */ | ||
4589 | if(!youveAnError) | ||
4590 | { | ||
4591 | if(cVal.type == null) | ||
4592 | throw new Exception("cVal has no type " + cVal.GetType()); | ||
4593 | string cValType = cVal.type.ToString(); | ||
4594 | string rValType = rVal.GetRValType(this, argsig).ToString(); | ||
4595 | if(cValType == "bool") | ||
4596 | cValType = "integer"; | ||
4597 | if(rValType == "bool") | ||
4598 | rValType = "integer"; | ||
4599 | if(cValType != rValType) | ||
4600 | { | ||
4601 | throw new Exception("cVal.type " + cValType + " != rVal.type " + rValType + | ||
4602 | " (" + rVal.GetType().Name + " " + rVal.SrcLoc + ")"); | ||
4603 | } | ||
4604 | } | ||
4605 | |||
4606 | return cVal; | ||
4607 | } | ||
4608 | |||
4609 | /** | ||
4610 | * @brief compute the result of a binary operator (eg, add, subtract, multiply, lessthan) | ||
4611 | * @param token = binary operator token, includes the left and right operands | ||
4612 | * @returns where the resultant R-value is as something that doesn't have side effects | ||
4613 | */ | ||
4614 | private CompValu GenerateFromRValOpBin(TokenRValOpBin token) | ||
4615 | { | ||
4616 | CompValu left, right; | ||
4617 | string opcodeIndex = token.opcode.ToString(); | ||
4618 | |||
4619 | /* | ||
4620 | * Comma operators are special, as they say to compute the left-hand value and | ||
4621 | * discard it, then compute the right-hand argument and that is the result. | ||
4622 | */ | ||
4623 | if(opcodeIndex == ",") | ||
4624 | { | ||
4625 | |||
4626 | /* | ||
4627 | * Compute left-hand operand but throw away result. | ||
4628 | */ | ||
4629 | GenerateFromRVal(token.rValLeft); | ||
4630 | |||
4631 | /* | ||
4632 | * Compute right-hand operand and that is the value of the expression. | ||
4633 | */ | ||
4634 | return GenerateFromRVal(token.rValRight); | ||
4635 | } | ||
4636 | |||
4637 | /* | ||
4638 | * Simple overwriting assignments are their own special case, | ||
4639 | * as we want to cast the R-value to the type of the L-value. | ||
4640 | * And in the case of delegates, we want to use the arg signature | ||
4641 | * of the delegate to select which overloaded method to use. | ||
4642 | */ | ||
4643 | if(opcodeIndex == "=") | ||
4644 | { | ||
4645 | if(!(token.rValLeft is TokenLVal)) | ||
4646 | { | ||
4647 | ErrorMsg(token, "invalid L-value for ="); | ||
4648 | return GenerateFromRVal(token.rValLeft); | ||
4649 | } | ||
4650 | left = GenerateFromLVal((TokenLVal)token.rValLeft); | ||
4651 | right = Trivialize(GenerateFromRVal(token.rValRight, left.GetArgTypes()), token.rValRight); | ||
4652 | left.PopPre(this, token.rValLeft); | ||
4653 | right.PushVal(this, token.rValRight, left.type); // push (left.type)right | ||
4654 | left.PopPost(this, token.rValLeft); // pop to left | ||
4655 | return left; | ||
4656 | } | ||
4657 | |||
4658 | /* | ||
4659 | * There are String.Concat() methods available for 2, 3 and 4 operands. | ||
4660 | * So see if we have a string concat op and optimize if so. | ||
4661 | */ | ||
4662 | if((opcodeIndex == "+") || | ||
4663 | ((opcodeIndex == "+=") && | ||
4664 | (token.rValLeft is TokenLVal) && | ||
4665 | (token.rValLeft.GetRValType(this, null) is TokenTypeStr))) | ||
4666 | { | ||
4667 | |||
4668 | /* | ||
4669 | * We are adding something. Maybe it's a bunch of strings together. | ||
4670 | */ | ||
4671 | List<TokenRVal> scorvs = new List<TokenRVal>(); | ||
4672 | if(StringConcatOperands(token.rValLeft, token.rValRight, scorvs, token.opcode)) | ||
4673 | { | ||
4674 | |||
4675 | /* | ||
4676 | * Evaluate all the operands, right-to-left on purpose per LSL scripting. | ||
4677 | */ | ||
4678 | int i; | ||
4679 | int n = scorvs.Count; | ||
4680 | CompValu[] scocvs = new CompValu[n]; | ||
4681 | for(i = n; --i >= 0;) | ||
4682 | { | ||
4683 | scocvs[i] = GenerateFromRVal(scorvs[i]); | ||
4684 | if(i > 0) | ||
4685 | scocvs[i] = Trivialize(scocvs[i], scorvs[i]); | ||
4686 | } | ||
4687 | |||
4688 | /* | ||
4689 | * Figure out where to put the result. | ||
4690 | * A temp if '+', or back in original L-value if '+='. | ||
4691 | */ | ||
4692 | CompValu retcv; | ||
4693 | if(opcodeIndex == "+") | ||
4694 | { | ||
4695 | retcv = new CompValuTemp(new TokenTypeStr(token.opcode), this); | ||
4696 | } | ||
4697 | else | ||
4698 | { | ||
4699 | retcv = GenerateFromLVal((TokenLVal)token.rValLeft); | ||
4700 | } | ||
4701 | retcv.PopPre(this, token); | ||
4702 | |||
4703 | /* | ||
4704 | * Call the String.Concat() methods, passing operands in left-to-right order. | ||
4705 | * Force a cast to string (retcv.type) for each operand. | ||
4706 | */ | ||
4707 | ++i; | ||
4708 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4709 | while(i + 3 < n) | ||
4710 | { | ||
4711 | ++i; | ||
4712 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4713 | ++i; | ||
4714 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4715 | ++i; | ||
4716 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4717 | ilGen.Emit(scorvs[i], OpCodes.Call, stringConcat4MethodInfo); | ||
4718 | } | ||
4719 | if(i + 2 < n) | ||
4720 | { | ||
4721 | ++i; | ||
4722 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4723 | ++i; | ||
4724 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4725 | ilGen.Emit(scorvs[i], OpCodes.Call, stringConcat3MethodInfo); | ||
4726 | } | ||
4727 | if(i + 1 < n) | ||
4728 | { | ||
4729 | ++i; | ||
4730 | scocvs[i].PushVal(this, scorvs[i], retcv.type); | ||
4731 | ilGen.Emit(scorvs[i], OpCodes.Call, stringConcat2MethodInfo); | ||
4732 | } | ||
4733 | |||
4734 | /* | ||
4735 | * Put the result where we want it and return where we put it. | ||
4736 | */ | ||
4737 | retcv.PopPost(this, token); | ||
4738 | return retcv; | ||
4739 | } | ||
4740 | } | ||
4741 | |||
4742 | /* | ||
4743 | * If "&&&", it is a short-circuiting AND. | ||
4744 | * Compute left-hand operand and if true, compute right-hand operand. | ||
4745 | */ | ||
4746 | if(opcodeIndex == "&&&") | ||
4747 | { | ||
4748 | bool leftVal, rightVal; | ||
4749 | left = GenerateFromRVal(token.rValLeft); | ||
4750 | if(!IsConstBoolExpr(left, out leftVal)) | ||
4751 | { | ||
4752 | ScriptMyLabel falseLabel = ilGen.DefineLabel("ssandfalse"); | ||
4753 | left.PushVal(this, tokenTypeBool); | ||
4754 | ilGen.Emit(token, OpCodes.Brfalse, falseLabel); | ||
4755 | right = GenerateFromRVal(token.rValRight); | ||
4756 | if(!IsConstBoolExpr(right, out rightVal)) | ||
4757 | { | ||
4758 | right.PushVal(this, tokenTypeBool); | ||
4759 | goto donessand; | ||
4760 | } | ||
4761 | if(!rightVal) | ||
4762 | { | ||
4763 | ilGen.MarkLabel(falseLabel); | ||
4764 | return new CompValuInteger(new TokenTypeInt(token.rValLeft), 0); | ||
4765 | } | ||
4766 | ilGen.Emit(token, OpCodes.Ldc_I4_1); | ||
4767 | donessand: | ||
4768 | ScriptMyLabel doneLabel = ilGen.DefineLabel("ssanddone"); | ||
4769 | ilGen.Emit(token, OpCodes.Br, doneLabel); | ||
4770 | ilGen.MarkLabel(falseLabel); | ||
4771 | ilGen.Emit(token, OpCodes.Ldc_I4_0); | ||
4772 | ilGen.MarkLabel(doneLabel); | ||
4773 | CompValuTemp retRVal = new CompValuTemp(new TokenTypeInt(token), this); | ||
4774 | retRVal.Pop(this, token); | ||
4775 | return retRVal; | ||
4776 | } | ||
4777 | |||
4778 | if(!leftVal) | ||
4779 | { | ||
4780 | return new CompValuInteger(new TokenTypeInt(token.rValLeft), 0); | ||
4781 | } | ||
4782 | |||
4783 | right = GenerateFromRVal(token.rValRight); | ||
4784 | if(!IsConstBoolExpr(right, out rightVal)) | ||
4785 | { | ||
4786 | right.PushVal(this, tokenTypeBool); | ||
4787 | CompValuTemp retRVal = new CompValuTemp(new TokenTypeInt(token), this); | ||
4788 | retRVal.Pop(this, token); | ||
4789 | return retRVal; | ||
4790 | } | ||
4791 | return new CompValuInteger(new TokenTypeInt(token), rightVal ? 1 : 0); | ||
4792 | } | ||
4793 | |||
4794 | /* | ||
4795 | * If "|||", it is a short-circuiting OR. | ||
4796 | * Compute left-hand operand and if false, compute right-hand operand. | ||
4797 | */ | ||
4798 | if(opcodeIndex == "|||") | ||
4799 | { | ||
4800 | bool leftVal, rightVal; | ||
4801 | left = GenerateFromRVal(token.rValLeft); | ||
4802 | if(!IsConstBoolExpr(left, out leftVal)) | ||
4803 | { | ||
4804 | ScriptMyLabel trueLabel = ilGen.DefineLabel("ssortrue"); | ||
4805 | left.PushVal(this, tokenTypeBool); | ||
4806 | ilGen.Emit(token, OpCodes.Brtrue, trueLabel); | ||
4807 | right = GenerateFromRVal(token.rValRight); | ||
4808 | if(!IsConstBoolExpr(right, out rightVal)) | ||
4809 | { | ||
4810 | right.PushVal(this, tokenTypeBool); | ||
4811 | goto donessor; | ||
4812 | } | ||
4813 | if(rightVal) | ||
4814 | { | ||
4815 | ilGen.MarkLabel(trueLabel); | ||
4816 | return new CompValuInteger(new TokenTypeInt(token.rValLeft), 1); | ||
4817 | } | ||
4818 | ilGen.Emit(token, OpCodes.Ldc_I4_0); | ||
4819 | donessor: | ||
4820 | ScriptMyLabel doneLabel = ilGen.DefineLabel("ssanddone"); | ||
4821 | ilGen.Emit(token, OpCodes.Br, doneLabel); | ||
4822 | ilGen.MarkLabel(trueLabel); | ||
4823 | ilGen.Emit(token, OpCodes.Ldc_I4_1); | ||
4824 | ilGen.MarkLabel(doneLabel); | ||
4825 | CompValuTemp retRVal = new CompValuTemp(new TokenTypeInt(token), this); | ||
4826 | retRVal.Pop(this, token); | ||
4827 | return retRVal; | ||
4828 | } | ||
4829 | |||
4830 | if(leftVal) | ||
4831 | { | ||
4832 | return new CompValuInteger(new TokenTypeInt(token.rValLeft), 1); | ||
4833 | } | ||
4834 | |||
4835 | right = GenerateFromRVal(token.rValRight); | ||
4836 | if(!IsConstBoolExpr(right, out rightVal)) | ||
4837 | { | ||
4838 | right.PushVal(this, tokenTypeBool); | ||
4839 | CompValuTemp retRVal = new CompValuTemp(new TokenTypeInt(token), this); | ||
4840 | retRVal.Pop(this, token); | ||
4841 | return retRVal; | ||
4842 | } | ||
4843 | return new CompValuInteger(new TokenTypeInt(token), rightVal ? 1 : 0); | ||
4844 | } | ||
4845 | |||
4846 | /* | ||
4847 | * Computation of some sort, compute right-hand operand value then left-hand value | ||
4848 | * because LSL is supposed to be right-to-left evaluation. | ||
4849 | */ | ||
4850 | right = Trivialize(GenerateFromRVal(token.rValRight), token.rValRight); | ||
4851 | |||
4852 | /* | ||
4853 | * If left is a script-defined class and there is a method with the operator's name, | ||
4854 | * convert this to a call to that method with the right value as its single parameter. | ||
4855 | * Except don't if the right value is 'undef' so they can always compare to undef. | ||
4856 | */ | ||
4857 | TokenType leftType = token.rValLeft.GetRValType(this, null); | ||
4858 | if((leftType is TokenTypeSDTypeClass) && !(right.type is TokenTypeUndef)) | ||
4859 | { | ||
4860 | TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)leftType; | ||
4861 | TokenDeclSDTypeClass sdtDecl = sdtType.decl; | ||
4862 | TokenType[] argsig = new TokenType[] { right.type }; | ||
4863 | TokenName funcName = new TokenName(token.opcode, "$op" + opcodeIndex); | ||
4864 | TokenDeclVar declFunc = FindThisMember(sdtDecl, funcName, argsig); | ||
4865 | if(declFunc != null) | ||
4866 | { | ||
4867 | CheckAccess(declFunc, funcName); | ||
4868 | left = GenerateFromRVal(token.rValLeft); | ||
4869 | CompValu method = AccessInstanceMember(declFunc, left, token, false); | ||
4870 | CompValu[] argRVals = new CompValu[] { right }; | ||
4871 | return GenerateACall(method, argRVals, token); | ||
4872 | } | ||
4873 | } | ||
4874 | |||
4875 | /* | ||
4876 | * Formulate key string for binOpStrings = (lefttype)(operator)(righttype) | ||
4877 | */ | ||
4878 | string leftIndex = leftType.ToString(); | ||
4879 | string rightIndex = right.type.ToString(); | ||
4880 | string key = leftIndex + opcodeIndex + rightIndex; | ||
4881 | |||
4882 | /* | ||
4883 | * If that key exists in table, then the operation is defined between those types | ||
4884 | * ... and it produces an R-value of type as given in the table. | ||
4885 | */ | ||
4886 | BinOpStr binOpStr; | ||
4887 | if(BinOpStr.defined.TryGetValue(key, out binOpStr)) | ||
4888 | { | ||
4889 | |||
4890 | /* | ||
4891 | * If table contained an explicit assignment type like +=, output the statement without | ||
4892 | * casting the L-value, then return the L-value as the resultant value. | ||
4893 | * | ||
4894 | * Make sure we don't include comparisons (such as ==, >=, etc). | ||
4895 | * Nothing like +=, -=, %=, etc, generate a boolean, only the comparisons. | ||
4896 | */ | ||
4897 | if((binOpStr.outtype != typeof(bool)) && opcodeIndex.EndsWith("=") && (opcodeIndex != "!=")) | ||
4898 | { | ||
4899 | if(!(token.rValLeft is TokenLVal)) | ||
4900 | { | ||
4901 | ErrorMsg(token.rValLeft, "invalid L-value"); | ||
4902 | return GenerateFromRVal(token.rValLeft); | ||
4903 | } | ||
4904 | left = GenerateFromLVal((TokenLVal)token.rValLeft); | ||
4905 | binOpStr.emitBO(this, token, left, right, left); | ||
4906 | return left; | ||
4907 | } | ||
4908 | |||
4909 | /* | ||
4910 | * It's of the form left binop right. | ||
4911 | * Compute left, perform operation then put result in a temp. | ||
4912 | */ | ||
4913 | left = GenerateFromRVal(token.rValLeft); | ||
4914 | CompValu retRVal = new CompValuTemp(TokenType.FromSysType(token.opcode, binOpStr.outtype), this); | ||
4915 | binOpStr.emitBO(this, token, left, right, retRVal); | ||
4916 | return retRVal; | ||
4917 | } | ||
4918 | |||
4919 | /* | ||
4920 | * Nothing in the table, check for comparing object pointers because of the myriad of types possible. | ||
4921 | * This will compare list pointers, null pointers, script-defined type pointers, array pointers, etc. | ||
4922 | * It will show equal iff the memory addresses are equal and that is good enough. | ||
4923 | */ | ||
4924 | if(!leftType.ToSysType().IsValueType && !right.type.ToSysType().IsValueType && ((opcodeIndex == "==") || (opcodeIndex == "!="))) | ||
4925 | { | ||
4926 | CompValuTemp retRVal = new CompValuTemp(new TokenTypeInt(token), this); | ||
4927 | left = GenerateFromRVal(token.rValLeft); | ||
4928 | left.PushVal(this, token.rValLeft); | ||
4929 | right.PushVal(this, token.rValRight); | ||
4930 | ilGen.Emit(token, OpCodes.Ceq); | ||
4931 | if(opcodeIndex == "!=") | ||
4932 | { | ||
4933 | ilGen.Emit(token, OpCodes.Ldc_I4_1); | ||
4934 | ilGen.Emit(token, OpCodes.Xor); | ||
4935 | } | ||
4936 | retRVal.Pop(this, token); | ||
4937 | return retRVal; | ||
4938 | } | ||
4939 | |||
4940 | /* | ||
4941 | * If the opcode ends with "=", it may be something like "+=". | ||
4942 | * So look up the key as if we didn't have the "=" to tell us if the operation is legal. | ||
4943 | * Also, the binary operation's output type must be the same as the L-value type. | ||
4944 | * Likewise, integer += float not allowed because result is float, but float += integer is ok. | ||
4945 | */ | ||
4946 | if(opcodeIndex.EndsWith("=")) | ||
4947 | { | ||
4948 | key = leftIndex + opcodeIndex.Substring(0, opcodeIndex.Length - 1) + rightIndex; | ||
4949 | if(BinOpStr.defined.TryGetValue(key, out binOpStr)) | ||
4950 | { | ||
4951 | if(!(token.rValLeft is TokenLVal)) | ||
4952 | { | ||
4953 | ErrorMsg(token, "invalid L-value for <op>="); | ||
4954 | return GenerateFromRVal(token.rValLeft); | ||
4955 | } | ||
4956 | if(!binOpStr.rmwOK) | ||
4957 | { | ||
4958 | ErrorMsg(token, "<op>= not allowed: " + leftIndex + " " + opcodeIndex + " " + rightIndex); | ||
4959 | return new CompValuVoid(token); | ||
4960 | } | ||
4961 | |||
4962 | /* | ||
4963 | * Now we know for something like %= that left%right is legal for the types given. | ||
4964 | */ | ||
4965 | left = GenerateFromLVal((TokenLVal)token.rValLeft); | ||
4966 | if(binOpStr.outtype == leftType.ToSysType()) | ||
4967 | { | ||
4968 | binOpStr.emitBO(this, token, left, right, left); | ||
4969 | } | ||
4970 | else | ||
4971 | { | ||
4972 | CompValu temp = new CompValuTemp(TokenType.FromSysType(token, binOpStr.outtype), this); | ||
4973 | binOpStr.emitBO(this, token, left, right, temp); | ||
4974 | left.PopPre(this, token); | ||
4975 | temp.PushVal(this, token, leftType); | ||
4976 | left.PopPost(this, token); | ||
4977 | } | ||
4978 | return left; | ||
4979 | } | ||
4980 | } | ||
4981 | |||
4982 | /* | ||
4983 | * Can't find it, oh well. | ||
4984 | */ | ||
4985 | ErrorMsg(token, "op not defined: " + leftIndex + " " + opcodeIndex + " " + rightIndex); | ||
4986 | return new CompValuVoid(token); | ||
4987 | } | ||
4988 | |||
4989 | /** | ||
4990 | * @brief Queue the given operands to the end of the scos list. | ||
4991 | * If it can be broken down into more string concat operands, do so. | ||
4992 | * Otherwise, just push it as one operand. | ||
4993 | * @param leftRVal = left-hand operand of a '+' operation | ||
4994 | * @param rightRVal = right-hand operand of a '+' operation | ||
4995 | * @param scos = left-to-right list of operands for the string concat so far | ||
4996 | * @param addop = the add operator token (either '+' or '+=') | ||
4997 | * @returns false: neither operand is a string, nothing added to scos | ||
4998 | * true: scos = updated with leftRVal then rightRVal added onto the end, possibly broken down further | ||
4999 | */ | ||
5000 | private bool StringConcatOperands(TokenRVal leftRVal, TokenRVal rightRVal, List<TokenRVal> scos, TokenKw addop) | ||
5001 | { | ||
5002 | /* | ||
5003 | * If neither operand is a string (eg, float+integer), then the result isn't going to be a string. | ||
5004 | */ | ||
5005 | TokenType leftType = leftRVal.GetRValType(this, null); | ||
5006 | TokenType rightType = rightRVal.GetRValType(this, null); | ||
5007 | if(!(leftType is TokenTypeStr) && !(rightType is TokenTypeStr)) | ||
5008 | return false; | ||
5009 | |||
5010 | /* | ||
5011 | * Also, list+string => list so reject that too. | ||
5012 | * Also, string+list => list so reject that too. | ||
5013 | */ | ||
5014 | if(leftType is TokenTypeList) | ||
5015 | return false; | ||
5016 | if(rightType is TokenTypeList) | ||
5017 | return false; | ||
5018 | |||
5019 | /* | ||
5020 | * Append values to the end of the list in left-to-right order. | ||
5021 | * If value is formed from a something+something => string, | ||
5022 | * push them as separate values, otherwise push as one value. | ||
5023 | */ | ||
5024 | StringConcatOperand(leftType, leftRVal, scos); | ||
5025 | StringConcatOperand(rightType, rightRVal, scos); | ||
5026 | |||
5027 | /* | ||
5028 | * Maybe constant strings can be concatted. | ||
5029 | */ | ||
5030 | try | ||
5031 | { | ||
5032 | int len; | ||
5033 | while(((len = scos.Count) >= 2) && | ||
5034 | ((leftRVal = scos[len - 2]) is TokenRValConst) && | ||
5035 | ((rightRVal = scos[len - 1]) is TokenRValConst)) | ||
5036 | { | ||
5037 | object sum = addop.binOpConst(((TokenRValConst)leftRVal).val, | ||
5038 | ((TokenRValConst)rightRVal).val); | ||
5039 | scos[len - 2] = new TokenRValConst(addop, sum); | ||
5040 | scos.RemoveAt(len - 1); | ||
5041 | } | ||
5042 | } | ||
5043 | catch | ||
5044 | { | ||
5045 | } | ||
5046 | |||
5047 | /* | ||
5048 | * We pushed some string stuff. | ||
5049 | */ | ||
5050 | return true; | ||
5051 | } | ||
5052 | |||
5053 | /** | ||
5054 | * @brief Queue the given operand to the end of the scos list. | ||
5055 | * If it can be broken down into more string concat operands, do so. | ||
5056 | * Otherwise, just push it as one operand. | ||
5057 | * @param type = rVal's resultant type | ||
5058 | * @param rVal = operand to examine | ||
5059 | * @param scos = left-to-right list of operands for the string concat so far | ||
5060 | * @returns with scos = updated with rVal added onto the end, possibly broken down further | ||
5061 | */ | ||
5062 | private void StringConcatOperand(TokenType type, TokenRVal rVal, List<TokenRVal> scos) | ||
5063 | { | ||
5064 | bool didOne; | ||
5065 | do | ||
5066 | { | ||
5067 | didOne = false; | ||
5068 | rVal = rVal.TryComputeConstant(LookupBodyConstants, ref didOne); | ||
5069 | } while(didOne); | ||
5070 | |||
5071 | if(!(type is TokenTypeStr)) | ||
5072 | goto pushasis; | ||
5073 | if(!(rVal is TokenRValOpBin)) | ||
5074 | goto pushasis; | ||
5075 | TokenRValOpBin rValOpBin = (TokenRValOpBin)rVal; | ||
5076 | if(!(rValOpBin.opcode is TokenKwAdd)) | ||
5077 | goto pushasis; | ||
5078 | if(StringConcatOperands(rValOpBin.rValLeft, rValOpBin.rValRight, scos, rValOpBin.opcode)) | ||
5079 | return; | ||
5080 | pushasis: | ||
5081 | scos.Add(rVal); | ||
5082 | } | ||
5083 | |||
5084 | /** | ||
5085 | * @brief compute the result of an unary operator | ||
5086 | * @param token = unary operator token, includes the operand | ||
5087 | * @returns where the resultant R-value is | ||
5088 | */ | ||
5089 | private CompValu GenerateFromRValOpUn(TokenRValOpUn token) | ||
5090 | { | ||
5091 | CompValu inRVal = GenerateFromRVal(token.rVal); | ||
5092 | |||
5093 | /* | ||
5094 | * Script-defined types can define their own methods to handle unary operators. | ||
5095 | */ | ||
5096 | if(inRVal.type is TokenTypeSDTypeClass) | ||
5097 | { | ||
5098 | TokenTypeSDTypeClass sdtType = (TokenTypeSDTypeClass)inRVal.type; | ||
5099 | TokenDeclSDTypeClass sdtDecl = sdtType.decl; | ||
5100 | TokenName funcName = new TokenName(token.opcode, "$op" + token.opcode.ToString()); | ||
5101 | TokenDeclVar declFunc = FindThisMember(sdtDecl, funcName, zeroArgs); | ||
5102 | if(declFunc != null) | ||
5103 | { | ||
5104 | CheckAccess(declFunc, funcName); | ||
5105 | CompValu method = AccessInstanceMember(declFunc, inRVal, token, false); | ||
5106 | return GenerateACall(method, zeroCompValus, token); | ||
5107 | } | ||
5108 | } | ||
5109 | |||
5110 | /* | ||
5111 | * Otherwise use the default. | ||
5112 | */ | ||
5113 | return UnOpGenerate(inRVal, token.opcode); | ||
5114 | } | ||
5115 | |||
5116 | /** | ||
5117 | * @brief postfix operator -- this returns the type and location of the resultant value | ||
5118 | */ | ||
5119 | private CompValu GenerateFromRValAsnPost(TokenRValAsnPost asnPost) | ||
5120 | { | ||
5121 | CompValu lVal = GenerateFromLVal(asnPost.lVal); | ||
5122 | |||
5123 | /* | ||
5124 | * Make up a temp to save original value in. | ||
5125 | */ | ||
5126 | CompValuTemp result = new CompValuTemp(lVal.type, this); | ||
5127 | |||
5128 | /* | ||
5129 | * Prepare to pop incremented value back into variable being incremented. | ||
5130 | */ | ||
5131 | lVal.PopPre(this, asnPost.lVal); | ||
5132 | |||
5133 | /* | ||
5134 | * Copy original value to temp and leave value on stack. | ||
5135 | */ | ||
5136 | lVal.PushVal(this, asnPost.lVal); | ||
5137 | ilGen.Emit(asnPost.lVal, OpCodes.Dup); | ||
5138 | result.Pop(this, asnPost.lVal); | ||
5139 | |||
5140 | /* | ||
5141 | * Perform the ++/--. | ||
5142 | */ | ||
5143 | if((lVal.type is TokenTypeChar) || (lVal.type is TokenTypeInt)) | ||
5144 | { | ||
5145 | ilGen.Emit(asnPost, OpCodes.Ldc_I4_1); | ||
5146 | } | ||
5147 | else if(lVal.type is TokenTypeFloat) | ||
5148 | { | ||
5149 | ilGen.Emit(asnPost, OpCodes.Ldc_R4, 1.0f); | ||
5150 | } | ||
5151 | else | ||
5152 | { | ||
5153 | lVal.PopPost(this, asnPost.lVal); | ||
5154 | ErrorMsg(asnPost, "invalid type for " + asnPost.postfix.ToString()); | ||
5155 | return lVal; | ||
5156 | } | ||
5157 | switch(asnPost.postfix.ToString()) | ||
5158 | { | ||
5159 | case "++": | ||
5160 | { | ||
5161 | ilGen.Emit(asnPost, OpCodes.Add); | ||
5162 | break; | ||
5163 | } | ||
5164 | case "--": | ||
5165 | { | ||
5166 | ilGen.Emit(asnPost, OpCodes.Sub); | ||
5167 | break; | ||
5168 | } | ||
5169 | default: | ||
5170 | throw new Exception("unknown asnPost op"); | ||
5171 | } | ||
5172 | |||
5173 | /* | ||
5174 | * Store new value in original variable. | ||
5175 | */ | ||
5176 | lVal.PopPost(this, asnPost.lVal); | ||
5177 | |||
5178 | return result; | ||
5179 | } | ||
5180 | |||
5181 | /** | ||
5182 | * @brief prefix operator -- this returns the type and location of the resultant value | ||
5183 | */ | ||
5184 | private CompValu GenerateFromRValAsnPre(TokenRValAsnPre asnPre) | ||
5185 | { | ||
5186 | CompValu lVal = GenerateFromLVal(asnPre.lVal); | ||
5187 | |||
5188 | /* | ||
5189 | * Make up a temp to put result in. | ||
5190 | */ | ||
5191 | CompValuTemp result = new CompValuTemp(lVal.type, this); | ||
5192 | |||
5193 | /* | ||
5194 | * Prepare to pop incremented value back into variable being incremented. | ||
5195 | */ | ||
5196 | lVal.PopPre(this, asnPre.lVal); | ||
5197 | |||
5198 | /* | ||
5199 | * Push original value. | ||
5200 | */ | ||
5201 | lVal.PushVal(this, asnPre.lVal); | ||
5202 | |||
5203 | /* | ||
5204 | * Perform the ++/--. | ||
5205 | */ | ||
5206 | if((lVal.type is TokenTypeChar) || (lVal.type is TokenTypeInt)) | ||
5207 | { | ||
5208 | ilGen.Emit(asnPre, OpCodes.Ldc_I4_1); | ||
5209 | } | ||
5210 | else if(lVal.type is TokenTypeFloat) | ||
5211 | { | ||
5212 | ilGen.Emit(asnPre, OpCodes.Ldc_R4, 1.0f); | ||
5213 | } | ||
5214 | else | ||
5215 | { | ||
5216 | lVal.PopPost(this, asnPre.lVal); | ||
5217 | ErrorMsg(asnPre, "invalid type for " + asnPre.prefix.ToString()); | ||
5218 | return lVal; | ||
5219 | } | ||
5220 | switch(asnPre.prefix.ToString()) | ||
5221 | { | ||
5222 | case "++": | ||
5223 | { | ||
5224 | ilGen.Emit(asnPre, OpCodes.Add); | ||
5225 | break; | ||
5226 | } | ||
5227 | case "--": | ||
5228 | { | ||
5229 | ilGen.Emit(asnPre, OpCodes.Sub); | ||
5230 | break; | ||
5231 | } | ||
5232 | default: | ||
5233 | throw new Exception("unknown asnPre op"); | ||
5234 | } | ||
5235 | |||
5236 | /* | ||
5237 | * Store new value in temp variable, keeping new value on stack. | ||
5238 | */ | ||
5239 | ilGen.Emit(asnPre.lVal, OpCodes.Dup); | ||
5240 | result.Pop(this, asnPre.lVal); | ||
5241 | |||
5242 | /* | ||
5243 | * Store new value in original variable. | ||
5244 | */ | ||
5245 | lVal.PopPost(this, asnPre.lVal); | ||
5246 | |||
5247 | return result; | ||
5248 | } | ||
5249 | |||
5250 | /** | ||
5251 | * @brief Generate code that calls a function or object's method. | ||
5252 | * @returns where the call's return value is stored (a TokenTypeVoid if void) | ||
5253 | */ | ||
5254 | private CompValu GenerateFromRValCall(TokenRValCall call) | ||
5255 | { | ||
5256 | CompValu method; | ||
5257 | CompValu[] argRVals; | ||
5258 | int i, nargs; | ||
5259 | TokenRVal arg; | ||
5260 | TokenType[] argTypes; | ||
5261 | |||
5262 | /* | ||
5263 | * Compute the values of all the function's call arguments. | ||
5264 | * Save where the computation results are in the argRVals[] array. | ||
5265 | * Might as well build the argument signature from the argument types, too. | ||
5266 | */ | ||
5267 | nargs = call.nArgs; | ||
5268 | argRVals = new CompValu[nargs]; | ||
5269 | argTypes = new TokenType[nargs]; | ||
5270 | if(nargs > 0) | ||
5271 | { | ||
5272 | i = 0; | ||
5273 | for(arg = call.args; arg != null; arg = (TokenRVal)arg.nextToken) | ||
5274 | { | ||
5275 | argRVals[i] = GenerateFromRVal(arg); | ||
5276 | argTypes[i] = argRVals[i].type; | ||
5277 | i++; | ||
5278 | } | ||
5279 | } | ||
5280 | |||
5281 | /* | ||
5282 | * Get function/method's entrypoint that matches the call argument types. | ||
5283 | */ | ||
5284 | method = GenerateFromRVal(call.meth, argTypes); | ||
5285 | if(method == null) | ||
5286 | return null; | ||
5287 | |||
5288 | return GenerateACall(method, argRVals, call); | ||
5289 | } | ||
5290 | |||
5291 | /** | ||
5292 | * @brief Generate call to a function/method. | ||
5293 | * @param method = function/method being called | ||
5294 | * @param argVRVals = its call parameters (zero length if none) | ||
5295 | * @param call = where in source code call is being made from (for error messages) | ||
5296 | * @returns type and location of return value (CompValuVoid if none) | ||
5297 | */ | ||
5298 | private CompValu GenerateACall(CompValu method, CompValu[] argRVals, Token call) | ||
5299 | { | ||
5300 | CompValuTemp result; | ||
5301 | int i, nArgs; | ||
5302 | TokenType retType; | ||
5303 | TokenType[] argTypes; | ||
5304 | |||
5305 | /* | ||
5306 | * Must be some kind of callable. | ||
5307 | */ | ||
5308 | retType = method.GetRetType(); // TokenTypeVoid if void; null means a variable | ||
5309 | if(retType == null) | ||
5310 | { | ||
5311 | ErrorMsg(call, "must be a delegate, function or method"); | ||
5312 | return new CompValuVoid(call); | ||
5313 | } | ||
5314 | |||
5315 | /* | ||
5316 | * Get a location for return value. | ||
5317 | */ | ||
5318 | if(retType is TokenTypeVoid) | ||
5319 | { | ||
5320 | result = new CompValuVoid(call); | ||
5321 | } | ||
5322 | else | ||
5323 | { | ||
5324 | result = new CompValuTemp(retType, this); | ||
5325 | } | ||
5326 | |||
5327 | /* | ||
5328 | * Make sure all arguments are trivial, ie, don't involve their own call labels. | ||
5329 | * For any that aren't, output code to calculate the arg and put in a temporary. | ||
5330 | */ | ||
5331 | nArgs = argRVals.Length; | ||
5332 | for(i = 0; i < nArgs; i++) | ||
5333 | { | ||
5334 | if(!argRVals[i].IsReadTrivial(this, call)) | ||
5335 | { | ||
5336 | argRVals[i] = Trivialize(argRVals[i], call); | ||
5337 | } | ||
5338 | } | ||
5339 | |||
5340 | /* | ||
5341 | * Inline functions know how to generate their own call. | ||
5342 | */ | ||
5343 | if(method is CompValuInline) | ||
5344 | { | ||
5345 | CompValuInline inline = (CompValuInline)method; | ||
5346 | inline.declInline.CodeGen(this, call, result, argRVals); | ||
5347 | return result; | ||
5348 | } | ||
5349 | |||
5350 | /* | ||
5351 | * Push whatever the function/method needs as a this argument, if anything. | ||
5352 | */ | ||
5353 | method.CallPre(this, call); | ||
5354 | |||
5355 | /* | ||
5356 | * Push the script-visible args, left-to-right. | ||
5357 | */ | ||
5358 | argTypes = method.GetArgTypes(); | ||
5359 | for(i = 0; i < nArgs; i++) | ||
5360 | { | ||
5361 | if(argTypes == null) | ||
5362 | { | ||
5363 | argRVals[i].PushVal(this, call); | ||
5364 | } | ||
5365 | else | ||
5366 | { | ||
5367 | argRVals[i].PushVal(this, call, argTypes[i]); | ||
5368 | } | ||
5369 | } | ||
5370 | |||
5371 | /* | ||
5372 | * Now output call instruction. | ||
5373 | */ | ||
5374 | method.CallPost(this, call); | ||
5375 | |||
5376 | /* | ||
5377 | * Deal with the return value (if any), by putting it in 'result'. | ||
5378 | */ | ||
5379 | result.Pop(this, call, retType); | ||
5380 | return result; | ||
5381 | } | ||
5382 | |||
5383 | /** | ||
5384 | * @brief This is needed to avoid nesting call labels around non-trivial properties. | ||
5385 | * It should be used for the second (and later) operands. | ||
5386 | * Note that a 'call' is considered an operator, so all arguments of a call | ||
5387 | * should be trivialized, but the method itself does not need to be. | ||
5388 | */ | ||
5389 | public CompValu Trivialize(CompValu operand, Token errorAt) | ||
5390 | { | ||
5391 | if(operand.IsReadTrivial(this, errorAt)) | ||
5392 | return operand; | ||
5393 | CompValuTemp temp = new CompValuTemp(operand.type, this); | ||
5394 | operand.PushVal(this, errorAt); | ||
5395 | temp.Pop(this, errorAt); | ||
5396 | return temp; | ||
5397 | } | ||
5398 | |||
5399 | /** | ||
5400 | * @brief Generate code that casts a value to a particular type. | ||
5401 | * @returns where the result of the conversion is stored. | ||
5402 | */ | ||
5403 | private CompValu GenerateFromRValCast(TokenRValCast cast) | ||
5404 | { | ||
5405 | /* | ||
5406 | * If casting to a delegate type, use the argment signature | ||
5407 | * of the delegate to help select the function/method, eg, | ||
5408 | * '(delegate string(integer))ToString' | ||
5409 | * will select 'string ToString(integer x)' | ||
5410 | * instaead of 'string ToString(float x)' or anything else | ||
5411 | */ | ||
5412 | TokenType[] argsig = null; | ||
5413 | TokenType outType = cast.castTo; | ||
5414 | if(outType is TokenTypeSDTypeDelegate) | ||
5415 | { | ||
5416 | argsig = ((TokenTypeSDTypeDelegate)outType).decl.GetArgTypes(); | ||
5417 | } | ||
5418 | |||
5419 | /* | ||
5420 | * Generate the value that is being cast. | ||
5421 | * If the value is already the requested type, just use it as is. | ||
5422 | */ | ||
5423 | CompValu inRVal = GenerateFromRVal(cast.rVal, argsig); | ||
5424 | if(inRVal.type == outType) | ||
5425 | return inRVal; | ||
5426 | |||
5427 | /* | ||
5428 | * Different type, generate casting code, putting the result in a temp of the output type. | ||
5429 | */ | ||
5430 | CompValu outRVal = new CompValuTemp(outType, this); | ||
5431 | outRVal.PopPre(this, cast); | ||
5432 | inRVal.PushVal(this, cast, outType, true); | ||
5433 | outRVal.PopPost(this, cast); | ||
5434 | return outRVal; | ||
5435 | } | ||
5436 | |||
5437 | /** | ||
5438 | * @brief Compute conditional expression value. | ||
5439 | * @returns type and location of computed value. | ||
5440 | */ | ||
5441 | private CompValu GenerateFromRValCondExpr(TokenRValCondExpr rValCondExpr) | ||
5442 | { | ||
5443 | bool condVal; | ||
5444 | CompValu condValu = GenerateFromRVal(rValCondExpr.condExpr); | ||
5445 | if(IsConstBoolExpr(condValu, out condVal)) | ||
5446 | { | ||
5447 | return GenerateFromRVal(condVal ? rValCondExpr.trueExpr : rValCondExpr.falseExpr); | ||
5448 | } | ||
5449 | |||
5450 | ScriptMyLabel falseLabel = ilGen.DefineLabel("condexfalse"); | ||
5451 | ScriptMyLabel doneLabel = ilGen.DefineLabel("condexdone"); | ||
5452 | |||
5453 | condValu.PushVal(this, rValCondExpr.condExpr, tokenTypeBool); | ||
5454 | ilGen.Emit(rValCondExpr, OpCodes.Brfalse, falseLabel); | ||
5455 | |||
5456 | CompValu trueValu = GenerateFromRVal(rValCondExpr.trueExpr); | ||
5457 | trueValu.PushVal(this, rValCondExpr.trueExpr); | ||
5458 | ilGen.Emit(rValCondExpr, OpCodes.Br, doneLabel); | ||
5459 | |||
5460 | ilGen.MarkLabel(falseLabel); | ||
5461 | CompValu falseValu = GenerateFromRVal(rValCondExpr.falseExpr); | ||
5462 | falseValu.PushVal(this, rValCondExpr.falseExpr); | ||
5463 | |||
5464 | if(trueValu.type.GetType() != falseValu.type.GetType()) | ||
5465 | { | ||
5466 | ErrorMsg(rValCondExpr, "? operands " + trueValu.type.ToString() + " : " + | ||
5467 | falseValu.type.ToString() + " must be of same type"); | ||
5468 | } | ||
5469 | |||
5470 | ilGen.MarkLabel(doneLabel); | ||
5471 | CompValuTemp retRVal = new CompValuTemp(trueValu.type, this); | ||
5472 | retRVal.Pop(this, rValCondExpr); | ||
5473 | return retRVal; | ||
5474 | } | ||
5475 | |||
5476 | /** | ||
5477 | * @brief Constant in the script somewhere | ||
5478 | * @returns where the constants value is stored | ||
5479 | */ | ||
5480 | private CompValu GenerateFromRValConst(TokenRValConst rValConst) | ||
5481 | { | ||
5482 | switch(rValConst.type) | ||
5483 | { | ||
5484 | case TokenRValConstType.CHAR: | ||
5485 | { | ||
5486 | return new CompValuChar(new TokenTypeChar(rValConst), (char)(rValConst.val)); | ||
5487 | } | ||
5488 | case TokenRValConstType.FLOAT: | ||
5489 | { | ||
5490 | return new CompValuFloat(new TokenTypeFloat(rValConst), (double)(rValConst.val)); | ||
5491 | } | ||
5492 | case TokenRValConstType.INT: | ||
5493 | { | ||
5494 | return new CompValuInteger(new TokenTypeInt(rValConst), (int)(rValConst.val)); | ||
5495 | } | ||
5496 | case TokenRValConstType.KEY: | ||
5497 | { | ||
5498 | return new CompValuString(new TokenTypeKey(rValConst), (string)(rValConst.val)); | ||
5499 | } | ||
5500 | case TokenRValConstType.STRING: | ||
5501 | { | ||
5502 | return new CompValuString(new TokenTypeStr(rValConst), (string)(rValConst.val)); | ||
5503 | } | ||
5504 | } | ||
5505 | throw new Exception("unknown constant type " + rValConst.val.GetType()); | ||
5506 | } | ||
5507 | |||
5508 | /** | ||
5509 | * @brief generate a new list object | ||
5510 | * @param rValList = an rVal to create it from | ||
5511 | */ | ||
5512 | private CompValu GenerateFromRValList(TokenRValList rValList) | ||
5513 | { | ||
5514 | /* | ||
5515 | * Compute all element values and remember where we put them. | ||
5516 | * Do it right-to-left as customary for LSL scripts. | ||
5517 | */ | ||
5518 | int i = 0; | ||
5519 | TokenRVal lastRVal = null; | ||
5520 | for(TokenRVal val = rValList.rVal; val != null; val = (TokenRVal)val.nextToken) | ||
5521 | { | ||
5522 | i++; | ||
5523 | val.prevToken = lastRVal; | ||
5524 | lastRVal = val; | ||
5525 | } | ||
5526 | CompValu[] vals = new CompValu[i]; | ||
5527 | for(TokenRVal val = lastRVal; val != null; val = (TokenRVal)val.prevToken) | ||
5528 | { | ||
5529 | vals[--i] = GenerateFromRVal(val); | ||
5530 | } | ||
5531 | |||
5532 | /* | ||
5533 | * This is the temp that will hold the created list. | ||
5534 | */ | ||
5535 | CompValuTemp newList = new CompValuTemp(new TokenTypeList(rValList.rVal), this); | ||
5536 | |||
5537 | /* | ||
5538 | * Create a temp object[] array to hold all the initial values. | ||
5539 | */ | ||
5540 | ilGen.Emit(rValList, OpCodes.Ldc_I4, rValList.nItems); | ||
5541 | ilGen.Emit(rValList, OpCodes.Newarr, typeof(object)); | ||
5542 | |||
5543 | /* | ||
5544 | * Populate the array. | ||
5545 | */ | ||
5546 | i = 0; | ||
5547 | for(TokenRVal val = rValList.rVal; val != null; val = (TokenRVal)val.nextToken) | ||
5548 | { | ||
5549 | |||
5550 | /* | ||
5551 | * Get pointer to temp array object. | ||
5552 | */ | ||
5553 | ilGen.Emit(rValList, OpCodes.Dup); | ||
5554 | |||
5555 | /* | ||
5556 | * Get index in that array. | ||
5557 | */ | ||
5558 | ilGen.Emit(rValList, OpCodes.Ldc_I4, i); | ||
5559 | |||
5560 | /* | ||
5561 | * Store initialization value in array location. | ||
5562 | * However, floats and ints need to be converted to LSL_Float and LSL_Integer, | ||
5563 | * or things like llSetPayPrice() will puque when they try to cast the elements | ||
5564 | * to LSL_Float or LSL_Integer. Likewise with string/LSL_String. | ||
5565 | * | ||
5566 | * Maybe it's already LSL-boxed so we don't do anything with it except make sure | ||
5567 | * it is an object, not a struct. | ||
5568 | */ | ||
5569 | CompValu eRVal = vals[i++]; | ||
5570 | eRVal.PushVal(this, val); | ||
5571 | if(eRVal.type.ToLSLWrapType() == null) | ||
5572 | { | ||
5573 | if(eRVal.type is TokenTypeFloat) | ||
5574 | { | ||
5575 | ilGen.Emit(val, OpCodes.Newobj, lslFloatConstructorInfo); | ||
5576 | ilGen.Emit(val, OpCodes.Box, typeof(LSL_Float)); | ||
5577 | } | ||
5578 | else if(eRVal.type is TokenTypeInt) | ||
5579 | { | ||
5580 | ilGen.Emit(val, OpCodes.Newobj, lslIntegerConstructorInfo); | ||
5581 | ilGen.Emit(val, OpCodes.Box, typeof(LSL_Integer)); | ||
5582 | } | ||
5583 | else if((eRVal.type is TokenTypeKey) || (eRVal.type is TokenTypeStr)) | ||
5584 | { | ||
5585 | ilGen.Emit(val, OpCodes.Newobj, lslStringConstructorInfo); | ||
5586 | ilGen.Emit(val, OpCodes.Box, typeof(LSL_String)); | ||
5587 | } | ||
5588 | else if(eRVal.type.ToSysType().IsValueType) | ||
5589 | { | ||
5590 | ilGen.Emit(val, OpCodes.Box, eRVal.type.ToSysType()); | ||
5591 | } | ||
5592 | } | ||
5593 | else if(eRVal.type.ToLSLWrapType().IsValueType) | ||
5594 | { | ||
5595 | |||
5596 | // Convert the LSL value structs to an object of the LSL-boxed type | ||
5597 | ilGen.Emit(val, OpCodes.Box, eRVal.type.ToLSLWrapType()); | ||
5598 | } | ||
5599 | ilGen.Emit(val, OpCodes.Stelem, typeof(object)); | ||
5600 | } | ||
5601 | |||
5602 | /* | ||
5603 | * Create new list object from temp initial value array (whose ref is still on the stack). | ||
5604 | */ | ||
5605 | ilGen.Emit(rValList, OpCodes.Newobj, lslListConstructorInfo); | ||
5606 | newList.Pop(this, rValList); | ||
5607 | return newList; | ||
5608 | } | ||
5609 | |||
5610 | /** | ||
5611 | * @brief New array allocation with initializer expressions. | ||
5612 | */ | ||
5613 | private CompValu GenerateFromRValNewArIni(TokenRValNewArIni rValNewArIni) | ||
5614 | { | ||
5615 | return MallocAndInitArray(rValNewArIni.arrayType, rValNewArIni.valueList); | ||
5616 | } | ||
5617 | |||
5618 | /** | ||
5619 | * @brief Mallocate and initialize an array from its initialization list. | ||
5620 | * @param arrayType = type of the array to be allocated and initialized | ||
5621 | * @param values = initialization value list used to size and initialize the array. | ||
5622 | * @returns memory location of the resultant initialized array. | ||
5623 | */ | ||
5624 | private CompValu MallocAndInitArray(TokenType arrayType, TokenList values) | ||
5625 | { | ||
5626 | TokenDeclSDTypeClass arrayDecl = ((TokenTypeSDTypeClass)arrayType).decl; | ||
5627 | TokenType eleType = arrayDecl.arrayOfType; | ||
5628 | int rank = arrayDecl.arrayOfRank; | ||
5629 | |||
5630 | // Get size of each of the dimensions by scanning the initialization value list | ||
5631 | int[] dimSizes = new int[rank]; | ||
5632 | FillInDimSizes(dimSizes, 0, rank, values); | ||
5633 | |||
5634 | // Figure out where the array's $new() method is | ||
5635 | TokenType[] newargsig = new TokenType[rank]; | ||
5636 | for(int k = 0; k < rank; k++) | ||
5637 | { | ||
5638 | newargsig[k] = tokenTypeInt; | ||
5639 | } | ||
5640 | TokenDeclVar newMeth = FindThisMember(arrayDecl, new TokenName(null, "$new"), newargsig); | ||
5641 | |||
5642 | // Output a call to malloc the array with all default values | ||
5643 | // array = ArrayType.$new (dimSizes[0], dimSizes[1], ...) | ||
5644 | CompValuTemp array = new CompValuTemp(arrayType, this); | ||
5645 | PushXMRInst(); | ||
5646 | for(int k = 0; k < rank; k++) | ||
5647 | { | ||
5648 | ilGen.Emit(values, OpCodes.Ldc_I4, dimSizes[k]); | ||
5649 | } | ||
5650 | ilGen.Emit(values, OpCodes.Call, newMeth.ilGen); | ||
5651 | array.Pop(this, arrayType); | ||
5652 | |||
5653 | // Figure out where the array's Set() method is | ||
5654 | TokenType[] setargsig = new TokenType[rank + 1]; | ||
5655 | for(int k = 0; k < rank; k++) | ||
5656 | { | ||
5657 | setargsig[k] = tokenTypeInt; | ||
5658 | } | ||
5659 | setargsig[rank] = eleType; | ||
5660 | TokenDeclVar setMeth = FindThisMember(arrayDecl, new TokenName(null, "Set"), setargsig); | ||
5661 | |||
5662 | // Fill in the array with the initializer values | ||
5663 | FillInInitVals(array, setMeth, dimSizes, 0, rank, values, eleType); | ||
5664 | |||
5665 | // The array is our resultant value | ||
5666 | return array; | ||
5667 | } | ||
5668 | |||
5669 | /** | ||
5670 | * @brief Compute an array's dimensions given its initialization value list | ||
5671 | * @param dimSizes = filled in with array's dimensions | ||
5672 | * @param dimNo = what dimension the 'values' list applies to | ||
5673 | * @param rank = total number of dimensions of the array | ||
5674 | * @param values = list of values to initialize the array's 'dimNo' dimension with | ||
5675 | * @returns with dimSizes[dimNo..rank-1] filled in | ||
5676 | */ | ||
5677 | private static void FillInDimSizes(int[] dimSizes, int dimNo, int rank, TokenList values) | ||
5678 | { | ||
5679 | // the size of a dimension is the largest number of initializer elements at this level | ||
5680 | // for dimNo 0, this is the number of elements in the top-level list | ||
5681 | if(dimSizes[dimNo] < values.tl.Count) | ||
5682 | dimSizes[dimNo] = values.tl.Count; | ||
5683 | |||
5684 | // see if there is another dimension to calculate | ||
5685 | if(++dimNo < rank) | ||
5686 | { | ||
5687 | |||
5688 | // its size is the size of the largest initializer list at the next inner level | ||
5689 | foreach(Token val in values.tl) | ||
5690 | { | ||
5691 | if(val is TokenList) | ||
5692 | { | ||
5693 | TokenList subvals = (TokenList)val; | ||
5694 | FillInDimSizes(dimSizes, dimNo, rank, subvals); | ||
5695 | } | ||
5696 | } | ||
5697 | } | ||
5698 | } | ||
5699 | |||
5700 | /** | ||
5701 | * @brief Output code to fill in array's initialization values | ||
5702 | * @param array = array to be filled in | ||
5703 | * @param setMeth = the array's Set() method | ||
5704 | * @param subscripts = holds subscripts being built | ||
5705 | * @param dimNo = which dimension the 'values' are for | ||
5706 | * @param values = list of initialization values for dimension 'dimNo' | ||
5707 | * @param rank = number of dimensions of 'array' | ||
5708 | * @param values = list of values to initialize the array's 'dimNo' dimension with | ||
5709 | * @param eleType = the element's type | ||
5710 | * @returns with code emitted to initialize array's [subscripts[0], ..., subscripts[dimNo-1], *, *, ...] | ||
5711 | * dimNo and up completely filled ---^ | ||
5712 | */ | ||
5713 | private void FillInInitVals(CompValu array, TokenDeclVar setMeth, int[] subscripts, int dimNo, int rank, TokenList values, TokenType eleType) | ||
5714 | { | ||
5715 | subscripts[dimNo] = 0; | ||
5716 | foreach(Token val in values.tl) | ||
5717 | { | ||
5718 | CompValu initValue = null; | ||
5719 | |||
5720 | /* | ||
5721 | * If it is a sublist, process it. | ||
5722 | * If we don't have enough subscripts yet, hopefully that sublist will have enough. | ||
5723 | * If we already have enough subscripts, then that sublist can be for an element of this supposedly jagged array. | ||
5724 | */ | ||
5725 | if(val is TokenList) | ||
5726 | { | ||
5727 | TokenList sublist = (TokenList)val; | ||
5728 | if(dimNo + 1 < rank) | ||
5729 | { | ||
5730 | |||
5731 | /* | ||
5732 | * We don't have enough subscripts yet, hopefully the sublist has the rest. | ||
5733 | */ | ||
5734 | FillInInitVals(array, setMeth, subscripts, dimNo + 1, rank, sublist, eleType); | ||
5735 | } | ||
5736 | else if((eleType is TokenTypeSDTypeClass) && (((TokenTypeSDTypeClass)eleType).decl.arrayOfType == null)) | ||
5737 | { | ||
5738 | |||
5739 | /* | ||
5740 | * If we aren't a jagged array either, we can't do anything with the sublist. | ||
5741 | */ | ||
5742 | ErrorMsg(val, "too many brace levels"); | ||
5743 | } | ||
5744 | else | ||
5745 | { | ||
5746 | |||
5747 | /* | ||
5748 | * We are a jagged array, so malloc a subarray and initialize it with the sublist. | ||
5749 | * Then we can use that subarray to fill this array's element. | ||
5750 | */ | ||
5751 | initValue = MallocAndInitArray(eleType, sublist); | ||
5752 | } | ||
5753 | } | ||
5754 | |||
5755 | /* | ||
5756 | * If it is a value expression, then output code to compute the value. | ||
5757 | */ | ||
5758 | if(val is TokenRVal) | ||
5759 | { | ||
5760 | if(dimNo + 1 < rank) | ||
5761 | { | ||
5762 | ErrorMsg((Token)val, "not enough brace levels"); | ||
5763 | } | ||
5764 | else | ||
5765 | { | ||
5766 | initValue = GenerateFromRVal((TokenRVal)val); | ||
5767 | } | ||
5768 | } | ||
5769 | |||
5770 | /* | ||
5771 | * If there is an initValue, output "array.Set (subscript[0], subscript[1], ..., initValue)" | ||
5772 | */ | ||
5773 | if(initValue != null) | ||
5774 | { | ||
5775 | array.PushVal(this, val); | ||
5776 | for(int i = 0; i <= dimNo; i++) | ||
5777 | { | ||
5778 | ilGen.Emit(val, OpCodes.Ldc_I4, subscripts[i]); | ||
5779 | } | ||
5780 | initValue.PushVal(this, val, eleType); | ||
5781 | ilGen.Emit(val, OpCodes.Call, setMeth.ilGen); | ||
5782 | } | ||
5783 | |||
5784 | /* | ||
5785 | * That subscript is processed one way or another, on to the next. | ||
5786 | */ | ||
5787 | subscripts[dimNo]++; | ||
5788 | } | ||
5789 | } | ||
5790 | |||
5791 | /** | ||
5792 | * @brief parenthesized expression | ||
5793 | * @returns type and location of the result of the computation. | ||
5794 | */ | ||
5795 | private CompValu GenerateFromRValParen(TokenRValParen rValParen) | ||
5796 | { | ||
5797 | return GenerateFromRVal(rValParen.rVal); | ||
5798 | } | ||
5799 | |||
5800 | /** | ||
5801 | * @brief create a rotation object from the x,y,z,w value expressions. | ||
5802 | */ | ||
5803 | private CompValu GenerateFromRValRot(TokenRValRot rValRot) | ||
5804 | { | ||
5805 | CompValu xRVal, yRVal, zRVal, wRVal; | ||
5806 | |||
5807 | xRVal = Trivialize(GenerateFromRVal(rValRot.xRVal), rValRot); | ||
5808 | yRVal = Trivialize(GenerateFromRVal(rValRot.yRVal), rValRot); | ||
5809 | zRVal = Trivialize(GenerateFromRVal(rValRot.zRVal), rValRot); | ||
5810 | wRVal = Trivialize(GenerateFromRVal(rValRot.wRVal), rValRot); | ||
5811 | return new CompValuRot(new TokenTypeRot(rValRot), xRVal, yRVal, zRVal, wRVal); | ||
5812 | } | ||
5813 | |||
5814 | /** | ||
5815 | * @brief Using 'this' as a pointer to the current script-defined instance object. | ||
5816 | * The value is located in arg #0 of the current instance method. | ||
5817 | */ | ||
5818 | private CompValu GenerateFromRValThis(TokenRValThis zhis) | ||
5819 | { | ||
5820 | if(!IsSDTInstMethod()) | ||
5821 | { | ||
5822 | ErrorMsg(zhis, "cannot access instance member of class from static method"); | ||
5823 | return new CompValuVoid(zhis); | ||
5824 | } | ||
5825 | return new CompValuArg(curDeclFunc.sdtClass.MakeRefToken(zhis), 0); | ||
5826 | } | ||
5827 | |||
5828 | /** | ||
5829 | * @brief 'undefined' constant. | ||
5830 | * If this constant gets written to an array element, it will delete that element from the array. | ||
5831 | * If the script retrieves an element by key that is not defined, it will get this value. | ||
5832 | * This value can be stored in and retrieved from variables of type 'object' or script-defined classes. | ||
5833 | * It is a runtime error to cast this value to any other type, eg, | ||
5834 | * we don't allow list or string variables to be null pointers. | ||
5835 | */ | ||
5836 | private CompValu GenerateFromRValUndef(TokenRValUndef rValUndef) | ||
5837 | { | ||
5838 | return new CompValuNull(new TokenTypeUndef(rValUndef)); | ||
5839 | } | ||
5840 | |||
5841 | /** | ||
5842 | * @brief create a vector object from the x,y,z value expressions. | ||
5843 | */ | ||
5844 | private CompValu GenerateFromRValVec(TokenRValVec rValVec) | ||
5845 | { | ||
5846 | CompValu xRVal, yRVal, zRVal; | ||
5847 | |||
5848 | xRVal = Trivialize(GenerateFromRVal(rValVec.xRVal), rValVec); | ||
5849 | yRVal = Trivialize(GenerateFromRVal(rValVec.yRVal), rValVec); | ||
5850 | zRVal = Trivialize(GenerateFromRVal(rValVec.zRVal), rValVec); | ||
5851 | return new CompValuVec(new TokenTypeVec(rValVec), xRVal, yRVal, zRVal); | ||
5852 | } | ||
5853 | |||
5854 | /** | ||
5855 | * @brief Generate code to get the default initialization value for a variable. | ||
5856 | */ | ||
5857 | private CompValu GenerateFromRValInitDef(TokenRValInitDef rValInitDef) | ||
5858 | { | ||
5859 | TokenType type = rValInitDef.type; | ||
5860 | |||
5861 | if(type is TokenTypeChar) | ||
5862 | { | ||
5863 | return new CompValuChar(type, (char)0); | ||
5864 | } | ||
5865 | if(type is TokenTypeRot) | ||
5866 | { | ||
5867 | CompValuFloat x = new CompValuFloat(type, ScriptBaseClass.ZERO_ROTATION.x); | ||
5868 | CompValuFloat y = new CompValuFloat(type, ScriptBaseClass.ZERO_ROTATION.y); | ||
5869 | CompValuFloat z = new CompValuFloat(type, ScriptBaseClass.ZERO_ROTATION.z); | ||
5870 | CompValuFloat s = new CompValuFloat(type, ScriptBaseClass.ZERO_ROTATION.s); | ||
5871 | return new CompValuRot(type, x, y, z, s); | ||
5872 | } | ||
5873 | if((type is TokenTypeKey) || (type is TokenTypeStr)) | ||
5874 | { | ||
5875 | return new CompValuString(type, ""); | ||
5876 | } | ||
5877 | if(type is TokenTypeVec) | ||
5878 | { | ||
5879 | CompValuFloat x = new CompValuFloat(type, ScriptBaseClass.ZERO_VECTOR.x); | ||
5880 | CompValuFloat y = new CompValuFloat(type, ScriptBaseClass.ZERO_VECTOR.y); | ||
5881 | CompValuFloat z = new CompValuFloat(type, ScriptBaseClass.ZERO_VECTOR.z); | ||
5882 | return new CompValuVec(type, x, y, z); | ||
5883 | } | ||
5884 | if(type is TokenTypeInt) | ||
5885 | { | ||
5886 | return new CompValuInteger(type, 0); | ||
5887 | } | ||
5888 | if(type is TokenTypeFloat) | ||
5889 | { | ||
5890 | return new CompValuFloat(type, 0); | ||
5891 | } | ||
5892 | if(type is TokenTypeVoid) | ||
5893 | { | ||
5894 | return new CompValuVoid(type); | ||
5895 | } | ||
5896 | |||
5897 | /* | ||
5898 | * Default for 'object' type is 'undef'. | ||
5899 | * Likewise for script-defined classes and interfaces. | ||
5900 | */ | ||
5901 | if((type is TokenTypeObject) || (type is TokenTypeSDTypeClass) || (type is TokenTypeSDTypeDelegate) || | ||
5902 | (type is TokenTypeSDTypeInterface) || (type is TokenTypeExc)) | ||
5903 | { | ||
5904 | return new CompValuNull(type); | ||
5905 | } | ||
5906 | |||
5907 | /* | ||
5908 | * array and list | ||
5909 | */ | ||
5910 | CompValuTemp temp = new CompValuTemp(type, this); | ||
5911 | PushDefaultValue(type); | ||
5912 | temp.Pop(this, rValInitDef, type); | ||
5913 | return temp; | ||
5914 | } | ||
5915 | |||
5916 | /** | ||
5917 | * @brief Generate code to process an <rVal> is <type> expression, and produce a boolean value. | ||
5918 | */ | ||
5919 | private CompValu GenerateFromRValIsType(TokenRValIsType rValIsType) | ||
5920 | { | ||
5921 | /* | ||
5922 | * Expression we want to know the type of. | ||
5923 | */ | ||
5924 | CompValu val = GenerateFromRVal(rValIsType.rValExp); | ||
5925 | |||
5926 | /* | ||
5927 | * Pass it in to top-level type expression decoder. | ||
5928 | */ | ||
5929 | return GenerateFromTypeExp(val, rValIsType.typeExp); | ||
5930 | } | ||
5931 | |||
5932 | /** | ||
5933 | * @brief See if the type of the given value matches the type expression. | ||
5934 | * @param val = where the value to be evaluated is stored | ||
5935 | * @param typeExp = script tokens representing type expression | ||
5936 | * @returns location where the boolean result is stored | ||
5937 | */ | ||
5938 | private CompValu GenerateFromTypeExp(CompValu val, TokenTypeExp typeExp) | ||
5939 | { | ||
5940 | if(typeExp is TokenTypeExpBinOp) | ||
5941 | { | ||
5942 | CompValu left = GenerateFromTypeExp(val, ((TokenTypeExpBinOp)typeExp).leftOp); | ||
5943 | CompValu right = GenerateFromTypeExp(val, ((TokenTypeExpBinOp)typeExp).rightOp); | ||
5944 | CompValuTemp result = new CompValuTemp(tokenTypeBool, this); | ||
5945 | Token op = ((TokenTypeExpBinOp)typeExp).binOp; | ||
5946 | left.PushVal(this, ((TokenTypeExpBinOp)typeExp).leftOp); | ||
5947 | right.PushVal(this, ((TokenTypeExpBinOp)typeExp).rightOp); | ||
5948 | if(op is TokenKwAnd) | ||
5949 | { | ||
5950 | ilGen.Emit(typeExp, OpCodes.And); | ||
5951 | } | ||
5952 | else if(op is TokenKwOr) | ||
5953 | { | ||
5954 | ilGen.Emit(typeExp, OpCodes.Or); | ||
5955 | } | ||
5956 | else | ||
5957 | { | ||
5958 | throw new Exception("unknown TokenTypeExpBinOp " + op.GetType()); | ||
5959 | } | ||
5960 | result.Pop(this, typeExp); | ||
5961 | return result; | ||
5962 | } | ||
5963 | if(typeExp is TokenTypeExpNot) | ||
5964 | { | ||
5965 | CompValu interm = GenerateFromTypeExp(val, ((TokenTypeExpNot)typeExp).typeExp); | ||
5966 | CompValuTemp result = new CompValuTemp(tokenTypeBool, this); | ||
5967 | interm.PushVal(this, ((TokenTypeExpNot)typeExp).typeExp, tokenTypeBool); | ||
5968 | ilGen.Emit(typeExp, OpCodes.Ldc_I4_1); | ||
5969 | ilGen.Emit(typeExp, OpCodes.Xor); | ||
5970 | result.Pop(this, typeExp); | ||
5971 | return result; | ||
5972 | } | ||
5973 | if(typeExp is TokenTypeExpPar) | ||
5974 | { | ||
5975 | return GenerateFromTypeExp(val, ((TokenTypeExpPar)typeExp).typeExp); | ||
5976 | } | ||
5977 | if(typeExp is TokenTypeExpType) | ||
5978 | { | ||
5979 | CompValuTemp result = new CompValuTemp(tokenTypeBool, this); | ||
5980 | val.PushVal(this, typeExp); | ||
5981 | ilGen.Emit(typeExp, OpCodes.Isinst, ((TokenTypeExpType)typeExp).typeToken.ToSysType()); | ||
5982 | ilGen.Emit(typeExp, OpCodes.Ldnull); | ||
5983 | ilGen.Emit(typeExp, OpCodes.Ceq); | ||
5984 | ilGen.Emit(typeExp, OpCodes.Ldc_I4_1); | ||
5985 | ilGen.Emit(typeExp, OpCodes.Xor); | ||
5986 | result.Pop(this, typeExp); | ||
5987 | return result; | ||
5988 | } | ||
5989 | if(typeExp is TokenTypeExpUndef) | ||
5990 | { | ||
5991 | CompValuTemp result = new CompValuTemp(tokenTypeBool, this); | ||
5992 | val.PushVal(this, typeExp); | ||
5993 | ilGen.Emit(typeExp, OpCodes.Ldnull); | ||
5994 | ilGen.Emit(typeExp, OpCodes.Ceq); | ||
5995 | result.Pop(this, typeExp); | ||
5996 | return result; | ||
5997 | } | ||
5998 | throw new Exception("unknown TokenTypeExp type " + typeExp.GetType()); | ||
5999 | } | ||
6000 | |||
6001 | /** | ||
6002 | * @brief Push the default (null) value for a particular variable | ||
6003 | * @param var = variable to get the default value for | ||
6004 | * @returns with value pushed on stack | ||
6005 | */ | ||
6006 | public void PushVarDefaultValue(TokenDeclVar var) | ||
6007 | { | ||
6008 | PushDefaultValue(var.type); | ||
6009 | } | ||
6010 | public void PushDefaultValue(TokenType type) | ||
6011 | { | ||
6012 | if(type is TokenTypeArray) | ||
6013 | { | ||
6014 | PushXMRInst(); // instance | ||
6015 | ilGen.Emit(type, OpCodes.Newobj, xmrArrayConstructorInfo); | ||
6016 | return; | ||
6017 | } | ||
6018 | if(type is TokenTypeChar) | ||
6019 | { | ||
6020 | ilGen.Emit(type, OpCodes.Ldc_I4_0); | ||
6021 | return; | ||
6022 | } | ||
6023 | if(type is TokenTypeList) | ||
6024 | { | ||
6025 | ilGen.Emit(type, OpCodes.Ldc_I4_0); | ||
6026 | ilGen.Emit(type, OpCodes.Newarr, typeof(object)); | ||
6027 | ilGen.Emit(type, OpCodes.Newobj, lslListConstructorInfo); | ||
6028 | return; | ||
6029 | } | ||
6030 | if(type is TokenTypeRot) | ||
6031 | { | ||
6032 | // Mono is tOO stOOpid to allow: ilGen.Emit (OpCodes.Ldsfld, zeroRotationFieldInfo); | ||
6033 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.x); | ||
6034 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.y); | ||
6035 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.z); | ||
6036 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_ROTATION.s); | ||
6037 | ilGen.Emit(type, OpCodes.Newobj, lslRotationConstructorInfo); | ||
6038 | return; | ||
6039 | } | ||
6040 | if((type is TokenTypeKey) || (type is TokenTypeStr)) | ||
6041 | { | ||
6042 | ilGen.Emit(type, OpCodes.Ldstr, ""); | ||
6043 | return; | ||
6044 | } | ||
6045 | if(type is TokenTypeVec) | ||
6046 | { | ||
6047 | // Mono is tOO stOOpid to allow: ilGen.Emit (OpCodes.Ldsfld, zeroVectorFieldInfo); | ||
6048 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_VECTOR.x); | ||
6049 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_VECTOR.y); | ||
6050 | ilGen.Emit(type, OpCodes.Ldc_R8, ScriptBaseClass.ZERO_VECTOR.z); | ||
6051 | ilGen.Emit(type, OpCodes.Newobj, lslVectorConstructorInfo); | ||
6052 | return; | ||
6053 | } | ||
6054 | if(type is TokenTypeInt) | ||
6055 | { | ||
6056 | ilGen.Emit(type, OpCodes.Ldc_I4_0); | ||
6057 | return; | ||
6058 | } | ||
6059 | if(type is TokenTypeFloat) | ||
6060 | { | ||
6061 | ilGen.Emit(type, OpCodes.Ldc_R4, 0.0f); | ||
6062 | return; | ||
6063 | } | ||
6064 | |||
6065 | /* | ||
6066 | * Default for 'object' type is 'undef'. | ||
6067 | * Likewise for script-defined classes and interfaces. | ||
6068 | */ | ||
6069 | if((type is TokenTypeObject) || (type is TokenTypeSDTypeClass) || (type is TokenTypeSDTypeInterface) || (type is TokenTypeExc)) | ||
6070 | { | ||
6071 | ilGen.Emit(type, OpCodes.Ldnull); | ||
6072 | return; | ||
6073 | } | ||
6074 | |||
6075 | /* | ||
6076 | * Void is pushed as the default return value of a void function. | ||
6077 | * So just push nothing as expected of void functions. | ||
6078 | */ | ||
6079 | if(type is TokenTypeVoid) | ||
6080 | { | ||
6081 | return; | ||
6082 | } | ||
6083 | |||
6084 | /* | ||
6085 | * Default for 'delegate' type is 'undef'. | ||
6086 | */ | ||
6087 | if(type is TokenTypeSDTypeDelegate) | ||
6088 | { | ||
6089 | ilGen.Emit(type, OpCodes.Ldnull); | ||
6090 | return; | ||
6091 | } | ||
6092 | |||
6093 | throw new Exception("unknown type " + type.GetType().ToString()); | ||
6094 | } | ||
6095 | |||
6096 | /** | ||
6097 | * @brief Determine if the expression has a constant boolean value | ||
6098 | * and if so, if the value is true or false. | ||
6099 | * @param expr = expression to evaluate | ||
6100 | * @returns true: expression is contant and has boolean value true | ||
6101 | * false: otherwise | ||
6102 | */ | ||
6103 | private bool IsConstBoolExprTrue(CompValu expr) | ||
6104 | { | ||
6105 | bool constVal; | ||
6106 | return IsConstBoolExpr(expr, out constVal) && constVal; | ||
6107 | } | ||
6108 | |||
6109 | private bool IsConstBoolExpr(CompValu expr, out bool constVal) | ||
6110 | { | ||
6111 | if(expr is CompValuChar) | ||
6112 | { | ||
6113 | constVal = ((CompValuChar)expr).x != 0; | ||
6114 | return true; | ||
6115 | } | ||
6116 | if(expr is CompValuFloat) | ||
6117 | { | ||
6118 | constVal = ((CompValuFloat)expr).x != (double)0; | ||
6119 | return true; | ||
6120 | } | ||
6121 | if(expr is CompValuInteger) | ||
6122 | { | ||
6123 | constVal = ((CompValuInteger)expr).x != 0; | ||
6124 | return true; | ||
6125 | } | ||
6126 | if(expr is CompValuString) | ||
6127 | { | ||
6128 | string s = ((CompValuString)expr).x; | ||
6129 | constVal = s != ""; | ||
6130 | if(constVal && (expr.type is TokenTypeKey)) | ||
6131 | { | ||
6132 | constVal = s != ScriptBaseClass.NULL_KEY; | ||
6133 | } | ||
6134 | return true; | ||
6135 | } | ||
6136 | |||
6137 | constVal = false; | ||
6138 | return false; | ||
6139 | } | ||
6140 | |||
6141 | /** | ||
6142 | * @brief Determine if the expression has a constant integer value | ||
6143 | * and if so, return the integer value. | ||
6144 | * @param expr = expression to evaluate | ||
6145 | * @returns true: expression is contant and has integer value | ||
6146 | * false: otherwise | ||
6147 | */ | ||
6148 | private bool IsConstIntExpr(CompValu expr, out int constVal) | ||
6149 | { | ||
6150 | if(expr is CompValuChar) | ||
6151 | { | ||
6152 | constVal = (int)((CompValuChar)expr).x; | ||
6153 | return true; | ||
6154 | } | ||
6155 | if(expr is CompValuInteger) | ||
6156 | { | ||
6157 | constVal = ((CompValuInteger)expr).x; | ||
6158 | return true; | ||
6159 | } | ||
6160 | |||
6161 | constVal = 0; | ||
6162 | return false; | ||
6163 | } | ||
6164 | |||
6165 | /** | ||
6166 | * @brief Determine if the expression has a constant string value | ||
6167 | * and if so, return the string value. | ||
6168 | * @param expr = expression to evaluate | ||
6169 | * @returns true: expression is contant and has string value | ||
6170 | * false: otherwise | ||
6171 | */ | ||
6172 | private bool IsConstStrExpr(CompValu expr, out string constVal) | ||
6173 | { | ||
6174 | if(expr is CompValuString) | ||
6175 | { | ||
6176 | constVal = ((CompValuString)expr).x; | ||
6177 | return true; | ||
6178 | } | ||
6179 | constVal = ""; | ||
6180 | return false; | ||
6181 | } | ||
6182 | |||
6183 | /** | ||
6184 | * @brief create table of legal event handler prototypes. | ||
6185 | * This is used to make sure script's event handler declrations are valid. | ||
6186 | */ | ||
6187 | private static VarDict CreateLegalEventHandlers() | ||
6188 | { | ||
6189 | /* | ||
6190 | * Get handler prototypes with full argument lists. | ||
6191 | */ | ||
6192 | VarDict leh = new InternalFuncDict(typeof(IEventHandlers), false); | ||
6193 | |||
6194 | /* | ||
6195 | * We want the scripts to be able to declare their handlers with | ||
6196 | * fewer arguments than the full argument lists. So define additional | ||
6197 | * prototypes with fewer arguments. | ||
6198 | */ | ||
6199 | TokenDeclVar[] fullArgProtos = new TokenDeclVar[leh.Count]; | ||
6200 | int i = 0; | ||
6201 | foreach(TokenDeclVar fap in leh) | ||
6202 | fullArgProtos[i++] = fap; | ||
6203 | |||
6204 | foreach(TokenDeclVar fap in fullArgProtos) | ||
6205 | { | ||
6206 | TokenArgDecl fal = fap.argDecl; | ||
6207 | int fullArgCount = fal.vars.Length; | ||
6208 | for(i = 0; i < fullArgCount; i++) | ||
6209 | { | ||
6210 | TokenArgDecl shortArgList = new TokenArgDecl(null); | ||
6211 | for(int j = 0; j < i; j++) | ||
6212 | { | ||
6213 | TokenDeclVar var = fal.vars[j]; | ||
6214 | shortArgList.AddArg(var.type, var.name); | ||
6215 | } | ||
6216 | TokenDeclVar shortArgProto = new TokenDeclVar(null, null, null); | ||
6217 | shortArgProto.name = new TokenName(null, fap.GetSimpleName()); | ||
6218 | shortArgProto.retType = fap.retType; | ||
6219 | shortArgProto.argDecl = shortArgList; | ||
6220 | leh.AddEntry(shortArgProto); | ||
6221 | } | ||
6222 | } | ||
6223 | |||
6224 | return leh; | ||
6225 | } | ||
6226 | |||
6227 | /** | ||
6228 | * @brief Emit a call to CheckRun(), (voluntary multitasking switch) | ||
6229 | */ | ||
6230 | public void EmitCallCheckRun(Token errorAt, bool stack) | ||
6231 | { | ||
6232 | if(curDeclFunc.IsFuncTrivial(this)) | ||
6233 | throw new Exception(curDeclFunc.fullName + " is supposed to be trivial"); | ||
6234 | new CallLabel(this, errorAt); // jump here when stack restored | ||
6235 | PushXMRInst(); // instance | ||
6236 | ilGen.Emit(errorAt, OpCodes.Call, stack ? checkRunStackMethInfo : checkRunQuickMethInfo); | ||
6237 | openCallLabel = null; | ||
6238 | } | ||
6239 | |||
6240 | /** | ||
6241 | * @brief Emit code to push a callNo var on the stack. | ||
6242 | */ | ||
6243 | public void GetCallNo(Token errorAt, ScriptMyLocal callNoVar) | ||
6244 | { | ||
6245 | ilGen.Emit(errorAt, OpCodes.Ldloc, callNoVar); | ||
6246 | //ilGen.Emit (errorAt, OpCodes.Ldloca, callNoVar); | ||
6247 | //ilGen.Emit (errorAt, OpCodes.Volatile); | ||
6248 | //ilGen.Emit (errorAt, OpCodes.Ldind_I4); | ||
6249 | } | ||
6250 | public void GetCallNo(Token errorAt, CompValu callNoVar) | ||
6251 | { | ||
6252 | callNoVar.PushVal(this, errorAt); | ||
6253 | //callNoVar.PushRef (this, errorAt); | ||
6254 | //ilGen.Emit (errorAt, OpCodes.Volatile); | ||
6255 | //ilGen.Emit (errorAt, OpCodes.Ldind_I4); | ||
6256 | } | ||
6257 | |||
6258 | /** | ||
6259 | * @brief Emit code to set a callNo var to a given constant. | ||
6260 | */ | ||
6261 | public void SetCallNo(Token errorAt, ScriptMyLocal callNoVar, int val) | ||
6262 | { | ||
6263 | ilGen.Emit(errorAt, OpCodes.Ldc_I4, val); | ||
6264 | ilGen.Emit(errorAt, OpCodes.Stloc, callNoVar); | ||
6265 | //ilGen.Emit (errorAt, OpCodes.Ldloca, callNoVar); | ||
6266 | //ilGen.Emit (errorAt, OpCodes.Ldc_I4, val); | ||
6267 | //ilGen.Emit (errorAt, OpCodes.Volatile); | ||
6268 | //ilGen.Emit (errorAt, OpCodes.Stind_I4); | ||
6269 | } | ||
6270 | public void SetCallNo(Token errorAt, CompValu callNoVar, int val) | ||
6271 | { | ||
6272 | callNoVar.PopPre(this, errorAt); | ||
6273 | ilGen.Emit(errorAt, OpCodes.Ldc_I4, val); | ||
6274 | callNoVar.PopPost(this, errorAt); | ||
6275 | //callNoVar.PushRef (this, errorAt); | ||
6276 | //ilGen.Emit (errorAt, OpCodes.Ldc_I4, val); | ||
6277 | //ilGen.Emit (errorAt, OpCodes.Volatile); | ||
6278 | //ilGen.Emit (errorAt, OpCodes.Stind_I4); | ||
6279 | } | ||
6280 | |||
6281 | /** | ||
6282 | * @brief handle a unary operator, such as -x. | ||
6283 | */ | ||
6284 | private CompValu UnOpGenerate(CompValu inRVal, Token opcode) | ||
6285 | { | ||
6286 | /* | ||
6287 | * - Negate | ||
6288 | */ | ||
6289 | if(opcode is TokenKwSub) | ||
6290 | { | ||
6291 | if(inRVal.type is TokenTypeFloat) | ||
6292 | { | ||
6293 | CompValuTemp outRVal = new CompValuTemp(new TokenTypeFloat(opcode), this); | ||
6294 | inRVal.PushVal(this, opcode, outRVal.type); // push value to negate, make sure not LSL-boxed | ||
6295 | ilGen.Emit(opcode, OpCodes.Neg); // compute the negative | ||
6296 | outRVal.Pop(this, opcode); // pop into result | ||
6297 | return outRVal; // tell caller where we put it | ||
6298 | } | ||
6299 | if(inRVal.type is TokenTypeInt) | ||
6300 | { | ||
6301 | CompValuTemp outRVal = new CompValuTemp(new TokenTypeInt(opcode), this); | ||
6302 | inRVal.PushVal(this, opcode, outRVal.type); // push value to negate, make sure not LSL-boxed | ||
6303 | ilGen.Emit(opcode, OpCodes.Neg); // compute the negative | ||
6304 | outRVal.Pop(this, opcode); // pop into result | ||
6305 | return outRVal; // tell caller where we put it | ||
6306 | } | ||
6307 | if(inRVal.type is TokenTypeRot) | ||
6308 | { | ||
6309 | CompValuTemp outRVal = new CompValuTemp(inRVal.type, this); | ||
6310 | inRVal.PushVal(this, opcode); // push rotation, then call negate routine | ||
6311 | ilGen.Emit(opcode, OpCodes.Call, lslRotationNegateMethodInfo); | ||
6312 | outRVal.Pop(this, opcode); // pop into result | ||
6313 | return outRVal; // tell caller where we put it | ||
6314 | } | ||
6315 | if(inRVal.type is TokenTypeVec) | ||
6316 | { | ||
6317 | CompValuTemp outRVal = new CompValuTemp(inRVal.type, this); | ||
6318 | inRVal.PushVal(this, opcode); // push vector, then call negate routine | ||
6319 | ilGen.Emit(opcode, OpCodes.Call, lslVectorNegateMethodInfo); | ||
6320 | outRVal.Pop(this, opcode); // pop into result | ||
6321 | return outRVal; // tell caller where we put it | ||
6322 | } | ||
6323 | ErrorMsg(opcode, "can't negate a " + inRVal.type.ToString()); | ||
6324 | return inRVal; | ||
6325 | } | ||
6326 | |||
6327 | /* | ||
6328 | * ~ Complement (bitwise integer) | ||
6329 | */ | ||
6330 | if(opcode is TokenKwTilde) | ||
6331 | { | ||
6332 | if(inRVal.type is TokenTypeInt) | ||
6333 | { | ||
6334 | CompValuTemp outRVal = new CompValuTemp(new TokenTypeInt(opcode), this); | ||
6335 | inRVal.PushVal(this, opcode, outRVal.type); // push value to negate, make sure not LSL-boxed | ||
6336 | ilGen.Emit(opcode, OpCodes.Not); // compute the complement | ||
6337 | outRVal.Pop(this, opcode); // pop into result | ||
6338 | return outRVal; // tell caller where we put it | ||
6339 | } | ||
6340 | ErrorMsg(opcode, "can't complement a " + inRVal.type.ToString()); | ||
6341 | return inRVal; | ||
6342 | } | ||
6343 | |||
6344 | /* | ||
6345 | * ! Not (boolean) | ||
6346 | * | ||
6347 | * We stuff the 0/1 result in an int because I've seen x+!y in scripts | ||
6348 | * and we don't want to have to create tables to handle int+bool and | ||
6349 | * everything like that. | ||
6350 | */ | ||
6351 | if(opcode is TokenKwExclam) | ||
6352 | { | ||
6353 | CompValuTemp outRVal = new CompValuTemp(new TokenTypeInt(opcode), this); | ||
6354 | inRVal.PushVal(this, opcode, tokenTypeBool); // anything converts to boolean | ||
6355 | ilGen.Emit(opcode, OpCodes.Ldc_I4_1); // then XOR with 1 to flip it | ||
6356 | ilGen.Emit(opcode, OpCodes.Xor); | ||
6357 | outRVal.Pop(this, opcode); // pop into result | ||
6358 | return outRVal; // tell caller where we put it | ||
6359 | } | ||
6360 | |||
6361 | throw new Exception("unhandled opcode " + opcode.ToString()); | ||
6362 | } | ||
6363 | |||
6364 | /** | ||
6365 | * @brief This is called while trying to compute the value of constant initializers. | ||
6366 | * It is passed a name and that name is looked up in the constant tables. | ||
6367 | */ | ||
6368 | private TokenRVal LookupInitConstants(TokenRVal rVal, ref bool didOne) | ||
6369 | { | ||
6370 | /* | ||
6371 | * If it is a static field of a script-defined type, look it up and hopefully we find a constant there. | ||
6372 | */ | ||
6373 | TokenDeclVar gblVar; | ||
6374 | if(rVal is TokenLValSField) | ||
6375 | { | ||
6376 | TokenLValSField lvsf = (TokenLValSField)rVal; | ||
6377 | if(lvsf.baseType is TokenTypeSDTypeClass) | ||
6378 | { | ||
6379 | TokenDeclSDTypeClass sdtClass = ((TokenTypeSDTypeClass)lvsf.baseType).decl; | ||
6380 | gblVar = sdtClass.members.FindExact(lvsf.fieldName.val, null); | ||
6381 | if(gblVar != null) | ||
6382 | { | ||
6383 | if(gblVar.constant && (gblVar.init is TokenRValConst)) | ||
6384 | { | ||
6385 | didOne = true; | ||
6386 | return gblVar.init; | ||
6387 | } | ||
6388 | } | ||
6389 | } | ||
6390 | return rVal; | ||
6391 | } | ||
6392 | |||
6393 | /* | ||
6394 | * Only other thing we handle is stand-alone names. | ||
6395 | */ | ||
6396 | if(!(rVal is TokenLValName)) | ||
6397 | return rVal; | ||
6398 | string name = ((TokenLValName)rVal).name.val; | ||
6399 | |||
6400 | /* | ||
6401 | * If we are doing the initializations for a script-defined type, | ||
6402 | * look for the constant among the fields for that type. | ||
6403 | */ | ||
6404 | if(currentSDTClass != null) | ||
6405 | { | ||
6406 | gblVar = currentSDTClass.members.FindExact(name, null); | ||
6407 | if(gblVar != null) | ||
6408 | { | ||
6409 | if(gblVar.constant && (gblVar.init is TokenRValConst)) | ||
6410 | { | ||
6411 | didOne = true; | ||
6412 | return gblVar.init; | ||
6413 | } | ||
6414 | return rVal; | ||
6415 | } | ||
6416 | } | ||
6417 | |||
6418 | /* | ||
6419 | * Look it up as a script-defined global variable. | ||
6420 | * Then if the variable is defined as a constant and has a constant value, | ||
6421 | * we are successful. If it is defined as something else, return failure. | ||
6422 | */ | ||
6423 | gblVar = tokenScript.variablesStack.FindExact(name, null); | ||
6424 | if(gblVar != null) | ||
6425 | { | ||
6426 | if(gblVar.constant && (gblVar.init is TokenRValConst)) | ||
6427 | { | ||
6428 | didOne = true; | ||
6429 | return gblVar.init; | ||
6430 | } | ||
6431 | return rVal; | ||
6432 | } | ||
6433 | |||
6434 | /* | ||
6435 | * Maybe it is a built-in symbolic constant. | ||
6436 | */ | ||
6437 | ScriptConst scriptConst = ScriptConst.Lookup(name); | ||
6438 | if(scriptConst != null) | ||
6439 | { | ||
6440 | rVal = CompValuConst2RValConst(scriptConst.rVal, rVal); | ||
6441 | if(rVal is TokenRValConst) | ||
6442 | { | ||
6443 | didOne = true; | ||
6444 | return rVal; | ||
6445 | } | ||
6446 | } | ||
6447 | |||
6448 | /* | ||
6449 | * Don't know what it is, return failure. | ||
6450 | */ | ||
6451 | return rVal; | ||
6452 | } | ||
6453 | |||
6454 | /** | ||
6455 | * @brief This is called while trying to compute the value of constant expressions. | ||
6456 | * It is passed a name and that name is looked up in the constant tables. | ||
6457 | */ | ||
6458 | private TokenRVal LookupBodyConstants(TokenRVal rVal, ref bool didOne) | ||
6459 | { | ||
6460 | /* | ||
6461 | * If it is a static field of a script-defined type, look it up and hopefully we find a constant there. | ||
6462 | */ | ||
6463 | TokenDeclVar gblVar; | ||
6464 | if(rVal is TokenLValSField) | ||
6465 | { | ||
6466 | TokenLValSField lvsf = (TokenLValSField)rVal; | ||
6467 | if(lvsf.baseType is TokenTypeSDTypeClass) | ||
6468 | { | ||
6469 | TokenDeclSDTypeClass sdtClass = ((TokenTypeSDTypeClass)lvsf.baseType).decl; | ||
6470 | gblVar = sdtClass.members.FindExact(lvsf.fieldName.val, null); | ||
6471 | if((gblVar != null) && gblVar.constant && (gblVar.init is TokenRValConst)) | ||
6472 | { | ||
6473 | didOne = true; | ||
6474 | return gblVar.init; | ||
6475 | } | ||
6476 | } | ||
6477 | return rVal; | ||
6478 | } | ||
6479 | |||
6480 | /* | ||
6481 | * Only other thing we handle is stand-alone names. | ||
6482 | */ | ||
6483 | if(!(rVal is TokenLValName)) | ||
6484 | return rVal; | ||
6485 | string name = ((TokenLValName)rVal).name.val; | ||
6486 | |||
6487 | /* | ||
6488 | * Scan through the variable stack and hopefully we find a constant there. | ||
6489 | * But we stop as soon as we get a match because that's what the script is referring to. | ||
6490 | */ | ||
6491 | CompValu val; | ||
6492 | for(VarDict vars = ((TokenLValName)rVal).stack; vars != null; vars = vars.outerVarDict) | ||
6493 | { | ||
6494 | TokenDeclVar var = vars.FindExact(name, null); | ||
6495 | if(var != null) | ||
6496 | { | ||
6497 | val = var.location; | ||
6498 | goto foundit; | ||
6499 | } | ||
6500 | |||
6501 | TokenDeclSDTypeClass baseClass = vars.thisClass; | ||
6502 | if(baseClass != null) | ||
6503 | { | ||
6504 | while((baseClass = baseClass.extends) != null) | ||
6505 | { | ||
6506 | var = baseClass.members.FindExact(name, null); | ||
6507 | if(var != null) | ||
6508 | { | ||
6509 | val = var.location; | ||
6510 | goto foundit; | ||
6511 | } | ||
6512 | } | ||
6513 | } | ||
6514 | } | ||
6515 | |||
6516 | /* | ||
6517 | * Maybe it is a built-in symbolic constant. | ||
6518 | */ | ||
6519 | ScriptConst scriptConst = ScriptConst.Lookup(name); | ||
6520 | if(scriptConst != null) | ||
6521 | { | ||
6522 | val = scriptConst.rVal; | ||
6523 | goto foundit; | ||
6524 | } | ||
6525 | |||
6526 | /* | ||
6527 | * Don't know what it is, return failure. | ||
6528 | */ | ||
6529 | return rVal; | ||
6530 | |||
6531 | /* | ||
6532 | * Found a CompValu. If it's a simple constant, then use it. | ||
6533 | * Otherwise tell caller we failed to simplify. | ||
6534 | */ | ||
6535 | foundit: | ||
6536 | rVal = CompValuConst2RValConst(val, rVal); | ||
6537 | if(rVal is TokenRValConst) | ||
6538 | { | ||
6539 | didOne = true; | ||
6540 | } | ||
6541 | return rVal; | ||
6542 | } | ||
6543 | |||
6544 | private static TokenRVal CompValuConst2RValConst(CompValu val, TokenRVal rVal) | ||
6545 | { | ||
6546 | if(val is CompValuChar) | ||
6547 | rVal = new TokenRValConst(rVal, ((CompValuChar)val).x); | ||
6548 | if(val is CompValuFloat) | ||
6549 | rVal = new TokenRValConst(rVal, ((CompValuFloat)val).x); | ||
6550 | if(val is CompValuInteger) | ||
6551 | rVal = new TokenRValConst(rVal, ((CompValuInteger)val).x); | ||
6552 | if(val is CompValuString) | ||
6553 | rVal = new TokenRValConst(rVal, ((CompValuString)val).x); | ||
6554 | return rVal; | ||
6555 | } | ||
6556 | |||
6557 | /** | ||
6558 | * @brief Generate code to push XMRInstanceSuperType pointer on stack. | ||
6559 | */ | ||
6560 | public void PushXMRInst() | ||
6561 | { | ||
6562 | if(instancePointer == null) | ||
6563 | { | ||
6564 | ilGen.Emit(null, OpCodes.Ldarg_0); | ||
6565 | } | ||
6566 | else | ||
6567 | { | ||
6568 | ilGen.Emit(null, OpCodes.Ldloc, instancePointer); | ||
6569 | } | ||
6570 | } | ||
6571 | |||
6572 | /** | ||
6573 | * @returns true: Ldarg_0 gives XMRSDTypeClObj pointer | ||
6574 | * - this is the case for instance methods | ||
6575 | * false: Ldarg_0 gives XMR_Instance pointer | ||
6576 | * - this is the case for both global functions and static methods | ||
6577 | */ | ||
6578 | public bool IsSDTInstMethod() | ||
6579 | { | ||
6580 | return (curDeclFunc.sdtClass != null) && | ||
6581 | ((curDeclFunc.sdtFlags & ScriptReduce.SDT_STATIC) == 0); | ||
6582 | } | ||
6583 | |||
6584 | /** | ||
6585 | * @brief Look for a simply named function or variable (not a field or method) | ||
6586 | */ | ||
6587 | public TokenDeclVar FindNamedVar(TokenLValName lValName, TokenType[] argsig) | ||
6588 | { | ||
6589 | /* | ||
6590 | * Look in variable stack for the given name. | ||
6591 | */ | ||
6592 | for(VarDict vars = lValName.stack; vars != null; vars = vars.outerVarDict) | ||
6593 | { | ||
6594 | |||
6595 | // first look for it possibly with an argument signature | ||
6596 | // so we pick the correct overloaded method | ||
6597 | TokenDeclVar var = FindSingleMember(vars, lValName.name, argsig); | ||
6598 | if(var != null) | ||
6599 | return var; | ||
6600 | |||
6601 | // if that fails, try it without the argument signature. | ||
6602 | // delegates get entered like any other variable, ie, | ||
6603 | // no signature on their name. | ||
6604 | if(argsig != null) | ||
6605 | { | ||
6606 | var = FindSingleMember(vars, lValName.name, null); | ||
6607 | if(var != null) | ||
6608 | return var; | ||
6609 | } | ||
6610 | |||
6611 | // if this is the frame for some class members, try searching base class members too | ||
6612 | TokenDeclSDTypeClass baseClass = vars.thisClass; | ||
6613 | if(baseClass != null) | ||
6614 | { | ||
6615 | while((baseClass = baseClass.extends) != null) | ||
6616 | { | ||
6617 | var = FindSingleMember(baseClass.members, lValName.name, argsig); | ||
6618 | if(var != null) | ||
6619 | return var; | ||
6620 | if(argsig != null) | ||
6621 | { | ||
6622 | var = FindSingleMember(baseClass.members, lValName.name, null); | ||
6623 | if(var != null) | ||
6624 | return var; | ||
6625 | } | ||
6626 | } | ||
6627 | } | ||
6628 | } | ||
6629 | |||
6630 | /* | ||
6631 | * If not found, try one of the built-in constants or functions. | ||
6632 | */ | ||
6633 | if(argsig == null) | ||
6634 | { | ||
6635 | ScriptConst scriptConst = ScriptConst.Lookup(lValName.name.val); | ||
6636 | if(scriptConst != null) | ||
6637 | { | ||
6638 | TokenDeclVar var = new TokenDeclVar(lValName.name, null, tokenScript); | ||
6639 | var.name = lValName.name; | ||
6640 | var.type = scriptConst.rVal.type; | ||
6641 | var.location = scriptConst.rVal; | ||
6642 | return var; | ||
6643 | } | ||
6644 | } | ||
6645 | else | ||
6646 | { | ||
6647 | TokenDeclVar inline = FindSingleMember(TokenDeclInline.inlineFunctions, lValName.name, argsig); | ||
6648 | if(inline != null) | ||
6649 | return inline; | ||
6650 | } | ||
6651 | |||
6652 | return null; | ||
6653 | } | ||
6654 | |||
6655 | |||
6656 | /** | ||
6657 | * @brief Find a member of an interface. | ||
6658 | * @param sdType = interface type | ||
6659 | * @param name = name of member to find | ||
6660 | * @param argsig = null: field/property; else: script-visible method argument types | ||
6661 | * @param baseRVal = pointer to interface object | ||
6662 | * @returns null: no such member | ||
6663 | * else: pointer to member | ||
6664 | * baseRVal = possibly modified to point to type-casted interface object | ||
6665 | */ | ||
6666 | private TokenDeclVar FindInterfaceMember(TokenTypeSDTypeInterface sdtType, TokenName name, TokenType[] argsig, ref CompValu baseRVal) | ||
6667 | { | ||
6668 | TokenDeclSDTypeInterface sdtDecl = sdtType.decl; | ||
6669 | TokenDeclSDTypeInterface impl; | ||
6670 | TokenDeclVar declVar = sdtDecl.FindIFaceMember(this, name, argsig, out impl); | ||
6671 | if((declVar != null) && (impl != sdtDecl)) | ||
6672 | { | ||
6673 | |||
6674 | /* | ||
6675 | * Accessing a method or propterty of another interface that the primary interface says it implements. | ||
6676 | * In this case, we have to cast from the primary interface to that secondary interface. | ||
6677 | * | ||
6678 | * interface IEnumerable { | ||
6679 | * IEnumerator GetEnumerator (); | ||
6680 | * } | ||
6681 | * interface ICountable : IEnumerable { | ||
6682 | * integer GetCount (); | ||
6683 | * } | ||
6684 | * class List : ICountable { | ||
6685 | * public GetCount () : ICountable { ... } | ||
6686 | * public GetEnumerator () : IEnumerable { ... } | ||
6687 | * } | ||
6688 | * | ||
6689 | * ICountable aList = new List (); | ||
6690 | * IEnumerator anEnumer = aList.GetEnumerator (); << we are here | ||
6691 | * << baseRVal = aList | ||
6692 | * << sdtDecl = ICountable | ||
6693 | * << impl = IEnumerable | ||
6694 | * << name = GetEnumerator | ||
6695 | * << argsig = () | ||
6696 | * So we have to cast aList from ICountable to IEnumerable. | ||
6697 | */ | ||
6698 | |||
6699 | // make type token for the secondary interface type | ||
6700 | TokenType subIntfType = impl.MakeRefToken(name); | ||
6701 | |||
6702 | // make a temp variable of the secondary interface type | ||
6703 | CompValuTemp castBase = new CompValuTemp(subIntfType, this); | ||
6704 | |||
6705 | // output code to cast from the primary interface to the secondary interface | ||
6706 | // this is 2 basic steps: | ||
6707 | // 1) cast from primary interface object -> class object | ||
6708 | // ...gets it from interfaceObject.delegateArray[0].Target | ||
6709 | // 2) cast from class object -> secondary interface object | ||
6710 | // ...gets it from classObject.sdtcITable[interfaceIndex] | ||
6711 | baseRVal.PushVal(this, name, subIntfType); | ||
6712 | |||
6713 | // save result of casting in temp | ||
6714 | castBase.Pop(this, name); | ||
6715 | |||
6716 | // return temp reference | ||
6717 | baseRVal = castBase; | ||
6718 | } | ||
6719 | |||
6720 | return declVar; | ||
6721 | } | ||
6722 | |||
6723 | /** | ||
6724 | * @brief Find a member of a script-defined type class. | ||
6725 | * @param sdtType = reference to class declaration | ||
6726 | * @param name = name of member to find | ||
6727 | * @param argsig = argument signature used to select among overloaded members | ||
6728 | * @returns null: no such member found | ||
6729 | * else: the member found | ||
6730 | */ | ||
6731 | public TokenDeclVar FindThisMember(TokenTypeSDTypeClass sdtType, TokenName name, TokenType[] argsig) | ||
6732 | { | ||
6733 | return FindThisMember(sdtType.decl, name, argsig); | ||
6734 | } | ||
6735 | public TokenDeclVar FindThisMember(TokenDeclSDTypeClass sdtDecl, TokenName name, TokenType[] argsig) | ||
6736 | { | ||
6737 | for(TokenDeclSDTypeClass sdtd = sdtDecl; sdtd != null; sdtd = sdtd.extends) | ||
6738 | { | ||
6739 | TokenDeclVar declVar = FindSingleMember(sdtd.members, name, argsig); | ||
6740 | if(declVar != null) | ||
6741 | return declVar; | ||
6742 | } | ||
6743 | return null; | ||
6744 | } | ||
6745 | |||
6746 | /** | ||
6747 | * @brief Look for a single member that matches the given name and argument signature | ||
6748 | * @param where = which dictionary to look in | ||
6749 | * @param name = basic name of the field or method, eg, "Printable" | ||
6750 | * @param argsig = argument types the method is being called with, eg, "(string)" | ||
6751 | * or null to find a field | ||
6752 | * @returns null: no member found | ||
6753 | * else: the member found | ||
6754 | */ | ||
6755 | public TokenDeclVar FindSingleMember(VarDict where, TokenName name, TokenType[] argsig) | ||
6756 | { | ||
6757 | TokenDeclVar[] members = where.FindCallables(name.val, argsig); | ||
6758 | if(members == null) | ||
6759 | return null; | ||
6760 | if(members.Length > 1) | ||
6761 | { | ||
6762 | ErrorMsg(name, "more than one matching member"); | ||
6763 | for(int i = 0; i < members.Length; i++) | ||
6764 | { | ||
6765 | ErrorMsg(members[i], " " + members[i].argDecl.GetArgSig()); | ||
6766 | } | ||
6767 | } | ||
6768 | return members[0]; | ||
6769 | } | ||
6770 | |||
6771 | /** | ||
6772 | * @brief Find an exact function name and argument signature match. | ||
6773 | * Also verify that the return value type is an exact match. | ||
6774 | * @param where = which method dictionary to look in | ||
6775 | * @param name = basic name of the method, eg, "Printable" | ||
6776 | * @param ret = expected return value type | ||
6777 | * @param argsig = argument types the method is being called with, eg, "(string)" | ||
6778 | * @returns null: no exact match found | ||
6779 | * else: the matching function | ||
6780 | */ | ||
6781 | private TokenDeclVar FindExactWithRet(VarDict where, TokenName name, TokenType ret, TokenType[] argsig) | ||
6782 | { | ||
6783 | TokenDeclVar func = where.FindExact(name.val, argsig); | ||
6784 | if((func != null) && (func.retType.ToString() != ret.ToString())) | ||
6785 | { | ||
6786 | ErrorMsg(name, "return type mismatch, have " + func.retType.ToString() + ", expect " + ret.ToString()); | ||
6787 | } | ||
6788 | if(func != null) | ||
6789 | CheckAccess(func, name); | ||
6790 | return func; | ||
6791 | } | ||
6792 | |||
6793 | /** | ||
6794 | * @brief Check the private/protected/public access flags of a member. | ||
6795 | */ | ||
6796 | private void CheckAccess(TokenDeclVar var, Token errorAt) | ||
6797 | { | ||
6798 | TokenDeclSDType nested; | ||
6799 | TokenDeclSDType definedBy = var.sdtClass; | ||
6800 | TokenDeclSDType accessedBy = curDeclFunc.sdtClass; | ||
6801 | |||
6802 | /*******************************\ | ||
6803 | * Check member-level access * | ||
6804 | \*******************************/ | ||
6805 | |||
6806 | /* | ||
6807 | * Note that if accessedBy is null, ie, accessing from global function (or event handlers), | ||
6808 | * anything tagged as SDT_PRIVATE or SDT_PROTECTED will fail. | ||
6809 | */ | ||
6810 | |||
6811 | /* | ||
6812 | * Private means accessed by the class that defined the member or accessed by a nested class | ||
6813 | * of the class that defined the member. | ||
6814 | */ | ||
6815 | if((var.sdtFlags & ScriptReduce.SDT_PRIVATE) != 0) | ||
6816 | { | ||
6817 | for(nested = accessedBy; nested != null; nested = nested.outerSDType) | ||
6818 | { | ||
6819 | if(nested == definedBy) | ||
6820 | goto acc1ok; | ||
6821 | } | ||
6822 | ErrorMsg(errorAt, "private member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName); | ||
6823 | return; | ||
6824 | } | ||
6825 | |||
6826 | /* | ||
6827 | * Protected means: | ||
6828 | * If being accessed by an inner class, the inner class has access to it if the inner class derives | ||
6829 | * from the declaring class. It also has access to it if an outer class derives from the declaring | ||
6830 | * class. | ||
6831 | */ | ||
6832 | if((var.sdtFlags & ScriptReduce.SDT_PROTECTED) != 0) | ||
6833 | { | ||
6834 | for(nested = accessedBy; nested != null; nested = nested.outerSDType) | ||
6835 | { | ||
6836 | for(TokenDeclSDType rootward = nested; rootward != null; rootward = rootward.extends) | ||
6837 | { | ||
6838 | if(rootward == definedBy) | ||
6839 | goto acc1ok; | ||
6840 | } | ||
6841 | } | ||
6842 | ErrorMsg(errorAt, "protected member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName); | ||
6843 | return; | ||
6844 | } | ||
6845 | acc1ok: | ||
6846 | |||
6847 | /******************************\ | ||
6848 | * Check class-level access * | ||
6849 | \******************************/ | ||
6850 | |||
6851 | /* | ||
6852 | * If being accessed by same or inner class than where defined, it is ok. | ||
6853 | * | ||
6854 | * class DefiningClass { | ||
6855 | * varBeingAccessed; | ||
6856 | * . | ||
6857 | * . | ||
6858 | * . | ||
6859 | * class AccessingClass { | ||
6860 | * functionDoingAccess() { } | ||
6861 | * } | ||
6862 | * . | ||
6863 | * . | ||
6864 | * . | ||
6865 | * } | ||
6866 | */ | ||
6867 | nested = accessedBy; | ||
6868 | while(true) | ||
6869 | { | ||
6870 | if(nested == definedBy) | ||
6871 | return; | ||
6872 | if(nested == null) | ||
6873 | break; | ||
6874 | nested = (TokenDeclSDTypeClass)nested.outerSDType; | ||
6875 | } | ||
6876 | |||
6877 | /* | ||
6878 | * It is being accessed by an outer class than where defined, | ||
6879 | * check for a 'private' or 'protected' class tag that blocks. | ||
6880 | */ | ||
6881 | do | ||
6882 | { | ||
6883 | |||
6884 | /* | ||
6885 | * If the field's class is defined directly inside the accessing class, | ||
6886 | * access is allowed regardless of class-level private or protected tags. | ||
6887 | * | ||
6888 | * class AccessingClass { | ||
6889 | * functionDoingAccess() { } | ||
6890 | * class DefiningClass { | ||
6891 | * varBeingAccessed; | ||
6892 | * } | ||
6893 | * } | ||
6894 | */ | ||
6895 | if(definedBy.outerSDType == accessedBy) | ||
6896 | return; | ||
6897 | |||
6898 | /* | ||
6899 | * If the field's class is defined two or more levels inside the accessing class, | ||
6900 | * access is denied if the defining class is tagged private. | ||
6901 | * | ||
6902 | * class AccessingClass { | ||
6903 | * functionDoingAccess() { } | ||
6904 | * . | ||
6905 | * . | ||
6906 | * . | ||
6907 | * class IntermediateClass { | ||
6908 | * private class DefiningClass { | ||
6909 | * varBeingAccessed; | ||
6910 | * } | ||
6911 | * } | ||
6912 | * . | ||
6913 | * . | ||
6914 | * . | ||
6915 | * } | ||
6916 | */ | ||
6917 | if((definedBy.accessLevel & ScriptReduce.SDT_PRIVATE) != 0) | ||
6918 | { | ||
6919 | ErrorMsg(errorAt, "member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName + | ||
6920 | " because of private class " + definedBy.longName.val); | ||
6921 | return; | ||
6922 | } | ||
6923 | |||
6924 | /* | ||
6925 | * Likewise, if DefiningClass is tagged protected, the AccessingClass must derive from the | ||
6926 | * IntermediateClass or access is denied. | ||
6927 | */ | ||
6928 | if((definedBy.accessLevel & ScriptReduce.SDT_PROTECTED) != 0) | ||
6929 | { | ||
6930 | for(TokenDeclSDType extends = accessedBy; extends != definedBy.outerSDType; extends = extends.extends) | ||
6931 | { | ||
6932 | if(extends == null) | ||
6933 | { | ||
6934 | ErrorMsg(errorAt, "member " + var.fullName + " cannot be accessed by " + curDeclFunc.fullName + | ||
6935 | " because of protected class " + definedBy.longName.val); | ||
6936 | return; | ||
6937 | } | ||
6938 | } | ||
6939 | } | ||
6940 | |||
6941 | /* | ||
6942 | * Check next outer level. | ||
6943 | */ | ||
6944 | definedBy = definedBy.outerSDType; | ||
6945 | } while(definedBy != null); | ||
6946 | } | ||
6947 | |||
6948 | /** | ||
6949 | * @brief Convert a list of argument types to printable string, eg, "(list,string,float,integer)" | ||
6950 | * If given a null, return "" indicating it is a field not a method | ||
6951 | */ | ||
6952 | public static string ArgSigString(TokenType[] argsig) | ||
6953 | { | ||
6954 | if(argsig == null) | ||
6955 | return ""; | ||
6956 | StringBuilder sb = new StringBuilder("("); | ||
6957 | for(int i = 0; i < argsig.Length; i++) | ||
6958 | { | ||
6959 | if(i > 0) | ||
6960 | sb.Append(","); | ||
6961 | sb.Append(argsig[i].ToString()); | ||
6962 | } | ||
6963 | sb.Append(")"); | ||
6964 | return sb.ToString(); | ||
6965 | } | ||
6966 | |||
6967 | /** | ||
6968 | * @brief output error message and remember that we did | ||
6969 | */ | ||
6970 | public void ErrorMsg(Token token, string message) | ||
6971 | { | ||
6972 | if((token == null) || (token.emsg == null)) | ||
6973 | token = errorMessageToken; | ||
6974 | if(!youveAnError || (token.file != lastErrorFile) || (token.line > lastErrorLine)) | ||
6975 | { | ||
6976 | token.ErrorMsg(message); | ||
6977 | youveAnError = true; | ||
6978 | lastErrorFile = token.file; | ||
6979 | lastErrorLine = token.line; | ||
6980 | } | ||
6981 | } | ||
6982 | |||
6983 | /** | ||
6984 | * @brief Find a private static method. | ||
6985 | * @param owner = class the method is part of | ||
6986 | * @param name = name of method to find | ||
6987 | * @param args = array of argument types | ||
6988 | * @returns pointer to method | ||
6989 | */ | ||
6990 | public static MethodInfo GetStaticMethod(Type owner, string name, Type[] args) | ||
6991 | { | ||
6992 | MethodInfo mi = owner.GetMethod(name, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, args, null); | ||
6993 | if(mi == null) | ||
6994 | { | ||
6995 | throw new Exception("undefined method " + owner.ToString() + "." + name); | ||
6996 | } | ||
6997 | return mi; | ||
6998 | } | ||
6999 | |||
7000 | // http://wiki.secondlife.com/wiki/Rotation 'negate a rotation' says just negate .s component | ||
7001 | // but http://wiki.secondlife.com/wiki/LSL_Language_Test (lslangtest1.lsl) says negate all 4 values | ||
7002 | public static LSL_Rotation LSLRotationNegate(LSL_Rotation r) | ||
7003 | { | ||
7004 | return new LSL_Rotation(-r.x, -r.y, -r.z, -r.s); | ||
7005 | } | ||
7006 | public static LSL_Vector LSLVectorNegate(LSL_Vector v) | ||
7007 | { | ||
7008 | return -v; | ||
7009 | } | ||
7010 | public static string CatchExcToStr(Exception exc) | ||
7011 | { | ||
7012 | return exc.ToString(); | ||
7013 | } | ||
7014 | //public static void ConsoleWrite (string str) { Console.Write(str); } | ||
7015 | |||
7016 | /** | ||
7017 | * @brief Defines an internal label that is used as a target for 'break' and 'continue' statements. | ||
7018 | */ | ||
7019 | private class BreakContTarg | ||
7020 | { | ||
7021 | public bool used; | ||
7022 | public ScriptMyLabel label; | ||
7023 | public TokenStmtBlock block; | ||
7024 | |||
7025 | public BreakContTarg(ScriptCodeGen scg, string name) | ||
7026 | { | ||
7027 | used = false; // assume it isn't referenced at all | ||
7028 | label = scg.ilGen.DefineLabel(name); // label that the break/continue jumps to | ||
7029 | block = scg.curStmtBlock; // { ... } that the break/continue label is in | ||
7030 | } | ||
7031 | } | ||
7032 | } | ||
7033 | |||
7034 | /** | ||
7035 | * @brief Marker interface indicates an exception that can't be caught by a script-level try/catch. | ||
7036 | */ | ||
7037 | public interface IXMRUncatchable | ||
7038 | { | ||
7039 | } | ||
7040 | |||
7041 | /** | ||
7042 | * @brief Thrown by a script when it attempts to change to an undefined state. | ||
7043 | * These can be detected at compile time but the moron XEngine compiles | ||
7044 | * such things, so we compile them as runtime errors. | ||
7045 | */ | ||
7046 | [SerializableAttribute] | ||
7047 | public class ScriptUndefinedStateException: Exception, ISerializable | ||
7048 | { | ||
7049 | public string stateName; | ||
7050 | public ScriptUndefinedStateException(string stateName) : base("undefined state " + stateName) | ||
7051 | { | ||
7052 | this.stateName = stateName; | ||
7053 | } | ||
7054 | protected ScriptUndefinedStateException(SerializationInfo info, StreamingContext context) : base(info, context) | ||
7055 | { | ||
7056 | } | ||
7057 | } | ||
7058 | |||
7059 | /** | ||
7060 | * @brief Created by a throw statement. | ||
7061 | */ | ||
7062 | [SerializableAttribute] | ||
7063 | public class ScriptThrownException: Exception, ISerializable | ||
7064 | { | ||
7065 | public object thrown; | ||
7066 | |||
7067 | /** | ||
7068 | * @brief Called by a throw statement to wrap the object in a unique | ||
7069 | * tag that capable of capturing a stack trace. Script can | ||
7070 | * unwrap it by calling xmrExceptionThrownValue(). | ||
7071 | */ | ||
7072 | public static Exception Wrap(object thrown) | ||
7073 | { | ||
7074 | return new ScriptThrownException(thrown); | ||
7075 | } | ||
7076 | private ScriptThrownException(object thrown) : base(thrown.ToString()) | ||
7077 | { | ||
7078 | this.thrown = thrown; | ||
7079 | } | ||
7080 | |||
7081 | /** | ||
7082 | * @brief Used by serialization/deserialization. | ||
7083 | */ | ||
7084 | protected ScriptThrownException(SerializationInfo info, StreamingContext context) : base(info, context) | ||
7085 | { | ||
7086 | } | ||
7087 | } | ||
7088 | |||
7089 | /** | ||
7090 | * @brief Thrown by a script when it attempts to change to a defined state. | ||
7091 | */ | ||
7092 | [SerializableAttribute] | ||
7093 | public class ScriptChangeStateException: Exception, ISerializable, IXMRUncatchable | ||
7094 | { | ||
7095 | public int newState; | ||
7096 | public ScriptChangeStateException(int newState) | ||
7097 | { | ||
7098 | this.newState = newState; | ||
7099 | } | ||
7100 | protected ScriptChangeStateException(SerializationInfo info, StreamingContext context) : base(info, context) | ||
7101 | { | ||
7102 | } | ||
7103 | } | ||
7104 | |||
7105 | /** | ||
7106 | * @brief We are restoring to the body of a catch { } so we need to | ||
7107 | * wrap the original exception in an outer exception, so the | ||
7108 | * system won't try to refill the stack trace. | ||
7109 | * | ||
7110 | * We don't mark this one serializable as it should never get | ||
7111 | * serialized out. It only lives from the throw to the very | ||
7112 | * beginning of the catch handler where it is promptly unwrapped. | ||
7113 | * No CheckRun() call can possibly intervene. | ||
7114 | */ | ||
7115 | public class ScriptRestoreCatchException: Exception | ||
7116 | { | ||
7117 | |||
7118 | // old code uses these | ||
7119 | private object e; | ||
7120 | public ScriptRestoreCatchException(object e) | ||
7121 | { | ||
7122 | this.e = e; | ||
7123 | } | ||
7124 | public static object Unwrap(object o) | ||
7125 | { | ||
7126 | if(o is IXMRUncatchable) | ||
7127 | return null; | ||
7128 | if(o is ScriptRestoreCatchException) | ||
7129 | return ((ScriptRestoreCatchException)o).e; | ||
7130 | return o; | ||
7131 | } | ||
7132 | |||
7133 | // new code uses these | ||
7134 | private Exception ee; | ||
7135 | public ScriptRestoreCatchException(Exception ee) | ||
7136 | { | ||
7137 | this.ee = ee; | ||
7138 | } | ||
7139 | public static Exception Unwrap(Exception oo) | ||
7140 | { | ||
7141 | if(oo is IXMRUncatchable) | ||
7142 | return null; | ||
7143 | if(oo is ScriptRestoreCatchException) | ||
7144 | return ((ScriptRestoreCatchException)oo).ee; | ||
7145 | return oo; | ||
7146 | } | ||
7147 | } | ||
7148 | |||
7149 | [SerializableAttribute] | ||
7150 | public class ScriptBadCallNoException: Exception | ||
7151 | { | ||
7152 | public ScriptBadCallNoException(int callNo) : base("bad callNo " + callNo) { } | ||
7153 | protected ScriptBadCallNoException(SerializationInfo info, StreamingContext context) : base(info, context) | ||
7154 | { | ||
7155 | } | ||
7156 | } | ||
7157 | |||
7158 | public class CVVMismatchException: Exception | ||
7159 | { | ||
7160 | public int oldcvv; | ||
7161 | public int newcvv; | ||
7162 | |||
7163 | public CVVMismatchException(int oldcvv, int newcvv) : base("object version is " + oldcvv.ToString() + | ||
7164 | " but accept only " + newcvv.ToString()) | ||
7165 | { | ||
7166 | this.oldcvv = oldcvv; | ||
7167 | this.newcvv = newcvv; | ||
7168 | } | ||
7169 | } | ||
7170 | } | ||