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