/* * Copyright (c) Contributors, http://opensimulator.org/ * See CONTRIBUTORS.TXT for a full list of copyright holders. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the OpenSimulator Project nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** * @brief Reduce parser tokens to abstract syntax tree tokens. * * Usage: * * tokenBegin = returned by TokenBegin.Analyze () * representing the whole script source * as a flat list of tokens * * TokenScript tokenScript = Reduce.Analyze (TokenBegin tokenBegin); * * tokenScript = represents the whole script source * as a tree of tokens */ using OpenSim.Region.ScriptEngine.Shared.ScriptBase; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Text; using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; namespace OpenSim.Region.ScriptEngine.Yengine { public class ScriptReduce { public const uint SDT_PRIVATE = 1; public const uint SDT_PROTECTED = 2; public const uint SDT_PUBLIC = 4; public const uint SDT_ABSTRACT = 8; public const uint SDT_FINAL = 16; public const uint SDT_NEW = 32; public const uint SDT_OVERRIDE = 64; public const uint SDT_STATIC = 128; public const uint SDT_VIRTUAL = 256; private const int ASNPR = 50; private readonly static Dictionary precedence = PrecedenceInit(); private static readonly Type[] brkCloseOnly = new Type[] { typeof(TokenKwBrkClose) }; private static readonly Type[] cmpGTOnly = new Type[] { typeof(TokenKwCmpGT) }; private static readonly Type[] colonOnly = new Type[] { typeof(TokenKwColon) }; private static readonly Type[] commaOrBrcClose = new Type[] { typeof(TokenKwComma), typeof(TokenKwBrcClose) }; private static readonly Type[] colonOrDotDotDot = new Type[] { typeof(TokenKwColon), typeof(TokenKwDotDotDot) }; private static readonly Type[] parCloseOnly = new Type[] { typeof(TokenKwParClose) }; private static readonly Type[] semiOnly = new Type[] { typeof(TokenKwSemi) }; /** * @brief Initialize operator precedence table * @returns with precedence table pointer */ private static Dictionary PrecedenceInit() { return new Dictionary() { // http://www.lslwiki.net/lslwiki/wakka.php?wakka=operators { typeof(TokenKwComma), 30 }, { typeof(TokenKwAsnLSh), ASNPR }, // all assignment operators of equal precedence { typeof(TokenKwAsnRSh), ASNPR }, // ... so they get processed strictly right-to-left { typeof(TokenKwAsnAdd), ASNPR }, { typeof(TokenKwAsnAnd), ASNPR }, { typeof(TokenKwAsnSub), ASNPR }, { typeof(TokenKwAsnMul), ASNPR }, { typeof(TokenKwAsnDiv), ASNPR }, { typeof(TokenKwAsnMod), ASNPR }, { typeof(TokenKwAsnOr), ASNPR }, { typeof(TokenKwAsnXor), ASNPR }, { typeof(TokenKwAssign), ASNPR }, { typeof(TokenKwQMark), 60 }, { typeof(TokenKwOrOrOr), 70 }, { typeof(TokenKwAndAndAnd), 80 }, { typeof(TokenKwOrOr), 100 }, //{ typeof(TokenKwAndAnd), 120 }, { typeof(TokenKwAndAnd), 100 }, { typeof(TokenKwOr), 140 }, { typeof(TokenKwXor), 160 }, { typeof(TokenKwAnd), 180 }, { typeof(TokenKwCmpEQ), 200 }, { typeof(TokenKwCmpNE), 200 }, { typeof(TokenKwCmpLT), 240 }, { typeof(TokenKwCmpLE), 240 }, { typeof(TokenKwCmpGT), 240 }, { typeof(TokenKwCmpGE), 240 }, { typeof(TokenKwRSh), 260 }, { typeof(TokenKwLSh), 260 }, { typeof(TokenKwAdd), 280 }, { typeof(TokenKwSub), 280 }, { typeof(TokenKwMul), 320 }, { typeof(TokenKwDiv), 320 }, { typeof(TokenKwMod), 320 } }; } /** * @brief Reduce raw token stream to a single script token. * Performs a little semantic testing, ie, undefined variables, etc. * @param tokenBegin = points to a TokenBegin * followed by raw tokens * and last token is a TokenEnd * @returns null: not a valid script, error messages have been output * else: valid script top token */ public static TokenScript Reduce(TokenBegin tokenBegin) { return new ScriptReduce(tokenBegin).tokenScript; } /* * Instance variables. */ private bool errors = false; private string lastErrorFile = ""; private int lastErrorLine = 0; private int numTypedefs = 0; private TokenDeclVar currentDeclFunc = null; private TokenDeclSDType currentDeclSDType = null; private TokenScript tokenScript; private TokenStmtBlock currentStmtBlock = null; /** * @brief the constructor does all the processing. * @param token = first token of script after the TokenBegin token * @returns tokenScript = null: there were errors * else: successful */ private ScriptReduce(TokenBegin tokenBegin) { // Create a place to put the top-level script components, // eg, state bodies, functions, global variables. tokenScript = new TokenScript(tokenBegin.nextToken); // 'class', 'delegate', 'instance' all define types. // So we pre-scan the source tokens for those keywords // to build a script-defined type table and substitute // type tokens for those names in the source. This is // done as a separate scan so they can cross-reference // each other. Also does likewise for fixed array types. // // Also, all 'typedef's are processed here. Their definitions // remain in the source token stream after this, but they can // be skipped over, because their bodies have been substituted // in the source for any references. ParseSDTypePreScanPassOne(tokenBegin); // catalog definitions ParseSDTypePreScanPassTwo(tokenBegin); // substitute references /* int braces = 0; Token prevTok = null; for (Token token = tokenBegin; token != null; token = token.nextToken) { if (token is TokenKwParClose) braces -= 2; if (token is TokenKwBrcClose) braces -= 4; StringBuilder sb = new StringBuilder ("ScriptReduce*: "); sb.Append (token.GetHashCode ().ToString ("X8")); sb.Append (" "); sb.Append (token.line.ToString ().PadLeft (3)); sb.Append ("."); sb.Append (token.posn.ToString ().PadLeft (3)); sb.Append (" "); sb.Append (token.GetType ().Name.PadRight (24)); sb.Append (" : "); for (int i = 0; i < braces; i ++) sb.Append (' '); token.DebString (sb); Console.WriteLine (sb.ToString ()); if (token.prevToken != prevTok) { Console.WriteLine ("ScriptReduce*: -- prevToken link bad => " + token.prevToken.GetHashCode ().ToString ("X8")); } if (token is TokenKwBrcOpen) braces += 4; if (token is TokenKwParOpen) braces += 2; prevTok = token; } */ // Create a function $globalvarinit to hold all explicit // global variable initializations. TokenDeclVar gviFunc = new (tokenBegin, null, tokenScript); gviFunc.name = new TokenName(gviFunc, "$globalvarinit"); gviFunc.retType = new TokenTypeVoid(gviFunc); gviFunc.argDecl = new TokenArgDecl(gviFunc); TokenStmtBlock gviBody = new (gviFunc) { function = gviFunc }; gviFunc.body = gviBody; tokenScript.globalVarInit = gviFunc; tokenScript.AddVarEntry(gviFunc); // Scan through the tokens until we reach the end. for(Token token = tokenBegin.nextToken; token is not TokenEnd;) { if(token is TokenKwSemi) { token = token.nextToken; continue; } // Script-defined type declarations. if(ParseDeclSDTypes(ref token, null, SDT_PUBLIC)) continue; // constant = ; if(token is TokenKwConst) { ParseDeclVar(ref token, null); continue; } // ; // = ; if((token is TokenType) && (token.nextToken is TokenName) && ((token.nextToken.nextToken is TokenKwSemi) || (token.nextToken.nextToken is TokenKwAssign))) { TokenDeclVar tdvar = ParseDeclVar(ref token, gviFunc); if(tdvar != null) { // = ; TokenLValName left = new (tdvar.name, tokenScript.variablesStack); DoVarInit(gviFunc, left, tdvar.init); } continue; } // { [ get { } ] [ set { } ] } if((token is TokenType) && (token.nextToken is TokenName) && (token.nextToken.nextToken is TokenKwBrcOpen)) { ParseProperty(ref token, false, true); continue; } // // global function returning specified type if(token is TokenType tokenType) { token = token.nextToken; if(token is not TokenName) { ErrorMsg(token, "expecting variable/function name"); token = SkipPastSemi(token); continue; } TokenName tokenName = (TokenName)token; token = token.nextToken; if(token is not TokenKwParOpen) { ErrorMsg(token, " must be followed by ; = or ("); token = SkipPastSemi(token); continue; } token = tokenType; TokenDeclVar tokenDeclFunc = ParseDeclFunc(ref token, false, false, false); if(tokenDeclFunc == null) continue; if(!tokenScript.AddVarEntry(tokenDeclFunc)) { ErrorMsg(tokenName, "duplicate function " + tokenDeclFunc.funcNameSig.val); } continue; } // // global function returning void if(token is TokenName) { TokenName tokenName = (TokenName)token; token = token.nextToken; if(token is not TokenKwParOpen) { ErrorMsg(token, "looking for open paren after assuming " + tokenName.val + " is a function name"); token = SkipPastSemi(token); continue; } token = tokenName; TokenDeclVar tokenDeclFunc = ParseDeclFunc(ref token, false, false, false); if(tokenDeclFunc == null) continue; if(!tokenScript.AddVarEntry(tokenDeclFunc)) ErrorMsg(tokenName, "duplicate function " + tokenDeclFunc.funcNameSig.val); continue; } // default if(token is TokenKwDefault) { TokenDeclState tokenDeclState = new (token); token = token.nextToken; tokenDeclState.body = ParseStateBody(ref token); if(tokenDeclState.body == null) continue; if(tokenScript.defaultState != null) { ErrorMsg(tokenDeclState, "default state already declared"); continue; } tokenScript.defaultState = tokenDeclState; continue; } // state if(token is TokenKwState) { TokenDeclState tokenDeclState = new (token); token = token.nextToken; if(token is not TokenName TokenNametoken) { ErrorMsg(token, "state must be followed by state name"); token = SkipPastSemi(token); continue; } tokenDeclState.name = TokenNametoken; token = token.nextToken; tokenDeclState.body = ParseStateBody(ref token); if(tokenDeclState.body == null) continue; if(tokenScript.states.ContainsKey(tokenDeclState.name.val)) { ErrorMsg(tokenDeclState.name, "duplicate state definition"); continue; } tokenScript.states.Add(tokenDeclState.name.val, tokenDeclState); continue; } // Doesn't fit any of those forms, output message and skip to next statement. ErrorMsg(token, "looking for var name, type, state or default, script-defined type declaration"); token = SkipPastSemi(token); continue; } // Must have a default state to start in. if(!errors && (tokenScript.defaultState == null)) { ErrorMsg(tokenScript, "no default state defined"); } // If any error messages were written out, set return value to null. if(errors) tokenScript = null; } /** * @brief Pre-scan the source for class, delegate, interface, typedef definition keywords. * Clump the keywords and name being defined together, but leave the body intact. * In the case of a delegate with an explicit return type, it reverses the name and return type. * After this completes there shouldn't be any TokenKw{Class,Delegate,Interface,Typedef} * keywords in the source, they are all replaced by TokenDeclSDType{Class,Delegate,Interface, * Typedef} tokens which also encapsulate the name of the type being defined and any generic * parameter names. The body remains intact in the source token stream following the * TokenDeclSDType* token. */ private void ParseSDTypePreScanPassOne(Token tokenBegin) { Stack braceLevels = new(); Stack outerLevels = new(); int openBraceLevel = 0; braceLevels.Push(-1); outerLevels.Push(null); Token t = tokenBegin; while((t = t.nextToken) is not TokenEnd) { // Keep track of nested definitions so we can link them up. // We also need to detect the end of class and interface definitions. if(t is TokenKwBrcOpen) { openBraceLevel++; continue; } if(t is TokenKwBrcClose) { if(--openBraceLevel < 0) { ErrorMsg(t, "{ } mismatch"); return; } if(braceLevels.Peek() == openBraceLevel) { braceLevels.Pop(); outerLevels.Pop().endToken = t; } continue; } // Check for 'class' or 'interface'. // They always define a new class or interface. // They can contain nested script-defined type definitions. if((t is TokenKwClass) || (t is TokenKwInterface)) { Token kw = t; t = t.nextToken; if(t is not TokenName) { ErrorMsg(t, "expecting class or interface name"); t = SkipPastSemi(t).prevToken; continue; } TokenName name = (TokenName)t; t = t.nextToken; // Malloc the script-defined type object. TokenDeclSDType decl; if(kw is TokenKwClass) decl = new TokenDeclSDTypeClass(name, kw.prevToken is TokenKwPartial); else decl = new TokenDeclSDTypeInterface(name); decl.outerSDType = outerLevels.Peek(); // Check for generic parameter list. if(!ParseGenProtoParamList(ref t, decl)) continue; // Splice in a TokenDeclSDType token that replaces the keyword and the name tokens // and any generic parameters including the '<', ','s and '>'. // kw = points to 'class' or 'interface' keyword. // t = points to just past last part of class name parsed, hopefully a ':' or '{'. decl.prevToken = decl.isPartial ? kw.prevToken.prevToken : kw.prevToken; decl.nextToken = t; decl.prevToken.nextToken = decl; decl.nextToken.prevToken = decl; // Enter it in name lists so it can be seen by others. Token partialNewBody = CatalogSDTypeDecl(decl); // Start inner type definitions. braceLevels.Push(openBraceLevel); outerLevels.Push(decl); // Scan the body starting on for before the '{'. // // If this body had an old partial merged into it, // resume scanning at the beginning of the new body, // ie, what used to be the first token after the '{' // before the old body was spliced in. if(partialNewBody != null) { // We have a partial that has had old partial body merged // into new partial body. So resume scanning at the beginning // of the new partial body so we don't get any duplicate scanning // of the old partial body. // // ... { } // ^- resume scanning here // but inc openBraceLevel because // we skipped scanning the '{' openBraceLevel++; t = partialNewBody; } t = t.prevToken; continue; } // Check for 'delegate'. // It always defines a new delegate. // Delegates never define nested types. if(t is TokenKwDelegate) { Token kw = t; t = t.nextToken; // Next thing might be an explicit return type or the delegate's name. // If it's a type token, then it's the return type, simple enough. // But if it's a name token, it might be the name of some other script-defined type. // The way to tell is that the delegate name is followed by a '(', whereas an // explicit return type is followed by the delegate name. Token retType = t; TokenName delName = null; Token u; int angles = 0; for(u = t; u is not TokenKwParOpen; u = u.nextToken) { if((u is TokenKwSemi) || (u is TokenEnd)) break; if(u is TokenKwCmpLT) angles++; if(u is TokenKwCmpGT) angles--; if(u is TokenKwRSh) angles -= 2; // idiot >> if((angles == 0) && (u is TokenName name)) delName = name; } if(u is not TokenKwParOpen) { ErrorMsg(u, "expecting ( for delegate parameter list"); t = SkipPastSemi(t).prevToken; continue; } if(delName == null) { ErrorMsg(u, "expecting delegate name"); t = SkipPastSemi(t).prevToken; continue; } if(retType == delName) retType = null; // Malloc the script-defined type object. TokenDeclSDTypeDelegate decl = new (delName) { outerSDType = outerLevels.Peek() }; // Check for generic parameter list. t = delName.nextToken; if(!ParseGenProtoParamList(ref t, decl)) continue; // Enter it in name lists so it can be seen by others. CatalogSDTypeDecl(decl); // Splice in the token that replaces the 'delegate' keyword and the whole name // (including the '<' name ... '>' parts). The return type token(s), if any, // follow the splice token and come before the '('. decl.prevToken = kw.prevToken; kw.prevToken.nextToken = decl; if(retType == null) { decl.nextToken = t; t.prevToken = decl; } else { decl.nextToken = retType; retType.prevToken = decl; retType.nextToken = t; t.prevToken = retType; } // Scan for terminating ';'. // There cannot be an intervening class, delegate, interfate, typedef, { or }. for(t = decl; t is not TokenKwSemi; t = u) { u = t.nextToken; if((u is TokenEnd) || (u is TokenKwClass) || (u is TokenKwDelegate) || (u is TokenKwInterface) || (u is TokenKwTypedef) || (u is TokenKwBrcOpen) || (u is TokenKwBrcClose)) { ErrorMsg(t, "delegate missing terminating ;"); break; } } decl.endToken = t; continue; } // Check for 'typedef'. // It always defines a new macro. // Typedefs never define nested types. if(t is TokenKwTypedef) { Token kw = t; t = t.nextToken; if(t is not TokenName) { ErrorMsg(t, "expecting typedef name"); t = SkipPastSemi(t).prevToken; continue; } TokenName tdName = (TokenName)t; t = t.nextToken; // Malloc the script-defined type object. TokenDeclSDTypeTypedef decl = new (tdName) { outerSDType = outerLevels.Peek() }; // Check for generic parameter list. if (!ParseGenProtoParamList(ref t, decl)) continue; // Enter it in name lists so it can be seen by others. CatalogSDTypeDecl(decl); numTypedefs++; // Splice in the token that replaces the 'typedef' keyword and the whole name // (including the '<' name ... '>' parts). decl.prevToken = kw.prevToken; kw.prevToken.nextToken = decl; decl.nextToken = t; t.prevToken = decl; // Scan for terminating ';'. // There cannot be an intervening class, delegate, interfate, typedef, { or }. Token u; for(t = decl; t is not TokenKwSemi; t = u) { u = t.nextToken; if((u is TokenEnd) || (u is TokenKwClass) || (u is TokenKwDelegate) || (u is TokenKwInterface) || (u is TokenKwTypedef) || (u is TokenKwBrcOpen) || (u is TokenKwBrcClose)) { ErrorMsg(t, "typedef missing terminating ;"); break; } } decl.endToken = t; continue; } } } /** * @brief Parse a possibly generic type definition's parameter list. * @param t = points to the possible opening '<' on entry * points just past the closing '>' on return * @param decl = the generic type being declared * @returns false: parse error * true: decl.genParams = filled in with parameter list * decl.innerSDTypes = filled in with parameter list */ private bool ParseGenProtoParamList(ref Token t, TokenDeclSDType decl) { // Maybe there aren't any generic parameters. // If so, leave decl.genParams = null. if(t is not TokenKwCmpLT) return true; // Build list of generic parameter names. Dictionary parms = new(); do { t = t.nextToken; if(t is not TokenName) { ErrorMsg(t, "expecting generic parameter name"); break; } TokenName tn = (TokenName)t; if(parms.ContainsKey(tn.val)) ErrorMsg(tn, "duplicate use of generic parameter name"); else parms.Add(tn.val, parms.Count); t = t.nextToken; } while(t is TokenKwComma); if(t is not TokenKwCmpGT) { ErrorMsg(t, "expecting , for more params or > to end param list"); return false; } t = t.nextToken; decl.genParams = parms; return true; } /** * @brief Catalog a script-defined type. * Its short name (eg, 'Node') gets put in the next outer level (eg, 'List')'s inner type definition table. * Its long name (eg, 'List.Node') gets put in the global type definition table. */ public Token CatalogSDTypeDecl(TokenDeclSDType decl) { string longName = decl.longName.val; if (!tokenScript.sdSrcTypesTryGetValue(longName, out TokenDeclSDType dupDecl)) { tokenScript.sdSrcTypesAdd(longName, decl); decl.outerSDType?.innerSDTypes.Add(decl.shortName.val, decl); return null; } if (!dupDecl.isPartial || !decl.isPartial) { ErrorMsg(decl, "duplicate definition of type " + longName); ErrorMsg(dupDecl, "previous definition here"); return null; } if(!GenericParametersMatch(decl, dupDecl)) ErrorMsg(decl, "all partial class generic parameters must match"); // Have new declaration be the cataloged one because body is going to get // snipped out of old declaration and pasted into new declaration. tokenScript.sdSrcTypesRep(longName, decl); if(decl.outerSDType != null) decl.outerSDType.innerSDTypes[decl.shortName.val] = decl; // Find old partial definition's opening brace. Token dupBrcOpen; for(dupBrcOpen = dupDecl; dupBrcOpen is not TokenKwBrcOpen; dupBrcOpen = dupBrcOpen.nextToken) { if(dupBrcOpen == dupDecl.endToken) { ErrorMsg(dupDecl, "missing {"); return null; } } // Find new partial definition's opening brace. Token brcOpen; for(brcOpen = decl; brcOpen is not TokenKwBrcOpen; brcOpen = brcOpen.nextToken) { if(brcOpen is TokenEnd) { ErrorMsg(decl, "missing {"); return null; } } Token body = brcOpen.nextToken; // Stick old partial definition's extends/implementeds list just // in front of new partial definition's extends/implementeds list. // // class oldextimp { oldbody } ... // dupDecl dupBrcOpen dupDecl.endToken // // class newextimp { newbody } ... // decl brcOpen body decl.endToken // // becomes // // class ... // dupDecl // dupDecl.endToken // // class oldextimp newextimp { oldbody newbody } ... // decl brcOpen body decl.endToken if(dupBrcOpen != dupDecl.nextToken) { dupBrcOpen.prevToken.nextToken = decl.nextToken; dupDecl.nextToken.prevToken = decl; decl.nextToken.prevToken = dupBrcOpen.prevToken; decl.nextToken = dupDecl.nextToken; } // Stick old partial definition's body just // in front of new partial definition's body. if(dupBrcOpen.nextToken != dupDecl.endToken) { dupBrcOpen.nextToken.prevToken = brcOpen; dupDecl.endToken.prevToken.nextToken = body; body.prevToken = dupDecl.endToken.prevToken; brcOpen.nextToken = dupBrcOpen.nextToken; } // Null out old definition's extends/implementeds list and body // by having the declaration token be the only thing left. dupDecl.nextToken = dupDecl.endToken.nextToken; dupDecl.nextToken.prevToken = dupDecl; dupDecl.endToken = dupDecl; return body; } /** * @brief Determine whether or not the generic parameters of two class declarations match exactly. */ private static bool GenericParametersMatch(TokenDeclSDType c1, TokenDeclSDType c2) { if((c1.genParams == null) && (c2.genParams == null)) return true; if((c1.genParams == null) || (c2.genParams == null)) return false; Dictionary gp1 = c1.genParams; Dictionary gp2 = c2.genParams; if(gp1.Count != gp2.Count) return false; foreach(KeyValuePair kvp1 in gp1) { if (!gp2.TryGetValue(kvp1.Key, out int v2)) return false; if (v2 != kvp1.Value) return false; } return true; } /** * @brief Replace all TokenName tokens that refer to the script-defined types with * corresponding TokenTypeSDType{Class,Delegate,GenParam,Interface} tokens. * Also handle generic references, ie, recognize that 'List' is an * instantiation of 'List<>' and instantiate the generic. */ private const uint REPEAT_NOTYPE = 1; private const uint REPEAT_INSTGEN = 2; private const uint REPEAT_SUBST = 4; private void ParseSDTypePreScanPassTwo(Token tokenBegin) { List noTypes = new(); TokenDeclSDType outerSDType; uint repeat; do { repeat = 0; outerSDType = null; noTypes.Clear(); for(Token t = tokenBegin; (t = t.nextToken) is not TokenEnd;) { // Maybe it's time to pop out of an outer class definition. if((outerSDType != null) && (outerSDType.endToken == t)) { outerSDType = outerSDType.outerSDType; continue; } // Skip completely over any script-defined generic prototypes. // We only need to process their instantiations which are non- // generic versions of the generics. if((t is TokenDeclSDType ttype) && ttype.genParams != null) { t = ttype.endToken; continue; } // Check for beginning of non-generic script-defined type definitions. // They can have nested definitions in their innerSDTypes[] that match // name tokens, so add them to the stack. // // But just ignore any preliminary partial definitions as they have had // their entire contents spliced out and spliced into a subsequent partial // definition. So if we originally had: // partial class Abc { public intenger one; } // partial class Abc { public intenger two; } // We now have: // partial_class_Abc <== if we are here, just ignore the partial_class_Abc token // partial_class_Abc { public intenger one; public intenger two; } if(t is TokenDeclSDType ttype2) { if(ttype2.endToken != t) outerSDType = (TokenDeclSDType)t; continue; } // For names not preceded by a '.', scan the script-defined type definition // stack for that name. Splice the name out and replace with equivalent token. if((t is TokenName) && t.prevToken is not TokenKwDot) t = TrySpliceTypeRef(t, outerSDType, ref repeat, noTypes); // This handles types such as integer[,][], List[], etc. // They are an instantiation of an internally generated type of the same name, brackets and all. // Note that to malloc an array, use something like 'new float[,][](3,5)', not 'new float[3,5][]'. // // Note that we must not get confused by $idxprop property declarations such as: // float [string kee] { get { ... } } // ... and try to convert 'float' '[' to an array type. if((t is TokenType) && (t.nextToken is TokenKwBrkOpen)) { if((t.nextToken.nextToken is TokenKwBrkClose) || (t.nextToken.nextToken is TokenKwComma)) { t = InstantiateJaggedArray(t, tokenBegin, ref repeat); } } } // If we instantiated a generic, loop back to process its contents // just as if the source code had the instantiated code to begin with. // Also repeat if we found a non-type inside the <> of a generic reference // provided we have made at least one name->type substitution. } while(((repeat & REPEAT_INSTGEN) != 0) || ((repeat & (REPEAT_NOTYPE | REPEAT_SUBST)) == (REPEAT_NOTYPE | REPEAT_SUBST))); // These are places where we required a type be present, // eg, a generic type argument or the body of a typedef. foreach(Token t in noTypes) ErrorMsg(t, "looking for type"); } /** * @brief Try to convert the source token string to a type reference * and splice the type reference into the source token string * replacing the original token(s). * @param t = points to the initial TokenName token * @param outerSDType = null: this is a top-level code reference * else: this code is within outerSDType * @returns pointer to last token parsed * possibly with spliced-in type token * repeat = possibly set true if need to do another pass */ private Token TrySpliceTypeRef(Token t, TokenDeclSDType outerSDType, ref uint repeat, List noTypes) { Token start = t; string tnamestr = ((TokenName)t).val; // Look for the name as a type declared by outerSDType or anything // even farther out than that. If not found, simply return // without updating t, meaning that t isn't the name of a type. TokenDeclSDType decl = null; while(outerSDType != null) { if(outerSDType.innerSDTypes.TryGetValue(tnamestr, out decl)) break; outerSDType = outerSDType.outerSDType; } if((outerSDType == null) && !tokenScript.sdSrcTypesTryGetValue(tnamestr, out decl)) return t; TokenDeclSDType instdecl; while(true) { // If it is a generic type, it must be followed by instantiation arguments. instdecl = decl; if(decl.genParams != null) { t = t.nextToken; if(t is not TokenKwCmpLT) { ErrorMsg(t, "expecting < for generic argument list"); return t; } tnamestr += "<"; int nArgs = decl.genParams.Count; TokenType[] genArgs = new TokenType[nArgs]; for(int i = 0; i < nArgs;) { t = t.nextToken; if(t is not TokenType) { repeat |= REPEAT_NOTYPE; noTypes.Add(t); return t.prevToken; // make sure name gets processed // so substitution can occur on it } TokenType ga = (TokenType)t; genArgs[i] = ga; tnamestr += ga.ToString(); t = t.nextToken; if(++i < nArgs) { if(t is not TokenKwComma) { ErrorMsg(t, "expecting , for more generic arguments"); return t; } tnamestr += ","; } } if(t is TokenKwRSh) { // idiot >> Token u = new TokenKwCmpGT(t); Token v = new TokenKwCmpGT(t); v.posn++; u.prevToken = t.prevToken; u.nextToken = v; v.nextToken = t.nextToken; v.prevToken = u; u.prevToken.nextToken = u; v.nextToken.prevToken = v; t = u; } if(t is not TokenKwCmpGT) { ErrorMsg(t, "expecting > at end of generic argument list"); return t; } tnamestr += ">"; if(outerSDType != null) { outerSDType.innerSDTypes.TryGetValue(tnamestr, out instdecl); } else { tokenScript.sdSrcTypesTryGetValue(tnamestr, out instdecl); } // Couldn't find 'List' but found 'List' and we have genArgs = 'string'. // Instantiate the generic to create 'List'. This splices the definition // of 'List' into the source token stream just as if it had been there all // along. We have to then repeat the scan to process the instance's contents. if(instdecl == null) { instdecl = decl.InstantiateGeneric(tnamestr, genArgs, this); CatalogSDTypeDecl(instdecl); repeat |= REPEAT_INSTGEN; } } // Maybe caller wants a subtype by putting a '.' following all that. if(t.nextToken is not TokenKwDot) break; if(t.nextToken.nextToken is not TokenName nextnextname) break; tnamestr = nextnextname.val; if(!instdecl.innerSDTypes.TryGetValue(tnamestr, out decl)) break; t = t.nextToken.nextToken; outerSDType = instdecl; } // Create a reference in the source to the definition // that encapsulates the long dotted type name given in // the source, and replace the long dotted type name in // the source with the reference token, eg, replace // 'Dictionary' '<' 'string' ',' 'integer' '>' '.' 'ValueList' // with 'Dictionary.ValueList'. TokenType refer = instdecl.MakeRefToken(start); if(refer == null) { // typedef body is not yet a type noTypes.Add(start); repeat |= REPEAT_NOTYPE; return start; } refer.prevToken = start.prevToken; // start points right at the first TokenName refer.nextToken = t.nextToken; // t points at the last TokenName or TokenKwCmpGT refer.prevToken.nextToken = refer; refer.nextToken.prevToken = refer; repeat |= REPEAT_SUBST; return refer; } /** * @brief We are known to have '[' so make an equivalent array type. * @param t = points to the TokenType * @param tokenBegin = where we can safely splice in new array class definitions * @param repeat = set REPEAT_INSTGEN if new type created * @returns pointer to last token parsed * possibly with spliced-in type token * repeat = possibly set true if need to do another pass */ private Token InstantiateJaggedArray(Token t, Token tokenBegin, ref uint repeat) { Token start = t; TokenType ofType = (TokenType)t; Stack ranks = new(); // When script specifies 'float[,][]' it means a two-dimensional matrix // that points to one-dimensional vectors of floats. So we would push // a 2 then a 1 in this parsing code... do { t = t.nextToken; // point at '[' int rank = 0; do { rank++; // count '[' and ','s t = t.nextToken; // point at ',' or ']' } while(t is TokenKwComma); if(t is not TokenKwBrkClose) { ErrorMsg(t, "expecting only [ , or ] for array type specification"); return t; } ranks.Push(rank); } while(t.nextToken is TokenKwBrkOpen); // Now we build the types in reverse order. For the example above we will: // first, create a type that is a one-dimensional vector of floats, float[] // second, create a type that is a two-dimensional matrix of that. // This keeps declaration and referencing similar, eg, // float[,][] jag = new float[,][] (3,4); // jag[i,j][k] ... is used to access the elements do { int rank = ranks.Pop(); TokenDeclSDType decl = InstantiateFixedArray(rank, ofType, tokenBegin, ref repeat); ofType = decl.MakeRefToken(ofType); } while(ranks.Count > 0); // Finally splice in the resultant array type to replace the original tokens. ofType.prevToken = start.prevToken; ofType.nextToken = t.nextToken; ofType.prevToken.nextToken = ofType; ofType.nextToken.prevToken = ofType; // Resume parsing just after the spliced-in array type token. return ofType; } /** * @brief Instantiate a script-defined class type to handle fixed-dimension arrays. * @param rank = number of dimensions for the array * @param ofType = type of each element of the array * @returns script-defined class declaration created to handle the array */ private TokenDeclSDType InstantiateFixedArray(int rank, TokenType ofType, Token tokenBegin, ref uint repeat) { // Create the array type's name. // If starting with a non-array type, just append the rank to it, eg, float + rank=1 -> float[] // If starting with an array type, slip this rank in front of existing array, eg, float[] + rank=2 -> float[,][]. // This makes it consistent with what the script-writer sees for both a type specification and when // referencing elements in a jagged array. string name = ofType.ToString(); StringBuilder sb = new(name); int ix = name.IndexOf('['); if(ix < 0) ix = name.Length; sb.Insert(ix++, '['); for(int i = 0; ++i < rank;) { sb.Insert(ix++, ','); } sb.Insert(ix, ']'); name = sb.ToString(); TokenDeclSDType fa; if(!tokenScript.sdSrcTypesTryGetValue(name, out fa)) { char suffix = 'O'; if(ofType is TokenTypeChar) suffix = 'C'; if(ofType is TokenTypeFloat) suffix = 'F'; if(ofType is TokenTypeInt) suffix = 'I'; // Don't already have one, create a new skeleton struct. // Splice in a definition for the class at beginning of source file. // // class { fa = new TokenDeclSDTypeClass(new TokenName(tokenScript, name), false); CatalogSDTypeDecl(fa); repeat |= REPEAT_INSTGEN; ((TokenDeclSDTypeClass)fa).arrayOfType = ofType; ((TokenDeclSDTypeClass)fa).arrayOfRank = rank; Token t = SpliceAfter(tokenBegin, fa); t = SpliceAfter(t, new TokenKwBrcOpen(t)); // public integer len0; // public integer len1; // ... // public object obj; for(int i = 0; i < rank; i++) { t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenTypeObject(t)); t = SpliceAfter(t, new TokenName(t, "obj")); t = SpliceAfter(t, new TokenKwSemi(t)); // public constructor (integer len0, integer len1, ...) { // this.len0 = len0; // this.len1 = len1; // ... // this.obj = xmrFixedArrayAlloc (len0 * len1 * ...); // } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenKwConstructor(t)); t = SpliceAfter(t, new TokenKwParOpen(t)); for(int i = 0; i < rank; i++) { if(i > 0) t = SpliceAfter(t, new TokenKwComma(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); } t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); for(int i = 0; i < rank; i++) { t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwAssign(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); } t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "obj")); t = SpliceAfter(t, new TokenKwAssign(t)); t = SpliceAfter(t, new TokenName(t, "xmrFixedArrayAlloc" + suffix)); t = SpliceAfter(t, new TokenKwParOpen(t)); for(int i = 0; i < rank; i++) { if(i > 0) t = SpliceAfter(t, new TokenKwMul(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); } t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); // public integer Length { get { // return this.len0 * this.len1 * ... ; // } } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "Length")); t = SpliceAfter(t, new TokenKwBrcOpen(t)); t = SpliceAfter(t, new TokenKwGet(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); t = SpliceAfter(t, new TokenKwRet(t)); for(int i = 0; i < rank; i++) { if(i > 0) t = SpliceAfter(t, new TokenKwMul(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); } t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); // public integer Length (integer dim) { // switch (dim) { // case 0: return this.len0; // case 1: return this.len1; // ... // } // return 0; // } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "Length")); t = SpliceAfter(t, new TokenKwParOpen(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "dim")); t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); t = SpliceAfter(t, new TokenKwSwitch(t)); t = SpliceAfter(t, new TokenKwParOpen(t)); t = SpliceAfter(t, new TokenName(t, "dim")); t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); for(int i = 0; i < rank; i++) { t = SpliceAfter(t, new TokenKwCase(t)); t = SpliceAfter(t, new TokenInt(t, i)); t = SpliceAfter(t, new TokenKwColon(t)); t = SpliceAfter(t, new TokenKwRet(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); } t = SpliceAfter(t, new TokenKwBrcClose(t)); t = SpliceAfter(t, new TokenKwRet(t)); t = SpliceAfter(t, new TokenInt(t, 0)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); // public integer Index (integer idx0, integet idx1, ...) { // integer idx = idx0; // idx *= this.len1; idx += idx1; // idx *= this.len2; idx += idx2; // ... // return idx; // } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "Index")); t = SpliceAfter(t, new TokenKwParOpen(t)); for(int i = 0; i < rank; i++) { if(i > 0) t = SpliceAfter(t, new TokenKwComma(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "idx" + i)); } t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAssign(t)); t = SpliceAfter(t, new TokenName(t, "idx0")); t = SpliceAfter(t, new TokenKwSemi(t)); for(int i = 1; i < rank; i++) { t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAsnMul(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAsnAdd(t)); t = SpliceAfter(t, new TokenName(t, "idx" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); } t = SpliceAfter(t, new TokenKwRet(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); // public Get (integer idx0, integet idx1, ...) { // integer idx = idx0; // idx *= this.len1; idx += idx1; // idx *= this.len2; idx += idx2; // ... // return () xmrFixedArrayGet (this.obj, idx); // } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, ofType.CopyToken(t)); t = SpliceAfter(t, new TokenName(t, "Get")); t = SpliceAfter(t, new TokenKwParOpen(t)); for(int i = 0; i < rank; i++) { if(i > 0) t = SpliceAfter(t, new TokenKwComma(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "idx" + i)); } t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAssign(t)); t = SpliceAfter(t, new TokenName(t, "idx0")); t = SpliceAfter(t, new TokenKwSemi(t)); for(int i = 1; i < rank; i++) { t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAsnMul(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAsnAdd(t)); t = SpliceAfter(t, new TokenName(t, "idx" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); } t = SpliceAfter(t, new TokenKwRet(t)); if(suffix == 'O') { t = SpliceAfter(t, new TokenKwParOpen(t)); t = SpliceAfter(t, ofType.CopyToken(t)); t = SpliceAfter(t, new TokenKwParClose(t)); } t = SpliceAfter(t, new TokenName(t, "xmrFixedArrayGet" + suffix)); t = SpliceAfter(t, new TokenKwParOpen(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "obj")); t = SpliceAfter(t, new TokenKwComma(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); // public void Set (integer idx0, integer idx1, ..., val) { // integer idx = idx0; // idx *= this.len1; idx += idx1; // idx *= this.len2; idx += idx2; // ... // xmrFixedArraySet (this.obj, idx, val); // } t = SpliceAfter(t, new TokenKwPublic(t)); t = SpliceAfter(t, new TokenTypeVoid(t)); t = SpliceAfter(t, new TokenName(t, "Set")); t = SpliceAfter(t, new TokenKwParOpen(t)); for(int i = 0; i < rank; i++) { t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "idx" + i)); t = SpliceAfter(t, new TokenKwComma(t)); } t = SpliceAfter(t, ofType.CopyToken(t)); t = SpliceAfter(t, new TokenName(t, "val")); t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwBrcOpen(t)); t = SpliceAfter(t, new TokenTypeInt(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAssign(t)); t = SpliceAfter(t, new TokenName(t, "idx0")); t = SpliceAfter(t, new TokenKwSemi(t)); for(int i = 1; i < rank; i++) { t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAsnMul(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "len" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwAsnAdd(t)); t = SpliceAfter(t, new TokenName(t, "idx" + i)); t = SpliceAfter(t, new TokenKwSemi(t)); } t = SpliceAfter(t, new TokenName(t, "xmrFixedArraySet" + suffix)); t = SpliceAfter(t, new TokenKwParOpen(t)); t = SpliceAfter(t, new TokenKwThis(t)); t = SpliceAfter(t, new TokenKwDot(t)); t = SpliceAfter(t, new TokenName(t, "obj")); t = SpliceAfter(t, new TokenKwComma(t)); t = SpliceAfter(t, new TokenName(t, "idx")); t = SpliceAfter(t, new TokenKwComma(t)); t = SpliceAfter(t, new TokenName(t, "val")); t = SpliceAfter(t, new TokenKwParClose(t)); t = SpliceAfter(t, new TokenKwSemi(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); t = SpliceAfter(t, new TokenKwBrcClose(t)); } return fa; } private Token SpliceAfter(Token before, Token after) { after.nextToken = before.nextToken; after.prevToken = before; before.nextToken = after; after.nextToken.prevToken = after; return after; } /** * @brief Parse script-defined type declarations. * @param token = points to possible script-defined type keyword * @param outerSDType = null: top-level type * else: sub-type of this type * @param flags = access level (SDT_{PRIVATE,PROTECTED,PUBLIC}) * @returns true: something defined; else: not a sd type def */ private bool ParseDeclSDTypes(ref Token token, TokenDeclSDType outerSDType, uint flags) { if(token is not TokenDeclSDType) return false; TokenDeclSDType decl = (TokenDeclSDType)token; /* * If declaration of generic type, skip it. * The instantiations get parsed (ie, when we know the concrete types) * below because they appear as non-generic types. */ if(decl.genParams != null) { token = decl.endToken.nextToken; return true; } /* * Also skip over any typedefs. They were all processed in * ParseSDTypePreScanPassTwo(). */ if(decl is TokenDeclSDTypeTypedef) { token = decl.endToken.nextToken; return true; } /* * Non-generic types get parsed inline because we know all their types. */ if(decl is TokenDeclSDTypeClass) { ParseDeclClass(ref token, outerSDType, flags); return true; } if(decl is TokenDeclSDTypeDelegate) { ParseDeclDelegate(ref token, outerSDType, flags); return true; } if(decl is TokenDeclSDTypeInterface) { ParseDeclInterface(ref token, outerSDType, flags); return true; } throw new Exception("unhandled token " + token.GetType().ToString()); } /** * @brief Parse a class declaration. * @param token = points to TokenDeclSDTypeClass token * points just past closing '}' on return * @param outerSDType = null: this is a top-level class * else: this class is being defined inside this type * @param flags = SDT_{PRIVATE,PROTECTED,PUBLIC} */ private void ParseDeclClass(ref Token token, TokenDeclSDType outerSDType, uint flags) { bool haveExplicitConstructor = false; Token u = token; TokenDeclSDTypeClass tokdeclcl; tokdeclcl = (TokenDeclSDTypeClass)u; tokdeclcl.outerSDType = outerSDType; tokdeclcl.accessLevel = flags; u = u.nextToken; // maybe it is a partial class that had its body snipped out // by a later partial class declaration of the same class if(tokdeclcl.endToken == tokdeclcl) { token = u; return; } // make this class the currently compiled class // used for retrieving stuff like 'this' possibly // in field initialization code TokenDeclSDType saveCurSDType = currentDeclSDType; currentDeclSDType = tokdeclcl; // next can be ':' followed by list of implemented // interfaces and one extended class if(u is TokenKwColon) { u = u.nextToken; while(true) { if(u is TokenTypeSDTypeClass uc) { TokenDeclSDTypeClass c = uc.decl; if(tokdeclcl.extends == null) { tokdeclcl.extends = c; } else if(tokdeclcl.extends != c) { ErrorMsg(u, "can extend from only one class"); } } else if(u is TokenTypeSDTypeInterface iu) { TokenDeclSDTypeInterface i = iu.decl; i.AddToClassDecl(tokdeclcl); } else { ErrorMsg(u, "expecting class or interface name"); if(u is TokenKwBrcOpen) break; } u = u.nextToken; // allow : in case it is spliced from multiple partial class definitions if(u is not TokenKwComma && u is not TokenKwColon) break; u = u.nextToken; } } // next must be '{' to open class declaration body if(u is not TokenKwBrcOpen) { ErrorMsg(u, "expecting { to open class declaration body"); token = SkipPastSemi(token); goto ret; } token = u.nextToken; // push a var frame to put all the class members in tokdeclcl.members.thisClass = tokdeclcl; tokenScript.PushVarFrame(tokdeclcl.members); // Create a function $instfieldnit to hold all explicit // instance field initializations. TokenDeclVar ifiFunc = new(tokdeclcl, null, tokenScript); ifiFunc.name = new TokenName(ifiFunc, "$instfieldinit"); ifiFunc.retType = new TokenTypeVoid(ifiFunc); ifiFunc.argDecl = new TokenArgDecl(ifiFunc); ifiFunc.sdtClass = tokdeclcl; ifiFunc.sdtFlags = SDT_PUBLIC | SDT_NEW; TokenStmtBlock ifiBody = new(ifiFunc) { function = ifiFunc }; ifiFunc.body = ifiBody; tokdeclcl.instFieldInit = ifiFunc; tokenScript.AddVarEntry(ifiFunc); // Create a function $staticfieldnit to hold all explicit // static field initializations. TokenDeclVar sfiFunc = new(tokdeclcl, null, tokenScript); sfiFunc.name = new TokenName(sfiFunc, "$staticfieldinit"); sfiFunc.retType = new TokenTypeVoid(sfiFunc); sfiFunc.argDecl = new TokenArgDecl(sfiFunc); sfiFunc.sdtClass = tokdeclcl; sfiFunc.sdtFlags = SDT_PUBLIC | SDT_STATIC | SDT_NEW; TokenStmtBlock sfiBody = new(sfiFunc) { function = sfiFunc }; sfiFunc.body = sfiBody; tokdeclcl.staticFieldInit = sfiFunc; tokenScript.AddVarEntry(sfiFunc); // process declaration statements until '}' while(!(token is TokenKwBrcClose)) { if(token is TokenKwSemi) { token = token.nextToken; continue; } // Check for all qualifiers. // typedef has an implied 'public' qualifier. flags = SDT_PUBLIC; if(!(token is TokenDeclSDTypeTypedef)) { flags = ParseQualifierFlags(ref token); } // Parse nested script-defined type definitions. if(ParseDeclSDTypes(ref token, tokdeclcl, flags)) continue; // constant = ; if(token is TokenKwConst) { if((flags & (SDT_ABSTRACT | SDT_NEW | SDT_OVERRIDE | SDT_VIRTUAL)) != 0) { ErrorMsg(token, "cannot have abstract, new, override or virtual field"); } TokenDeclVar var = ParseDeclVar(ref token, null); if(var != null) { var.sdtClass = tokdeclcl; var.sdtFlags = flags | SDT_STATIC; } continue; } // ; // = ; if((token is TokenType) && (token.nextToken is TokenName) && ((token.nextToken.nextToken is TokenKwSemi) || (token.nextToken.nextToken is TokenKwAssign))) { if((flags & (SDT_ABSTRACT | SDT_FINAL | SDT_NEW | SDT_OVERRIDE | SDT_VIRTUAL)) != 0) { ErrorMsg(token, "cannot have abstract, final, new, override or virtual field"); } TokenDeclVar var = ParseDeclVar(ref token, ifiFunc); if(var != null) { var.sdtClass = tokdeclcl; var.sdtFlags = flags; if((flags & SDT_STATIC) != 0) { // . = ; TokenLValSField left = new(var.init) { baseType = tokdeclcl.MakeRefToken(var), fieldName = var.name }; DoVarInit(sfiFunc, left, var.init); } else if(var.init != null) { // this. = ; TokenLValIField left = new(var.init) { baseRVal = new TokenRValThis(var.init, tokdeclcl), fieldName = var.name }; DoVarInit(ifiFunc, left, var.init); } } continue; } // [ : ] { [ get { } ] [ set { } ] } // '[' ... ']' [ : ] { [ get { } ] [ set { } ] } bool prop = (token is TokenType) && (token.nextToken is TokenName) && (token.nextToken.nextToken is TokenKwBrcOpen || token.nextToken.nextToken is TokenKwColon); prop |= (token is TokenType) && (token.nextToken is TokenKwBrkOpen); if(prop) { TokenDeclVar var = ParseProperty(ref token, (flags & SDT_ABSTRACT) != 0, true); if(var != null) { var.sdtClass = tokdeclcl; var.sdtFlags = flags; if(var.getProp != null) { var.getProp.sdtClass = tokdeclcl; var.getProp.sdtFlags = flags; } if(var.setProp != null) { var.setProp.sdtClass = tokdeclcl; var.setProp.sdtFlags = flags; } } continue; } // 'constructor' '(' arglist ')' [ ':' [ 'base' ] '(' baseconstructorcall ')' ] '{' body '}' if(token is TokenKwConstructor) { ParseSDTClassCtorDecl(ref token, flags, tokdeclcl); haveExplicitConstructor = true; continue; } // // method with explicit return type if(token is TokenType) { ParseSDTClassMethodDecl(ref token, flags, tokdeclcl); continue; } // // method returning void if((token is TokenName) || (token is TokenKw kwt && kwt.sdtClassOp)) { ParseSDTClassMethodDecl(ref token, flags, tokdeclcl); continue; } // That's all we support in a class declaration. ErrorMsg(token, "expecting field or method declaration"); token = SkipPastSemi(token); } // If script didn't specify any constructor, create a default no-argument one. if(!haveExplicitConstructor) { TokenDeclVar tokenDeclFunc = new(token, null, tokenScript) { name = new TokenName(token, "$ctor"), retType = new TokenTypeVoid(token), argDecl = new TokenArgDecl(token), sdtClass = tokdeclcl, sdtFlags = SDT_PUBLIC | SDT_NEW, body = new TokenStmtBlock(token) }; tokenDeclFunc.body.function = tokenDeclFunc; if(tokdeclcl.extends != null) { SetUpDefaultBaseCtorCall(tokenDeclFunc); } else { // default constructor that doesn't do anything is trivial tokenDeclFunc.triviality = Triviality.trivial; } tokenScript.AddVarEntry(tokenDeclFunc); } // Skip over the closing brace and pop corresponding var frame. token = token.nextToken; tokenScript.PopVarFrame(); ret: currentDeclSDType = saveCurSDType; } /** * @brief Parse out abstract/override/private/protected/public/static/virtual keywords. * @param token = first token to evaluate * @returns flags found; token = unprocessed token */ private Dictionary foundFlags = new(); private uint ParseQualifierFlags(ref Token token) { foundFlags.Clear(); while(true) { if(token is TokenKwPrivate) { token = AddQualifierFlag(token, SDT_PRIVATE, SDT_PROTECTED | SDT_PUBLIC); continue; } if(token is TokenKwProtected) { token = AddQualifierFlag(token, SDT_PROTECTED, SDT_PRIVATE | SDT_PUBLIC); continue; } if(token is TokenKwPublic) { token = AddQualifierFlag(token, SDT_PUBLIC, SDT_PRIVATE | SDT_PROTECTED); continue; } if(token is TokenKwAbstract) { token = AddQualifierFlag(token, SDT_ABSTRACT, SDT_FINAL | SDT_STATIC | SDT_VIRTUAL); continue; } if(token is TokenKwFinal) { token = AddQualifierFlag(token, SDT_FINAL, SDT_ABSTRACT | SDT_VIRTUAL); continue; } if(token is TokenKwNew) { token = AddQualifierFlag(token, SDT_NEW, SDT_OVERRIDE); continue; } if(token is TokenKwOverride) { token = AddQualifierFlag(token, SDT_OVERRIDE, SDT_NEW | SDT_STATIC); continue; } if(token is TokenKwStatic) { token = AddQualifierFlag(token, SDT_STATIC, SDT_ABSTRACT | SDT_OVERRIDE | SDT_VIRTUAL); continue; } if(token is TokenKwVirtual) { token = AddQualifierFlag(token, SDT_VIRTUAL, SDT_ABSTRACT | SDT_STATIC); continue; } break; } uint flags = 0; foreach(uint flag in foundFlags.Keys) flags |= flag; if((flags & (SDT_PRIVATE | SDT_PROTECTED | SDT_PUBLIC)) == 0) ErrorMsg(token, "must specify exactly one of private, protected or public"); return flags; } private Token AddQualifierFlag(Token token, uint add, uint confs) { while(confs != 0) { uint conf = (uint)(confs & -confs); if (foundFlags.TryGetValue(conf, out Token confToken)) { ErrorMsg(token, "conflicts with " + confToken.ToString()); } confs -= conf; } foundFlags[add] = token; return token.nextToken; } /** * @brief Parse a property declaration. * @param token = points to the property type token on entry * points just past the closing brace on return * @param abs = true: property is abstract * false: property is concrete * @param imp = allow implemented interface specs * @returns null: parse failure * else: property * * [ : ] { [ get { } ] [ set { } ] } * '[' ... ']' [ : ] { [ get { } ] [ set { } ] } */ private TokenDeclVar ParseProperty(ref Token token, bool abs, bool imp) { // Parse out the property's type and name. // TokenType type = (TokenType)token; TokenName name; TokenArgDecl args; Token argTokens = null; token = token.nextToken; if(token is TokenKwBrkOpen) { argTokens = token; name = new TokenName(token, "$idxprop"); args = ParseFuncArgs(ref token, typeof(TokenKwBrkClose)); } else { name = (TokenName)token; token = token.nextToken; args = new TokenArgDecl(token); } // Maybe it claims to implement some interface properties. // [ ':' [.] ',' ... ] TokenIntfImpl implements = null; if(token is TokenKwColon) { implements = ParseImplements(ref token, name); if(implements == null) return null; if(!imp) { ErrorMsg(token, "cannot implement interface property"); } } // Should have an opening brace. if(token is not TokenKwBrcOpen) { ErrorMsg(token, "expect { to open property definition"); token = SkipPastSemi(token); return null; } token = token.nextToken; // Parse out the getter and/or setter. // 'get' { | ';' } // 'set' { | ';' } TokenDeclVar getFunc = null; TokenDeclVar setFunc = null; while(token is not TokenKwBrcClose) { // Maybe create a getter function. if(token is TokenKwGet) { getFunc = new TokenDeclVar(token, null, tokenScript) { name = new TokenName(token, name.val + "$get"), retType = type, argDecl = args, implements = MakePropertyImplements(implements, "$get") }; token = token.nextToken; if(!ParseFunctionBody(ref token, getFunc, abs)) { getFunc = null; } else if(!tokenScript.AddVarEntry(getFunc)) { ErrorMsg(getFunc, "duplicate getter"); } continue; } // Maybe create a setter function. if(token is TokenKwSet) { TokenArgDecl argDecl = args; if(getFunc != null) { argDecl = (argTokens == null) ? new TokenArgDecl(token) : ParseFuncArgs(ref argTokens, typeof(TokenKwBrkClose)); } argDecl.AddArg(type, new TokenName(token, "value")); setFunc = new TokenDeclVar(token, null, tokenScript) { name = new TokenName(token, name.val + "$set"), retType = new TokenTypeVoid(token), argDecl = argDecl, implements = MakePropertyImplements(implements, "$set") }; token = token.nextToken; if(!ParseFunctionBody(ref token, setFunc, abs)) { setFunc = null; } else if(!tokenScript.AddVarEntry(setFunc)) { ErrorMsg(setFunc, "duplicate setter"); } continue; } ErrorMsg(token, "expecting get or set"); token = SkipPastSemi(token); return null; } token = token.nextToken; if((getFunc == null) && (setFunc == null)) { ErrorMsg(name, "must specify at least one of get, set"); return null; } // Set up a variable for the property. TokenDeclVar tokenDeclVar = new(name, null, tokenScript) { type = type, name = name, getProp = getFunc, setProp = setFunc }; // Can't be same name already in block. if (!tokenScript.AddVarEntry(tokenDeclVar)) { ErrorMsg(tokenDeclVar, "duplicate member " + name.val); return null; } return tokenDeclVar; } /** * @brief Given a list of implemented interface methods, create a similar list with suffix added to all the names * @param implements = original list of implemented interface methods * @param suffix = string to be added to end of implemented interface method names * @returns list similar to implements with suffix added to end of implemented interface method names */ private TokenIntfImpl MakePropertyImplements(TokenIntfImpl implements, string suffix) { TokenIntfImpl gsimpls = null; for(TokenIntfImpl impl = implements; impl != null; impl = (TokenIntfImpl)impl.nextToken) { TokenIntfImpl gsimpl = new(impl.intfType, new TokenName(impl.methName, impl.methName.val + suffix)) { nextToken = gsimpls }; gsimpls = gsimpl; } return gsimpls; } /** * @brief Parse a constructor definition for a script-defined type class. * @param token = points to 'constructor' keyword * @param flags = abstract/override/static/virtual flags * @param tokdeclcl = which script-defined type class this method is in * @returns with method parsed and cataloged (or error message(s) printed) */ private void ParseSDTClassCtorDecl(ref Token token, uint flags, TokenDeclSDTypeClass tokdeclcl) { if((flags & (SDT_ABSTRACT | SDT_OVERRIDE | SDT_STATIC | SDT_VIRTUAL)) != 0) { ErrorMsg(token, "cannot have abstract, override, static or virtual constructor"); } TokenDeclVar tokenDeclFunc = new(token, null, tokenScript); tokenDeclFunc.name = new TokenName(tokenDeclFunc, "$ctor"); tokenDeclFunc.retType = new TokenTypeVoid(token); tokenDeclFunc.sdtClass = tokdeclcl; tokenDeclFunc.sdtFlags = flags | SDT_NEW; token = token.nextToken; if(token is not TokenKwParOpen) { ErrorMsg(token, "expecting ( for constructor argument list"); token = SkipPastSemi(token); return; } tokenDeclFunc.argDecl = ParseFuncArgs(ref token, typeof(TokenKwParClose)); if(tokenDeclFunc.argDecl == null) return; TokenDeclVar saveDeclFunc = currentDeclFunc; currentDeclFunc = tokenDeclFunc; tokenScript.PushVarFrame(tokenDeclFunc.argDecl.varDict); try { // Set up reference to base constructor. TokenLValBaseField baseCtor = new(token, new TokenName(token, "$ctor"), tokdeclcl); // Parse any base constructor call as if it were the first statement of the // constructor itself. if(token is TokenKwColon) { token = token.nextToken; if(token is TokenKwBase) { token = token.nextToken; } if(token is not TokenKwParOpen) { ErrorMsg(token, "expecting ( for base constructor call arguments"); token = SkipPastSemi(token); return; } TokenRValCall rvc = ParseRValCall(ref token, baseCtor); if(rvc == null) return; if(tokdeclcl.extends != null) { tokenDeclFunc.baseCtorCall = rvc; tokenDeclFunc.unknownTrivialityCalls.AddLast(rvc); } else { ErrorMsg(rvc, "base constructor call cannot be specified if not extending anything"); } } else if(tokdeclcl.extends != null) { // Caller didn't specify a constructor but we are extending, so we will // call the extended class's default constructor. SetUpDefaultBaseCtorCall(tokenDeclFunc); } // Parse the constructor body. tokenDeclFunc.body = ParseStmtBlock(ref token); if(tokenDeclFunc.body == null) return; if(tokenDeclFunc.argDecl == null) return; } finally { tokenScript.PopVarFrame(); currentDeclFunc = saveDeclFunc; } // Add to list of methods defined by this class. // It has the name "$ctor(argsig)". if(!tokenScript.AddVarEntry(tokenDeclFunc)) { ErrorMsg(tokenDeclFunc, "duplicate constructor definition"); } } /** * @brief Set up a call from a constructor to its default base constructor. */ private void SetUpDefaultBaseCtorCall(TokenDeclVar thisCtor) { TokenLValBaseField baseCtor = new(thisCtor, new TokenName(thisCtor, "$ctor"), (TokenDeclSDTypeClass)thisCtor.sdtClass); TokenRValCall rvc = new(thisCtor) { meth = baseCtor }; thisCtor.baseCtorCall = rvc; thisCtor.unknownTrivialityCalls.AddLast(rvc); } /** * @brief Parse a method definition for a script-defined type class. * @param token = points to return type (or method name for implicit return type of void) * @param flags = abstract/override/static/virtual flags * @param tokdeclcl = which script-defined type class this method is in * @returns with method parsed and cataloged (or error message(s) printed) */ private void ParseSDTClassMethodDecl(ref Token token, uint flags, TokenDeclSDTypeClass tokdeclcl) { TokenDeclVar tokenDeclFunc = ParseDeclFunc(ref token, (flags & SDT_ABSTRACT) != 0, (flags & SDT_STATIC) == 0, (flags & SDT_STATIC) == 0); if(tokenDeclFunc != null) { tokenDeclFunc.sdtClass = tokdeclcl; tokenDeclFunc.sdtFlags = flags; if(!tokenScript.AddVarEntry(tokenDeclFunc)) { string funcNameSig = tokenDeclFunc.funcNameSig.val; ErrorMsg(tokenDeclFunc.funcNameSig, "duplicate method name " + funcNameSig); } } } /** * @brief Parse a delegate declaration statement. * @param token = points to TokenDeclSDTypeDelegate token on entry * points just past ';' on return * @param outerSDType = null: this is a top-level delegate * else: this delegate is being defined inside this type * @param flags = SDT_{PRIVATE,PROTECTED,PUBLIC} */ private void ParseDeclDelegate(ref Token token, TokenDeclSDType outerSDType, uint flags) { Token u = token; TokenDeclSDTypeDelegate tokdecldel; TokenType retType; tokdecldel = (TokenDeclSDTypeDelegate)u; tokdecldel.outerSDType = outerSDType; tokdecldel.accessLevel = flags; // first thing following that should be return type // but we will fill in 'void' if it is missing u = u.nextToken; if(u is TokenType tu) { retType = tu; u = u.nextToken; } else { retType = new TokenTypeVoid(u); } // get list of argument types until we see a ')' List args = new(); bool first = true; do { if(first) { // first time should have '(' ')' or '(' if(u is not TokenKwParOpen) { ErrorMsg(u, "expecting ( after delegate name"); token = SkipPastSemi(token); return; } first = false; u = u.nextToken; if(u is TokenKwParClose) break; } else { // other times should have ',' if(u is not TokenKwComma) { ErrorMsg(u, "expecting , separating arg types"); token = SkipPastSemi(token); return; } u = u.nextToken; } if(u is not TokenType) { ErrorMsg(u, "expecting argument type"); token = SkipPastSemi(token); return; } args.Add((TokenType)u); u = u.nextToken; // they can put in a dummy name that we toss out if(u is TokenName) u = u.nextToken; // scanning ends on a ')' } while(u is not TokenKwParClose); // fill in the return type and argment type array tokdecldel.SetRetArgTypes(retType, args.ToArray()); // and finally must have ';' to finish the delegate declaration statement u = u.nextToken; if(u is not TokenKwSemi) { ErrorMsg(u, "expecting ; after ) in delegate"); token = SkipPastSemi(token); return; } token = u.nextToken; } /** * @brief Parse an interface declaration. * @param token = points to TokenDeclSDTypeInterface token on entry * points just past closing '}' on return * @param outerSDType = null: this is a top-level interface * else: this interface is being defined inside this type * @param flags = SDT_{PRIVATE,PROTECTED,PUBLIC} */ private void ParseDeclInterface(ref Token token, TokenDeclSDType outerSDType, uint flags) { Token u = token; TokenDeclSDTypeInterface tokdeclin; tokdeclin = (TokenDeclSDTypeInterface)u; tokdeclin.outerSDType = outerSDType; tokdeclin.accessLevel = flags; u = u.nextToken; // next can be ':' followed by list of implemented interfaces if(u is TokenKwColon) { u = u.nextToken; while(true) { if(u is TokenTypeSDTypeInterface iu) { TokenDeclSDTypeInterface i = iu.decl; if(!tokdeclin.implements.Contains(i)) { tokdeclin.implements.Add(i); } } else { ErrorMsg(u, "expecting interface name"); if(u is TokenKwBrcOpen) break; } u = u.nextToken; if(u is not TokenKwComma) break; u = u.nextToken; } } // next must be '{' to open interface declaration body if(u is not TokenKwBrcOpen) { ErrorMsg(u, "expecting { to open interface declaration body"); token = SkipPastSemi(token); return; } token = u.nextToken; // start a var definition frame to collect the interface members tokenScript.PushVarFrame(false); tokdeclin.methsNProps = tokenScript.variablesStack; // process declaration statements until '}' while(token is not TokenKwBrcClose) { if(token is TokenKwSemi) { token = token.nextToken; continue; } // Parse nested script-defined type definitions. if(ParseDeclSDTypes(ref token, tokdeclin, SDT_PUBLIC)) continue; // ; // abstract method with explicit return type if((token is TokenType) && (token.nextToken is TokenName) && (token.nextToken.nextToken is TokenKwParOpen)) { Token name = token.nextToken; TokenDeclVar tokenDeclFunc = ParseDeclFunc(ref token, true, false, false); if(tokenDeclFunc == null) continue; if(!tokenScript.AddVarEntry(tokenDeclFunc)) { ErrorMsg(name, "duplicate method name"); continue; } continue; } // ; // abstract method returning void if((token is TokenName) && (token.nextToken is TokenKwParOpen)) { Token name = token; TokenDeclVar tokenDeclFunc = ParseDeclFunc(ref token, true, false, false); if(tokenDeclFunc == null) continue; if(!tokenScript.AddVarEntry(tokenDeclFunc)) { ErrorMsg(name, "duplicate method name"); } continue; } // { [ get ; ] [ set ; ] } // '[' ... ']' { [ get ; ] [ set ; ] } // abstract property bool prop = (token is TokenType) && (token.nextToken is TokenName) && (token.nextToken.nextToken is TokenKwBrcOpen || token.nextToken.nextToken is TokenKwColon); prop |= (token is TokenType) && (token.nextToken is TokenKwBrkOpen); if(prop) { ParseProperty(ref token, true, false); continue; } // That's all we support in an interface declaration. ErrorMsg(token, "expecting method or property prototype"); token = SkipPastSemi(token); } // Skip over the closing brace and pop the corresponding var frame. token = token.nextToken; tokenScript.PopVarFrame(); } /** * @brief parse state body (including all its event handlers) * @param token = points to TokenKwBrcOpen * @returns null: state body parse error * else: token representing state * token = points past close brace */ private TokenStateBody ParseStateBody(ref Token token) { TokenStateBody tokenStateBody = new(token); if(token is not TokenKwBrcOpen) { ErrorMsg(token, "expecting { at beg of state"); token = SkipPastSemi(token); return null; } token = token.nextToken; while(token is not TokenKwBrcClose) { if(token is TokenEnd) { ErrorMsg(tokenStateBody, "eof parsing state body"); return null; } TokenDeclVar tokenDeclFunc = ParseDeclFunc(ref token, false, false, false); if(tokenDeclFunc == null) return null; if(tokenDeclFunc.retType is not TokenTypeVoid) { ErrorMsg(tokenDeclFunc.retType, "event handlers don't have return types"); return null; } tokenDeclFunc.nextToken = tokenStateBody.eventFuncs; tokenStateBody.eventFuncs = tokenDeclFunc; } token = token.nextToken; return tokenStateBody; } /** * @brief Parse a function declaration, including its arg list and body * @param token = points to function return type token (or function name token if return type void) * @param abs = false: concrete function; true: abstract declaration * @param imp = allow implemented interface specs * @param ops = accept operators (==, +, etc) for function name * @returns null: error parsing function definition * else: function declaration * token = advanced just past function, ie, just past the closing brace */ private TokenDeclVar ParseDeclFunc(ref Token token, bool abs, bool imp, bool ops) { if(token is TokenType retType) token = token.nextToken; else retType = new TokenTypeVoid(token); TokenName simpleName; if((token is TokenKw Kwt) && Kwt.sdtClassOp) { if(!ops) ErrorMsg(token, "operator functions disallowed in static contexts"); simpleName = new TokenName(token, "$op" + token.ToString()); } else if(token is not TokenName tname) { ErrorMsg(token, "expecting function name"); token = SkipPastSemi(token); return null; } else { simpleName = tname; } token = token.nextToken; return ParseDeclFunc(ref token, abs, imp, retType, simpleName); } /** * @brief Parse a function declaration, including its arg list and body * This version enters with token pointing to the '(' at beginning of arg list * @param token = points to the '(' of the arg list * @param abs = false: concrete function; true: abstract declaration * @param imp = allow implemented interface specs * @param retType = return type (TokenTypeVoid if void, never null) * @param simpleName = function name without any signature * @returns null: error parsing remainder of function definition * else: function declaration * token = advanced just past function, ie, just past the closing brace */ private TokenDeclVar ParseDeclFunc(ref Token token, bool abs, bool imp, TokenType retType, TokenName simpleName) { TokenDeclVar tokenDeclFunc = new(simpleName, null, tokenScript) { name = simpleName, retType = retType, argDecl = ParseFuncArgs(ref token, typeof(TokenKwParClose)) }; if (tokenDeclFunc.argDecl == null) return null; if(token is TokenKwColon) { tokenDeclFunc.implements = ParseImplements(ref token, simpleName); if(tokenDeclFunc.implements == null) return null; if(!imp) { ErrorMsg(tokenDeclFunc.implements, "cannot implement interface method"); tokenDeclFunc.implements = null; } } if(!ParseFunctionBody(ref token, tokenDeclFunc, abs)) return null; if(tokenDeclFunc.argDecl == null) return null; return tokenDeclFunc; } /** * @brief Parse interface implementation list. * @param token = points to ':' on entry * points just past list on return * @param simpleName = simple name (no arg signature) of method/property that * is implementing the interface method/property * @returns list of implemented interface methods/properties */ private TokenIntfImpl ParseImplements(ref Token token, TokenName simpleName) { TokenIntfImpl implements = null; do { token = token.nextToken; if(token is not TokenTypeSDTypeInterface) { ErrorMsg(token, "expecting interface type"); token = SkipPastSemi(token); return null; } TokenTypeSDTypeInterface intfType = (TokenTypeSDTypeInterface)token; token = token.nextToken; TokenName methName = simpleName; if((token is TokenKwDot) && (token.nextToken is TokenName tname)) { methName = tname; token = token.nextToken.nextToken; } TokenIntfImpl intfImpl = new(intfType, methName) { nextToken = implements }; implements = intfImpl; } while(token is TokenKwComma); return implements; } /** * @brief Parse function declaration's body * @param token = points to body, ie, ';' or '{' * @param tokenDeclFunc = function being declared * @param abs = false: concrete function; true: abstract declaration * @returns whether or not the function definition parsed correctly */ private bool ParseFunctionBody(ref Token token, TokenDeclVar tokenDeclFunc, bool abs) { if(token is TokenKwSemi) { if(!abs) { ErrorMsg(token, "concrete function must have body"); token = SkipPastSemi(token); return false; } token = token.nextToken; return true; } // Declare this function as being the one currently being processed // for anything that cares. We also start a variable frame that // includes all the declared parameters. TokenDeclVar saveDeclFunc = currentDeclFunc; currentDeclFunc = tokenDeclFunc; tokenScript.PushVarFrame(tokenDeclFunc.argDecl.varDict); // Now parse the function statement block. tokenDeclFunc.body = ParseStmtBlock(ref token); // Pop the var frame that contains the arguments. tokenScript.PopVarFrame(); currentDeclFunc = saveDeclFunc; // Check final errors. if(tokenDeclFunc.body == null) return false; if(abs) { ErrorMsg(tokenDeclFunc.body, "abstract function must not have body"); tokenDeclFunc.body = null; return false; } return true; } /** * @brief Parse statement * @param token = first token of statement * @returns null: parse error * else: token representing whole statement * token = points past statement */ private TokenStmt ParseStmt(ref Token token) { // Statements that begin with a specific keyword. if(token is TokenKwAt) return ParseStmtLabel(ref token); if(token is TokenKwBrcOpen) return ParseStmtBlock(ref token); if(token is TokenKwBreak) return ParseStmtBreak(ref token); if(token is TokenKwCont) return ParseStmtCont(ref token); if(token is TokenKwDo) return ParseStmtDo(ref token); if(token is TokenKwFor) return ParseStmtFor(ref token); if(token is TokenKwForEach) return ParseStmtForEach(ref token); if(token is TokenKwIf) return ParseStmtIf(ref token); if(token is TokenKwJump) return ParseStmtJump(ref token); if(token is TokenKwRet) return ParseStmtRet(ref token); if(token is TokenKwSemi) return ParseStmtNull(ref token); if(token is TokenKwState) return ParseStmtState(ref token); if(token is TokenKwSwitch) return ParseStmtSwitch(ref token); if(token is TokenKwThrow) return ParseStmtThrow(ref token); if(token is TokenKwTry) return ParseStmtTry(ref token); if(token is TokenKwWhile) return ParseStmtWhile(ref token); // Try to parse anything else as an expression, possibly calling // something and/or writing to a variable. TokenRVal tokenRVal = ParseRVal(ref token, semiOnly); if(tokenRVal != null) { TokenStmtRVal tokenStmtRVal = new(tokenRVal) { rVal = tokenRVal }; return tokenStmtRVal; } // Who knows what it is... ErrorMsg(token, "unknown statement"); token = SkipPastSemi(token); return null; } /** * @brief parse a statement block, ie, group of statements between braces * @param token = points to { token * @returns null: error parsing * else: statements bundled in this token * token = advanced just past the } token */ private TokenStmtBlock ParseStmtBlock(ref Token token) { if(token is not TokenKwBrcOpen) { ErrorMsg(token, "statement block body must begin with a {"); token = SkipPastSemi(token); return null; } TokenStmtBlock tokenStmtBlock = new(token) { function = currentDeclFunc, outerStmtBlock = currentStmtBlock }; currentStmtBlock = tokenStmtBlock; VarDict outerVariablesStack = tokenScript.variablesStack; try { Token prevStmt = null; token = token.nextToken; while(token is not TokenKwBrcClose) { if(token is TokenEnd) { ErrorMsg(tokenStmtBlock, "missing }"); return null; } Token thisStmt; if(((token is TokenType) && (token.nextToken is TokenName)) || (token is TokenKwConst)) { thisStmt = ParseDeclVar(ref token, null); } else { thisStmt = ParseStmt(ref token); } if(thisStmt == null) return null; if(prevStmt == null) tokenStmtBlock.statements = thisStmt; else prevStmt.nextToken = thisStmt; prevStmt = thisStmt; } token = token.nextToken; } finally { tokenScript.variablesStack = outerVariablesStack; currentStmtBlock = tokenStmtBlock.outerStmtBlock; } return tokenStmtBlock; } /** * @brief parse a 'break' statement * @param token = points to break keyword token * @returns null: error parsing * else: statements bundled in this token * token = advanced just past the ; token */ private TokenStmtBreak ParseStmtBreak(ref Token token) { TokenStmtBreak tokenStmtBreak = new(token); token = token.nextToken; if(token is not TokenKwSemi) { ErrorMsg(token, "expecting ;"); token = SkipPastSemi(token); return null; } token = token.nextToken; return tokenStmtBreak; } /** * @brief parse a 'continue' statement * @param token = points to continue keyword token * @returns null: error parsing * else: statements bundled in this token * token = advanced just past the ; token */ private TokenStmtCont ParseStmtCont(ref Token token) { TokenStmtCont tokenStmtCont = new(token); token = token.nextToken; if(token is not TokenKwSemi) { ErrorMsg(token, "expecting ;"); token = SkipPastSemi(token); return null; } token = token.nextToken; return tokenStmtCont; } /** * @brief parse a 'do' statement * @params token = points to 'do' keyword token * @returns null: parse error * else: pointer to token encapsulating the do statement, including body * token = advanced just past the body statement */ private TokenStmtDo ParseStmtDo(ref Token token) { currentDeclFunc.triviality = Triviality.complex; TokenStmtDo tokenStmtDo = new(token); token = token.nextToken; tokenStmtDo.bodyStmt = ParseStmt(ref token); if(tokenStmtDo.bodyStmt == null) return null; if(token is not TokenKwWhile) { ErrorMsg(token, "expecting while clause"); return null; } token = token.nextToken; tokenStmtDo.testRVal = ParseRValParen(ref token); if(tokenStmtDo.testRVal == null) return null; if(token is not TokenKwSemi) { ErrorMsg(token, "while clause must terminate on semicolon"); token = SkipPastSemi(token); return null; } token = token.nextToken; return tokenStmtDo; } /** * @brief parse a for statement * @param token = points to 'for' keyword token * @returns null: parse error * else: pointer to encapsulated for statement token * token = advanced just past for body statement */ private TokenStmt ParseStmtFor(ref Token token) { currentDeclFunc.triviality = Triviality.complex; // Create encapsulating token and skip past 'for (' TokenStmtFor tokenStmtFor = new(token); token = token.nextToken; if(token is not TokenKwParOpen) { ErrorMsg(token, "for must be followed by ("); return null; } token = token.nextToken; // If a plain for, ie, not declaring a variable, it's straightforward. if(token is not TokenType) { tokenStmtFor.initStmt = ParseStmt(ref token); if(tokenStmtFor.initStmt == null) return null; return ParseStmtFor2(tokenStmtFor, ref token) ? tokenStmtFor : null; } // Initialization declares a variable, so encapsulate it in a block so // variable has scope only in the for statement, including its body. TokenStmtBlock forStmtBlock = new(tokenStmtFor) { outerStmtBlock = currentStmtBlock, function = currentDeclFunc }; currentStmtBlock = forStmtBlock; tokenScript.PushVarFrame(true); TokenDeclVar tokenDeclVar = ParseDeclVar(ref token, null); if(tokenDeclVar == null) { tokenScript.PopVarFrame(); currentStmtBlock = forStmtBlock.outerStmtBlock; return null; } forStmtBlock.statements = tokenDeclVar; tokenDeclVar.nextToken = tokenStmtFor; bool ok = ParseStmtFor2(tokenStmtFor, ref token); tokenScript.PopVarFrame(); currentStmtBlock = forStmtBlock.outerStmtBlock; return ok ? forStmtBlock : null; } /** * @brief parse rest of 'for' statement starting with the test expression. * @param tokenStmtFor = token encapsulating the for statement * @param token = points to test expression * @returns false: parse error * true: successful * token = points just past body statement */ private bool ParseStmtFor2(TokenStmtFor tokenStmtFor, ref Token token) { if(token is TokenKwSemi) { token = token.nextToken; } else { tokenStmtFor.testRVal = ParseRVal(ref token, semiOnly); if(tokenStmtFor.testRVal == null) return false; } if(token is TokenKwParClose) { token = token.nextToken; } else { tokenStmtFor.incrRVal = ParseRVal(ref token, parCloseOnly); if(tokenStmtFor.incrRVal == null) return false; } tokenStmtFor.bodyStmt = ParseStmt(ref token); return tokenStmtFor.bodyStmt != null; } /** * @brief parse a foreach statement * @param token = points to 'foreach' keyword token * @returns null: parse error * else: pointer to encapsulated foreach statement token * token = advanced just past for body statement */ private TokenStmt ParseStmtForEach(ref Token token) { currentDeclFunc.triviality = Triviality.complex; // Create encapsulating token and skip past 'foreach (' TokenStmtForEach tokenStmtForEach = new(token); token = token.nextToken; if(token is not TokenKwParOpen) { ErrorMsg(token, "foreach must be followed by ("); return null; } token = token.nextToken; if(token is TokenName tname) { tokenStmtForEach.keyLVal = new TokenLValName(tname, tokenScript.variablesStack); token = token.nextToken; } if(token is not TokenKwComma) { ErrorMsg(token, "expecting comma"); token = SkipPastSemi(token); return null; } token = token.nextToken; if(token is TokenName tname2) { tokenStmtForEach.valLVal = new TokenLValName(tname2, tokenScript.variablesStack); token = token.nextToken; } if(token is not TokenKwIn) { ErrorMsg(token, "expecting 'in'"); token = SkipPastSemi(token); return null; } token = token.nextToken; tokenStmtForEach.arrayRVal = GetOperand(ref token); if(tokenStmtForEach.arrayRVal == null) return null; if(token is not TokenKwParClose) { ErrorMsg(token, "expecting )"); token = SkipPastSemi(token); return null; } token = token.nextToken; tokenStmtForEach.bodyStmt = ParseStmt(ref token); if(tokenStmtForEach.bodyStmt == null) return null; return tokenStmtForEach; } private TokenStmtIf ParseStmtIf(ref Token token) { TokenStmtIf tokenStmtIf = new(token); token = token.nextToken; tokenStmtIf.testRVal = ParseRValParen(ref token); if(tokenStmtIf.testRVal == null) return null; tokenStmtIf.trueStmt = ParseStmt(ref token); if(tokenStmtIf.trueStmt == null) return null; if(token is TokenKwElse) { token = token.nextToken; tokenStmtIf.elseStmt = ParseStmt(ref token); if(tokenStmtIf.elseStmt == null) return null; } return tokenStmtIf; } private TokenStmtJump ParseStmtJump(ref Token token) { // Create jump statement token to encapsulate the whole statement. TokenStmtJump tokenStmtJump = new(token); token = token.nextToken; if(token is not TokenName || token.nextToken is not TokenKwSemi) { ErrorMsg(token, "expecting label;"); token = SkipPastSemi(token); return null; } tokenStmtJump.label = (TokenName)token; token = token.nextToken.nextToken; // If label is already defined, it means this is a backward (looping) // jump, so remember the label has backward jump references. // We also then assume the function is complex, ie, it has a loop. if(currentDeclFunc.labels.ContainsKey(tokenStmtJump.label.val)) { currentDeclFunc.labels[tokenStmtJump.label.val].hasBkwdRefs = true; currentDeclFunc.triviality = Triviality.complex; } return tokenStmtJump; } /** * @brief parse a jump target label statement * @param token = points to the '@' token * @returns null: error parsing * else: the label * token = advanced just past the ; */ private TokenStmtLabel ParseStmtLabel(ref Token token) { if(token.nextToken is not TokenName tname || token.nextToken.nextToken is not TokenKwSemi) { ErrorMsg(token, "invalid label"); token = SkipPastSemi(token); return null; } TokenStmtLabel stmtLabel = new(token) { name = tname, block = currentStmtBlock }; if (currentDeclFunc.labels.ContainsKey(stmtLabel.name.val)) { ErrorMsg(token.nextToken, "duplicate label"); ErrorMsg(currentDeclFunc.labels[stmtLabel.name.val], "previously defined here"); token = SkipPastSemi(token); return null; } currentDeclFunc.labels.Add(stmtLabel.name.val, stmtLabel); token = token.nextToken.nextToken.nextToken; return stmtLabel; } private TokenStmtNull ParseStmtNull(ref Token token) { TokenStmtNull tokenStmtNull = new(token); token = token.nextToken; return tokenStmtNull; } private TokenStmtRet ParseStmtRet(ref Token token) { TokenStmtRet tokenStmtRet = new(token); token = token.nextToken; if(token is TokenKwSemi) { token = token.nextToken; } else { tokenStmtRet.rVal = ParseRVal(ref token, semiOnly); if(tokenStmtRet.rVal == null) return null; } return tokenStmtRet; } private TokenStmtSwitch ParseStmtSwitch(ref Token token) { TokenStmtSwitch tokenStmtSwitch = new(token); token = token.nextToken; tokenStmtSwitch.testRVal = ParseRValParen(ref token); if(tokenStmtSwitch.testRVal == null) return null; if(!(token is TokenKwBrcOpen)) { ErrorMsg(token, "expecting open brace"); token = SkipPastSemi(token); return null; } token = token.nextToken; TokenSwitchCase tokenSwitchCase = null; bool haveComplained = false; while(!(token is TokenKwBrcClose)) { if(token is TokenKwCase) { tokenSwitchCase = new TokenSwitchCase(token); if(tokenStmtSwitch.lastCase == null) { tokenStmtSwitch.cases = tokenSwitchCase; } else { tokenStmtSwitch.lastCase.nextCase = tokenSwitchCase; } tokenStmtSwitch.lastCase = tokenSwitchCase; token = token.nextToken; tokenSwitchCase.rVal1 = ParseRVal(ref token, colonOrDotDotDot); if(tokenSwitchCase.rVal1 == null) return null; if(token is TokenKwDotDotDot) { token = token.nextToken; tokenSwitchCase.rVal2 = ParseRVal(ref token, colonOnly); if(tokenSwitchCase.rVal2 == null) return null; } else { if(!(token is TokenKwColon)) { ErrorMsg(token, "expecting : or ..."); token = SkipPastSemi(token); return null; } token = token.nextToken; } } else if(token is TokenKwDefault) { tokenSwitchCase = new TokenSwitchCase(token); if(tokenStmtSwitch.lastCase == null) { tokenStmtSwitch.cases = tokenSwitchCase; } else { tokenStmtSwitch.lastCase.nextCase = tokenSwitchCase; } tokenStmtSwitch.lastCase = tokenSwitchCase; token = token.nextToken; if(!(token is TokenKwColon)) { ErrorMsg(token, "expecting :"); token = SkipPastSemi(token); return null; } token = token.nextToken; } else if(tokenSwitchCase != null) { TokenStmt bodyStmt = ParseStmt(ref token); if(bodyStmt == null) return null; if(tokenSwitchCase.lastStmt == null) { tokenSwitchCase.stmts = bodyStmt; } else { tokenSwitchCase.lastStmt.nextToken = bodyStmt; } tokenSwitchCase.lastStmt = bodyStmt; bodyStmt.nextToken = null; } else if(!haveComplained) { ErrorMsg(token, "expecting case or default label"); token = SkipPastSemi(token); haveComplained = true; } } token = token.nextToken; return tokenStmtSwitch; } private TokenStmtState ParseStmtState(ref Token token) { TokenStmtState tokenStmtState = new(token); token = token.nextToken; if((token is not TokenName && token is not TokenKwDefault) || token.nextToken is not TokenKwSemi) { ErrorMsg(token, "expecting state;"); token = SkipPastSemi(token); return null; } if(token is TokenName tname) { tokenStmtState.state = tname; } token = token.nextToken.nextToken; return tokenStmtState; } private TokenStmtThrow ParseStmtThrow(ref Token token) { TokenStmtThrow tokenStmtThrow = new(token); token = token.nextToken; if(token is TokenKwSemi) { token = token.nextToken; } else { tokenStmtThrow.rVal = ParseRVal(ref token, semiOnly); if(tokenStmtThrow.rVal == null) return null; } return tokenStmtThrow; } /** * @brief Parse a try { ... } catch { ... } finally { ... } statement block * @param token = point to 'try' keyword on entry * points past last '}' processed on return * @returns encapsulated try/catch/finally or null if parsing error */ private TokenStmtTry ParseStmtTry(ref Token token) { // Parse out the 'try { ... }' part Token tryKw = token; token = token.nextToken; TokenStmt body = ParseStmtBlock(ref token); while(true) { TokenStmtTry tokenStmtTry; if(token is TokenKwCatch) { if(token.nextToken is not TokenKwParOpen || token.nextToken.nextToken is not TokenType || token.nextToken.nextToken.nextToken is not TokenName || token.nextToken.nextToken.nextToken.nextToken is not TokenKwParClose) { ErrorMsg(token, "catch must be followed by ( ) { ... }"); return null; } token = token.nextToken.nextToken; // skip over 'catch' '(' TokenDeclVar tag = new(token.nextToken, currentDeclFunc, tokenScript) { type = (TokenType)token }; token = token.nextToken; // skip over tag.name = (TokenName)token; token = token.nextToken.nextToken; // skip over ')' if((tag.type is not TokenTypeExc) && (tag.type is not TokenTypeStr)) { ErrorMsg(tag.type, "must be type 'exception' or 'string'"); } tokenStmtTry = new TokenStmtTry(tryKw) { tryStmt = WrapTryCatFinInBlock(body), catchVar = tag }; tokenScript.PushVarFrame(false); tokenScript.AddVarEntry(tag); tokenStmtTry.catchStmt = ParseStmtBlock(ref token); tokenScript.PopVarFrame(); if(tokenStmtTry.catchStmt == null) return null; tokenStmtTry.tryStmt.isTry = true; tokenStmtTry.tryStmt.tryStmt = tokenStmtTry; tokenStmtTry.catchStmt.isCatch = true; tokenStmtTry.catchStmt.tryStmt = tokenStmtTry; } else if(token is TokenKwFinally) { token = token.nextToken; tokenStmtTry = new TokenStmtTry(tryKw) { tryStmt = WrapTryCatFinInBlock(body), finallyStmt = ParseStmtBlock(ref token) }; if (tokenStmtTry.finallyStmt == null) return null; tokenStmtTry.tryStmt.isTry = true; tokenStmtTry.tryStmt.tryStmt = tokenStmtTry; tokenStmtTry.finallyStmt.isFinally = true; tokenStmtTry.finallyStmt.tryStmt = tokenStmtTry; } else break; body = tokenStmtTry; } if(!(body is TokenStmtTry)) { ErrorMsg(body, "try must have a matching catch and/or finally"); return null; } return (TokenStmtTry)body; } /** * @brief Wrap a possible try/catch/finally statement block in a block statement. * * Given body = try { } catch (string s) { } * * we return { try { } catch (string s) { } } * * @param body = a TokenStmtTry or a TokenStmtBlock * @returns a TokenStmtBlock */ private TokenStmtBlock WrapTryCatFinInBlock(TokenStmt body) { if(body is TokenStmtBlock tb) return tb; TokenStmtTry innerTry = (TokenStmtTry)body; TokenStmtBlock wrapper = new(body) { statements = innerTry, outerStmtBlock = currentStmtBlock, function = currentDeclFunc }; innerTry.tryStmt.outerStmtBlock = wrapper; if(innerTry.catchStmt != null) innerTry.catchStmt.outerStmtBlock = wrapper; if(innerTry.finallyStmt != null) innerTry.finallyStmt.outerStmtBlock = wrapper; return wrapper; } private TokenStmtWhile ParseStmtWhile(ref Token token) { currentDeclFunc.triviality = Triviality.complex; TokenStmtWhile tokenStmtWhile = new(token); token = token.nextToken; tokenStmtWhile.testRVal = ParseRValParen(ref token); if(tokenStmtWhile.testRVal == null) return null; tokenStmtWhile.bodyStmt = ParseStmt(ref token); if(tokenStmtWhile.bodyStmt == null) return null; return tokenStmtWhile; } /** * @brief parse a variable declaration statement, including init value if any. * @param token = points to type or 'constant' token * @param initFunc = null: parsing a local var declaration * put initialization code in .init * else: parsing a global var or field var declaration * put initialization code in initFunc.body * @returns null: parsing error * else: variable declaration encapulating token * token = advanced just past semi-colon * variables = modified to include the new variable */ private TokenDeclVar ParseDeclVar(ref Token token, TokenDeclVar initFunc) { TokenDeclVar tokenDeclVar = new(token.nextToken, currentDeclFunc, tokenScript); // Handle constant declaration. // It ends up in the declared variables list for the statement block just like // any other variable, except it has .constant = true. // The code generator will test that the initialization expression is constant. // // constant = ; if(token is TokenKwConst) { token = token.nextToken; if(token is not TokenName) { ErrorMsg(token, "expecting constant name"); token = SkipPastSemi(token); return null; } tokenDeclVar.name = (TokenName)token; token = token.nextToken; if(token is not TokenKwAssign) { ErrorMsg(token, "expecting ="); token = SkipPastSemi(token); return null; } token = token.nextToken; TokenRVal rVal = ParseRVal(ref token, semiOnly); if(rVal == null) return null; tokenDeclVar.init = rVal; tokenDeclVar.constant = true; } // Otherwise, normal variable declaration with optional initialization value. else { // Build basic encapsulating token with type and name. tokenDeclVar.type = (TokenType)token; token = token.nextToken; if(!(token is TokenName)) { ErrorMsg(token, "expecting variable name"); token = SkipPastSemi(token); return null; } tokenDeclVar.name = (TokenName)token; token = token.nextToken; // If just a ;, there is no explicit initialization value. // Otherwise, look for an =RVal; expression that has init value. if(token is TokenKwSemi) { token = token.nextToken; if(initFunc != null) { tokenDeclVar.init = TokenRValInitDef.Construct(tokenDeclVar); } } else if(token is TokenKwAssign) { token = token.nextToken; if(initFunc != null) { currentDeclFunc = initFunc; tokenDeclVar.init = ParseRVal(ref token, semiOnly); currentDeclFunc = null; } else { tokenDeclVar.init = ParseRVal(ref token, semiOnly); } if(tokenDeclVar.init == null) return null; } else { ErrorMsg(token, "expecting = or ;"); token = SkipPastSemi(token); return null; } } // If doing local vars, each var goes in its own var frame, // to make sure no code before this point can reference it. if (currentStmtBlock != null) { tokenScript.PushVarFrame(true); } /* ScriptConst scriptConst = ScriptConst.Lookup(tokenDeclVar.name.val); if (scriptConst != null) { ErrorMsg(tokenDeclVar, "reserved constant name " + tokenDeclVar.name.val); return null; } */ // Can't be same name already in block. if (!tokenScript.AddVarEntry(tokenDeclVar)) { ErrorMsg(tokenDeclVar, "duplicate variable " + tokenDeclVar.name.val); return null; } /* if (TokenDeclInline.inlineFunctions.HasAnyExact(tokenDeclVar.name.val)) { ErrorMsg(tokenDeclVar, "reserved name " + tokenDeclVar.name.val); return null; } */ return tokenDeclVar; } /** * @brief Add variable initialization to $globalvarinit, $staticfieldinit or $instfieldinit function. * @param initFunc = $globalvarinit, $staticfieldinit or $instfieldinit function * @param left = variable being initialized * @param init = null: initialize to default value * else: initialize to this value */ private void DoVarInit(TokenDeclVar initFunc, TokenLVal left, TokenRVal init) { // Make a statement that assigns the initialization value to the variable. TokenStmt stmt; if(init == null) { stmt = new TokenStmtVarIniDef(left) { var = left }; } else { TokenKw op = new TokenKwAssign(left); stmt = new TokenStmtRVal(init) { rVal = new TokenRValOpBin(left, op, init) }; } // Add statement to end of initialization function. // Be sure to execute them in same order as in source // as some doofus scripts depend on it. Token lastStmt = initFunc.body.statements; if(lastStmt == null) { initFunc.body.statements = stmt; } else { Token nextStmt; while((nextStmt = lastStmt.nextToken) != null) { lastStmt = nextStmt; } lastStmt.nextToken = stmt; } } /** * @brief parse function declaration argument list * @param token = points to TokenKwParOpen * @returns null: parse error * else: points to token with types and names * token = updated past the TokenKw{Brk,Par}Close */ private TokenArgDecl ParseFuncArgs(ref Token token, Type end) { TokenArgDecl tokenArgDecl = new(token); bool first = true; do { token = token.nextToken; if((token.GetType() == end) && first) break; if(!(token is TokenType type)) { ErrorMsg(token, "expecting arg type"); token = SkipPastSemi(token); return null; } token = token.nextToken; if(token is not TokenName name) { ErrorMsg(token, "expecting arg name"); token = SkipPastSemi(token); return null; } token = token.nextToken; if(!tokenArgDecl.AddArg(type, name)) { ErrorMsg(name, "duplicate arg name"); } first = false; } while(token is TokenKwComma); if(token.GetType() != end) { ErrorMsg(token, "expecting comma or close bracket/paren"); token = SkipPastSemi(token); return null; } token = token.nextToken; return tokenArgDecl; } /** * @brief parse right-hand value expression * this is where arithmetic-like expressions are processed * @param token = points to first token expression * @param termTokenType = expression termination token type * @returns null: not an RVal * else: single token representing whole expression * token = if termTokenType.Length == 1, points just past terminating token * else, points right at terminating token */ public TokenRVal ParseRVal(ref Token token, Type[] termTokenTypes) { // Start with pushing the first operand on operand stack. BinOp binOps = null; TokenRVal operands = GetOperand(ref token); if(operands == null) return null; // Keep scanning until we hit the termination token. while(true) { Type tokType = token.GetType(); for(int i = termTokenTypes.Length; --i >= 0;) { if(tokType == termTokenTypes[i]) goto done; } // Special form: // is if(token is TokenKwIs) { TokenRValIsType tokenRValIsType = new(token); token = token.nextToken; // Parse the . tokenRValIsType.typeExp = ParseTypeExp(ref token); if(tokenRValIsType.typeExp == null) return null; // Replace top operand with result of is tokenRValIsType.rValExp = operands; tokenRValIsType.nextToken = operands.nextToken; operands = tokenRValIsType; // token points just past so see if it is another operator. continue; } // Peek at next operator. BinOp binOp = GetOperator(ref token); if(binOp == null) return null; // If there are stacked operators of higher or same precedence than new one, // perform their computation then push result back on operand stack. // // higher or same = left-to-right application of operators // eg, a - b - c becomes (a - b) - c // // higher precedence = right-to-left application of operators // eg, a - b - c becomes a - (b - c) // // Now of course, there is some ugliness necessary: // we want: a - b - c => (a - b) - c so we do 'higher or same' // but we want: a += b = c => a += (b = c) so we do 'higher only' // // binOps is the first operator (or null if only one) // binOp is the second operator (or first if only one) while(binOps != null) { if(binOps.preced < binOp.preced) break; // 1st operator lower than 2nd, so leave 1st on stack to do later if(binOps.preced > binOp.preced) goto do1st; // 1st op higher than 2nd, so we always do 1st op first if(binOps.preced == ASNPR) break; // equal preced, if assignment type, leave 1st on stack to do later // if non-asn type, do 1st op first (ie left-to-right) do1st: TokenRVal result = PerformBinOp((TokenRVal)operands.prevToken, binOps, (TokenRVal)operands); result.prevToken = operands.prevToken.prevToken; operands = result; binOps = binOps.pop; } // Handle conditional expression as a special form: // ? : if(binOp.token is TokenKwQMark) { TokenRValCondExpr condExpr = new(binOp.token) { condExpr = operands, trueExpr = ParseRVal(ref token, new Type[] { typeof(TokenKwColon) }), falseExpr = ParseRVal(ref token, termTokenTypes), prevToken = operands.prevToken }; operands = condExpr; termTokenTypes = Array.Empty(); goto done; } // Push new operator on its stack. binOp.pop = binOps; binOps = binOp; // Push next operand on its stack. TokenRVal operand = GetOperand(ref token); if(operand == null) return null; operand.prevToken = operands; operands = operand; } done: // At end of expression, perform any stacked computations. while(binOps != null) { TokenRVal result = PerformBinOp((TokenRVal)operands.prevToken, binOps, (TokenRVal)operands); result.prevToken = operands.prevToken.prevToken; operands = result; binOps = binOps.pop; } // There should be exactly one remaining operand on the stack which is our final result. if(operands.prevToken != null) throw new Exception("too many operands"); // If only one terminator type possible, advance past the terminator. if(termTokenTypes.Length == 1) token = token.nextToken; return operands; } private TokenTypeExp ParseTypeExp(ref Token token) { TokenTypeExp leftOperand = GetTypeExp(ref token); if(leftOperand == null) return null; while((token is TokenKwAnd) || (token is TokenKwOr)) { Token typeBinOp = token; token = token.nextToken; TokenTypeExp rightOperand = GetTypeExp(ref token); if(rightOperand == null) return null; TokenTypeExpBinOp typeExpBinOp = new(typeBinOp) { leftOp = leftOperand, binOp = typeBinOp, rightOp = rightOperand }; leftOperand = typeExpBinOp; } return leftOperand; } private TokenTypeExp GetTypeExp(ref Token token) { if(token is TokenKwTilde) { TokenTypeExpNot typeExpNot = new(token); token = token.nextToken; typeExpNot.typeExp = GetTypeExp(ref token); if(typeExpNot.typeExp == null) return null; return typeExpNot; } if(token is TokenKwParOpen) { TokenTypeExpPar typeExpPar = new(token); token = token.nextToken; typeExpPar.typeExp = GetTypeExp(ref token); if(typeExpPar.typeExp == null) return null; if(!(token is TokenKwParClose)) { ErrorMsg(token, "expected close parenthesis"); token = SkipPastSemi(token); return null; } return typeExpPar; } if(token is TokenKwUndef) { TokenTypeExpUndef typeExpUndef = new(token); token = token.nextToken; return typeExpUndef; } if(token is TokenType ttype) { TokenTypeExpType typeExpType = new(token) { typeToken = ttype }; token = token.nextToken; return typeExpType; } ErrorMsg(token, "expected type"); token = SkipPastSemi(token); return null; } /** * @brief get a right-hand operand expression token * @param token = first token of operand to parse * @returns null: invalid operand * else: token that bundles or wraps the operand * token = points to token following last operand token */ private TokenRVal GetOperand(ref Token token) { // Prefix unary operators (eg ++, --) requiring an L-value. if((token is TokenKwIncr) || (token is TokenKwDecr)) { TokenRValAsnPre asnPre = new(token) { prefix = token }; token = token.nextToken; TokenRVal op = GetOperand(ref token); if(op == null) return null; if(op is not TokenLVal) { ErrorMsg(op, "can pre{in,de}crement only an L-value"); return null; } asnPre.lVal = (TokenLVal)op; return asnPre; } // Get the bulk of the operand, ie, without any of the below suffixes. TokenRVal operand = GetOperandNoMods(ref token); if(operand == null) return null; modifiers: // If followed by '++' or '--', it is post-{in,de}cremented. if((token is TokenKwIncr) || (token is TokenKwDecr)) { TokenRValAsnPost asnPost = new(token) { postfix = token }; token = token.nextToken; if(operand is not TokenLVal) { ErrorMsg(operand, "can post{in,de}crement only an L-value"); return null; } asnPost.lVal = (TokenLVal)operand; return asnPost; } // If followed by a '.', it is an instance field or instance method reference. if(token is TokenKwDot) { token = token.nextToken; if(token is not TokenName) { ErrorMsg(token, ". must be followed by field/method name"); return null; } TokenLValIField field = new(token) { baseRVal = operand, fieldName = (TokenName)token }; operand = field; token = token.nextToken; goto modifiers; } // If followed by a '[', it is an array subscript. if(token is TokenKwBrkOpen) { TokenLValArEle tokenLValArEle = new(token); token = token.nextToken; // Parse subscript(s) expression. tokenLValArEle.subRVal = ParseRVal(ref token, brkCloseOnly); if(tokenLValArEle.subRVal == null) { ErrorMsg(tokenLValArEle, "invalid subscript"); return null; } // See if comma-separated list of values. int numSubscripts = SplitCommaRVals(tokenLValArEle.subRVal, out TokenRVal subscriptRVals); if (numSubscripts > 1) { // If so, put the values in an LSL_List object. TokenRValList rValList = new(tokenLValArEle) { rVal = subscriptRVals, nItems = numSubscripts }; tokenLValArEle.subRVal = rValList; } // Either way, save array variable name // and substitute whole reference for L-value tokenLValArEle.baseRVal = operand; operand = tokenLValArEle; goto modifiers; } // If followed by a '(', it is a function/method call. if(token is TokenKwParOpen) { operand = ParseRValCall(ref token, operand); goto modifiers; } // If 'new' arraytipe '{', it is an array initializer. if((token is TokenKwBrcOpen) && (operand is TokenLValSField) && (((TokenLValSField)operand).fieldName.val == "$new") && ((TokenLValSField)operand).baseType.ToString().EndsWith("]")) { operand = ParseRValNewArIni(ref token, (TokenLValSField)operand); if(operand != null) goto modifiers; } return operand; } /** * @brief same as GetOperand() except doesn't check for any modifiers */ private TokenRVal GetOperandNoMods(ref Token token) { // Simple unary operators. if((token is TokenKwSub) || (token is TokenKwTilde) || (token is TokenKwExclam)) { Token uop = token; token = token.nextToken; TokenRVal rVal = GetOperand(ref token); if(rVal == null) return null; return PerformUnOp(uop, rVal); } // Type casting. if((token is TokenKwParOpen) && (token.nextToken is TokenType) && (token.nextToken.nextToken is TokenKwParClose)) { TokenType type = (TokenType)token.nextToken; token = token.nextToken.nextToken.nextToken; TokenRVal rVal = GetOperand(ref token); if(rVal == null) return null; return new TokenRValCast(type, rVal); } // Parenthesized expression. if(token is TokenKwParOpen) { return ParseRValParen(ref token); } // Constants. if(token is TokenChar tchar) { TokenRValConst rValConst = new(token, tchar.val); token = token.nextToken; return rValConst; } if(token is TokenFloat tfloat) { TokenRValConst rValConst = new(token, tfloat.val); token = token.nextToken; return rValConst; } if(token is TokenInt tint) { TokenRValConst rValConst = new(token, tint.val); token = token.nextToken; return rValConst; } if(token is TokenStr tstr) { TokenRValConst rValConst = new(token, tstr.val); token = token.nextToken; return rValConst; } if(token is TokenKwUndef tundef) { TokenRValUndef rValUndef = new(tundef); token = token.nextToken; return rValUndef; } // '<'value,...'>', ie, rotation or vector if(token is TokenKwCmpLT) { Token openBkt = token; token = token.nextToken; TokenRVal rValAll = ParseRVal(ref token, cmpGTOnly); if(rValAll == null) return null; int nVals = SplitCommaRVals(rValAll, out TokenRVal rVals); switch (nVals) { case 3: { return new TokenRValVec(openBkt) { xRVal = rVals, yRVal = (TokenRVal)rVals.nextToken, zRVal = (TokenRVal)rVals.nextToken.nextToken }; } case 4: { return new TokenRValRot(openBkt) { xRVal = rVals, yRVal = (TokenRVal)rVals.nextToken, zRVal = (TokenRVal)rVals.nextToken.nextToken, wRVal = (TokenRVal)rVals.nextToken.nextToken.nextToken }; } default: { ErrorMsg(openBkt, "bad rotation/vector"); token = SkipPastSemi(token); return null; } } } // '['value,...']', ie, list if(token is TokenKwBrkOpen) { TokenRValList rValList = new(token); token = token.nextToken; if(token is TokenKwBrkClose) { token = token.nextToken; // empty list } else { TokenRVal rValAll = ParseRVal(ref token, brkCloseOnly); if(rValAll == null) return null; rValList.nItems = SplitCommaRVals(rValAll, out rValList.rVal); } return rValList; } // Maybe we have . referencing a static field or method of some type. if((token is TokenType) && (token.nextToken is TokenKwDot) && (token.nextToken.nextToken is TokenName)) { TokenLValSField field = new(token.nextToken.nextToken) { baseType = (TokenType)token, fieldName = (TokenName)token.nextToken.nextToken }; token = token.nextToken.nextToken.nextToken; return field; } // Maybe we have 'this' referring to the object of the instance method. if(token is TokenKwThis) { if((currentDeclSDType == null) || currentDeclSDType is not TokenDeclSDTypeClass) { ErrorMsg(token, "using 'this' outside class definition"); token = SkipPastSemi(token); return null; } TokenRValThis zhis = new(token, (TokenDeclSDTypeClass)currentDeclSDType); token = token.nextToken; return zhis; } // Maybe we have 'base' referring to a field/method of the extended class. if(token is TokenKwBase) { if((currentDeclFunc == null) || (currentDeclFunc.sdtClass == null) || !(currentDeclFunc.sdtClass is TokenDeclSDTypeClass)) { ErrorMsg(token, "using 'base' outside method"); token = SkipPastSemi(token); return null; } if(token.nextToken is not TokenKwDot || token.nextToken.nextToken is not TokenName nextnexttname) { ErrorMsg(token, "base must be followed by . then field or method name"); TokenRValThis zhis = new(token, (TokenDeclSDTypeClass)currentDeclFunc.sdtClass); token = token.nextToken; return zhis; } TokenLValBaseField baseField = new(token, nextnexttname, (TokenDeclSDTypeClass)currentDeclFunc.sdtClass); token = token.nextToken.nextToken.nextToken; return baseField; } // Maybe we have 'new ' saying to create an object instance. // This ends up generating a call to static function .$new(...) // whose CIL code is generated by GenerateNewobjBody(). if(token is TokenKwNew) { if(token.nextToken is not TokenType) { ErrorMsg(token.nextToken, "new must be followed by type"); token = SkipPastSemi(token); return null; } TokenLValSField field = new(token.nextToken.nextToken) { baseType = (TokenType)token.nextToken, fieldName = new TokenName(token, "$new") }; token = token.nextToken.nextToken; return field; } // All we got left is , eg, arg, function, global or local variable reference if(token is TokenName) { TokenLValName name = new((TokenName)token, tokenScript.variablesStack); token = token.nextToken; return name; } // Who knows what it is supposed to be? ErrorMsg(token, "invalid operand token"); token = SkipPastSemi(token); return null; } /** * @brief Parse a call expression * @param token = points to arg list '(' * @param meth = points to method name being called * @returns call expression value * token = points just past arg list ')' */ private TokenRValCall ParseRValCall(ref Token token, TokenRVal meth) { // Set up basic function call struct with function name. TokenRValCall rValCall = new(token) { meth = meth }; // Parse the call parameters, if any. token = token.nextToken; if(token is TokenKwParClose) { token = token.nextToken; } else { rValCall.args = ParseRVal(ref token, parCloseOnly); if(rValCall.args == null) return null; rValCall.nArgs = SplitCommaRVals(rValCall.args, out rValCall.args); } currentDeclFunc.unknownTrivialityCalls.AddLast(rValCall); return rValCall; } /** * @brief decode binary operator token * @param token = points to token to decode * @returns null: invalid operator token * else: operator token and precedence */ private BinOp GetOperator(ref Token token) { BinOp binOp = new(); if(precedence.TryGetValue(token.GetType(), out binOp.preced)) { binOp.token = (TokenKw)token; token = token.nextToken; return binOp; } if((token is TokenKwSemi) || (token is TokenKwBrcOpen) || (token is TokenKwBrcClose)) { ErrorMsg(token, "premature expression end"); } else { ErrorMsg(token, "invalid operator"); } token = SkipPastSemi(token); return null; } private class BinOp { public BinOp pop; public TokenKw token; public int preced; } /** * @brief Return an R-value expression token that will be used to * generate code to perform the operation at runtime. * @param left = left-hand operand * @param binOp = operator * @param right = right-hand operand * @returns resultant expression */ private static TokenRVal PerformBinOp(TokenRVal left, BinOp binOp, TokenRVal right) { return new TokenRValOpBin(left, binOp.token, right); } /** * @brief Return an R-value expression token that will be used to * generate code to perform the operation at runtime. * @param unOp = operator * @param right = right-hand operand * @returns resultant constant or expression */ private static TokenRVal PerformUnOp(Token unOp, TokenRVal right) { return new TokenRValOpUn((TokenKw)unOp, right); } /** * @brief Parse an array initialization expression. * @param token = points to '{' on entry * @param newCall = encapsulates a '$new' call * @return resultant operand encapsulating '$new' call and initializers * token = points just past terminating '}' * ...or null if parse error */ private TokenRVal ParseRValNewArIni(ref Token token, TokenLValSField newCall) { Stack stack = new(); TokenRValNewArIni arini = new(token) { arrayType = newCall.baseType }; TokenList values = null; while(true) { // open brace means start a (sub-)list if(token is TokenKwBrcOpen) { stack.Push(values); values = new TokenList(token); token = token.nextToken; continue; } // close brace means end of (sub-)list // if final '}' all done parsing if(token is TokenKwBrcClose) { token = token.nextToken; // skip over the '}' TokenList innerds = values; // save the list just closed arini.valueList = innerds; // it's the top list if it's the last closed values = stack.Pop(); // pop to next outer list if(values == null) return arini; // final '}', we are done values.tl.Add(innerds); // put the inner list on end of outer list if(token is TokenKwComma) { // should have a ',' or '}' next token = token.nextToken; // skip over the ',' } else if(!(token is TokenKwBrcClose)) { ErrorMsg(token, "expecting , or } after sublist"); } continue; } // this is a comma that doesn't have a value expression before it // so we take it to mean skip initializing element (leave it zeroes/null etc) if(token is TokenKwComma) { values.tl.Add(token); token = token.nextToken; continue; } // parse value expression and skip terminating ',' if any TokenRVal append = ParseRVal(ref token, commaOrBrcClose); if(append == null) return null; values.tl.Add(append); if(token is TokenKwComma) { token = token.nextToken; } } } /** * @brief parse out a parenthesized expression. * @param token = points to open parenthesis * @returns null: invalid expression * else: parenthesized expression token or constant token * token = points past the close parenthesis */ private TokenRValParen ParseRValParen(ref Token token) { if(!(token is TokenKwParOpen)) { ErrorMsg(token, "expecting ("); token = SkipPastSemi(token); return null; } TokenRValParen tokenRValParen = new(token); token = token.nextToken; tokenRValParen.rVal = ParseRVal(ref token, parCloseOnly); if(tokenRValParen.rVal == null) return null; return tokenRValParen; } /** * @brief Split a comma'd RVal into separate expressions * @param rValAll = expression containing commas * @returns number of comma separated values * rVals = values in a null-terminated list linked by rVals.nextToken */ private int SplitCommaRVals(TokenRVal rValAll, out TokenRVal rVals) { if(rValAll is not TokenRValOpBin opBin || opBin.opcode is not TokenKwComma) { rVals = rValAll; if(rVals.nextToken != null) throw new Exception("expected null"); return 1; } int leftCount = SplitCommaRVals(opBin.rValLeft, out TokenRVal rValLeft); int rightCount = SplitCommaRVals(opBin.rValRight, out TokenRVal rValRight); rVals = rValLeft; while(rValLeft.nextToken != null) rValLeft = (TokenRVal)rValLeft.nextToken; rValLeft.nextToken = rValRight; return leftCount + rightCount; } /** * @brief output error message and remember that there is an error. * @param token = what token is associated with the error * @param message = error message string */ private void ErrorMsg(Token token, string message) { if(!errors || (token.file != lastErrorFile) || (token.line > lastErrorLine)) { errors = true; lastErrorFile = token.file; lastErrorLine = token.line; token.ErrorMsg(message); } } /** * @brief Skip past the next semicolon (or matched braces) * @param token = points to token to skip over * @returns token just after the semicolon or close brace */ private Token SkipPastSemi(Token token) { int braceLevel = 0; while(!(token is TokenEnd)) { if((token is TokenKwSemi) && (braceLevel == 0)) { return token.nextToken; } if(token is TokenKwBrcOpen) { braceLevel++; } if((token is TokenKwBrcClose) && (--braceLevel <= 0)) { return token.nextToken; } token = token.nextToken; } return token; } } /** * @brief Script-defined type declarations */ public abstract class TokenDeclSDType: Token { protected const byte CLASS = 0; protected const byte DELEGATE = 1; protected const byte INTERFACE = 2; protected const byte TYPEDEF = 3; // stuff that gets cloned/copied/transformed when instantiating a generic // see InstantiateGeneric() below public TokenDeclSDType outerSDType; // null if top-level // else points to defining script-defined type public Dictionary innerSDTypes = new(); // indexed by shortName public Token begToken; // token that begins the definition (might be this or something like 'public') public Token endToken; // the '}' or ';' that ends the definition // generic instantiation assumes none of the rest needs to be cloned (well except for the shortName) public int sdTypeIndex = -1; // index in scriptObjCode.sdObjTypesIndx[] array public TokenDeclSDTypeClass extends; // only non-null for TokenDeclSDTypeClass's public uint accessLevel; // SDT_PRIVATE, SDT_PROTECTED or SDT_PUBLIC // ... all top-level types are SDT_PUBLIC public VarDict members = new (false); // declared fields, methods, properties if any public Dictionary genParams; // list of parameters for generic prototypes // null for non-generic prototypes // eg, for 'Dictionary' // ...genParams gives K->0; V->1 public bool isPartial; // was declared with 'partial' keyword // classes only, all others always false /* * Name of the type. * shortName = doesn't include outer class type names * eg, 'Engine' for non-generic * 'Dictionary<,>' for generic prototype * 'Dictionary' for generic instantiation * longName = includes all outer class type names if any */ private TokenName _shortName; private TokenName _longName; public TokenName shortName { get { return _shortName; } set { _shortName = value; _longName = null; } } public TokenName longName { get { if(_longName == null) { _longName = _shortName; if(outerSDType != null) { _longName = new TokenName(_shortName, outerSDType.longName.val + "." + _shortName.val); } } return _longName; } } /* * Dictionary used when reading from object file that holds all script-defined types. * Not complete though until all types have been read from the object file. */ private Dictionary sdTypes; public TokenDeclSDType(Token t) : base(t) { } protected abstract TokenDeclSDType MakeBlank(TokenName shortName); public abstract TokenType MakeRefToken(Token t); public abstract Type GetSysType(); public abstract void WriteToFile(BinaryWriter objFileWriter); public abstract void ReadFromFile(BinaryReader objFileReader, TextWriter asmFileWriter); /** * @brief Given that this is a generic prototype, apply the supplied genArgs * to create an equivalent instantiated non-generic. This basically * makes a copy replacing all the parameter types with the actual * argument types. * @param this = the prototype to be instantiated, eg, 'Dictionary.Converter' * @param name = short name with arguments, eg, 'Converter'. * @param genArgs = argument types of just this level, eg, 'float'. * @returns clone of this but with arguments applied and spliced in source token stream */ public TokenDeclSDType InstantiateGeneric(string name, TokenType[] genArgs, ScriptReduce reduce) { // Malloc the struct and give it a name. TokenDeclSDType instdecl = this.MakeBlank(new TokenName(this, name)); // If the original had an outer type, then so does the new one. // The outer type will never be a generic prototype, eg, if this // is 'ValueList' it will always be inside 'Dictionary' // not 'Dictionary' at this point. if((this.outerSDType != null) && (this.outerSDType.genParams != null)) throw new Exception(); instdecl.outerSDType = this.outerSDType; // The generic prototype may have stuff like 'public' just before it and we need to copy that too. Token prefix = this; while((prefix = prefix.prevToken) != null) { if(prefix is not TokenKwPublic && prefix is not TokenKwProtected && prefix is not TokenKwPrivate) break; } this.begToken = prefix.nextToken; // Splice in a copy of the prefix tokens, just before the beginning token of prototype (this.begToken). while((prefix = prefix.nextToken) != this) { SpliceSourceToken(prefix.CopyToken(prefix)); } // Splice instantiation (instdecl) in just before the beginning token of prototype (this.begToken). SpliceSourceToken(instdecl); // Now for the fun part... Copy the rest of the prototype body to the // instantiated body, replacing all generic parameter type tokens with // the corresponding generic argument types. Note that the parameters // are numbered starting with the outermost so we need the full genArgs // array. Eg if we are doing 'Converter' from // 'Dictionary.Converter', any V's are // numbered [2]. Any [0]s or [1]s should be gone by now but it doesn't // matter. Token it, pt; TokenDeclSDType innerProto = this; TokenDeclSDType innerInst = instdecl; for(pt = this; (pt = pt.nextToken) != this.endToken;) { // Coming across a sub-type's declaration involves a deep copy of the // declaration token. Fortunately we are early on in parsing, so there // really isn't much to copy: // 1) short name is the same, eg, doing List of Dictionary.List is same short name as Dictionary.List // if generic, eg doing Converter of Dictionary.Converter, we have to manually copy the W as well. // 2) outerSDType is transformed from Dictionary to Dictionary. // 3) innerSDTypes is rebuilt when/if we find classes that are inner to this one. if(pt is TokenDeclSDType ptSDType) { // Make a new TokenDeclSDType{Class,Delegate,Interface}. TokenDeclSDType itSDType = ptSDType.MakeBlank(new TokenName(ptSDType.shortName, ptSDType.shortName.val)); // Set up the transformed outerSDType. // Eg, if we are creating Enumerator of Dictionary.Enumerator, // innerProto = Dictionary and innerInst = Dictionary. itSDType.outerSDType = innerInst; // This clone is an inner type of its next outer level. reduce.CatalogSDTypeDecl(itSDType); // We need to manually copy any generic parameters of the class declaration being cloned. // eg, if we are cloning Converter, this is where the W gets copied. // Since it is an immutable array of strings, just copy the array pointer, if any. itSDType.genParams = ptSDType.genParams; // We are now processing tokens for this cloned type declaration. innerProto = ptSDType; innerInst = itSDType; // Splice this clone token in. it = itSDType; } // Check for an generic parameter to substitute out. else if((pt is TokenName namept) && this.genParams.TryGetValue(namept.val, out int index)) { it = genArgs[index].CopyToken(pt); } // Everything else is a simple copy. else it = pt.CopyToken(pt); // Whatever we came up with, splice it into the source token stream. SpliceSourceToken(it); // Maybe we just finished copying an inner type definition. // If so, remember where it ends and pop it from the stack. if(innerProto.endToken == pt) { innerInst.endToken = it; innerProto = innerProto.outerSDType; innerInst = innerInst.outerSDType; } } // Clone and insert the terminator, either '}' or ';' it = pt.CopyToken(pt); SpliceSourceToken(it); instdecl.endToken = it; return instdecl; } /** * @brief Splice a source token in just before the type's beginning keyword. */ private void SpliceSourceToken(Token it) { it.nextToken = this.begToken; (it.prevToken = this.begToken.prevToken).nextToken = it; this.begToken.prevToken = it; } /** * @brief Read one of these in from the object file. * @param sdTypes = dictionary of script-defined types, not yet complete * @param name = script-visible name of this type * @param objFileReader = reads from the object file * @param asmFileWriter = writes to the disassembly file (might be null) */ public static TokenDeclSDType ReadFromFile(Dictionary sdTypes, string name, BinaryReader objFileReader, TextWriter asmFileWriter) { string file = objFileReader.ReadString(); int line = objFileReader.ReadInt32(); int posn = objFileReader.ReadInt32(); byte code = objFileReader.ReadByte(); TokenName n = new(null, file, line, posn, name); TokenDeclSDType sdt; switch(code) { case CLASS: { sdt = new TokenDeclSDTypeClass(n, false); break; } case DELEGATE: { sdt = new TokenDeclSDTypeDelegate(n); break; } case INTERFACE: { sdt = new TokenDeclSDTypeInterface(n); break; } case TYPEDEF: { sdt = new TokenDeclSDTypeTypedef(n); break; } default: throw new Exception(); } sdt.sdTypes = sdTypes; sdt.sdTypeIndex = objFileReader.ReadInt32(); sdt.ReadFromFile(objFileReader, asmFileWriter); return sdt; } /** * @brief Convert a typename string to a type token * @param name = script-visible name of token to create, * either a script-defined type or an LSL-defined type * @returns type token */ protected TokenType MakeTypeToken(string name) { if (sdTypes.TryGetValue(name, out TokenDeclSDType sdtdecl)) return sdtdecl.MakeRefToken(this); return TokenType.FromLSLType(this, name); } // debugging - returns, eg, 'Dictionary.Enumerator.Node' public override void DebString(StringBuilder sb) { // get long name broken down into segments from outermost to this Stack declStack = new(); for(TokenDeclSDType decl = this; decl != null; decl = decl.outerSDType) { declStack.Push(decl); } // output each segment's name followed by our args for it // starting with outermost and ending with this while(declStack.Count > 0) { TokenDeclSDType decl = declStack.Pop(); sb.Append(decl.shortName.val); if(decl.genParams != null) { sb.Append('<'); string[] parms = new string[decl.genParams.Count]; foreach(KeyValuePair kvp in decl.genParams) { parms[kvp.Value] = kvp.Key; } for(int j = 0; j < parms.Length;) { sb.Append(parms[j]); if(++j < parms.Length) sb.Append(','); } sb.Append('>'); } if(declStack.Count > 0) sb.Append('.'); } } } public class TokenDeclSDTypeClass: TokenDeclSDType { public List implements = new(); public TokenDeclVar instFieldInit; // $instfieldinit function to do instance field initializations public TokenDeclVar staticFieldInit; // $staticfieldinit function to do static field initializations public Dictionary intfIndices = new(); // longname => this.iFaces index public TokenDeclSDTypeInterface[] iFaces; // array of implemented interfaces // low-end entries copied from rootward classes public TokenDeclVar[][] iImplFunc; // iImplFunc[i][j]: // low-end [i] entries copied from rootward classes // i = interface number from this.intfIndices[name] // j = method of interface from iface.methods[name].vTableIndex public TokenType arrayOfType; // if array, it's an array of this type, else null public int arrayOfRank; // if array, it has this number of dimensions, else zero public bool slotsAssigned; // set true when slots have been assigned... public XMRInstArSizes instSizes = new(); // number of instance fields of various types public int numVirtFuncs; // number of virtual functions public int numInterfaces; // number of implemented interfaces private string extendsStr; private string arrayOfTypeStr; private List stackedMethods; private List stackedIFaces; public DynamicMethod[] vDynMeths; // virtual method entrypoints public Type[] vMethTypes; // virtual method delegate types public DynamicMethod[][] iDynMeths; // interface method entrypoints public Type[][] iMethTypes; // interface method types // low-end [i] entries copied from rootward classes // i = interface number from this.intfIndices[name] // j = method of interface from iface.methods[name].vTableIndex public TokenDeclSDTypeClass(TokenName shortName, bool isPartial) : base(shortName) { this.shortName = shortName; this.isPartial = isPartial; } protected override TokenDeclSDType MakeBlank(TokenName shortName) { return new TokenDeclSDTypeClass(shortName, false); } public override TokenType MakeRefToken(Token t) { return new TokenTypeSDTypeClass(t, this); } public override Type GetSysType() { return typeof(XMRSDTypeClObj); } /** * @brief See if the class implements the interface. * Do a recursive (deep) check in all rootward classes. */ public bool CanCastToIntf(TokenDeclSDTypeInterface intf) { if(this.implements.Contains(intf)) return true; if(this.extends == null) return false; return this.extends.CanCastToIntf(intf); } /** * @brief Write enough out so we can reconstruct with ReadFromFile. */ public override void WriteToFile(BinaryWriter objFileWriter) { objFileWriter.Write(this.file); objFileWriter.Write(this.line); objFileWriter.Write(this.posn); objFileWriter.Write((byte)CLASS); objFileWriter.Write(this.sdTypeIndex); this.instSizes.WriteToFile(objFileWriter); objFileWriter.Write(numVirtFuncs); if(extends == null) { objFileWriter.Write(""); } else { objFileWriter.Write(extends.longName.val); } objFileWriter.Write(arrayOfRank); if(arrayOfRank > 0) objFileWriter.Write(arrayOfType.ToString()); foreach(TokenDeclVar meth in members) { if((meth.retType != null) && (meth.vTableIndex >= 0)) { objFileWriter.Write(meth.vTableIndex); objFileWriter.Write(meth.GetObjCodeName()); objFileWriter.Write(meth.GetDelType().decl.GetWholeSig()); } } objFileWriter.Write(-1); int numIFaces = iImplFunc.Length; objFileWriter.Write(numIFaces); for(int i = 0; i < numIFaces; i++) { objFileWriter.Write(iFaces[i].longName.val); TokenDeclVar[] meths = iImplFunc[i]; int numMeths = 0; if(meths != null) numMeths = meths.Length; objFileWriter.Write(numMeths); for(int j = 0; j < numMeths; j++) { TokenDeclVar meth = meths[j]; objFileWriter.Write(meth.vTableIndex); objFileWriter.Write(meth.GetObjCodeName()); objFileWriter.Write(meth.GetDelType().decl.GetWholeSig()); } } } /** * @brief Reconstruct from the file. */ public override void ReadFromFile(BinaryReader objFileReader, TextWriter asmFileWriter) { instSizes.ReadFromFile(objFileReader); numVirtFuncs = objFileReader.ReadInt32(); extendsStr = objFileReader.ReadString(); arrayOfRank = objFileReader.ReadInt32(); if(arrayOfRank > 0) arrayOfTypeStr = objFileReader.ReadString(); if(asmFileWriter != null) { instSizes.WriteAsmFile(asmFileWriter, extendsStr + "." + shortName.val + ".numInst"); } stackedMethods = new List(); int vTableIndex; while((vTableIndex = objFileReader.ReadInt32()) >= 0) { StackedMethod sm; sm.methVTI = vTableIndex; sm.methName = objFileReader.ReadString(); sm.methSig = objFileReader.ReadString(); stackedMethods.Add(sm); } int numIFaces = objFileReader.ReadInt32(); if(numIFaces > 0) { iDynMeths = new DynamicMethod[numIFaces][]; iMethTypes = new Type[numIFaces][]; stackedIFaces = new List(); for(int i = 0; i < numIFaces; i++) { string iFaceName = objFileReader.ReadString(); intfIndices[iFaceName] = i; int numMeths = objFileReader.ReadInt32(); iDynMeths[i] = new DynamicMethod[numMeths]; iMethTypes[i] = new Type[numMeths]; for(int j = 0; j < numMeths; j++) { StackedIFace si; si.iFaceIndex = i; si.methIndex = j; si.vTableIndex = objFileReader.ReadInt32(); si.methName = objFileReader.ReadString(); si.methSig = objFileReader.ReadString(); stackedIFaces.Add(si); } } } } private struct StackedMethod { public int methVTI; public string methName; public string methSig; } private struct StackedIFace { public int iFaceIndex; // which implemented interface public int methIndex; // which method of that interface public int vTableIndex; // <0: implemented by non-virtual; else: implemented by virtual public string methName; // object code name of implementing method (GetObjCodeName) public string methSig; // method signature incl return type (GetWholeSig) } /** * @brief Called after all dynamic method code has been generated to fill in vDynMeths and vMethTypes * Also fills in iDynMeths, iMethTypes. */ public void FillVTables(ScriptObjCode scriptObjCode) { if(extendsStr != null) { if(extendsStr != "") { extends = (TokenDeclSDTypeClass)scriptObjCode.sdObjTypesName[extendsStr]; extends.FillVTables(scriptObjCode); } extendsStr = null; } if(arrayOfTypeStr != null) { arrayOfType = MakeTypeToken(arrayOfTypeStr); arrayOfTypeStr = null; } if((numVirtFuncs > 0) && (stackedMethods != null)) { // Allocate arrays big enough for mine plus type we are extending. vDynMeths = new DynamicMethod[numVirtFuncs]; vMethTypes = new Type[numVirtFuncs]; // Fill in low parts from type we are extending. if(extends != null) { int n = extends.numVirtFuncs; for(int i = 0; i < n; i++) { vDynMeths[i] = extends.vDynMeths[i]; vMethTypes[i] = extends.vMethTypes[i]; } } // Fill in high parts with my own methods. // Might also overwrite lower ones with 'override' methods. foreach(StackedMethod sm in stackedMethods) { int i = sm.methVTI; string methName = sm.methName; if (scriptObjCode.dynamicMethods.TryGetValue(methName, out DynamicMethod dm)) { // method is not abstract vDynMeths[i] = dm; vMethTypes[i] = GetDynamicMethodDelegateType(dm, sm.methSig); } } stackedMethods = null; } if(stackedIFaces != null) { foreach(StackedIFace si in stackedIFaces) { int i = si.iFaceIndex; int j = si.methIndex; int vti = si.vTableIndex; string methName = si.methName; DynamicMethod dm = scriptObjCode.dynamicMethods[methName]; iDynMeths[i][j] = (vti < 0) ? dm : vDynMeths[vti]; iMethTypes[i][j] = GetDynamicMethodDelegateType(dm, si.methSig); } stackedIFaces = null; } } private Type GetDynamicMethodDelegateType(DynamicMethod dm, string methSig) { Type retType = dm.ReturnType; ParameterInfo[] pi = dm.GetParameters(); Type[] argTypes = new Type[pi.Length]; for(int j = 0; j < pi.Length; j++) { argTypes[j] = pi[j].ParameterType; } return DelegateCommon.GetType(retType, argTypes, methSig); } public override void DebString(StringBuilder sb) { // Don't output if array of some type. // They will be re-instantiated as referenced by rest of script. if(arrayOfType != null) return; // This class name and extended/implemented type declaration. sb.Append("class "); sb.Append(shortName.val); bool first = true; if(extends != null) { sb.Append(" : "); sb.Append(extends.longName); first = false; } foreach(TokenDeclSDType impld in implements) { sb.Append(first ? " : " : ", "); sb.Append(impld.longName); first = false; } sb.Append(" {"); // Inner type definitions. foreach(TokenDeclSDType subs in innerSDTypes.Values) { subs.DebString(sb); } // Members (fields, methods, properties). foreach(TokenDeclVar memb in members) { if((memb == instFieldInit) || (memb == staticFieldInit)) { memb.DebStringInitFields(sb); } else if(memb.retType != null) { memb.DebString(sb); } } sb.Append('}'); } } public class TokenDeclSDTypeDelegate: TokenDeclSDType { private TokenType retType; private TokenType[] argTypes; private string argSig; private string wholeSig; private Type sysType; private Type retSysType; private Type[] argSysTypes; private string retStr; private string[] argStrs; private static Dictionary inlines = new(); private static Dictionary inlrevs = new(); public TokenDeclSDTypeDelegate(TokenName shortName) : base(shortName) { this.shortName = shortName; } public void SetRetArgTypes(TokenType retType, TokenType[] argTypes) { this.retType = retType; this.argTypes = argTypes; } protected override TokenDeclSDType MakeBlank(TokenName shortName) { return new TokenDeclSDTypeDelegate(shortName); } public override TokenType MakeRefToken(Token t) { return new TokenTypeSDTypeDelegate(t, this); } /** * @brief Get system type for the whole delegate. */ public override Type GetSysType() { if(sysType == null) FillInStuff(); return sysType; } /** * @brief Get the function's return value type (TokenTypeVoid if void, never null) */ public TokenType GetRetType() { if(retType == null) FillInStuff(); return retType; } /** * @brief Get the function's argument types */ public TokenType[] GetArgTypes() { if(argTypes == null) FillInStuff(); return argTypes; } /** * @brief Get signature for the whole delegate, eg, "void(integer,list)" */ public string GetWholeSig() { if(wholeSig == null) FillInStuff(); return wholeSig; } /** * @brief Get signature for the arguments, eg, "(integer,list)" */ public string GetArgSig() { if(argSig == null) FillInStuff(); return argSig; } /** * @brief Find out how to create one of these delegates. */ public ConstructorInfo GetConstructorInfo() { if(sysType == null) FillInStuff(); return sysType.GetConstructor(DelegateCommon.constructorArgTypes); } /** * @brief Find out how to call what one of these delegates points to. */ public MethodInfo GetInvokerInfo() { if(sysType == null) FillInStuff(); return sysType.GetMethod("Invoke", argSysTypes); } /** * @brief Write enough out to a file so delegate can be reconstructed in ReadFromFile(). */ public override void WriteToFile(BinaryWriter objFileWriter) { objFileWriter.Write(this.file); objFileWriter.Write(this.line); objFileWriter.Write(this.posn); objFileWriter.Write((byte)DELEGATE); objFileWriter.Write(this.sdTypeIndex); objFileWriter.Write(retType.ToString()); int nArgs = argTypes.Length; objFileWriter.Write(nArgs); for(int i = 0; i < nArgs; i++) { objFileWriter.Write(argTypes[i].ToString()); } } /** * @brief Read that data from file so we can reconstruct. * Don't actually reconstruct yet in case any forward-referenced types are undefined. */ public override void ReadFromFile(BinaryReader objFileReader, TextWriter asmFileWriter) { retStr = objFileReader.ReadString(); int nArgs = objFileReader.ReadInt32(); asmFileWriter?.Write(" delegate " + retStr + " " + longName.val + "("); argStrs = new string[nArgs]; for(int i = 0; i < nArgs; i++) { argStrs[i] = objFileReader.ReadString(); if(asmFileWriter != null) { if(i > 0) asmFileWriter.Write(","); asmFileWriter.Write(argStrs[i]); } } asmFileWriter?.WriteLine(");"); } /** * @brief Fill in missing internal data. */ private void FillInStuff() { // This happens when the node was restored via ReadFromFile(). // It leaves the types in retStr/argStrs for resolution after // all definitions have been read from the object file in case // there are forward references. retType ??= MakeTypeToken(retStr); if(argTypes == null) { argTypes = new TokenType[argStrs.Length]; for(int i = 0; i < argStrs.Length; i++) { argTypes[i] = MakeTypeToken(argStrs[i]); } } // Fill in system types from token types. // Might as well build the signature strings too from token types. retSysType = retType.ToSysType(); StringBuilder sb = new(); sb.Append('('); argSysTypes = new Type[argTypes.Length]; for(int i = 0; i < argTypes.Length; i++) { if(i > 0) sb.Append(','); sb.Append(argTypes[i].ToString()); argSysTypes[i] = argTypes[i].ToSysType(); } sb.Append(')'); argSig = sb.ToString(); wholeSig = retType.ToString() + argSig; // Now we can create a system delegate type from the given // return and argument types. Give it an unique name using // the whole signature string. sysType = DelegateCommon.GetType(retSysType, argSysTypes, wholeSig); } /** * @brief create delegate reference token for inline functions. * there is just one instance of these per inline function * shared by all scripts, and it is just used when the * script engine is loaded. */ public static TokenDeclSDTypeDelegate CreateInline(TokenType retType, TokenType[] argTypes) { // Name it after the whole signature string. StringBuilder sb = new("$inline"); sb.Append(retType.ToString()); sb.Append('('); bool first = true; foreach(TokenType at in argTypes) { if(!first) sb.Append(','); sb.Append(at.ToString()); first = false; } sb.Append(')'); string inlname = sb.ToString(); if(!inlines.TryGetValue(inlname, out TokenDeclSDTypeDelegate decldel)) { // Create the corresponding declaration and link to it TokenName name = new(null, inlname); decldel = new TokenDeclSDTypeDelegate(name) { retType = retType, argTypes = argTypes }; inlines.Add(inlname, decldel); inlrevs.Add(decldel.GetSysType(), inlname); } return decldel; } public static string TryGetInlineName(Type sysType) { if(inlrevs.TryGetValue(sysType, out string name)) return name; return null; } public static Type TryGetInlineSysType(string name) { if (inlines.TryGetValue(name, out TokenDeclSDTypeDelegate decl)) return decl.GetSysType(); return null; } } public class TokenDeclSDTypeInterface: TokenDeclSDType { public VarDict methsNProps = new(false); // any class that implements this interface // must implement all of these methods & properties public List implements = new(); // any class that implements this interface // must also implement all of the methods & properties // of all of these interfaces public TokenDeclSDTypeInterface(TokenName shortName) : base(shortName) { this.shortName = shortName; } protected override TokenDeclSDType MakeBlank(TokenName shortName) { return new TokenDeclSDTypeInterface(shortName); } public override TokenType MakeRefToken(Token t) { return new TokenTypeSDTypeInterface(t, this); } public override Type GetSysType() { // interfaces are implemented as arrays of delegates // they are taken from iDynMeths[interfaceIndex] of a script-defined class object return typeof(Delegate[]); } public override void WriteToFile(BinaryWriter objFileWriter) { objFileWriter.Write(this.file); objFileWriter.Write(this.line); objFileWriter.Write(this.posn); objFileWriter.Write((byte)INTERFACE); objFileWriter.Write(this.sdTypeIndex); } public override void ReadFromFile(BinaryReader objFileReader, TextWriter asmFileWriter) { } /** * @brief Add this interface to the list of interfaces implemented by a class if not already. * And also add this interface's implemented interfaces to the class for those not already there, * just as if the class itself had declared to implement those interfaces. */ public void AddToClassDecl(TokenDeclSDTypeClass tokdeclcl) { if(!tokdeclcl.implements.Contains(this)) { tokdeclcl.implements.Add(this); foreach(TokenDeclSDTypeInterface subimpl in this.implements) { subimpl.AddToClassDecl(tokdeclcl); } } } /** * @brief See if the 'this' interface implements the new interface. * Do a recursive (deep) check. */ public bool Implements(TokenDeclSDTypeInterface newDecl) { foreach(TokenDeclSDTypeInterface ii in this.implements) { if(ii == newDecl) return true; if(ii.Implements(newDecl)) return true; } return false; } /** * @brief Scan an interface and all its implemented interfaces for a method or property * @param scg = script code generator (ie, which script is being compiled) * @param fieldName = name of the member being looked for * @param argsig = the method's argument signature * @returns null: no such member; intf = undefined * else: member; intf = which interface actually found in */ public TokenDeclVar FindIFaceMember(ScriptCodeGen scg, TokenName fieldName, TokenType[] argsig, out TokenDeclSDTypeInterface intf) { intf = this; TokenDeclVar var = scg.FindSingleMember(this.methsNProps, fieldName, argsig); if(var == null) { foreach(TokenDeclSDTypeInterface ii in this.implements) { var = ii.FindIFaceMember(scg, fieldName, argsig, out intf); if(var != null) break; } } return var; } } public class TokenDeclSDTypeTypedef: TokenDeclSDType { public TokenDeclSDTypeTypedef(TokenName shortName) : base(shortName) { this.shortName = shortName; } protected override TokenDeclSDType MakeBlank(TokenName shortName) { return new TokenDeclSDTypeTypedef(shortName); } public override TokenType MakeRefToken(Token t) { // if our body is a single type token, that is what we return // otherwise return null saying maybe our body needs some substitutions if(nextToken is not TokenType) return null; if(nextToken.nextToken != this.endToken) { nextToken.nextToken.ErrorMsg("extra tokens for typedef"); return null; } return (TokenType)nextToken.CopyToken(t); } public override Type GetSysType() { // we are just a macro // we are asked for system type because we are cataloged // but we don't really have one so return null return null; } public override void WriteToFile(BinaryWriter objFileWriter) { objFileWriter.Write(this.file); objFileWriter.Write(this.line); objFileWriter.Write(this.posn); objFileWriter.Write((byte)TYPEDEF); objFileWriter.Write(this.sdTypeIndex); } public override void ReadFromFile(BinaryReader objFileReader, TextWriter asmFileWriter) { } } /** * @brief Script-defined type references. * These occur in the source code wherever it specifies (eg, variable declaration) a script-defined type. * These must be copyable via CopyToken(). */ public abstract class TokenTypeSDType: TokenType { public TokenTypeSDType(TokenErrorMessage emsg, string file, int line, int posn) : base(emsg, file, line, posn) { } public TokenTypeSDType(Token t) : base(t) { } public abstract TokenDeclSDType GetDecl(); public abstract void SetDecl(TokenDeclSDType decl); } public class TokenTypeSDTypeClass: TokenTypeSDType { private static readonly FieldInfo iarSDTClObjsFieldInfo = typeof(XMRInstArrays).GetField("iarSDTClObjs"); public TokenDeclSDTypeClass decl; public TokenTypeSDTypeClass(Token t, TokenDeclSDTypeClass decl) : base(t) { this.decl = decl; } public override TokenDeclSDType GetDecl() { return decl; } public override void SetDecl(TokenDeclSDType decl) { this.decl = (TokenDeclSDTypeClass)decl; } public override string ToString() { return decl.longName.val; } public override Type ToSysType() { return typeof(XMRSDTypeClObj); } public override void AssignVarSlot(TokenDeclVar declVar, XMRInstArSizes ias) { declVar.vTableArray = iarSDTClObjsFieldInfo; declVar.vTableIndex = ias.iasSDTClObjs++; } // debugging public override void DebString(StringBuilder sb) { sb.Append(decl.longName); } } public class TokenTypeSDTypeDelegate: TokenTypeSDType { private static readonly FieldInfo iarObjectsFieldInfo = typeof(XMRInstArrays).GetField("iarObjects"); public TokenDeclSDTypeDelegate decl; /** * @brief create a reference to an explicitly declared delegate * @param t = where the reference is being made in the source file * @param decl = the explicit delegate declaration */ public TokenTypeSDTypeDelegate(Token t, TokenDeclSDTypeDelegate decl) : base(t) { this.decl = decl; } public override TokenDeclSDType GetDecl() { return decl; } public override void SetDecl(TokenDeclSDType decl) { this.decl = (TokenDeclSDTypeDelegate)decl; } /** * @brief create a reference to a possibly anonymous delegate * @param t = where the reference is being made in the source file * @param retType = return type (TokenTypeVoid if void, never null) * @param argTypes = script-visible argument types * @param tokenScript = what script this is part of */ public TokenTypeSDTypeDelegate(Token t, TokenType retType, TokenType[] argTypes, TokenScript tokenScript) : base(t) { TokenDeclSDTypeDelegate decldel; // See if we already have a matching declared one cataloged. int nArgs = argTypes.Length; foreach(TokenDeclSDType decl in tokenScript.sdSrcTypesValues) { if(decl is TokenDeclSDTypeDelegate decldelg) { TokenType rt = decldelg.GetRetType(); TokenType[] ats = decldelg.GetArgTypes(); if((rt.ToString() == retType.ToString()) && (ats.Length == nArgs)) { for(int i = 0; i < nArgs; i++) { if(ats[i].ToString() != argTypes[i].ToString()) goto nomatch; } this.decl = decldelg; return; } } nomatch: ; } // No such luck, create a new anonymous declaration. StringBuilder sb = new("$anondel$"); sb.Append(retType.ToString()); sb.Append('('); bool first = true; foreach(TokenType at in argTypes) { if(!first) sb.Append(','); sb.Append(at.ToString()); first = false; } sb.Append(')'); TokenName name = new(t, sb.ToString()); decldel = new TokenDeclSDTypeDelegate(name); decldel.SetRetArgTypes(retType, argTypes); tokenScript.sdSrcTypesAdd(name.val, decldel); this.decl = decldel; } public override Type ToSysType() { return decl.GetSysType(); } public override string ToString() { return decl.longName.val; } /** * @brief Assign slots in the gblObjects[] array because we have to typecast out in any case. * Likewise with the sdtcObjects[] array. */ public override void AssignVarSlot(TokenDeclVar declVar, XMRInstArSizes ias) { declVar.vTableArray = iarObjectsFieldInfo; declVar.vTableIndex = ias.iasObjects++; } /** * @brief create delegate reference token for inline functions. */ public TokenTypeSDTypeDelegate(TokenType retType, TokenType[] argTypes) : base(null) { this.decl = TokenDeclSDTypeDelegate.CreateInline(retType, argTypes); } // debugging public override void DebString(StringBuilder sb) { sb.Append(decl.longName); } } public class TokenTypeSDTypeInterface: TokenTypeSDType { private static readonly FieldInfo iarSDTIntfObjsFieldInfo = typeof(XMRInstArrays).GetField("iarSDTIntfObjs"); public TokenDeclSDTypeInterface decl; public TokenTypeSDTypeInterface(Token t, TokenDeclSDTypeInterface decl) : base(t) { this.decl = decl; } public override TokenDeclSDType GetDecl() { return decl; } public override void SetDecl(TokenDeclSDType decl) { this.decl = (TokenDeclSDTypeInterface)decl; } public override string ToString() { return decl.longName.val; } public override Type ToSysType() { return typeof(Delegate[]); } /** * @brief Assign slots in the gblSDTIntfObjs[] array * Likewise with the sdtcSDTIntfObjs[] array. */ public override void AssignVarSlot(TokenDeclVar declVar, XMRInstArSizes ias) { declVar.vTableArray = iarSDTIntfObjsFieldInfo; declVar.vTableIndex = ias.iasSDTIntfObjs++; } // debugging public override void DebString(StringBuilder sb) { sb.Append(decl.longName); } } /** * @brief function argument list declaration */ public class TokenArgDecl: Token { public VarDict varDict = new (false); public TokenArgDecl(Token original) : base(original) { } public bool AddArg(TokenType type, TokenName name) { TokenDeclVar var = new (name, null, null) { name = name, type = type, vTableIndex = varDict.Count }; return varDict.AddEntry(var); } /** * @brief Get an array of the argument types. */ private TokenType[] _types; public TokenType[] types { get { if(_types == null) { _types = new TokenType[varDict.Count]; foreach(TokenDeclVar var in varDict) { _types[var.vTableIndex] = var.type; } } return _types; } } /** * @brief Access the arguments as an array of variables. */ private TokenDeclVar[] _vars; public TokenDeclVar[] vars { get { if(_vars == null) { _vars = new TokenDeclVar[varDict.Count]; foreach(TokenDeclVar var in varDict) { _vars[var.vTableIndex] = var; } } return _vars; } } /** * @brief Get argument signature string, eg, "(list,vector,integer)" */ private string argSig = null; public string GetArgSig() { argSig ??= ScriptCodeGen.ArgSigString(types); return argSig; } } /** * @brief encapsulate a state declaration in a single token */ public class TokenDeclState: Token { public TokenName name; // null for default state public TokenStateBody body; public TokenDeclState(Token original) : base(original) { } public override void DebString(StringBuilder sb) { if(name == null) { sb.Append("default"); } else { sb.Append("state "); sb.Append(name); } body.DebString(sb); } } /** * @brief encapsulate the declaration of a field/function/method/property/variable. */ public enum Triviality { // function triviality: has no loops and doesn't call anything that has loops // such a function does not need all the CheckRun() and stack serialization stuff unknown, // function's Triviality unknown as of yet // - it does not have any loops or backward gotos // - nothing it calls is known to be complex trivial, // function known to be trivial // - it does not have any loops or backward gotos // - everything it calls is known to be trivial complex, // function known to be complex // - it has loops or backward gotos // - something it calls is known to be complex analyzing // triviality is being analyzed (used to detect recursive loops) }; public class TokenDeclVar: TokenStmt { public TokenName name; // vars: name; funcs: bare name, ie, no signature public TokenRVal init; // vars: null if none; funcs: null public bool constant; // vars: 'constant'; funcs: false public uint sdtFlags; // SDT_<*> flags public CompValu location; // used by codegen to keep track of location public FieldInfo vTableArray; public int vTableIndex = -1; // local vars: not used (-1) // arg vars: index in the arg list // global vars: which slot in gbls[] array it is stored // instance vars: which slot in insts[] array it is stored // static vars: which slot in gbls[] array it is stored // global funcs: not used (-1) // virt funcs: which slot in vTable[] array it is stored // instance func: not used (-1) public TokenDeclVar getProp; // if property, function that reads value public TokenDeclVar setProp; // if property, function that writes value public TokenScript tokenScript; // what script this function is part of public TokenDeclSDType sdtClass; // null: script global member // else: member is part of this script-defined type // function-only data: public TokenType retType; // vars: null; funcs: TokenTypeVoid if void public TokenArgDecl argDecl; // vars: null; funcs: argument list prototypes public TokenStmtBlock body; // vars: null; funcs: statements (null iff abstract) public Dictionary labels = new(); // all labels defined in the function public LinkedList localVars = new(); // all local variables declared by this function // - doesn't include argument variables public TokenIntfImpl implements; // if script-defined type method, what interface method(s) this func implements public TokenRValCall baseCtorCall; // if script-defined type constructor, call to base constructor, if any public Triviality triviality = Triviality.unknown; // vars: unknown (not used for any thing); funcs: unknown/trivial/complex public LinkedList unknownTrivialityCalls = new(); // reduction puts all calls here // compilation sorts it all out public ScriptObjWriter ilGen; // codegen stores emitted code here /** * @brief Set up a variable declaration token. * @param original = original source token that triggered definition * (for error messages) * @param func = null: global variable * else: local to the given function */ public TokenDeclVar(Token original, TokenDeclVar func, TokenScript ts) : base(original) { func?.localVars.AddLast(this); tokenScript = ts; } /** * @brief Get/Set overall type * For vars, this is the type of the location * For funcs, this is the delegate type */ private TokenType _type; public TokenType type { get { if(_type == null) { GetDelType(); } return _type; } set { _type = value; } } /** * @brief Full name: .() * () missing for fields/variables * . missing for top-level functions/variables */ public string fullName { get { if(sdtClass == null) { if(retType == null) return name.val; return funcNameSig.val; } if(retType == null) return sdtClass.longName.val + "." + name.val; return sdtClass.longName.val + "." + funcNameSig.val; } } /** * @brief See if reading or writing the variable is trivial. * Note that for functions, this is reading the function itself, * as in 'someDelegate = SomeFunction;', not calling it as such. * The triviality of actually calling the function is IsFuncTrivial(). */ public bool IsVarTrivial(ScriptCodeGen scg) { // reading or writing a property involves a function call however // so we need to check the triviality of the property functions if((getProp != null) && !getProp.IsFuncTrivial(scg)) return false; if((setProp != null) && !setProp.IsFuncTrivial(scg)) return false; // otherwise for variables it is a trivial access // and likewise for getting a delegate that points to a function return true; } /***************************\ * FUNCTION-only methods * \***************************/ private TokenName _funcNameSig; // vars: null; funcs: function name including argumet signature, eg, "PrintStuff(list,string)" public TokenName funcNameSig { get { if(_funcNameSig == null) { if(argDecl == null) return null; _funcNameSig = new TokenName(name, name.val + argDecl.GetArgSig()); } return _funcNameSig; } } /** * @brief The bare function name, ie, without any signature info */ public string GetSimpleName() { return name.val; } /** * @brief The function name as it appears in the object code, * ie, script-defined type name if any, * bare function name and argument signature, * eg, "MyClass.PrintStuff(string)" */ public string GetObjCodeName() { if(sdtClass != null) { return sdtClass.longName.val + "." + funcNameSig.val; } return funcNameSig.val; } /** * @brief Get delegate type. * This is the function's script-visible type, * It includes return type and all script-visible argument types. * @returns null for vars; else delegate type for funcs */ public TokenTypeSDTypeDelegate GetDelType() { if(argDecl == null) return null; if(_type == null) { if(tokenScript == null) { // used during startup to define inline function delegate types _type = new TokenTypeSDTypeDelegate(retType, argDecl.types); } else { // used for normal script processing _type = new TokenTypeSDTypeDelegate(this, retType, argDecl.types, tokenScript); } } if(_type is TokenTypeSDTypeDelegate TokenTypeSDTypeDelegate_type) return TokenTypeSDTypeDelegate_type; return null; } /** * @brief See if the function's code itself is trivial or not. * If it contains any loops (calls to CheckRun()), it is not trivial. * If it calls anything that is not trivial, it is not trivial. * Otherwise it is trivial. */ public bool IsFuncTrivial(ScriptCodeGen scg) { // If not really a function, assume it's a delegate. // And since we don't really know what functions it can point to, // assume it can point to a non-trivial one. if(retType == null) return false; // All virtual functions are non-trivial because although a particular // one might be trivial, it might be overidden with a non-trivial one. if((sdtFlags & (ScriptReduce.SDT_ABSTRACT | ScriptReduce.SDT_OVERRIDE | ScriptReduce.SDT_VIRTUAL)) != 0) { return false; } // Check the triviality status of the function. switch(triviality) { // Don't yet know if it is trivial. // We know at this point it doesn't have any direct looping. // But if it calls something that has loops, it isn't trivial. // Otherwise it is trivial. case Triviality.unknown: { // Mark that we are analyzing this function now. So if there are // any recursive call loops, that will show that the function is // non-trivial and the analysis will terminate at that point. triviality = Triviality.analyzing; // Check out everything else this function calls. If any say they // aren't trivial, then we say this function isn't trivial. foreach(TokenRValCall call in unknownTrivialityCalls) { if(!call.IsRValTrivial(scg, null)) { triviality = Triviality.complex; return false; } } // All functions called by this function are trivial, and this // function's code doesn't have any loops, so we can mark this // function as being trivial. triviality = Triviality.trivial; return true; } // We already know that it is trivial. case Triviality.trivial: { return true; } // We either know it is complex or are trying to analyze it already. // If we are already analyzing it, it means it has a recursive loop // and we assume those are non-trivial. default: return false; } } // debugging public override void DebString(StringBuilder sb) { DebStringSDTFlags(sb); if(retType == null) { sb.Append(constant ? "constant" : type.ToString()); sb.Append(' '); sb.Append(name.val); if(init != null) { sb.Append(" = "); init.DebString(sb); } sb.Append(';'); } else { if(retType is not TokenTypeVoid) { sb.Append(retType.ToString()); sb.Append(' '); } string namestr = name.val; if(namestr == "$ctor") namestr = "constructor"; sb.Append(namestr); sb.Append(" ("); for(int i = 0; i < argDecl.vars.Length; i++) { if(i > 0) sb.Append(", "); sb.Append(argDecl.vars[i].type.ToString()); sb.Append(' '); sb.Append(argDecl.vars[i].name.val); } sb.Append(')'); if(body == null) sb.Append(';'); else { sb.Append(' '); body.DebString(sb); } } } // debugging // - used to output contents of a $globalvarinit(), $instfieldinit() or $statisfieldinit() function // as a series of variable declaration statements with initial value assignments // so we get the initial value assignments done in same order as specified in script public void DebStringInitFields(StringBuilder sb) { if(retType is not TokenTypeVoid) throw new Exception("bad return type " + retType.GetType().Name); if(argDecl.vars.Length != 0) throw new Exception("has " + argDecl.vars.Length + " arg(s)"); for(Token stmt = body.statements; stmt != null; stmt = stmt.nextToken) { // Body of the function should all be arithmetic statements (not eg for loops, if statements etc). TokenRVal rval = ((TokenStmtRVal)stmt).rVal; // And the opcode should be a simple assignment operator. TokenRValOpBin rvob = (TokenRValOpBin)rval; if(rvob.opcode is not TokenKwAssign) throw new Exception("bad op type " + rvob.opcode.GetType().Name); // Get field or variable being assigned to. TokenDeclVar tdvar = null; TokenRVal left = rvob.rValLeft; if(left is TokenLValIField ifield) { TokenRValThis zhis = (TokenRValThis)ifield.baseRVal; TokenDeclSDTypeClass sdt = zhis.sdtClass; tdvar = sdt.members.FindExact(ifield.fieldName.val, null); } if(left is TokenLValName global) { tdvar = global.stack.FindExact(global.name.val, null); } if(left is TokenLValSField sfield) { TokenTypeSDTypeClass sdtc = (TokenTypeSDTypeClass)sfield.baseType; TokenDeclSDTypeClass decl = sdtc.decl; tdvar = decl.members.FindExact(sfield.fieldName.val, null); } if(tdvar == null) throw new Exception("unknown var type " + left.GetType().Name); // Output flags, type name and bare variable name. // This should look like a declaration in the 'sb' // as it is not enclosed in a function. tdvar.DebStringSDTFlags(sb); tdvar.type.DebString(sb); sb.Append(' '); sb.Append(tdvar.name.val); // Maybe it has a non-default initialization value. if((tdvar.init != null) && tdvar.init is not TokenRValInitDef) { sb.Append(" = "); tdvar.init.DebString(sb); } // End of declaration statement. sb.Append(';'); } } private void DebStringSDTFlags(StringBuilder sb) { if((sdtFlags & ScriptReduce.SDT_PRIVATE) != 0) sb.Append("private "); if((sdtFlags & ScriptReduce.SDT_PROTECTED) != 0) sb.Append("protected "); if((sdtFlags & ScriptReduce.SDT_PUBLIC) != 0) sb.Append("public "); if((sdtFlags & ScriptReduce.SDT_ABSTRACT) != 0) sb.Append("abstract "); if((sdtFlags & ScriptReduce.SDT_FINAL) != 0) sb.Append("final "); if((sdtFlags & ScriptReduce.SDT_NEW) != 0) sb.Append("new "); if((sdtFlags & ScriptReduce.SDT_OVERRIDE) != 0) sb.Append("override "); if((sdtFlags & ScriptReduce.SDT_STATIC) != 0) sb.Append("static "); if((sdtFlags & ScriptReduce.SDT_VIRTUAL) != 0) sb.Append("virtual "); } } /** * @brief Indicates an interface type.method that is implemented by the function */ public class TokenIntfImpl: Token { public TokenTypeSDTypeInterface intfType; public TokenName methName; // simple name, no arg signature public TokenIntfImpl(TokenTypeSDTypeInterface intfType, TokenName methName) : base(intfType) { this.intfType = intfType; this.methName = methName; } } /** * @brief any expression that can go on left side of an "=" */ public abstract class TokenLVal: TokenRVal { public TokenLVal(Token original) : base(original) { } public abstract override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig); public abstract override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig); } /** * @brief an element of an array is an L-value */ public class TokenLValArEle: TokenLVal { public TokenRVal baseRVal; public TokenRVal subRVal; public TokenLValArEle(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { TokenType baseType = baseRVal.GetRValType(scg, null); // Maybe referencing element of a fixed-dimension array. if((baseType is TokenTypeSDTypeClass bbtype) && bbtype.decl.arrayOfType != null) { return bbtype.decl.arrayOfType; } // Maybe referencing $idxprop property of script-defined class or interface. if(baseType is TokenTypeSDTypeClass bc) { TokenDeclSDTypeClass sdtDecl = bc.decl; TokenDeclVar idxProp = scg.FindSingleMember(sdtDecl.members, new TokenName(this, "$idxprop"), null); if(idxProp != null) return idxProp.type; } if(baseType is TokenTypeSDTypeInterface bi) { TokenDeclSDTypeInterface sdtDecl = bi.decl; TokenDeclVar idxProp = sdtDecl.FindIFaceMember(scg, new TokenName(this, "$idxprop"), null, out sdtDecl); if(idxProp != null) return idxProp.type; } // Maybe referencing single character of a string. if((baseType is TokenTypeKey) || (baseType is TokenTypeStr)) { return new TokenTypeChar(this); } // Assume XMR_Array element or extracting element from list. if((baseType is TokenTypeArray) || (baseType is TokenTypeList)) { return new TokenTypeObject(this); } scg.ErrorMsg(this, "unknown array reference"); return new TokenTypeVoid(this); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return baseRVal.IsRValTrivial(scg, null) && subRVal.IsRValTrivial(scg, null); } public override void DebString(StringBuilder sb) { baseRVal.DebString(sb); sb.Append('['); subRVal.DebString(sb); sb.Append(']'); } } /** * @brief 'base.' being used to reference a field/method of the extended class. */ public class TokenLValBaseField: TokenLVal { public TokenName fieldName; private TokenDeclSDTypeClass thisClass; public TokenLValBaseField(Token original, TokenName fieldName, TokenDeclSDTypeClass thisClass) : base(original) { this.fieldName = fieldName; this.thisClass = thisClass; } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { TokenDeclVar var = scg.FindThisMember(thisClass.extends, fieldName, argsig); if(var != null) return var.type; scg.ErrorMsg(fieldName, "unknown member of " + thisClass.extends.ToString()); return new TokenTypeVoid(fieldName); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { TokenDeclVar var = scg.FindThisMember(thisClass.extends, fieldName, argsig); return (var != null) && var.IsVarTrivial(scg); } public override bool IsCallTrivial(ScriptCodeGen scg, TokenType[] argsig) { TokenDeclVar var = scg.FindThisMember(thisClass.extends, fieldName, argsig); return (var != null) && var.IsFuncTrivial(scg); } } /** * @brief a field within an struct is an L-value */ public class TokenLValIField: TokenLVal { public TokenRVal baseRVal; public TokenName fieldName; public TokenLValIField(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { TokenType baseType = baseRVal.GetRValType(scg, null); if(baseType is TokenTypeSDTypeClass TokenTypeSDTypeClassbaseType) { TokenDeclVar tvar = scg.FindThisMember(TokenTypeSDTypeClassbaseType, fieldName, argsig); if(tvar != null) return tvar.type; } if(baseType is TokenTypeSDTypeInterface TokenTypeSDTypeInterfacebaseType) { TokenDeclSDTypeInterface baseIntfDecl = (TokenTypeSDTypeInterfacebaseType).decl; TokenDeclVar tvar = baseIntfDecl.FindIFaceMember(scg, fieldName, argsig, out baseIntfDecl); if(tvar != null) return tvar.type; } if(baseType is TokenTypeArray) { return XMR_Array.GetRValType(fieldName); } if((baseType is TokenTypeRot) || (baseType is TokenTypeVec)) { return new TokenTypeFloat(fieldName); } scg.ErrorMsg(fieldName, "unknown member of " + baseType.ToString()); return new TokenTypeVoid(fieldName); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { // If getting pointer to instance isn't trivial, then accessing the member isn't trivial either. if(!baseRVal.IsRValTrivial(scg, null)) return false; // Accessing a member of a class depends on the member. // In the case of a method, this is accessing it as a delegate, not calling it, and // argsig simply serves as selecting which of possibly overloaded methods to select. // The case of accessing a property, however, depends on the property implementation, // as there could be looping inside the property code. TokenType baseType = baseRVal.GetRValType(scg, null); if(baseType is TokenTypeSDTypeClass TokenTypeSDTypeClassbaseType) { TokenDeclVar tvar = scg.FindThisMember(TokenTypeSDTypeClassbaseType, fieldName, argsig); return (tvar != null) && tvar.IsVarTrivial(scg); } // Accessing the members of anything else (arrays, rotations, vectors) is always trivial. return true; } /** * @brief Check to see if the case of calling an instance method of some object is trivial. * @param scg = script making the call * @param argsig = argument types of the call (used to select among overloads) * @returns true iff we can tell at compile time that the call will always call a trivial method */ public override bool IsCallTrivial(ScriptCodeGen scg, TokenType[] argsig) { // If getting pointer to instance isn't trivial, then calling the method isn't trivial either. if(!baseRVal.IsRValTrivial(scg, null)) return false; // Calling a method of a class depends on the method. TokenType baseType = baseRVal.GetRValType(scg, null); if(baseType is TokenTypeSDTypeClass TokenTypeSDTypeClassbaseType) { TokenDeclVar tvar = scg.FindThisMember(TokenTypeSDTypeClassbaseType, fieldName, argsig); return (tvar != null) && tvar.IsFuncTrivial(scg); } // Calling via a pointer to an interface instance is never trivial. // (It is really a pointer to an array of delegates). // We can't tell for this call site whether the actual method being called is trivial or not, // so we have to assume it isn't. // ??? We could theoretically check to see if *all* implementations of this method of // this interface are trivial, then we could conclude that this call is trivial. if(baseType is TokenTypeSDTypeInterface) return false; // Calling a method of anything else (arrays, rotations, vectors) is always trivial. // Even methods of delegates, such as ".GetArgTypes()" that we may someday do is trivial. return true; } // debugging public override void DebString(StringBuilder sb) { baseRVal.DebString(sb); sb.Append('.'); sb.Append(fieldName.val); } } /** * @brief a name is being used as an L-value */ public class TokenLValName: TokenLVal { public TokenName name; public VarDict stack; public TokenLValName(TokenName name, VarDict stack) : base(name) { // Save name of variable/method/function/field. this.name = name; // Save where in the stack it can be looked up. // If the current stack is for locals, do not allow forward references. // this allows idiocy like: // list buttons = [ 1, 2, 3 ]; // x () { // list buttons = llList2List (buttons, 0, 1); // llOwnerSay (llList2CSV (buttons)); // } // If it is not locals, allow forward references. // this allows function X() to call Y() and Y() to call X(). this.stack = stack.FreezeLocals(); } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { TokenDeclVar var = scg.FindNamedVar(this, argsig); if(var != null) return var.type; scg.ErrorMsg(name, "undefined name " + name.val + ScriptCodeGen.ArgSigString(argsig)); return new TokenTypeVoid(name); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { TokenDeclVar var = scg.FindNamedVar(this, argsig); return (var != null) && var.IsVarTrivial(scg); } /** * @brief Check to see if the case of calling a global method is trivial. * @param scg = script making the call * @param argsig = argument types of the call (used to select among overloads) * @returns true iff we can tell at compile time that the call will always call a trivial method */ public override bool IsCallTrivial(ScriptCodeGen scg, TokenType[] argsig) { TokenDeclVar var = scg.FindNamedVar(this, argsig); return (var != null) && var.IsFuncTrivial(scg); } // debugging public override void DebString(StringBuilder sb) { sb.Append(name.val); } } /** * @brief a static field within a struct is an L-value */ public class TokenLValSField: TokenLVal { public TokenType baseType; public TokenName fieldName; public TokenLValSField(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { if(baseType is TokenTypeSDTypeClass TokenTypeSDTypeClassbaseType) { TokenDeclVar var = scg.FindThisMember(TokenTypeSDTypeClassbaseType, fieldName, argsig); if(var != null) return var.type; } scg.ErrorMsg(fieldName, "unknown member of " + baseType.ToString()); return new TokenTypeVoid(fieldName); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { // Accessing a member of a class depends on the member. // In the case of a method, this is accessing it as a delegate, not calling it, and // argsig simply serves as selecting which of possibly overloaded methods to select. // The case of accessing a property, however, depends on the property implementation, // as there could be looping inside the property code. if(baseType is TokenTypeSDTypeClass TokenTypeSDTypeClassbaseType) { TokenDeclVar tvar = scg.FindThisMember(TokenTypeSDTypeClassbaseType, fieldName, argsig); return (tvar != null) && tvar.IsVarTrivial(scg); } // Accessing the fields/methods/properties of anything else (arrays, rotations, vectors) is always trivial. return true; } /** * @brief Check to see if the case of calling a class' static method is trivial. * @param scg = script making the call * @param argsig = argument types of the call (used to select among overloads) * @returns true iff we can tell at compile time that the call will always call a trivial method */ public override bool IsCallTrivial(ScriptCodeGen scg, TokenType[] argsig) { // Calling a static method of a class depends on the method. if(baseType is TokenTypeSDTypeClass TokenTypeSDTypeClassbaseType) { TokenDeclVar tvar = scg.FindThisMember(TokenTypeSDTypeClassbaseType, fieldName, argsig); return (tvar != null) && tvar.IsFuncTrivial(scg); } // Calling a static method of anything else (arrays, rotations, vectors) is always trivial. return true; } public override void DebString(StringBuilder sb) { if(fieldName.val == "$new") { sb.Append("new "); baseType.DebString(sb); } else { baseType.DebString(sb); sb.Append('.'); fieldName.DebString(sb); } } } /** * @brief any expression that can go on right side of "=" */ public delegate TokenRVal TCCLookup(TokenRVal rVal, ref bool didOne); public abstract class TokenRVal: Token { public TokenRVal(Token original) : base(original) { } /** * @brief Tell us the type of the expression. */ public abstract TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig); /** * @brief Tell us if reading and writing the value is trivial. * * @param scg = script code generator of script making the access * @param argsig = argument types of the call (used to select among overloads) * @returns true: we can tell at compile time that reading/writing this location * will always be trivial (no looping or CheckRun() calls possible). * false: otherwise */ public abstract bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig); /** * @brief Tell us if calling the method is trivial. * * This is the default implementation that returns false. * It is only used if the location is holding a delegate * and the method that the delegate is pointing to is being * called. Since we can't tell if the actual runtime method * is trivial or not, we assume it isn't. * * For the more usual ways of calling functions, see the * various overrides of IsCallTrivial(). * * @param scg = script code generator of script making the call * @param argsig = argument types of the call (used to select among overloads) * @returns true: we can tell at compile time that this call will always * be to a trivial function/method (no looping or CheckRun() * calls possible). * false: otherwise */ public virtual bool IsCallTrivial(ScriptCodeGen scg, TokenType[] argsig) { return false; } /** * @brief If the result of the expression is a constant, * create a TokenRValConst equivalent, set didOne, and return that. * Otherwise, just return the original without changing didOne. */ public virtual TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { return lookup(this, ref didOne); } } /** * @brief a postfix operator and corresponding L-value */ public class TokenRValAsnPost: TokenRVal { public TokenLVal lVal; public Token postfix; public TokenRValAsnPost(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return lVal.GetRValType(scg, argsig); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return lVal.IsRValTrivial(scg, null); } public override void DebString(StringBuilder sb) { lVal.DebString(sb); sb.Append(' '); postfix.DebString(sb); } } /** * @brief a prefix operator and corresponding L-value */ public class TokenRValAsnPre: TokenRVal { public Token prefix; public TokenLVal lVal; public TokenRValAsnPre(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return lVal.GetRValType(scg, argsig); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return lVal.IsRValTrivial(scg, null); } public override void DebString(StringBuilder sb) { prefix.DebString(sb); sb.Append(' '); lVal.DebString(sb); } } /** * @brief calling a function or method, ie, may have side-effects */ public class TokenRValCall: TokenRVal { public TokenRVal meth; // points to the function to be called // - might be a reference to a global function (TokenLValName) // - or an instance method of a class (TokenLValIField) // - or a static method of a class (TokenLValSField) // - or a delegate stored in a variable (assumption for anything else) public TokenRVal args; // null-terminated TokenRVal list public int nArgs; // number of elements in args public TokenRValCall(Token original) : base(original) { } private TokenType[] myArgSig; /** * @brief The type of a call is the type of the return value. */ public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { // Build type signature so we select correct overloaded function. if(myArgSig == null) { myArgSig = new TokenType[nArgs]; int i = 0; for(Token t = args; t != null; t = t.nextToken) { myArgSig[i++] = ((TokenRVal)t).GetRValType(scg, null); } } // Get the type of the method itself. This should get us a delegate type. TokenType delType = meth.GetRValType(scg, myArgSig); if(delType is not TokenTypeSDTypeDelegate) { scg.ErrorMsg(meth, "must be function or method"); return new TokenTypeVoid(meth); } // Get the return type from the delegate type. return ((TokenTypeSDTypeDelegate)delType).decl.GetRetType(); } /** * @brief See if the call to the function/method is trivial. * It is trivial if all the argument computations are trivial and * the function is not being called via virtual table or delegate * and the function body is trivial. */ public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { // Build type signature so we select correct overloaded function. if(myArgSig == null) { myArgSig = new TokenType[nArgs]; int i = 0; for(Token t = args; t != null; t = t.nextToken) { myArgSig[i++] = ((TokenRVal)t).GetRValType(scg, null); } } // Make sure all arguments can be computed trivially. for(Token t = args; t != null; t = t.nextToken) { if(!((TokenRVal)t).IsRValTrivial(scg, null)) return false; } // See if the function call itself and the function body are trivial. return meth.IsCallTrivial(scg, myArgSig); } // debugging public override void DebString(StringBuilder sb) { meth.DebString(sb); sb.Append(" ("); bool first = true; for(Token t = args; t != null; t = t.nextToken) { if(!first) sb.Append(", "); t.DebString(sb); first = false; } sb.Append(')'); } } /** * @brief encapsulates a typecast, ie, (type) */ public class TokenRValCast: TokenRVal { public TokenType castTo; public TokenRVal rVal; public TokenRValCast(TokenType type, TokenRVal value) : base(type) { castTo = type; rVal = value; } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return castTo; } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { if(castTo is TokenTypeSDTypeDelegate tokencastto) argsig = tokencastto.decl.GetArgTypes(); else argsig = null; return rVal.IsRValTrivial(scg, argsig); } /** * @brief If operand is constant, maybe we can say the whole thing is a constant. */ public override TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { rVal = rVal.TryComputeConstant(lookup, ref didOne); if(rVal is TokenRValConst TokenRValConstrVal) { try { object val = TokenRValConstrVal.val; object nval = null; if(castTo is TokenTypeChar) { if(val is char) return rVal; if(val is int intval) nval = (char)intval; } else if (castTo is TokenTypeFloat) { if(val is double) return rVal; if(val is int intval) nval = (double)intval; if(val is string sval) nval = new LSL_Float(sval).value; } else if (castTo is TokenTypeInt) { if(val is int) return rVal; if(val is char charval) nval = (int)charval; if(val is double dval) nval = (int)dval; if(val is string sval) nval = new LSL_Integer(sval).value; } else if (castTo is TokenTypeRot) { if(val is LSL_Rotation) return rVal; if(val is string sval) nval = new LSL_Rotation(sval); } else if ((castTo is TokenTypeKey) || (castTo is TokenTypeStr)) { if(val is string) nval = val; // in case of key/string conversion if(val is char cval) nval = TypeCast.CharToString(cval); if(val is double dval) nval = TypeCast.FloatToString(dval); if(val is int ival) nval = TypeCast.IntegerToString(ival); if(val is LSL_Rotation lslrot) nval = TypeCast.RotationToString(lslrot); if(val is LSL_Vector lslvec) nval = TypeCast.VectorToString(lslvec); } else if (castTo is TokenTypeVec) { if(val is LSL_Vector) return rVal; if(val is string sval) nval = new LSL_Vector(sval); } if(nval != null) { TokenRVal rValConst = new TokenRValConst(castTo, nval); didOne = true; return rValConst; } } catch { } } return this; } public override void DebString(StringBuilder sb) { sb.Append('('); castTo.DebString(sb); sb.Append(')'); rVal.DebString(sb); } } /** * @brief Encapsulate a conditional expression: * ? : */ public class TokenRValCondExpr: TokenRVal { public TokenRVal condExpr; public TokenRVal trueExpr; public TokenRVal falseExpr; public TokenRValCondExpr(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { TokenType trueType = trueExpr.GetRValType(scg, argsig); TokenType falseType = falseExpr.GetRValType(scg, argsig); if(trueType.ToString() != falseType.ToString()) { scg.ErrorMsg(condExpr, "true & false expr types don't match"); } return trueType; } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return condExpr.IsRValTrivial(scg, null) && trueExpr.IsRValTrivial(scg, argsig) && falseExpr.IsRValTrivial(scg, argsig); } /** * @brief If condition is constant, then the whole expression is constant * iff the corresponding trueExpr or falseExpr is constant. */ public override TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { TokenRVal rValCond = condExpr.TryComputeConstant(lookup, ref didOne); if(rValCond is TokenRValConst TokenRValConstrValCond) { didOne = true; return (TokenRValConstrValCond.IsConstBoolTrue() ? trueExpr : falseExpr).TryComputeConstant(lookup, ref didOne); } return this; } // debugging public override void DebString(StringBuilder sb) { condExpr.DebString(sb); sb.Append(" ? "); trueExpr.DebString(sb); sb.Append(" : "); falseExpr.DebString(sb); } } /** * @brief all constants supposed to end up here */ public enum TokenRValConstType: byte { CHAR = 0, FLOAT = 1, INT = 2, KEY = 3, STRING = 4 }; public class TokenRValConst: TokenRVal { public object val; // always a system type (char, int, double, string), never LSL-wrapped public TokenRValConstType type; public TokenType tokType; public TokenRValConst(Token original, object value) : base(original) { val = value; TokenType tt; if(val is char) { type = TokenRValConstType.CHAR; tt = new TokenTypeChar(this); } else if(val is int) { type = TokenRValConstType.INT; tt = new TokenTypeInt(this); } else if(val is double) { type = TokenRValConstType.FLOAT; tt = new TokenTypeFloat(this); } else if(val is string) { type = TokenRValConstType.STRING; tt = new TokenTypeStr(this); } else { throw new Exception("invalid constant type " + val.GetType()); } tokType = (original is TokenType type1) ? type1 : tt; if(tokType is TokenTypeKey) { type = TokenRValConstType.KEY; } } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return tokType; } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return true; } public CompValu GetCompValu() { switch(type) { case TokenRValConstType.CHAR: { return new CompValuChar(tokType, (char)val); } case TokenRValConstType.FLOAT: { return new CompValuFloat(tokType, (double)val); } case TokenRValConstType.INT: { return new CompValuInteger(tokType, (int)val); } case TokenRValConstType.KEY: case TokenRValConstType.STRING: { return new CompValuString(tokType, (string)val); } default: throw new Exception("unknown type"); } } public override TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { // gotta end somewhere return this; } public bool IsConstBoolTrue() { switch(type) { case TokenRValConstType.CHAR: { return (char)val != 0; } case TokenRValConstType.FLOAT: { return (double)val != 0; } case TokenRValConstType.INT: { return (int)val != 0; } case TokenRValConstType.KEY: { return (string)val != "" && (string)val != ScriptBaseClass.NULL_KEY; } case TokenRValConstType.STRING: { return (string)val != ""; } default: throw new Exception("unknown type"); } } public override void DebString(StringBuilder sb) { if(val is char charval) { sb.Append('\''); EscapeQuotes(sb, new string(new char[] { charval })); sb.Append('\''); } else if(val is int intval) { sb.Append(intval); } else if(val is double dval) { string str = dval.ToString(); sb.Append(str); if((str.IndexOf('.') < 0) && (str.IndexOf('E') < 0) && (str.IndexOf('e') < 0)) { sb.Append(".0"); } } else if(val is string sval) { sb.Append('"'); EscapeQuotes(sb, sval); sb.Append('"'); } else { throw new Exception("invalid constant type " + val.GetType()); } } private static void EscapeQuotes(StringBuilder sb, string s) { foreach(char c in s) { switch(c) { case '\n': { sb.Append("\\n"); break; } case '\t': { sb.Append("\\t"); break; } case '\\': { sb.Append("\\\\"); break; } case '\'': { sb.Append("\\'"); break; } case '\"': { sb.Append("\\\""); break; } default: { sb.Append(c); break; } } } } } /** * @brief Default initialization value for the corresponding variable. */ public class TokenRValInitDef: TokenRVal { public TokenType type; public static TokenRValInitDef Construct(TokenDeclVar tokenDeclVar) { TokenRValInitDef zhis = new(tokenDeclVar) { type = tokenDeclVar.type }; return zhis; } private TokenRValInitDef(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return type; } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { // it's always just a constant so it's always very trivial return true; } public override void DebString(StringBuilder sb) { sb.Append("'); } } /** * @brief encapsulation of is */ public class TokenRValIsType: TokenRVal { public TokenRVal rValExp; public TokenTypeExp typeExp; public TokenRValIsType(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return new TokenTypeBool(rValExp); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return rValExp.IsRValTrivial(scg, argsig); } } /** * @brief an R-value enclosed in brackets is an LSLList */ public class TokenRValList: TokenRVal { public TokenRVal rVal; // null-terminated list of TokenRVal objects public int nItems; public TokenRValList(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return new TokenTypeList(rVal); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { for(Token t = rVal; t != null; t = t.nextToken) { if(!((TokenRVal)t).IsRValTrivial(scg, null)) return false; } return true; } public override void DebString(StringBuilder sb) { bool first = true; sb.Append('['); for(Token t = rVal; t != null; t = t.nextToken) { if(!first) sb.Append(','); sb.Append(' '); t.DebString(sb); first = false; } sb.Append(" ]"); } } /** * @brief encapsulates '$new' arraytype '{' ... '}' */ public class TokenRValNewArIni: TokenRVal { public TokenType arrayType; public TokenList valueList; // TokenList : a sub-list // TokenKwComma : a default value // TokenRVal : init expression public TokenRValNewArIni(Token original) : base(original) { valueList = new TokenList(original); } // type of the expression = the array type allocated by $new() public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return arrayType; } // The expression is trivial if all the initializers are trivial. // An array's constructor is always trivial (no CheckRun() calls). public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return ListIsTrivial(scg, valueList); } private bool ListIsTrivial(ScriptCodeGen scg, TokenList valList) { foreach(Token val in valList.tl) { if(val is TokenRVal TokenRValval) { if(!TokenRValval.IsRValTrivial(scg, null)) return false; } if(val is TokenList TokenListval) { if(!ListIsTrivial(scg, TokenListval)) return false; } } return true; } public override void DebString(StringBuilder sb) { sb.Append("new "); arrayType.DebString(sb); sb.Append(' '); valueList.DebString(sb); } } public class TokenList: Token { public List tl = new(); public TokenList(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append('{'); bool first = true; foreach(Token t in tl) { if(!first) sb.Append(", "); t.DebString(sb); first = false; } sb.Append('}'); } } /** * @brief a binary operator and its two operands */ public class TokenRValOpBin: TokenRVal { public TokenRVal rValLeft; public TokenKw opcode; public TokenRVal rValRight; public TokenRValOpBin(TokenRVal left, TokenKw op, TokenRVal right) : base(op) { rValLeft = left; opcode = op; rValRight = right; } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { // Comparisons and the like always return bool. string opstr = opcode.ToString(); if((opstr == "==") || (opstr == "!=") || (opstr == ">=") || (opstr == ">") || (opstr == "&&") || (opstr == "||") || (opstr == "<=") || (opstr == "<") || (opstr == "&&&") || (opstr == "|||")) { return new TokenTypeBool(opcode); } // Comma is always type of right-hand operand. if(opstr == ",") return rValRight.GetRValType(scg, argsig); // Assignments are always the type of the left-hand operand, // including stuff like "+=". if(opstr.EndsWith("=")) { return rValLeft.GetRValType(scg, argsig); } // string+something or something+string is always string. // except list+something or something+list is always a list. string lType = rValLeft.GetRValType(scg, argsig).ToString(); string rType = rValRight.GetRValType(scg, argsig).ToString(); if((opstr == "+") && ((lType == "list") || (rType == "list"))) { return new TokenTypeList(opcode); } if((opstr == "+") && ((lType == "key") || (lType == "string") || (rType == "key") || (rType == "string"))) { return new TokenTypeStr(opcode); } // Everything else depends on both operands. string key = lType + opstr + rType; if(BinOpStr.defined.TryGetValue(key, out BinOpStr binOpStr)) { return TokenType.FromSysType(opcode, binOpStr.outtype); } scg.ErrorMsg(opcode, "undefined operation " + key); return new TokenTypeVoid(opcode); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return rValLeft.IsRValTrivial(scg, null) && rValRight.IsRValTrivial(scg, null); } /** * @brief If both operands are constants, maybe we can say the whole thing is a constant. */ public override TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { rValLeft = rValLeft.TryComputeConstant(lookup, ref didOne); rValRight = rValRight.TryComputeConstant(lookup, ref didOne); if((rValLeft is TokenRValConst TRValConstrValLeft) && (rValRight is TokenRValConst TRValConstrValRight)) { //try { object val = opcode.binOpConst(TRValConstrValLeft.val, TRValConstrValRight.val); TokenRVal rValConst = new TokenRValConst(opcode, val); didOne = true; return rValConst; } //catch { } } return this; } // debugging public override void DebString(StringBuilder sb) { rValLeft.DebString(sb); sb.Append(' '); sb.Append(opcode.ToString()); sb.Append(' '); rValRight.DebString(sb); } } /** * @brief an unary operator and its one operand */ public class TokenRValOpUn: TokenRVal { public TokenKw opcode; public TokenRVal rVal; public TokenRValOpUn(TokenKw op, TokenRVal right) : base(op) { opcode = op; rVal = right; } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { if(opcode is TokenKwExclam) return new TokenTypeInt(opcode); return rVal.GetRValType(scg, null); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return rVal.IsRValTrivial(scg, null); } /** * @brief If operand is constant, maybe we can say the whole thing is a constant. */ public override TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { rVal = rVal.TryComputeConstant(lookup, ref didOne); if(rVal is TokenRValConst TokenRValConstrVal) { try { object val = opcode.unOpConst(TokenRValConstrVal.val); TokenRVal rValConst = new TokenRValConst(opcode, val); didOne = true; return rValConst; } catch { } } return this; } /** * @brief Serialization/Deserialization. */ public TokenRValOpUn(Token original) : base(original) { } // debugging public override void DebString(StringBuilder sb) { sb.Append(opcode.ToString()); rVal.DebString(sb); } } /** * @brief an R-value enclosed in parentheses */ public class TokenRValParen: TokenRVal { public TokenRVal rVal; public TokenRValParen(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { // pass argsig through in this simple case, ie, let // them do something like (llOwnerSay)("blabla..."); return rVal.GetRValType(scg, argsig); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { // pass argsig through in this simple case, ie, let // them do something like (llOwnerSay)("blabla..."); return rVal.IsRValTrivial(scg, argsig); } /** * @brief If operand is constant, we can say the whole thing is a constant. */ public override TokenRVal TryComputeConstant(TCCLookup lookup, ref bool didOne) { rVal = rVal.TryComputeConstant(lookup, ref didOne); if(rVal is TokenRValConst) { didOne = true; return rVal; } return this; } public override void DebString(StringBuilder sb) { sb.Append('('); rVal.DebString(sb); sb.Append(')'); } } public class TokenRValRot: TokenRVal { public TokenRVal xRVal; public TokenRVal yRVal; public TokenRVal zRVal; public TokenRVal wRVal; public TokenRValRot(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return new TokenTypeRot(xRVal); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return xRVal.IsRValTrivial(scg, null) && yRVal.IsRValTrivial(scg, null) && zRVal.IsRValTrivial(scg, null) && wRVal.IsRValTrivial(scg, null); } public override void DebString(StringBuilder sb) { sb.Append('<'); xRVal.DebString(sb); sb.Append(','); yRVal.DebString(sb); sb.Append(','); zRVal.DebString(sb); sb.Append(','); wRVal.DebString(sb); sb.Append('>'); } } /** * @brief 'this' is being used as an rval inside an instance method. */ public class TokenRValThis: TokenRVal { public Token original; public TokenDeclSDTypeClass sdtClass; public TokenRValThis(Token original, TokenDeclSDTypeClass sdtClass) : base(original) { this.original = original; this.sdtClass = sdtClass; } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return sdtClass.MakeRefToken(original); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return true; // ldarg.0/starg.0 can't possibly loop } // debugging public override void DebString(StringBuilder sb) { sb.Append("this"); } } /** * @brief the 'undef' keyword is being used as a value in an expression. * It is the null object pointer and has type TokenTypeUndef. */ public class TokenRValUndef: TokenRVal { readonly Token original; public TokenRValUndef(Token original) : base(original) { this.original = original; } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return new TokenTypeUndef(original); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return true; } public override void DebString(StringBuilder sb) { sb.Append("undef"); } } /** * @brief put 3 RVals together as a Vector value. */ public class TokenRValVec: TokenRVal { public TokenRVal xRVal; public TokenRVal yRVal; public TokenRVal zRVal; public TokenRValVec(Token original) : base(original) { } public override TokenType GetRValType(ScriptCodeGen scg, TokenType[] argsig) { return new TokenTypeVec(xRVal); } public override bool IsRValTrivial(ScriptCodeGen scg, TokenType[] argsig) { return xRVal.IsRValTrivial(scg, null) && yRVal.IsRValTrivial(scg, null) && zRVal.IsRValTrivial(scg, null); } public override void DebString(StringBuilder sb) { sb.Append('<'); xRVal.DebString(sb); sb.Append(','); yRVal.DebString(sb); sb.Append(','); zRVal.DebString(sb); sb.Append('>'); } } /** * @brief encapsulates the whole script in a single token */ public class TokenScript: Token { public int expiryDays = Int32.MaxValue; public TokenDeclState defaultState; public Dictionary states = new(); public VarDict variablesStack = new (false); // initial one is used for global functions and variables public TokenDeclVar globalVarInit; // $globalvarinit function // - performs explicit global var and static field inits private Dictionary sdSrcTypes = new(); private bool sdSrcTypesSealed = false; public TokenScript(Token original) : base(original) { } /* * Handle variable definition stack. * Generally a '{' pushes a new frame and a '}' pops the frame. * Function parameters are pushed in an additional frame (just outside the body's { ... } block) */ public void PushVarFrame(bool locals) { PushVarFrame(new VarDict(locals)); } public void PushVarFrame(VarDict newFrame) { newFrame.outerVarDict = variablesStack; variablesStack = newFrame; } public void PopVarFrame() { variablesStack = variablesStack.outerVarDict; } public bool AddVarEntry(TokenDeclVar var) { return variablesStack.AddEntry(var); } /* * Handle list of script-defined types. */ public void sdSrcTypesSeal() { sdSrcTypesSealed = true; } public bool sdSrcTypesContainsKey(string key) { return sdSrcTypes.ContainsKey(key); } public bool sdSrcTypesTryGetValue(string key, out TokenDeclSDType value) { return sdSrcTypes.TryGetValue(key, out value); } public void sdSrcTypesAdd(string key, TokenDeclSDType value) { if(sdSrcTypesSealed) throw new Exception("sdSrcTypes is sealed"); value.sdTypeIndex = sdSrcTypes.Count; sdSrcTypes.Add(key, value); } public void sdSrcTypesRep(string key, TokenDeclSDType value) { if(sdSrcTypesSealed) throw new Exception("sdSrcTypes is sealed"); value.sdTypeIndex = sdSrcTypes[key].sdTypeIndex; sdSrcTypes[key] = value; } public void sdSrcTypesReplace(string key, TokenDeclSDType value) { if(sdSrcTypesSealed) throw new Exception("sdSrcTypes is sealed"); sdSrcTypes[key] = value; } public Dictionary.ValueCollection sdSrcTypesValues { get { return sdSrcTypes.Values; } } public int sdSrcTypesCount { get { return sdSrcTypes.Count; } } /** * @brief Debug output. */ public override void DebString(StringBuilder sb) { /* * Script-defined types. */ foreach(TokenDeclSDType srcType in sdSrcTypes.Values) { srcType.DebString(sb); } /* * Global constants. * Variables are handled by outputting the $globalvarinit function. */ foreach(TokenDeclVar var in variablesStack) { if(var.constant) { var.DebString(sb); } } /* * Global functions. */ foreach(TokenDeclVar var in variablesStack) { if(var == globalVarInit) { var.DebStringInitFields(sb); } else if(var.retType != null) { var.DebString(sb); } } /* * States and their event handler functions. */ defaultState.DebString(sb); foreach(TokenDeclState st in states.Values) { st.DebString(sb); } } } /** * @brief state body declaration */ public class TokenStateBody: Token { public TokenDeclVar eventFuncs; public int index = -1; // (codegen) row in ScriptHandlerEventTable (0=default) public TokenStateBody(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append(" { "); for(Token t = eventFuncs; t != null; t = t.nextToken) { t.DebString(sb); } sb.Append(" } "); } } /** * @brief a single statement, such as ending on a semicolon or enclosed in braces * TokenStmt includes the terminating semicolon or the enclosing braces * Also includes @label; for jump targets. * Also includes stray ; null statements. * Also includes local variable declarations with or without initialization value. */ public class TokenStmt: Token { public TokenStmt(Token original) : base(original) { } } /** * @brief a group of statements enclosed in braces */ public class TokenStmtBlock: TokenStmt { public Token statements; // null-terminated list of statements, can also have TokenDeclVar's in here public TokenStmtBlock outerStmtBlock; // next outer stmtBlock or null if top-level, ie, function definition public TokenDeclVar function; // function it is part of public bool isTry; // true iff it's a try statement block public bool isCatch; // true iff it's a catch statement block public bool isFinally; // true iff it's a finally statement block public TokenStmtTry tryStmt; // set iff isTry|isCatch|isFinally is set public TokenStmtBlock(Token original) : base(original) { } // debugging public override void DebString(StringBuilder sb) { sb.Append("{ "); for(Token stmt = statements; stmt != null; stmt = stmt.nextToken) { stmt.DebString(sb); } sb.Append("} "); } } /** * @brief definition of branch target name */ public class TokenStmtLabel: TokenStmt { public TokenName name; // the label's name public TokenStmtBlock block; // which block it is defined in public bool hasBkwdRefs = false; public bool labelTagged; // code gen: location of label public ScriptMyLabel labelStruct; public TokenStmtLabel(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append('@'); name.DebString(sb); sb.Append(';'); } } /** * @brief those types of RVals with a semi-colon on the end * that are allowed to stand alone as statements */ public class TokenStmtRVal: TokenStmt { public TokenRVal rVal; public TokenStmtRVal(Token original) : base(original) { } // debugging public override void DebString(StringBuilder sb) { rVal.DebString(sb); sb.Append("; "); } } public class TokenStmtBreak: TokenStmt { public TokenStmtBreak(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("break;"); } } public class TokenStmtCont: TokenStmt { public TokenStmtCont(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("continue;"); } } /** * @brief "do" statement */ public class TokenStmtDo: TokenStmt { public TokenStmt bodyStmt; public TokenRValParen testRVal; public TokenStmtDo(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("do "); bodyStmt.DebString(sb); sb.Append(" while "); testRVal.DebString(sb); sb.Append(';'); } } /** * @brief "for" statement */ public class TokenStmtFor: TokenStmt { public TokenStmt initStmt; // there is always an init statement, though it may be a null statement public TokenRVal testRVal; // there may or may not be a test (null if not) public TokenRVal incrRVal; // there may or may not be an increment (null if not) public TokenStmt bodyStmt; // there is always a body statement, though it may be a null statement public TokenStmtFor(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("for ("); if(initStmt != null) initStmt.DebString(sb); else sb.Append(';'); testRVal?.DebString(sb); sb.Append(';'); incrRVal?.DebString(sb); sb.Append(") "); bodyStmt.DebString(sb); } } /** * @brief "foreach" statement */ public class TokenStmtForEach: TokenStmt { public TokenLVal keyLVal; public TokenLVal valLVal; public TokenRVal arrayRVal; public TokenStmt bodyStmt; // there is always a body statement, though it may be a null statement public TokenStmtForEach(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("foreach ("); keyLVal?.DebString(sb); sb.Append(','); valLVal?.DebString(sb); sb.Append(" in "); arrayRVal.DebString(sb); sb.Append(')'); bodyStmt.DebString(sb); } } public class TokenStmtIf: TokenStmt { public TokenRValParen testRVal; public TokenStmt trueStmt; public TokenStmt elseStmt; public TokenStmtIf(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("if "); testRVal.DebString(sb); sb.Append(' '); trueStmt.DebString(sb); if(elseStmt != null) { sb.Append(" else "); elseStmt.DebString(sb); } } } public class TokenStmtJump: TokenStmt { public TokenName label; public TokenStmtJump(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("jump "); label.DebString(sb); sb.Append(';'); } } public class TokenStmtNull: TokenStmt { public TokenStmtNull(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append(';'); } } public class TokenStmtRet: TokenStmt { public TokenRVal rVal; // null if void public TokenStmtRet(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("return"); if(rVal != null) { sb.Append(' '); rVal.DebString(sb); } sb.Append(';'); } } /** * @brief statement that changes the current state. */ public class TokenStmtState: TokenStmt { public TokenName state; // null for default public TokenStmtState(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("state "); sb.Append((state == null) ? "default" : state.val); sb.Append(';'); } } /** * @brief Encapsulates a whole switch statement including the body and all cases. */ public class TokenStmtSwitch: TokenStmt { public TokenRValParen testRVal; // the integer index expression public TokenSwitchCase cases = null; // list of all cases, linked by .nextCase public TokenSwitchCase lastCase = null; // used during reduce to point to last in 'cases' list public TokenStmtSwitch(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("switch "); testRVal.DebString(sb); sb.Append('{'); for(TokenSwitchCase kase = cases; kase != null; kase = kase.nextCase) { kase.DebString(sb); } sb.Append('}'); } } /** * @brief Encapsulates a case/default clause from a switch statement including the * two values and the corresponding body statements. */ public class TokenSwitchCase: Token { public TokenSwitchCase nextCase; // next case in source-code order public TokenRVal rVal1; // null means 'default', else 'case' public TokenRVal rVal2; // null means 'case expr:', else 'case expr ... expr:' public TokenStmt stmts; // statements associated with the case public TokenStmt lastStmt; // used during reduce for building statement list public int val1; // codegen: value of rVal1 here public int val2; // codegen: value of rVal2 here public ScriptMyLabel label; // codegen: target label here public TokenSwitchCase nextSortedCase; // codegen: next case in ascending val order public string str1; public string str2; public TokenSwitchCase lowerCase; public TokenSwitchCase higherCase; public TokenSwitchCase(Token original) : base(original) { } public override void DebString(StringBuilder sb) { if(rVal1 == null) { sb.Append("default: "); } else { sb.Append("case "); rVal1.DebString(sb); if(rVal2 != null) { sb.Append(" ... "); rVal2.DebString(sb); } sb.Append(": "); } for(Token t = stmts; t != null; t = t.nextToken) { t.DebString(sb); } } } public class TokenStmtThrow: TokenStmt { public TokenRVal rVal; // null if rethrow style public TokenStmtThrow(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("throw "); rVal.DebString(sb); sb.Append(';'); } } /** * @brief Encapsulates related try, catch and finally statements. */ public class TokenStmtTry: TokenStmt { public TokenStmtBlock tryStmt; public TokenDeclVar catchVar; // null iff catchStmt is null public TokenStmtBlock catchStmt; // can be null public TokenStmtBlock finallyStmt; // can be null public Dictionary iLeaves = new(); public TokenStmtTry(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("try "); tryStmt.DebString(sb); if(catchStmt != null) { sb.Append("catch ("); sb.Append(catchVar.type.ToString()); sb.Append(' '); sb.Append(catchVar.name.val); sb.Append(") "); catchStmt.DebString(sb); } if(finallyStmt != null) { sb.Append("finally "); finallyStmt.DebString(sb); } } } public class IntermediateLeave { public ScriptMyLabel jumpIntoLabel; public ScriptMyLabel jumpAwayLabel; } public class TokenStmtVarIniDef: TokenStmt { public TokenLVal var; public TokenStmtVarIniDef(Token original) : base(original) { } } public class TokenStmtWhile: TokenStmt { public TokenRValParen testRVal; public TokenStmt bodyStmt; public TokenStmtWhile(Token original) : base(original) { } public override void DebString(StringBuilder sb) { sb.Append("while "); testRVal.DebString(sb); sb.Append(' '); bodyStmt.DebString(sb); } } /** * @brief type expressions (right-hand of 'is' keyword). */ public class TokenTypeExp: Token { public TokenTypeExp(Token original) : base(original) { } } public class TokenTypeExpBinOp: TokenTypeExp { public TokenTypeExp leftOp; public Token binOp; public TokenTypeExp rightOp; public TokenTypeExpBinOp(Token original) : base(original) { } } public class TokenTypeExpNot: TokenTypeExp { public TokenTypeExp typeExp; public TokenTypeExpNot(Token original) : base(original) { } } public class TokenTypeExpPar: TokenTypeExp { public TokenTypeExp typeExp; public TokenTypeExpPar(Token original) : base(original) { } } public class TokenTypeExpType: TokenTypeExp { public TokenType typeToken; public TokenTypeExpType(Token original) : base(original) { } } public class TokenTypeExpUndef: TokenTypeExp { public TokenTypeExpUndef(Token original) : base(original) { } } }