|
@@ -0,0 +1,7719 @@
|
|
|
+/*
|
|
|
+ * 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.XMREngine {
|
|
|
+
|
|
|
+ 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 static Dictionary<Type, int> 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<Type, int> PrecedenceInit ()
|
|
|
+ {
|
|
|
+ Dictionary<Type, int> p = new Dictionary<Type, int> ();
|
|
|
+
|
|
|
+ // http://www.lslwiki.net/lslwiki/wakka.php?wakka=operators
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwComma), 30);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwAsnLSh), ASNPR); // all assignment operators of equal precedence
|
|
|
+ p.Add (typeof (TokenKwAsnRSh), ASNPR); // ... so they get processed strictly right-to-left
|
|
|
+ p.Add (typeof (TokenKwAsnAdd), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnAnd), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnSub), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnMul), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnDiv), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnMod), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnOr), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAsnXor), ASNPR);
|
|
|
+ p.Add (typeof (TokenKwAssign), ASNPR);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwQMark), 60);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwOrOrOr), 70);
|
|
|
+ p.Add (typeof (TokenKwAndAndAnd), 80);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwOrOr), 100);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwAndAnd), 120);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwOr), 140);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwXor), 160);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwAnd), 180);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwCmpEQ), 200);
|
|
|
+ p.Add (typeof (TokenKwCmpNE), 200);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwCmpLT), 240);
|
|
|
+ p.Add (typeof (TokenKwCmpLE), 240);
|
|
|
+ p.Add (typeof (TokenKwCmpGT), 240);
|
|
|
+ p.Add (typeof (TokenKwCmpGE), 240);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwRSh), 260);
|
|
|
+ p.Add (typeof (TokenKwLSh), 260);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwAdd), 280);
|
|
|
+ p.Add (typeof (TokenKwSub), 280);
|
|
|
+
|
|
|
+ p.Add (typeof (TokenKwMul), 320);
|
|
|
+ p.Add (typeof (TokenKwDiv), 320);
|
|
|
+ p.Add (typeof (TokenKwMod), 320);
|
|
|
+
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @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);
|
|
|
+ tokenScript.expiryDays = tokenBegin.expiryDays;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * '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 TokenDeclVar (tokenBegin, null, tokenScript);
|
|
|
+ gviFunc.name = new TokenName (gviFunc, "$globalvarinit");
|
|
|
+ gviFunc.retType = new TokenTypeVoid (gviFunc);
|
|
|
+ gviFunc.argDecl = new TokenArgDecl (gviFunc);
|
|
|
+ TokenStmtBlock gviBody = new TokenStmtBlock (gviFunc);
|
|
|
+ gviBody.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 TokenEnd);) {
|
|
|
+ if (token is TokenKwSemi) {
|
|
|
+ token = token.nextToken;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Script-defined type declarations.
|
|
|
+ */
|
|
|
+ if (ParseDeclSDTypes (ref token, null, SDT_PUBLIC)) continue;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * constant <name> = <rval> ;
|
|
|
+ */
|
|
|
+ if (token is TokenKwConst) {
|
|
|
+ ParseDeclVar (ref token, null);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> ;
|
|
|
+ * <type> <name> = <rval> ;
|
|
|
+ */
|
|
|
+ if ((token is TokenType) &&
|
|
|
+ (token.nextToken is TokenName) &&
|
|
|
+ ((token.nextToken.nextToken is TokenKwSemi) ||
|
|
|
+ (token.nextToken.nextToken is TokenKwAssign))) {
|
|
|
+ TokenDeclVar var = ParseDeclVar (ref token, gviFunc);
|
|
|
+ if (var != null) {
|
|
|
+ // <name> = <init>;
|
|
|
+ TokenLValName left = new TokenLValName (var.name, tokenScript.variablesStack);
|
|
|
+ DoVarInit (gviFunc, left, var.init);
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> { [ get { <body> } ] [ set { <body> } ] }
|
|
|
+ */
|
|
|
+ if ((token is TokenType) &&
|
|
|
+ (token.nextToken is TokenName) &&
|
|
|
+ (token.nextToken.nextToken is TokenKwBrcOpen)) {
|
|
|
+ ParseProperty (ref token, false, true);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> <funcargs> <funcbody>
|
|
|
+ * global function returning specified type
|
|
|
+ */
|
|
|
+ if (token is TokenType) {
|
|
|
+ TokenType tokenType = (TokenType)token;
|
|
|
+
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenName)) {
|
|
|
+ ErrorMsg (token, "expecting variable/function name");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ TokenName tokenName = (TokenName)token;
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenKwParOpen)) {
|
|
|
+ ErrorMsg (token, "<type> <name> 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <name> <funcargs> <funcbody>
|
|
|
+ * global function returning void
|
|
|
+ */
|
|
|
+ if (token is TokenName) {
|
|
|
+ TokenName tokenName = (TokenName)token;
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is 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 <statebody>
|
|
|
+ */
|
|
|
+ if (token is TokenKwDefault) {
|
|
|
+ TokenDeclState tokenDeclState = new TokenDeclState (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 <name> <statebody>
|
|
|
+ */
|
|
|
+ if (token is TokenKwState) {
|
|
|
+ TokenDeclState tokenDeclState = new TokenDeclState (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenName)) {
|
|
|
+ ErrorMsg (token, "state must be followed by state name");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ tokenDeclState.name = (TokenName)token;
|
|
|
+ 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<int> braceLevels = new Stack<int> ();
|
|
|
+ Stack<TokenDeclSDType> outerLevels = new Stack<TokenDeclSDType> ();
|
|
|
+ int openBraceLevel = 0;
|
|
|
+ braceLevels.Push (-1);
|
|
|
+ outerLevels.Push (null);
|
|
|
+
|
|
|
+ for (Token t = tokenBegin; !((t = t.nextToken) is 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 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.
|
|
|
+ *
|
|
|
+ * <decl> ... { <oldbody> <newbody> }
|
|
|
+ * ^- 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 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)) delName = (TokenName)u;
|
|
|
+ }
|
|
|
+ if (!(u is 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 TokenDeclSDTypeDelegate (delName);
|
|
|
+ decl.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 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 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 TokenDeclSDTypeTypedef (tdName);
|
|
|
+ decl.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 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 TokenKwCmpLT)) return true;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Build list of generic parameter names.
|
|
|
+ */
|
|
|
+ Dictionary<string, int> parms = new Dictionary<string, int> ();
|
|
|
+ do {
|
|
|
+ t = t.nextToken;
|
|
|
+ if (!(t is 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 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;
|
|
|
+ TokenDeclSDType dupDecl;
|
|
|
+ if (!tokenScript.sdSrcTypesTryGetValue (longName, out dupDecl)) {
|
|
|
+ tokenScript.sdSrcTypesAdd (longName, decl);
|
|
|
+ if (decl.outerSDType != null) {
|
|
|
+ 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 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 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<string, int> gp1 = c1.genParams;
|
|
|
+ Dictionary<string, int> gp2 = c2.genParams;
|
|
|
+ if (gp1.Count != gp2.Count) return false;
|
|
|
+ foreach (KeyValuePair<string, int> kvp1 in gp1) {
|
|
|
+ int v2;
|
|
|
+ if (!gp2.TryGetValue (kvp1.Key, out 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<integer>' 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<Token> noTypes = new List<Token> ();
|
|
|
+ TokenDeclSDType outerSDType;
|
|
|
+ uint repeat;
|
|
|
+
|
|
|
+ do {
|
|
|
+ repeat = 0;
|
|
|
+ outerSDType = null;
|
|
|
+ noTypes.Clear ();
|
|
|
+
|
|
|
+ for (Token t = tokenBegin; !((t = t.nextToken) is 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) && (((TokenDeclSDType)t).genParams != null)) {
|
|
|
+ t = ((TokenDeclSDType)t).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) {
|
|
|
+ if (((TokenDeclSDType)t).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 TokenKwDot)) {
|
|
|
+ t = TrySpliceTypeRef (t, outerSDType, ref repeat, noTypes);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * This handles types such as integer[,][], List<string>[], 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<Token> 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 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 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 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 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<string>' but found 'List' and we have genArgs = 'string'.
|
|
|
+ * Instantiate the generic to create 'List<string>'. This splices the definition
|
|
|
+ * of 'List<string>' 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 TokenKwDot)) break;
|
|
|
+ if (!(t.nextToken.nextToken is TokenName)) break;
|
|
|
+ tnamestr = ((TokenName)t.nextToken.nextToken).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<string,integer>.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 <type>'[' 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<int> ranks = new Stack<int> ();
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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 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 StringBuilder (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 <arraytypename> {
|
|
|
+ */
|
|
|
+ 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<suffix> (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 <oftype> Get (integer idx0, integet idx1, ...) {
|
|
|
+ * integer idx = idx0;
|
|
|
+ * idx *= this.len1; idx += idx1;
|
|
|
+ * idx *= this.len2; idx += idx2;
|
|
|
+ * ...
|
|
|
+ * return (<oftype>) xmrFixedArrayGet<suffix> (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, ..., <oftype> val) {
|
|
|
+ * integer idx = idx0;
|
|
|
+ * idx *= this.len1; idx += idx1;
|
|
|
+ * idx *= this.len2; idx += idx2;
|
|
|
+ * ...
|
|
|
+ * xmrFixedArraySet<suffix> (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 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) {
|
|
|
+ TokenDeclSDTypeClass c = ((TokenTypeSDTypeClass)u).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) {
|
|
|
+ TokenDeclSDTypeInterface i = ((TokenTypeSDTypeInterface)u).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 TokenKwComma) && !(u is TokenKwColon)) break;
|
|
|
+ u = u.nextToken;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // next must be '{' to open class declaration body
|
|
|
+ if (!(u is 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 TokenDeclVar (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 TokenStmtBlock (ifiFunc);
|
|
|
+ ifiBody.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 TokenDeclVar (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 TokenStmtBlock (sfiFunc);
|
|
|
+ sfiBody.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 <name> = <rval> ;
|
|
|
+ */
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> ;
|
|
|
+ * <type> <name> = <rval> ;
|
|
|
+ */
|
|
|
+ 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) {
|
|
|
+ // <type>.<name> = <init>;
|
|
|
+ TokenLValSField left = new TokenLValSField (var.init);
|
|
|
+ left.baseType = tokdeclcl.MakeRefToken (var);
|
|
|
+ left.fieldName = var.name;
|
|
|
+ DoVarInit (sfiFunc, left, var.init);
|
|
|
+ } else if (var.init != null) {
|
|
|
+ // this.<name> = <init>;
|
|
|
+ TokenLValIField left = new TokenLValIField (var.init);
|
|
|
+ left.baseRVal = new TokenRValThis (var.init, tokdeclcl);
|
|
|
+ left.fieldName = var.name;
|
|
|
+ DoVarInit (ifiFunc, left, var.init);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> [ : <implintfs> ] { [ get { <body> } ] [ set { <body> } ] }
|
|
|
+ * <type> '[' ... ']' [ : <implintfs> ] { [ get { <body> } ] [ set { <body> } ] }
|
|
|
+ */
|
|
|
+ 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> <funcargs> <funcbody>
|
|
|
+ * method with explicit return type
|
|
|
+ */
|
|
|
+ if (token is TokenType) {
|
|
|
+ ParseSDTClassMethodDecl (ref token, flags, tokdeclcl);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <name> <funcargs> <funcbody>
|
|
|
+ * method returning void
|
|
|
+ */
|
|
|
+ if ((token is TokenName) || ((token is TokenKw) && ((TokenKw)token).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 TokenDeclVar (token, null, tokenScript);
|
|
|
+ tokenDeclFunc.name = new TokenName (token, "$ctor");
|
|
|
+ tokenDeclFunc.retType = new TokenTypeVoid (token);
|
|
|
+ tokenDeclFunc.argDecl = new TokenArgDecl (token);
|
|
|
+ tokenDeclFunc.sdtClass = tokdeclcl;
|
|
|
+ tokenDeclFunc.sdtFlags = SDT_PUBLIC | SDT_NEW;
|
|
|
+ tokenDeclFunc.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<uint, Token> foundFlags = new Dictionary<uint, Token> ();
|
|
|
+ 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);
|
|
|
+ Token confToken;
|
|
|
+ if (foundFlags.TryGetValue (conf, out 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
|
|
|
+ *
|
|
|
+ * <type> <name> [ : <implintfs> ] { [ get { <body> } ] [ set { <body> } ] }
|
|
|
+ * <type> '[' ... ']' [ : <implintfs> ] { [ get { <body> } ] [ set { <body> } ] }
|
|
|
+ */
|
|
|
+ private TokenDeclVar ParseProperty (ref Token token, bool abs, bool imp)
|
|
|
+ {
|
|
|
+ /*
|
|
|
+ * Parse out the property's type and name.
|
|
|
+ * <type> <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.
|
|
|
+ * [ ':' <ifacetype>[.<propname>] ',' ... ]
|
|
|
+ */
|
|
|
+ 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 TokenKwBrcOpen)) {
|
|
|
+ ErrorMsg (token, "expect { to open property definition");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ token = token.nextToken;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Parse out the getter and/or setter.
|
|
|
+ * 'get' { <body> | ';' }
|
|
|
+ * 'set' { <body> | ';' }
|
|
|
+ */
|
|
|
+ TokenDeclVar getFunc = null;
|
|
|
+ TokenDeclVar setFunc = null;
|
|
|
+ while (!(token is TokenKwBrcClose)) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Maybe create a getter function.
|
|
|
+ */
|
|
|
+ if (token is TokenKwGet) {
|
|
|
+ getFunc = new TokenDeclVar (token, null, tokenScript);
|
|
|
+ getFunc.name = new TokenName (token, name.val + "$get");
|
|
|
+ getFunc.retType = type;
|
|
|
+ getFunc.argDecl = args;
|
|
|
+ getFunc.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);
|
|
|
+ setFunc.name = new TokenName (token, name.val + "$set");
|
|
|
+ setFunc.retType = new TokenTypeVoid (token);
|
|
|
+ setFunc.argDecl = argDecl;
|
|
|
+ setFunc.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 TokenDeclVar (name, null, tokenScript);
|
|
|
+ tokenDeclVar.type = type;
|
|
|
+ tokenDeclVar.name = name;
|
|
|
+ tokenDeclVar.getProp = getFunc;
|
|
|
+ tokenDeclVar.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 TokenIntfImpl (impl.intfType,
|
|
|
+ new TokenName (impl.methName, impl.methName.val + suffix));
|
|
|
+ gsimpl.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 TokenDeclVar (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 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 TokenLValBaseField (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 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 TokenLValBaseField (thisCtor,
|
|
|
+ new TokenName (thisCtor, "$ctor"),
|
|
|
+ (TokenDeclSDTypeClass)thisCtor.sdtClass);
|
|
|
+ TokenRValCall rvc = new TokenRValCall (thisCtor);
|
|
|
+ rvc.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) {
|
|
|
+ retType = (TokenType)u;
|
|
|
+ u = u.nextToken;
|
|
|
+ } else {
|
|
|
+ retType = new TokenTypeVoid (u);
|
|
|
+ }
|
|
|
+
|
|
|
+ // get list of argument types until we see a ')'
|
|
|
+ List<TokenType> args = new List<TokenType> ();
|
|
|
+ bool first = true;
|
|
|
+ do {
|
|
|
+ if (first) {
|
|
|
+
|
|
|
+ // first time should have '(' ')' or '(' <type>
|
|
|
+ if (!(u is 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 ',' <type>
|
|
|
+ if (!(u is TokenKwComma)) {
|
|
|
+ ErrorMsg (u, "expecting , separating arg types");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ u = u.nextToken;
|
|
|
+ }
|
|
|
+ if (!(u is 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 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 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) {
|
|
|
+ TokenDeclSDTypeInterface i = ((TokenTypeSDTypeInterface)u).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 TokenKwComma)) break;
|
|
|
+ u = u.nextToken;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // next must be '{' to open interface declaration body
|
|
|
+ if (!(u is 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 TokenKwBrcClose)) {
|
|
|
+ if (token is TokenKwSemi) {
|
|
|
+ token = token.nextToken;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Parse nested script-defined type definitions.
|
|
|
+ */
|
|
|
+ if (ParseDeclSDTypes (ref token, tokdeclin, SDT_PUBLIC)) continue;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> <funcargs> ;
|
|
|
+ * 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <name> <funcargs> ;
|
|
|
+ * 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;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * <type> <name> { [ get ; ] [ set ; ] }
|
|
|
+ * <type> '[' ... ']' { [ 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 TokenStateBody (token);
|
|
|
+
|
|
|
+ if (!(token is TokenKwBrcOpen)) {
|
|
|
+ ErrorMsg (token, "expecting { at beg of state");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ token = token.nextToken;
|
|
|
+ while (!(token is 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 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)
|
|
|
+ {
|
|
|
+ TokenType retType;
|
|
|
+ if (token is TokenType) {
|
|
|
+ retType = (TokenType)token;
|
|
|
+ token = token.nextToken;
|
|
|
+ } else {
|
|
|
+ retType = new TokenTypeVoid (token);
|
|
|
+ }
|
|
|
+
|
|
|
+ TokenName simpleName;
|
|
|
+ if ((token is TokenKw) && ((TokenKw)token).sdtClassOp) {
|
|
|
+ if (!ops) ErrorMsg (token, "operator functions disallowed in static contexts");
|
|
|
+ simpleName = new TokenName (token, "$op" + token.ToString ());
|
|
|
+ } else if (!(token is TokenName)) {
|
|
|
+ ErrorMsg (token, "expecting function name");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ } else {
|
|
|
+ simpleName = (TokenName)token;
|
|
|
+ }
|
|
|
+ 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 TokenDeclVar (simpleName, null, tokenScript);
|
|
|
+ tokenDeclFunc.name = simpleName;
|
|
|
+ tokenDeclFunc.retType = retType;
|
|
|
+ tokenDeclFunc.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 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)) {
|
|
|
+ methName = (TokenName)token.nextToken;
|
|
|
+ token = token.nextToken.nextToken;
|
|
|
+ }
|
|
|
+ TokenIntfImpl intfImpl = new TokenIntfImpl (intfType, methName);
|
|
|
+ intfImpl.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 TokenStmtRVal (tokenRVal);
|
|
|
+ tokenStmtRVal.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 TokenKwBrcOpen)) {
|
|
|
+ ErrorMsg (token, "statement block body must begin with a {");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenStmtBlock tokenStmtBlock = new TokenStmtBlock (token);
|
|
|
+ tokenStmtBlock.function = currentDeclFunc;
|
|
|
+ tokenStmtBlock.outerStmtBlock = currentStmtBlock;
|
|
|
+ currentStmtBlock = tokenStmtBlock;
|
|
|
+ VarDict outerVariablesStack = tokenScript.variablesStack;
|
|
|
+ try {
|
|
|
+ Token prevStmt = null;
|
|
|
+ token = token.nextToken;
|
|
|
+ while (!(token is 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 TokenStmtBreak (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is 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 TokenStmtCont (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is 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 TokenStmtDo (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ tokenStmtDo.bodyStmt = ParseStmt (ref token);
|
|
|
+ if (tokenStmtDo.bodyStmt == null) return null;
|
|
|
+ if (!(token is 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 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 TokenStmtFor (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is 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 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 TokenStmtBlock (tokenStmtFor);
|
|
|
+ forStmtBlock.outerStmtBlock = currentStmtBlock;
|
|
|
+ forStmtBlock.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 TokenStmtForEach (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenKwParOpen)) {
|
|
|
+ ErrorMsg (token, "foreach must be followed by (");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ token = token.nextToken;
|
|
|
+
|
|
|
+ if (token is TokenName) {
|
|
|
+ tokenStmtForEach.keyLVal = new TokenLValName ((TokenName)token, tokenScript.variablesStack);
|
|
|
+ token = token.nextToken;
|
|
|
+ }
|
|
|
+ if (!(token is TokenKwComma)) {
|
|
|
+ ErrorMsg (token, "expecting comma");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ token = token.nextToken;
|
|
|
+ if (token is TokenName) {
|
|
|
+ tokenStmtForEach.valLVal = new TokenLValName ((TokenName)token, tokenScript.variablesStack);
|
|
|
+ token = token.nextToken;
|
|
|
+ }
|
|
|
+ if (!(token is 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 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 TokenStmtIf (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 TokenStmtJump (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenName) || !(token.nextToken is 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 TokenName) ||
|
|
|
+ !(token.nextToken.nextToken is TokenKwSemi)) {
|
|
|
+ ErrorMsg (token, "invalid label");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenStmtLabel stmtLabel = new TokenStmtLabel (token);
|
|
|
+ stmtLabel.name = (TokenName)token.nextToken;
|
|
|
+ stmtLabel.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 TokenStmtNull (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ return tokenStmtNull;
|
|
|
+ }
|
|
|
+
|
|
|
+ private TokenStmtRet ParseStmtRet (ref Token token)
|
|
|
+ {
|
|
|
+ TokenStmtRet tokenStmtRet = new TokenStmtRet (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 TokenStmtSwitch (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 TokenStmtState (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ if ((!(token is TokenName) && !(token is TokenKwDefault)) || !(token.nextToken is TokenKwSemi)) {
|
|
|
+ ErrorMsg (token, "expecting state;");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (token is TokenName) {
|
|
|
+ tokenStmtState.state = (TokenName)token;
|
|
|
+ }
|
|
|
+ token = token.nextToken.nextToken;
|
|
|
+ return tokenStmtState;
|
|
|
+ }
|
|
|
+
|
|
|
+ private TokenStmtThrow ParseStmtThrow (ref Token token)
|
|
|
+ {
|
|
|
+ TokenStmtThrow tokenStmtThrow = new TokenStmtThrow (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 TokenKwParOpen) ||
|
|
|
+ !(token.nextToken.nextToken is TokenType) ||
|
|
|
+ !(token.nextToken.nextToken.nextToken is TokenName) ||
|
|
|
+ !(token.nextToken.nextToken.nextToken.nextToken is TokenKwParClose)) {
|
|
|
+ ErrorMsg (token, "catch must be followed by ( <type> <varname> ) { <statement>... }");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ token = token.nextToken.nextToken; // skip over 'catch' '('
|
|
|
+ TokenDeclVar tag = new TokenDeclVar (token.nextToken, currentDeclFunc, tokenScript);
|
|
|
+ tag.type = (TokenType)token;
|
|
|
+ token = token.nextToken; // skip over <type>
|
|
|
+ tag.name = (TokenName)token;
|
|
|
+ token = token.nextToken.nextToken; // skip over <varname> ')'
|
|
|
+
|
|
|
+ if ((!(tag.type is TokenTypeExc)) && (!(tag.type is TokenTypeStr))) {
|
|
|
+ ErrorMsg (tag.type, "must be type 'exception' or 'string'");
|
|
|
+ }
|
|
|
+
|
|
|
+ tokenStmtTry = new TokenStmtTry (tryKw);
|
|
|
+ tokenStmtTry.tryStmt = WrapTryCatFinInBlock (body);
|
|
|
+ tokenStmtTry.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);
|
|
|
+ tokenStmtTry.tryStmt = WrapTryCatFinInBlock (body);
|
|
|
+ tokenStmtTry.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) return (TokenStmtBlock)body;
|
|
|
+
|
|
|
+ TokenStmtTry innerTry = (TokenStmtTry)body;
|
|
|
+
|
|
|
+ TokenStmtBlock wrapper = new TokenStmtBlock (body);
|
|
|
+ wrapper.statements = innerTry;
|
|
|
+ wrapper.outerStmtBlock = currentStmtBlock;
|
|
|
+ wrapper.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 TokenStmtWhile (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 TokenDeclVar (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 <name> = <value> ;
|
|
|
+ */
|
|
|
+ if (token is TokenKwConst) {
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenName)) {
|
|
|
+ ErrorMsg (token, "expecting constant name");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ tokenDeclVar.name = (TokenName)token;
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is 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);
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Can't be same name already in block.
|
|
|
+ */
|
|
|
+ if (!tokenScript.AddVarEntry (tokenDeclVar)) {
|
|
|
+ ErrorMsg (tokenDeclVar, "duplicate variable " + 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) {
|
|
|
+ TokenStmtVarIniDef tsvid = new TokenStmtVarIniDef (left);
|
|
|
+ tsvid.var = left;
|
|
|
+ stmt = tsvid;
|
|
|
+ } else {
|
|
|
+ TokenKw op = new TokenKwAssign (left);
|
|
|
+ TokenStmtRVal tsrv = new TokenStmtRVal (init);
|
|
|
+ tsrv.rVal = new TokenRValOpBin (left, op, init);
|
|
|
+ stmt = tsrv;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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 TokenArgDecl (token);
|
|
|
+
|
|
|
+ bool first = true;
|
|
|
+ do {
|
|
|
+ token = token.nextToken;
|
|
|
+ if ((token.GetType () == end) && first) break;
|
|
|
+ if (!(token is TokenType)) {
|
|
|
+ ErrorMsg (token, "expecting arg type");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenType type = (TokenType)token;
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(token is TokenName)) {
|
|
|
+ ErrorMsg (token, "expecting arg name");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenName name = (TokenName)token;
|
|
|
+ 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:
|
|
|
+ * <operand> is <typeexp>
|
|
|
+ */
|
|
|
+ if (token is TokenKwIs) {
|
|
|
+ TokenRValIsType tokenRValIsType = new TokenRValIsType (token);
|
|
|
+ token = token.nextToken;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Parse the <typeexp>.
|
|
|
+ */
|
|
|
+ tokenRValIsType.typeExp = ParseTypeExp (ref token);
|
|
|
+ if (tokenRValIsType.typeExp == null) return null;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Replace top operand with result of <operand> is <typeexp>
|
|
|
+ */
|
|
|
+ tokenRValIsType.rValExp = operands;
|
|
|
+ tokenRValIsType.nextToken = operands.nextToken;
|
|
|
+ operands = tokenRValIsType;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * token points just past <typeexp> 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:
|
|
|
+ * <condexp> ? <trueexp> : <falseexp>
|
|
|
+ */
|
|
|
+ if (binOp.token is TokenKwQMark) {
|
|
|
+ TokenRValCondExpr condExpr = new TokenRValCondExpr (binOp.token);
|
|
|
+ condExpr.condExpr = operands;
|
|
|
+ condExpr.trueExpr = ParseRVal (ref token, new Type[] { typeof (TokenKwColon) });
|
|
|
+ condExpr.falseExpr = ParseRVal (ref token, termTokenTypes);
|
|
|
+ condExpr.prevToken = operands.prevToken;
|
|
|
+ operands = condExpr;
|
|
|
+ termTokenTypes = new Type[0];
|
|
|
+ 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 TokenTypeExpBinOp (typeBinOp);
|
|
|
+ typeExpBinOp.leftOp = leftOperand;
|
|
|
+ typeExpBinOp.binOp = typeBinOp;
|
|
|
+ typeExpBinOp.rightOp = rightOperand;
|
|
|
+ leftOperand = typeExpBinOp;
|
|
|
+ }
|
|
|
+ return leftOperand;
|
|
|
+ }
|
|
|
+
|
|
|
+ private TokenTypeExp GetTypeExp (ref Token token)
|
|
|
+ {
|
|
|
+ if (token is TokenKwTilde) {
|
|
|
+ TokenTypeExpNot typeExpNot = new TokenTypeExpNot (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ typeExpNot.typeExp = GetTypeExp (ref token);
|
|
|
+ if (typeExpNot.typeExp == null) return null;
|
|
|
+ return typeExpNot;
|
|
|
+ }
|
|
|
+ if (token is TokenKwParOpen) {
|
|
|
+ TokenTypeExpPar typeExpPar = new TokenTypeExpPar (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 TokenTypeExpUndef (token);
|
|
|
+ token = token.nextToken;
|
|
|
+ return typeExpUndef;
|
|
|
+ }
|
|
|
+ if (token is TokenType) {
|
|
|
+ TokenTypeExpType typeExpType = new TokenTypeExpType (token);
|
|
|
+ typeExpType.typeToken = (TokenType)token;
|
|
|
+ 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 TokenRValAsnPre (token);
|
|
|
+ asnPre.prefix = token;
|
|
|
+ token = token.nextToken;
|
|
|
+ TokenRVal op = GetOperand (ref token);
|
|
|
+ if (op == null) return null;
|
|
|
+ if (!(op is 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 TokenRValAsnPost (token);
|
|
|
+ asnPost.postfix = token;
|
|
|
+ token = token.nextToken;
|
|
|
+ if (!(operand is 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 TokenName)) {
|
|
|
+ ErrorMsg (token, ". must be followed by field/method name");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenLValIField field = new TokenLValIField (token);
|
|
|
+ field.baseRVal = operand;
|
|
|
+ field.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 TokenLValArEle (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.
|
|
|
+ */
|
|
|
+ TokenRVal subscriptRVals;
|
|
|
+ int numSubscripts = SplitCommaRVals (tokenLValArEle.subRVal, out subscriptRVals);
|
|
|
+ if (numSubscripts > 1) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If so, put the values in an LSL_List object.
|
|
|
+ */
|
|
|
+ TokenRValList rValList = new TokenRValList (tokenLValArEle);
|
|
|
+ rValList.rVal = subscriptRVals;
|
|
|
+ rValList.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) {
|
|
|
+ TokenRValConst rValConst = new TokenRValConst (token, ((TokenChar)token).val);
|
|
|
+ token = token.nextToken;
|
|
|
+ return rValConst;
|
|
|
+ }
|
|
|
+ if (token is TokenFloat) {
|
|
|
+ TokenRValConst rValConst = new TokenRValConst (token, ((TokenFloat)token).val);
|
|
|
+ token = token.nextToken;
|
|
|
+ return rValConst;
|
|
|
+ }
|
|
|
+ if (token is TokenInt) {
|
|
|
+ TokenRValConst rValConst = new TokenRValConst (token, ((TokenInt)token).val);
|
|
|
+ token = token.nextToken;
|
|
|
+ return rValConst;
|
|
|
+ }
|
|
|
+ if (token is TokenStr) {
|
|
|
+ TokenRValConst rValConst = new TokenRValConst (token, ((TokenStr)token).val);
|
|
|
+ token = token.nextToken;
|
|
|
+ return rValConst;
|
|
|
+ }
|
|
|
+ if (token is TokenKwUndef) {
|
|
|
+ TokenRValUndef rValUndef = new TokenRValUndef ((TokenKwUndef)token);
|
|
|
+ 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;
|
|
|
+ TokenRVal rVals;
|
|
|
+ int nVals = SplitCommaRVals (rValAll, out rVals);
|
|
|
+ switch (nVals) {
|
|
|
+ case 3: {
|
|
|
+ TokenRValVec rValVec = new TokenRValVec (openBkt);
|
|
|
+ rValVec.xRVal = rVals;
|
|
|
+ rValVec.yRVal = (TokenRVal)rVals.nextToken;
|
|
|
+ rValVec.zRVal = (TokenRVal)rVals.nextToken.nextToken;
|
|
|
+ return rValVec;
|
|
|
+ }
|
|
|
+ case 4: {
|
|
|
+ TokenRValRot rValRot = new TokenRValRot (openBkt);
|
|
|
+ rValRot.xRVal = rVals;
|
|
|
+ rValRot.yRVal = (TokenRVal)rVals.nextToken;
|
|
|
+ rValRot.zRVal = (TokenRVal)rVals.nextToken.nextToken;
|
|
|
+ rValRot.wRVal = (TokenRVal)rVals.nextToken.nextToken.nextToken;
|
|
|
+ return rValRot;
|
|
|
+ }
|
|
|
+ default: {
|
|
|
+ ErrorMsg (openBkt, "bad rotation/vector");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * '['value,...']', ie, list
|
|
|
+ */
|
|
|
+ if (token is TokenKwBrkOpen) {
|
|
|
+ TokenRValList rValList = new TokenRValList (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 <type>.<name> 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 TokenLValSField (token.nextToken.nextToken);
|
|
|
+ field.baseType = (TokenType)token;
|
|
|
+ field.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 TokenDeclSDTypeClass)) {
|
|
|
+ ErrorMsg (token, "using 'this' outside class definition");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenRValThis zhis = new TokenRValThis (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 TokenKwDot) || !(token.nextToken.nextToken is TokenName)) {
|
|
|
+ ErrorMsg (token, "base must be followed by . then field or method name");
|
|
|
+ TokenRValThis zhis = new TokenRValThis (token, (TokenDeclSDTypeClass)currentDeclFunc.sdtClass);
|
|
|
+ token = token.nextToken;
|
|
|
+ return zhis;
|
|
|
+ }
|
|
|
+ TokenLValBaseField baseField = new TokenLValBaseField (token,
|
|
|
+ (TokenName)token.nextToken.nextToken,
|
|
|
+ (TokenDeclSDTypeClass)currentDeclFunc.sdtClass);
|
|
|
+ token = token.nextToken.nextToken.nextToken;
|
|
|
+ return baseField;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Maybe we have 'new <script-defined-type>' saying to create an object instance.
|
|
|
+ * This ends up generating a call to static function <script-defined-type>.$new(...)
|
|
|
+ * whose CIL code is generated by GenerateNewobjBody().
|
|
|
+ */
|
|
|
+ if (token is TokenKwNew) {
|
|
|
+ if (!(token.nextToken is TokenType)) {
|
|
|
+ ErrorMsg (token.nextToken, "new must be followed by type");
|
|
|
+ token = SkipPastSemi (token);
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ TokenLValSField field = new TokenLValSField (token.nextToken.nextToken);
|
|
|
+ field.baseType = (TokenType)token.nextToken;
|
|
|
+ field.fieldName = new TokenName (token, "$new");
|
|
|
+ token = token.nextToken.nextToken;
|
|
|
+ return field;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * All we got left is <name>, eg, arg, function, global or local variable reference
|
|
|
+ */
|
|
|
+ if (token is TokenName) {
|
|
|
+ TokenLValName name = new TokenLValName ((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 TokenRValCall (token);
|
|
|
+ rValCall.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 BinOp ();
|
|
|
+ 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 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 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<TokenList> stack = new Stack<TokenList> ();
|
|
|
+ TokenRValNewArIni arini = new TokenRValNewArIni (token);
|
|
|
+ arini.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 TokenRValParen (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 TokenRValOpBin) || !(((TokenRValOpBin)rValAll).opcode is TokenKwComma)) {
|
|
|
+ rVals = rValAll;
|
|
|
+ if (rVals.nextToken != null) throw new Exception ("expected null");
|
|
|
+ return 1;
|
|
|
+ }
|
|
|
+ TokenRValOpBin opBin = (TokenRValOpBin)rValAll;
|
|
|
+ TokenRVal rValLeft, rValRight;
|
|
|
+ int leftCount = SplitCommaRVals (opBin.rValLeft, out rValLeft);
|
|
|
+ int rightCount = SplitCommaRVals (opBin.rValRight, out 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<string, TokenDeclSDType> innerSDTypes = new Dictionary<string, TokenDeclSDType> ();
|
|
|
+ // 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 VarDict (false); // declared fields, methods, properties if any
|
|
|
+
|
|
|
+ public Dictionary<string, int> genParams; // list of parameters for generic prototypes
|
|
|
+ // null for non-generic prototypes
|
|
|
+ // eg, for 'Dictionary<K,V>'
|
|
|
+ // ...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<string,integer>' 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<string, TokenDeclSDType> 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<string,integer>.Converter'
|
|
|
+ * @param name = short name with arguments, eg, 'Converter<float>'.
|
|
|
+ * @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<string,integer>'
|
|
|
+ * 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;
|
|
|
+ for (prefix = this; (prefix = prefix.prevToken) != null;) {
|
|
|
+ if (!(prefix is TokenKwPublic) && !(prefix is TokenKwProtected) && !(prefix is 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<V=float>' from
|
|
|
+ * 'Dictionary<T=string,U=integer>.Converter<V=float>', any V's are
|
|
|
+ * numbered [2]. Any [0]s or [1]s should be gone by now but it doesn't
|
|
|
+ * matter.
|
|
|
+ */
|
|
|
+ int index;
|
|
|
+ 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<string,integer>.List is same short name as Dictionary<T,U>.List
|
|
|
+ * if generic, eg doing Converter<W> of Dictionary<T,U>.Converter<W>, we have to manually copy the W as well.
|
|
|
+ * 2) outerSDType is transformed from Dictionary<T,U> to Dictionary<string,integer>.
|
|
|
+ * 3) innerSDTypes is rebuilt when/if we find classes that are inner to this one.
|
|
|
+ */
|
|
|
+ if (pt is TokenDeclSDType) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Make a new TokenDeclSDType{Class,Delegate,Interface}.
|
|
|
+ */
|
|
|
+ TokenDeclSDType ptSDType = (TokenDeclSDType)pt;
|
|
|
+ TokenDeclSDType itSDType = ptSDType.MakeBlank (new TokenName (ptSDType.shortName, ptSDType.shortName.val));
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Set up the transformed outerSDType.
|
|
|
+ * Eg, if we are creating Enumerator of Dictionary<string,integer>.Enumerator,
|
|
|
+ * innerProto = Dictionary<T,U> and innerInst = Dictionary<string,integer>.
|
|
|
+ */
|
|
|
+ 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<W>, 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) && this.genParams.TryGetValue (((TokenName)pt).val, out 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<string, TokenDeclSDType> 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 TokenName (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)
|
|
|
+ {
|
|
|
+ TokenDeclSDType sdtdecl;
|
|
|
+ if (sdTypes.TryGetValue (name, out sdtdecl)) return sdtdecl.MakeRefToken (this);
|
|
|
+ return TokenType.FromLSLType (this, name);
|
|
|
+ }
|
|
|
+
|
|
|
+ // debugging - returns, eg, 'Dictionary<T,U>.Enumerator.Node'
|
|
|
+ public override void DebString (StringBuilder sb)
|
|
|
+ {
|
|
|
+ // get long name broken down into segments from outermost to this
|
|
|
+ Stack<TokenDeclSDType> declStack = new Stack<TokenDeclSDType> ();
|
|
|
+ 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<string, int> 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<TokenDeclSDTypeInterface> implements = new List<TokenDeclSDTypeInterface> ();
|
|
|
+ public TokenDeclVar instFieldInit; // $instfieldinit function to do instance field initializations
|
|
|
+ public TokenDeclVar staticFieldInit; // $staticfieldinit function to do static field initializations
|
|
|
+
|
|
|
+ public Dictionary<string, int> intfIndices = new Dictionary<string, int> (); // 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 XMRInstArSizes ();
|
|
|
+ // 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<StackedMethod> stackedMethods;
|
|
|
+ private List<StackedIFace> 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<StackedMethod> ();
|
|
|
+ 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<StackedIFace> ();
|
|
|
+ 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;
|
|
|
+ DynamicMethod dm;
|
|
|
+ if (scriptObjCode.dynamicMethods.TryGetValue (methName, out 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<string, TokenDeclSDTypeDelegate> inlines = new Dictionary<string, TokenDeclSDTypeDelegate> ();
|
|
|
+ private static Dictionary<Type, string> inlrevs = new Dictionary<Type, string> ();
|
|
|
+
|
|
|
+ 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 ();
|
|
|
+ if (asmFileWriter != null) {
|
|
|
+ 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]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (asmFileWriter != null) {
|
|
|
+ asmFileWriter.WriteLine (");");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @brief Fill in missing internal data.
|
|
|
+ */
|
|
|
+ private void FillInStuff ()
|
|
|
+ {
|
|
|
+ int nArgs;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * 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.
|
|
|
+ */
|
|
|
+ if (retType == null) {
|
|
|
+ retType = MakeTypeToken (retStr);
|
|
|
+ }
|
|
|
+ if (argTypes == null) {
|
|
|
+ nArgs = argStrs.Length;
|
|
|
+ argTypes = new TokenType[nArgs];
|
|
|
+ for (int i = 0; i < nArgs; 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();
|
|
|
+
|
|
|
+ nArgs = argTypes.Length;
|
|
|
+ StringBuilder sb = new StringBuilder ();
|
|
|
+ argSysTypes = new Type[nArgs];
|
|
|
+ sb.Append ('(');
|
|
|
+ for (int i = 0; i < nArgs; 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)
|
|
|
+ {
|
|
|
+ TokenDeclSDTypeDelegate decldel;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Name it after the whole signature string.
|
|
|
+ */
|
|
|
+ StringBuilder sb = new StringBuilder ("$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 decldel)) {
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Create the corresponding declaration and link to it
|
|
|
+ */
|
|
|
+ TokenName name = new TokenName (null, inlname);
|
|
|
+ decldel = new TokenDeclSDTypeDelegate (name);
|
|
|
+ decldel.retType = retType;
|
|
|
+ decldel.argTypes = argTypes;
|
|
|
+ inlines.Add (inlname, decldel);
|
|
|
+ inlrevs.Add (decldel.GetSysType (), inlname);
|
|
|
+ }
|
|
|
+ return decldel;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string TryGetInlineName (Type sysType)
|
|
|
+ {
|
|
|
+ string name;
|
|
|
+ if (!inlrevs.TryGetValue (sysType, out name)) return null;
|
|
|
+ return name;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static Type TryGetInlineSysType (string name)
|
|
|
+ {
|
|
|
+ TokenDeclSDTypeDelegate decl;
|
|
|
+ if (!inlines.TryGetValue (name, out decl)) return null;
|
|
|
+ return decl.GetSysType ();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class TokenDeclSDTypeInterface : TokenDeclSDType {
|
|
|
+ public VarDict methsNProps = new VarDict (false);
|
|
|
+ // any class that implements this interface
|
|
|
+ // must implement all of these methods & properties
|
|
|
+
|
|
|
+ public List<TokenDeclSDTypeInterface> implements = new List<TokenDeclSDTypeInterface> ();
|
|
|
+ // 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 (!(this.nextToken is TokenType)) return null;
|
|
|
+ if (this.nextToken.nextToken != this.endToken) {
|
|
|
+ this.nextToken.nextToken.ErrorMsg ("extra tokens for typedef");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return (TokenType)this.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) {
|
|
|
+ decldel = (TokenDeclSDTypeDelegate)decl;
|
|
|
+ TokenType rt = decldel.GetRetType ();
|
|
|
+ TokenType[] ats = decldel.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 = decldel;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ nomatch:;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * No such luck, create a new anonymous declaration.
|
|
|
+ */
|
|
|
+ StringBuilder sb = new StringBuilder ("$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 TokenName (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 VarDict (false);
|
|
|
+
|
|
|
+ public TokenArgDecl (Token original) : base (original) { }
|
|
|
+
|
|
|
+ public bool AddArg (TokenType type, TokenName name)
|
|
|
+ {
|
|
|
+ TokenDeclVar var = new TokenDeclVar (name, null, null);
|
|
|
+ var.name = name;
|
|
|
+ var.type = type;
|
|
|
+ var.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 ()
|
|
|
+ {
|
|
|
+ if (argSig == null) {
|
|
|
+ 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 gbl<Type>s[] array it is stored
|
|
|
+ // instance vars: which slot in inst<Types>s[] array it is stored
|
|
|
+ // static vars: which slot in gbl<Type>s[] 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<string, TokenStmtLabel> labels = new Dictionary<string, TokenStmtLabel> ();
|
|
|
+ // all labels defined in the function
|
|
|
+ public LinkedList<TokenDeclVar> localVars = new LinkedList<TokenDeclVar> ();
|
|
|
+ // 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<TokenRValCall> unknownTrivialityCalls = new LinkedList<TokenRValCall> ();
|
|
|
+ // 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)
|
|
|
+ {
|
|
|
+ if (func != null) {
|
|
|
+ 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: <fulltype>.<name>(<argsig>)
|
|
|
+ * (<argsig>) missing for fields/variables
|
|
|
+ * <fulltype>. missing for top-level functions/variables
|
|
|
+ */
|
|
|
+ public string fullName {
|
|
|
+ get {
|
|
|
+ if (sdtClass == null) {
|
|
|
+ if (retType == null) return name.val;
|
|
|
+ return funcNameSig.val;
|
|
|
+ }
|
|
|
+ string ln = sdtClass.longName.val;
|
|
|
+ if (retType == null) return ln + "." + name.val;
|
|
|
+ return ln + "." + 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 ()
|
|
|
+ {
|
|
|
+ string objCodeName = "";
|
|
|
+ if (sdtClass != null) {
|
|
|
+ objCodeName += sdtClass.longName.val + ".";
|
|
|
+ }
|
|
|
+ objCodeName += funcNameSig.val;
|
|
|
+ return objCodeName;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @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)) return null;
|
|
|
+ return (TokenTypeSDTypeDelegate)_type;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @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 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 == null) || !(retType is 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 TokenKwAssign)) throw new Exception ("bad op type " + rvob.opcode.GetType ().Name);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Get field or variable being assigned to.
|
|
|
+ */
|
|
|
+ TokenDeclVar var = null;
|
|
|
+ TokenRVal left = rvob.rValLeft;
|
|
|
+ if (left is TokenLValIField) {
|
|
|
+ TokenLValIField ifield = (TokenLValIField)left;
|
|
|
+ TokenRValThis zhis = (TokenRValThis)ifield.baseRVal;
|
|
|
+ TokenDeclSDTypeClass sdt = zhis.sdtClass;
|
|
|
+ var = sdt.members.FindExact (ifield.fieldName.val, null);
|
|
|
+ }
|
|
|
+ if (left is TokenLValName) {
|
|
|
+ TokenLValName global = (TokenLValName)left;
|
|
|
+ var = global.stack.FindExact (global.name.val, null);
|
|
|
+ }
|
|
|
+ if (left is TokenLValSField) {
|
|
|
+ TokenLValSField sfield = (TokenLValSField)left;
|
|
|
+ TokenTypeSDTypeClass sdtc = (TokenTypeSDTypeClass)sfield.baseType;
|
|
|
+ TokenDeclSDTypeClass decl = sdtc.decl;
|
|
|
+ var = decl.members.FindExact (sfield.fieldName.val, null);
|
|
|
+ }
|
|
|
+ if (var == 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.
|
|
|
+ */
|
|
|
+ var.DebStringSDTFlags (sb);
|
|
|
+ var.type.DebString (sb);
|
|
|
+ sb.Append (' ');
|
|
|
+ sb.Append (var.name.val);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Maybe it has a non-default initialization value.
|
|
|
+ */
|
|
|
+ if ((var.init != null) && !(var.init is TokenRValInitDef)) {
|
|
|
+ sb.Append (" = ");
|
|
|
+ var.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) && (((TokenTypeSDTypeClass)baseType).decl.arrayOfType != null)) {
|
|
|
+ return ((TokenTypeSDTypeClass)baseType).decl.arrayOfType;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Maybe referencing $idxprop property of script-defined class or interface.
|
|
|
+ */
|
|
|
+ if (baseType is TokenTypeSDTypeClass) {
|
|
|
+ TokenDeclSDTypeClass sdtDecl = ((TokenTypeSDTypeClass)baseType).decl;
|
|
|
+ TokenDeclVar idxProp = scg.FindSingleMember (sdtDecl.members, new TokenName (this, "$idxprop"), null);
|
|
|
+ if (idxProp != null) return idxProp.type;
|
|
|
+ }
|
|
|
+ if (baseType is TokenTypeSDTypeInterface) {
|
|
|
+ TokenDeclSDTypeInterface sdtDecl = ((TokenTypeSDTypeInterface)baseType).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) {
|
|
|
+ TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig);
|
|
|
+ if (var != null) return var.type;
|
|
|
+ }
|
|
|
+ if (baseType is TokenTypeSDTypeInterface) {
|
|
|
+ TokenDeclSDTypeInterface baseIntfDecl = ((TokenTypeSDTypeInterface)baseType).decl;
|
|
|
+ TokenDeclVar var = baseIntfDecl.FindIFaceMember (scg, fieldName, argsig, out baseIntfDecl);
|
|
|
+ if (var != null) return var.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) {
|
|
|
+ TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig);
|
|
|
+ return (var != null) && var.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) {
|
|
|
+ TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig);
|
|
|
+ return (var != null) && var.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) {
|
|
|
+ TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, 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) {
|
|
|
+ TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig);
|
|
|
+ return (var != null) && var.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) {
|
|
|
+ TokenDeclVar var = scg.FindThisMember ((TokenTypeSDTypeClass)baseType, fieldName, argsig);
|
|
|
+ return (var != null) && var.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 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)
|
|
|
+ {
|
|
|
+ argsig = null;
|
|
|
+ if (castTo is TokenTypeSDTypeDelegate) {
|
|
|
+ argsig = ((TokenTypeSDTypeDelegate)castTo).decl.GetArgTypes ();
|
|
|
+ }
|
|
|
+ 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) {
|
|
|
+ try {
|
|
|
+ object val = ((TokenRValConst)rVal).val;
|
|
|
+ object nval = null;
|
|
|
+ if (castTo is TokenTypeChar) {
|
|
|
+ if (val is char) return rVal;
|
|
|
+ if (val is int) nval = (char)(int)val;
|
|
|
+ }
|
|
|
+ if (castTo is TokenTypeFloat) {
|
|
|
+ if (val is double) return rVal;
|
|
|
+ if (val is int) nval = (double)(int)val;
|
|
|
+ if (val is string) nval = new LSL_Float ((string)val).value;
|
|
|
+ }
|
|
|
+ if (castTo is TokenTypeInt) {
|
|
|
+ if (val is int) return rVal;
|
|
|
+ if (val is char) nval = (int)(char)val;
|
|
|
+ if (val is double) nval = (int)(double)val;
|
|
|
+ if (val is string) nval = new LSL_Integer ((string)val).value;
|
|
|
+ }
|
|
|
+ if (castTo is TokenTypeRot) {
|
|
|
+ if (val is LSL_Rotation) return rVal;
|
|
|
+ if (val is string) nval = new LSL_Rotation ((string)val);
|
|
|
+ }
|
|
|
+ if ((castTo is TokenTypeKey) || (castTo is TokenTypeStr)) {
|
|
|
+ if (val is string) nval = val; // in case of key/string conversion
|
|
|
+ if (val is char) nval = TypeCast.CharToString ((char)val);
|
|
|
+ if (val is double) nval = TypeCast.FloatToString ((double)val);
|
|
|
+ if (val is int) nval = TypeCast.IntegerToString ((int)val);
|
|
|
+ if (val is LSL_Rotation) nval = TypeCast.RotationToString ((LSL_Rotation)val);
|
|
|
+ if (val is LSL_Vector) nval = TypeCast.VectorToString ((LSL_Vector)val);
|
|
|
+ }
|
|
|
+ if (castTo is TokenTypeVec) {
|
|
|
+ if (val is LSL_Vector) return rVal;
|
|
|
+ if (val is string) nval = new LSL_Vector ((string)val);
|
|
|
+ }
|
|
|
+ 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:
|
|
|
+ * <condExpr> ? <trueExpr> : <falseExpr>
|
|
|
+ */
|
|
|
+ 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) {
|
|
|
+ didOne = true;
|
|
|
+ bool isTrue = ((TokenRValConst)rValCond).IsConstBoolTrue ();
|
|
|
+ return (isTrue ? 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 = null;
|
|
|
+ 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) ? (TokenType)original : 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) {
|
|
|
+ sb.Append ('\'');
|
|
|
+ EscapeQuotes (sb, new string (new char [] { (char)val }));
|
|
|
+ sb.Append ('\'');
|
|
|
+ } else if (val is int) {
|
|
|
+ sb.Append ((int)val);
|
|
|
+ } else if (val is double) {
|
|
|
+ string str = ((double)val).ToString ();
|
|
|
+ sb.Append (str);
|
|
|
+ if ((str.IndexOf ('.') < 0) &&
|
|
|
+ (str.IndexOf ('E') < 0) &&
|
|
|
+ (str.IndexOf ('e') < 0)) {
|
|
|
+ sb.Append (".0");
|
|
|
+ }
|
|
|
+ } else if (val is string) {
|
|
|
+ sb.Append ('"');
|
|
|
+ EscapeQuotes (sb, (string)val);
|
|
|
+ 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 TokenRValInitDef (tokenDeclVar);
|
|
|
+ zhis.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 ("<default ");
|
|
|
+ sb.Append (type.ToString ());
|
|
|
+ sb.Append ('>');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @brief encapsulation of <rval> is <typeexp>
|
|
|
+ */
|
|
|
+ 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) {
|
|
|
+ if (!((TokenRVal)val).IsRValTrivial (scg, null)) return false;
|
|
|
+ }
|
|
|
+ if (val is TokenList) {
|
|
|
+ if (!ListIsTrivial (scg, (TokenList)val)) 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<Token> tl = new List<Token> ();
|
|
|
+ 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;
|
|
|
+ BinOpStr binOpStr;
|
|
|
+ if (BinOpStr.defined.TryGetValue (key, out 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) && (rValRight is TokenRValConst)) {
|
|
|
+ try {
|
|
|
+ object val = opcode.binOpConst (((TokenRValConst)rValLeft).val,
|
|
|
+ ((TokenRValConst)rValRight).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) {
|
|
|
+ try {
|
|
|
+ object val = opcode.unOpConst (((TokenRValConst)rVal).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 {
|
|
|
+ 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<string, TokenDeclState> states = new Dictionary<string, TokenDeclState> ();
|
|
|
+ public VarDict variablesStack = new VarDict (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<string, TokenDeclSDType> sdSrcTypes = new Dictionary<string, TokenDeclSDType> ();
|
|
|
+ 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<string, TokenDeclSDType>.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 (';');
|
|
|
+ if (testRVal != null) testRVal.DebString (sb);
|
|
|
+ sb.Append (';');
|
|
|
+ if (incrRVal != null) 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 (");
|
|
|
+ if (keyLVal != null) keyLVal.DebString (sb);
|
|
|
+ sb.Append (',');
|
|
|
+ if (valLVal != null) 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<string, IntermediateLeave> iLeaves = new Dictionary<string, IntermediateLeave> ();
|
|
|
+
|
|
|
+ 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) { }
|
|
|
+ }
|
|
|
+}
|