OpenSimMirror/OpenSim/Region/ScriptEngine/YEngine/MMRScriptReduce.cs

8681 lines
319 KiB
C#

/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @brief Reduce parser tokens to abstract syntax tree tokens.
*
* Usage:
*
* tokenBegin = returned by TokenBegin.Analyze ()
* representing the whole script source
* as a flat list of tokens
*
* TokenScript tokenScript = Reduce.Analyze (TokenBegin tokenBegin);
*
* tokenScript = represents the whole script source
* as a tree of tokens
*/
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
namespace OpenSim.Region.ScriptEngine.Yengine
{
public class ScriptReduce
{
public const uint SDT_PRIVATE = 1;
public const uint SDT_PROTECTED = 2;
public const uint SDT_PUBLIC = 4;
public const uint SDT_ABSTRACT = 8;
public const uint SDT_FINAL = 16;
public const uint SDT_NEW = 32;
public const uint SDT_OVERRIDE = 64;
public const uint SDT_STATIC = 128;
public const uint SDT_VIRTUAL = 256;
private const int ASNPR = 50;
private 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);
/*
* '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) { }
}
}