// ======================================================================================= // CLASS INITIALISATION // ======================================================================================= /// <summary> /// A statement should be solely an expression with no return value or whose return value is ignored - eg. a function call. Tokens that /// represent a statement where the return value is used to set another variable's value should be described by a ValueSettingStatement. /// It is recommended to pass any tokens that are thought to be one Statement through the StatementHandler, which will break down the /// content into multiple Statements (if there are any AbstractEndOfStatementToken tokens). /// </summary> public Statement(IEnumerable <IToken> tokens, CallPrefixOptions callPrefix) { if (tokens == null) { throw new ArgumentNullException("tokens"); } if (!Enum.IsDefined(typeof(CallPrefixOptions), callPrefix)) { throw new ArgumentOutOfRangeException("callPrefix"); } Tokens = tokens.ToList().AsReadOnly(); if (!Tokens.Any()) { throw new ArgumentException("Statements must contain at least one token"); } if (!Tokens.Any()) { throw new ArgumentException("Empty tokens specified - invalid"); } if (Tokens.Any(t => t == null)) { throw new ArgumentException("Null token passed into Statement constructor"); } var firstTokenAsAtom = tokens.First() as AtomToken; if ((firstTokenAsAtom != null) && firstTokenAsAtom.Content.Equals("Call", StringComparison.InvariantCultureIgnoreCase)) { throw new ArgumentException("The first token may not be the Call keyword, that must be specified through the CallPrefixOption value where present"); } CallPrefix = callPrefix; }
// ======================================================================================= // STANDARDISE BRACKETS, REMOVE VBScript WOBBLINESS // "Test 1" bad // "Test(1) good // "a.Test 1" bad // "a.Test(1)" good // "a(0).Test 1" bad // "a(0).Test(1)" good // Note: Allow "Test" or "a.Test", if there are no arguments then it's less important // 2014-03-24 DWR: This has been tightened up since "Test a" and "Test(a)" are not the // same to VBScript, the latter actually associates the brackets with the "a" and not // the "Test" call, brackets around an argument to function mean that the argument // should be passed ByVal, not ByRef - if the argument would otherwise be ByRef. // This is why "Test(1, 2)" is not valid (the error "Cannot use parentheses when calling // a Sub" will be raised) since the parentheses are associated with the function call, // which is not acceptable when not considering the return value - eg. "a = Test(1, 2)" // IS acceptable since the return value IS being considered. As such, the following // transformations should be applied: // "Test a1" => "Test(a1)" (argument not forced to be ByVal) // "Test(a1)" => "Test((a1))" (argument forced to be ByVal) // "r = Test a1" is invalid ("Expected end of statement") // "r = Test(a1)" requires no transformation (ByVal not enforced) // "r = Test((a1))" requires no transformation (ByVal IS enforced) // "Test(a1, a2)" is invalid ("Cannot use parentheses when calling a Sub") // "r = Test(a1, a2)" requires no transformation (ByVal not enforced) // "r = Test((a1), a2)" requires no transformation (ByVal enforced for a1 but not a2) // "CALL Test(a1, a2)" requires no transformation (ByVal not enforced) // "CALL Test((a1), a2)" requires no transformation (ByVal enforced for a1 but not a2) // See http://blogs.msdn.com/b/ericlippert/archive/2003/09/15/52996.aspx // (In all of the above examples, the value-setting statements such as "r = Test(a1)" // would not be represented by this class, the ValueSettingStatment class would be used, // but the examples are included to illustrate variations on the cases that must be // dealt with), // ======================================================================================= private static IEnumerable <IToken> GetBracketStandardisedTokens(IEnumerable <IToken> tokens, CallPrefixOptions callPrefix, NameToken withReferenceIfAny) { if (tokens == null) { throw new ArgumentNullException("tokens"); } if (!Enum.IsDefined(typeof(CallPrefixOptions), callPrefix)) { throw new ArgumentOutOfRangeException("callPrefix"); } var tokenArray = tokens.ToArray(); if (tokenArray.Length == 0) { throw new ArgumentException("Empty tokens set specified - invalid for a Statement"); } // TODO: Add a method that will detect all forms of invalid content first and require that this method be called before the Statement is // processed further (dealing with all run time or compile time errors). Such a method would enable the work to be performed under the // assumption that the content was valid VBScript, which would make things easier. (Should this method be in a translator and this data // be returned from a method GetBracketStandardisedTokensIfContentValid?) // No need to try to re-arrange things if this is a new-instance expression, no brackets are required if ((tokenArray[0] is KeyWordToken) && tokenArray[0].Content.Equals("new", StringComparison.InvariantCultureIgnoreCase)) { return(tokenArray); } // If the first token is a member access and we're inside a with block then insert the with block's target reference token into the start // of the token stream. This isn't strictly necessary to standardise the brackets but it makes it easier to do so. if ((tokenArray[0] is MemberAccessorOrDecimalPointToken) && (withReferenceIfAny != null)) { tokenArray = new IToken[] { withReferenceIfAny } }