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