MegaBites-AI/Windows-powershell
0372
1// Copyright (c) Microsoft Corporation.2// Licensed under the MIT License.3 4using System.Collections;5using System.Collections.Generic;6using System.Collections.ObjectModel;7using System.Diagnostics.Tracing;8using System.Globalization;9using System.IO;10using System.Linq;11using System.Management.Automation.Runspaces;12using System.Management.Automation.Security;13using System.Management.Automation.Subsystem;14using System.Management.Automation.Subsystem.DSC;15using System.Reflection;16using System.Runtime.CompilerServices;17using System.Threading.Tasks;18using Dsc = Microsoft.PowerShell.DesiredStateConfiguration.Internal;19 20namespace System.Management.Automation.Language21{22 using KeyValuePair = Tuple<ExpressionAst, StatementAst>;23 using IfClause = Tuple<PipelineBaseAst, StatementBlockAst>;24 using SwitchClause = Tuple<ExpressionAst, StatementBlockAst>;25 26 internal enum ParseMode27 {28 Default = 0,29 ModuleAnalysis = 130 }31 32 /// <summary>33 /// The parser that parses PowerShell script and returns a <see cref="ScriptBlockAst"/>, tokens, and error messages34 /// if the script cannot be parsed successfully.35 /// </summary>36 public sealed class Parser37 {38 private readonly Tokenizer _tokenizer;39 internal Token _ungotToken;40 private bool _disableCommaOperator;41 private bool _savingTokens;42 private bool _inConfiguration;43 private ParseMode _parseMode;44 45 internal string _fileName;46 47 internal bool ProduceV2Tokens { get; set; }48 49 internal const string VERBATIM_ARGUMENT = "--%";50 internal const string VERBATIM_PARAMETERNAME = "-%"; // Same as VERBATIM_ARGUMENT w/o the first '-'.51 52 internal Parser()53 {54 _tokenizer = new Tokenizer(this);55 ErrorList = new List<ParseError>();56 _fileName = null;57 }58 59 /// <summary>60 /// Parse input from the specified file.61 /// </summary>62 /// <param name="fileName">The name of the file to parse.</param>63 /// <param name="tokens">Returns the tokens from parsing the script.</param>64 /// <param name="errors">Returns errors, if any, discovered while parsing the script.</param>65 /// <returns>The <see cref="ScriptBlockAst"/> that represents the input script file.</returns>66 public static ScriptBlockAst ParseFile(string fileName, out Token[] tokens, out ParseError[] errors)67 {68 const string scriptSchemaExtension = ".schema.psm1";69 var parseDscResource = false;70 // If the file has the 'schema.psm1' extension, then it is a 'DSC module file' however we don't actually load the71 // module at parse time so we can't use the normal mechanisms to bind the module name for configuration commands.72 // As an alternative, we extract the base name of the module file and use that as the module name for any keywords exported by this file.73 var parser = new Parser();74 if (!string.IsNullOrEmpty(fileName) && fileName.Length > scriptSchemaExtension.Length && fileName.EndsWith(scriptSchemaExtension, StringComparison.OrdinalIgnoreCase))75 {76 parser._keywordModuleName = Path.GetFileName(fileName.AsSpan(0, fileName.Length - scriptSchemaExtension.Length)).ToString();77 parseDscResource = true;78 }79 80 string scriptContents;81 try82 {83 var esi = new ExternalScriptInfo(fileName, fileName);84 scriptContents = esi.ScriptContents;85 }86 catch (Exception e)87 {88 var emptyExtent = new EmptyScriptExtent();89 var errorMsg = string.Format(CultureInfo.CurrentCulture, ParserStrings.FileReadError, e.Message);90 errors = new[] { new ParseError(emptyExtent, "FileReadError", errorMsg) };91 tokens = Array.Empty<Token>();92 return new ScriptBlockAst(emptyExtent, null, new StatementBlockAst(emptyExtent, null, null), false);93 }94 95 var tokenList = new List<Token>();96 ScriptBlockAst result;97 try98 {99 if (!parseDscResource)100 {101 DynamicKeyword.Push();102 }103 104 result = parser.Parse(fileName, scriptContents, tokenList, out errors, ParseMode.Default);105 }106 catch (Exception e)107 {108 throw new ParseException(ParserStrings.UnrecoverableParserError, e);109 }110 finally111 {112 if (!parseDscResource)113 {114 DynamicKeyword.Pop();115 }116 }117 118 tokens = tokenList.ToArray();119 return result;120 }121 122 private string _keywordModuleName;123 124 /// <summary>125 /// Parse input that does not come from a file.126 /// </summary>127 /// <param name="input">The input to parse.</param>128 /// <param name="tokens">Returns the tokens from parsing the script.</param>129 /// <param name="errors">Returns errors, if any, discovered while parsing the script.</param>130 /// <returns>The <see cref="ScriptBlockAst"/> that represents the input script file.</returns>131 public static ScriptBlockAst ParseInput(string input, out Token[] tokens, out ParseError[] errors)132 {133 return ParseInput(input, null /* fileName */, out tokens, out errors);134 }135 136 /// <summary>137 /// Parse input that does not come from a file.138 /// </summary>139 /// <param name="input">The input to parse.</param>140 /// <param name="fileName">The fileName if present or null.</param>141 /// <param name="tokens">Returns the tokens from parsing the script.</param>142 /// <param name="errors">Returns errors, if any, discovered while parsing the script.</param>143 /// <returns>The <see cref="ScriptBlockAst"/> that represents the input script file.</returns>144 public static ScriptBlockAst ParseInput(string input, string fileName, out Token[] tokens, out ParseError[] errors)145 {146 ArgumentNullException.ThrowIfNull(input);147 148 Parser parser = new Parser();149 List<Token> tokenList = new List<Token>();150 ScriptBlockAst result;151 try152 {153 result = parser.Parse(fileName, input, tokenList, out errors, ParseMode.Default);154 }155 catch (Exception e)156 {157 throw new ParseException(ParserStrings.UnrecoverableParserError, e);158 }159 160 tokens = tokenList.ToArray();161 return result;162 }163 164 internal ScriptBlockAst Parse(string fileName, string input, List<Token> tokenList, out ParseError[] errors, ParseMode parseMode)165 {166 try167 {168 return ParseTask(fileName, input, tokenList, false, parseMode);169 }170 finally171 {172 errors = ErrorList.ToArray();173 }174 }175 176 private ScriptBlockAst ParseTask(string fileName, string input, List<Token> tokenList, bool recursed, ParseMode parseMode)177 {178 bool etwEnabled = ParserEventSource.Log.IsEnabled();179 if (etwEnabled) ParserEventSource.Log.ParseStart(ParserEventSource.GetFileOrScript(fileName, input), input.Length);180 181 ScriptBlockAst ast = null;182 183 _parseMode = parseMode;184 _fileName = fileName;185 _tokenizer.Initialize(fileName, input, tokenList);186 _savingTokens = (tokenList != null);187 ErrorList.Clear();188 189 try190 {191 ast = ScriptBlockRule(null, isFilter: false);192#if DEBUG193 ast.InternalVisit(new CheckAllParentsSet(ast));194#endif195 ast.ScriptRequirements = _tokenizer.GetScriptRequirements();196 197 if (parseMode == ParseMode.Default)198 {199 ast.PerformPostParseChecks(this);200 }201#if DEBUG202 ast.InternalVisit(new CheckTypeBuilder());203#endif204 }205 catch (InsufficientExecutionStackException)206 {207 if (!recursed)208 {209 // We'll try parsing once more, this time on a new thread. The assumption here is210 // that the stack was close to overflowing before we tried to parse, and that won't211 // be a problem on a new thread.212 var task = new Task<ScriptBlockAst>(() => ParseTask(fileName, input, tokenList, true, parseMode));213 task.Start();214 task.Wait();215 ast = task.Result;216 }217 else218 {219 ReportError(_tokenizer.CurrentExtent(),220 nameof(ParserStrings.ScriptTooComplicated),221 ParserStrings.ScriptTooComplicated);222 }223 }224 225 if (etwEnabled) ParserEventSource.Log.ParseStop();226 return ast;227 }228 229 // This helper routine is used from the runtime to convert a string to a number.230 internal static object ScanNumber(string str, Type toType, bool shouldTryCoercion = true)231 {232 str = str.Trim();233 if (str.Length == 0)234 {235 // For backwards compatibility, we treat the empty string (w/ or w/o whitespace) as the236 // integer constant 0. This is a slight change in semantics, the empty string was previously237 // only considered a number if we committed to a numeric operation, e.g.:238 // 5 - "" - this results in 5239 // Returning 0 here means that240 // "" - "" - will result in 0, previously was an error.241 // This is mostly of interest for the operators -, /, %, -band, -bor, -bxor as these operators242 // allow strings as the lval and still do a numeric operation, whereas + and * treat string243 // lvals differently.244 return 0;245 }246 247 var tokenizer = (new Parser())._tokenizer;248 tokenizer.Initialize(null, str, null);249 tokenizer.AllowSignedNumbers = true;250 var token = tokenizer.NextToken() as NumberToken;251 252 if (token == null || !tokenizer.IsAtEndOfScript(token.Extent))253 {254 if (shouldTryCoercion)255 {256 // We call ConvertTo, primarily because we expect it will throw an exception,257 // but it's possible it could succeed, e.g. if the string had commas, our lexer258 // will fail, but Convert.ChangeType could succeed.259 return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture);260 }261 else262 {263 throw new ParseException();264 }265 }266 267 return token.Value;268 }269 270 internal static ITypeName ScanType(string typename, bool ignoreErrors)271 {272 typename = typename.Trim();273 if (typename.Length == 0)274 {275 return null;276 }277 278 var parser = new Parser();279 var tokenizer = parser._tokenizer;280 tokenizer.Initialize(null, typename, null);281 var result = parser.TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out _);282 283 SemanticChecks.CheckArrayTypeNameDepth(result, PositionUtilities.EmptyExtent, parser);284 if (!ignoreErrors && result is not null && (parser.ErrorList.Count > 0 || !result.Extent.Text.Equals(typename, StringComparison.OrdinalIgnoreCase)))285 {286 result = null;287 }288 289 return result;290 }291 292 internal static ExpressionAst ScanString(string str)293 {294 str = str.Replace("\"", "\"\"");295 var parser = new Parser();296 parser._tokenizer.Initialize(null, '"' + str + '"', null);297 var strToken = (StringExpandableToken)parser._tokenizer.NextToken();298 var ast = parser.ExpandableStringRule(strToken);299 if (parser.ErrorList.Count > 0)300 {301 throw new ParseException(parser.ErrorList.ToArray());302 }303 304 return ast;305 }306 307 private string _previousFirstTokenText;308 private string _previousLastTokenText;309 310 private static bool IgnoreTokenWhenUpdatingPreviousFirstLast(Token token)311 {312 return (token.Kind == TokenKind.Variable || token.Kind == TokenKind.Generic) &&313 (token.Text.Equals("$^", StringComparison.OrdinalIgnoreCase) ||314 token.Text.Equals("$$", StringComparison.OrdinalIgnoreCase));315 }316 317 internal void SetPreviousFirstLastToken(ExecutionContext context)318 {319 var firstToken = _tokenizer.FirstToken;320 if (firstToken != null)321 {322 context.SetVariable(SpecialVariables.FirstTokenVarPath, _previousFirstTokenText);323 if (!IgnoreTokenWhenUpdatingPreviousFirstLast(firstToken))324 {325 var stringToken = firstToken as StringToken;326 _previousFirstTokenText = stringToken != null327 ? stringToken.Value328 : firstToken.Text;329 }330 331 context.SetVariable(SpecialVariables.LastTokenVarPath, _previousLastTokenText);332 333 var lastToken = _tokenizer.LastToken;334 if (!IgnoreTokenWhenUpdatingPreviousFirstLast(lastToken))335 {336 var stringToken = lastToken as StringToken;337 _previousLastTokenText = stringToken != null338 ? stringToken.Value339 : lastToken.Text;340 }341 }342 }343 344 internal List<ParseError> ErrorList { get; }345 346 #region Utilities347 348 private void SkipNewlines()349 {350 if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine)351 {352 _ungotToken = null;353 _tokenizer.SkipNewlines(skipSemis: false);354 }355 }356 357 private void SkipNewlinesAndSemicolons()358 {359 if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine || _ungotToken.Kind == TokenKind.Semi)360 {361 _ungotToken = null;362 _tokenizer.SkipNewlines(skipSemis: true);363 }364 }365 366 private void SyncOnError(bool consumeClosingToken, params TokenKind[] syncTokens)367 {368 int parens = syncTokens.Contains(TokenKind.RParen) ? 1 : 0;369 int curlies = syncTokens.Contains(TokenKind.RCurly) ? 1 : 0;370 int braces = syncTokens.Contains(TokenKind.RBracket) ? 1 : 0;371 while (true)372 {373 Token token = NextToken();374 switch (token.Kind)375 {376 case TokenKind.LParen: ++parens; break;377 case TokenKind.RParen:378 --parens;379 if (parens == 0 && syncTokens.Contains(TokenKind.RParen))380 {381 if (!consumeClosingToken)382 {383 UngetToken(token);384 }385 386 return;387 }388 389 break;390 391 case TokenKind.LCurly: ++curlies; break;392 case TokenKind.RCurly:393 --curlies;394 if (curlies == 0 && syncTokens.Contains(TokenKind.RCurly))395 {396 if (!consumeClosingToken)397 {398 UngetToken(token);399 }400 401 return;402 }403 404 break;405 406 case TokenKind.LBracket: ++braces; break;407 case TokenKind.RBracket:408 --braces;409 if (braces == 0 && syncTokens.Contains(TokenKind.RBracket))410 {411 if (!consumeClosingToken)412 {413 UngetToken(token);414 }415 416 return;417 }418 419 break;420 421 case TokenKind.EndOfInput:422 // Never consume <EOF>, but return it to caller423 UngetToken(token);424 return;425 }426 427 if (syncTokens.Contains(token.Kind) && parens == 0 && curlies == 0 && braces == 0)428 {429 break;430 }431 }432 }433 434 private Token NextToken()435 {436 Token token = _ungotToken ?? _tokenizer.NextToken();437 _ungotToken = null;438 return token;439 }440 441 private Token PeekToken()442 {443 Token token = _ungotToken ?? _tokenizer.NextToken();444 _ungotToken ??= token;445 return token;446 }447 448 private Token NextMemberAccessToken(bool allowLBracket)449 {450 // If _ungotToken is not null, we're in some sort of error state, don't return the token.451 if (_ungotToken != null)452 return null;453 return _tokenizer.GetMemberAccessOperator(allowLBracket);454 }455 456 private Token NextInvokeMemberToken()457 {458 // If _ungotToken is not null, we're in some sort of error state, don't return the token.459 if (_ungotToken != null)460 return null;461 return _tokenizer.GetInvokeMemberOpenParen();462 }463 464 private Token NextLBracket()465 {466 if (_ungotToken != null)467 {468 if (_ungotToken.Kind == TokenKind.LBracket) return NextToken();469 // If _ungotToken is not null, we're in some sort of error state, don't return the token.470 return null;471 }472 473 return _tokenizer.GetLBracket();474 }475 476 private StringToken GetVerbatimCommandArgumentToken()477 {478 if (_ungotToken == null || _ungotToken.Kind == TokenKind.Parameter)479 {480 _ungotToken = null;481 return _tokenizer.GetVerbatimCommandArgument();482 }483 484 return null;485 }486 487 private void SkipToken()488 {489 Diagnostics.Assert(_ungotToken != null, "Don't skip a token you didn't unget");490 _ungotToken = null;491 }492 493 private void UngetToken(Token token)494 {495 Diagnostics.Assert(_ungotToken == null, "Only 1 token lookahead is supported");496 _ungotToken = token;497 }498 499 private void SetTokenizerMode(TokenizerMode mode)500 {501 if (mode != _tokenizer.Mode && _ungotToken != null)502 {503 // Only rescan tokens that differ b/w command and expression modes.504 if (!_ungotToken.Kind.HasTrait(TokenFlags.ParseModeInvariant))505 {506 Resync(_ungotToken);507 }508#if DEBUG509 else if (_ungotToken.Kind != TokenKind.EndOfInput)510 {511 // Verify the comment above.512 Token ungotToken = _ungotToken;513 var oldTokenList = _tokenizer.TokenList;514 _tokenizer.TokenList = null;515 Resync(_ungotToken);516 _tokenizer.Mode = mode;517 Token rescan = _tokenizer.NextToken();518 Diagnostics.Assert(ungotToken.Kind == rescan.Kind, "Rescan failed to return same kind");519 Diagnostics.Assert(ungotToken.Text == rescan.Text, "Rescan failed to return same text");520 IScriptPosition pos1 = ungotToken.Extent.StartScriptPosition;521 IScriptPosition pos2 = rescan.Extent.StartScriptPosition;522 Diagnostics.Assert(pos1.ColumnNumber == pos2.ColumnNumber, "Rescan failed to return same start column");523 Diagnostics.Assert(pos1.LineNumber == pos2.LineNumber, "Rescan failed to return same start line#");524 pos1 = ungotToken.Extent.EndScriptPosition;525 pos2 = rescan.Extent.EndScriptPosition;526 Diagnostics.Assert(pos1.ColumnNumber == pos2.ColumnNumber, "Rescan failed to return same end column");527 Diagnostics.Assert(pos1.LineNumber == pos2.LineNumber, "Rescan failed to return same end line#");528 // Make sure we leave things as they were - Resync clears _ungotToken.529 _ungotToken = ungotToken;530 _tokenizer.TokenList = oldTokenList;531 }532#endif533 }534 535 _tokenizer.Mode = mode;536 }537 538 private void Resync(Token token)539 {540 _ungotToken = null;541 _tokenizer.Resync(token);542 }543 544 private void Resync(int restorePoint)545 {546 _ungotToken = null;547 _tokenizer.Resync(restorePoint);548 }549 550 private static bool IsSpecificParameter(Token token, string parameter)551 {552 Diagnostics.Assert(token.Kind == TokenKind.Parameter, "Token must be a ParameterToken");553 var paramToken = (ParameterToken)token;554 return parameter.StartsWith(paramToken.ParameterName, StringComparison.OrdinalIgnoreCase);555 }556 557 internal void RequireStatementTerminator()558 {559 var terminatorToken = PeekToken();560 if (terminatorToken.Kind == TokenKind.NewLine || terminatorToken.Kind == TokenKind.Semi)561 {562 SkipToken();563 }564 else if (terminatorToken.Kind != TokenKind.EndOfInput)565 {566 ReportIncompleteInput(terminatorToken.Extent,567 nameof(ParserStrings.MissingStatementTerminator),568 ParserStrings.MissingStatementTerminator);569 }570 }571 572 internal static IScriptExtent ExtentOf(IScriptExtent first, IScriptExtent last)573 {574 if (first is EmptyScriptExtent) return last;575 if (last is EmptyScriptExtent) return first;576 577 Diagnostics.Assert(first is InternalScriptExtent && last is InternalScriptExtent,578 "Private method expects internal position representation");579 InternalScriptExtent l = (InternalScriptExtent)first;580 InternalScriptExtent r = (InternalScriptExtent)last;581 Diagnostics.Assert(l.PositionHelper == r.PositionHelper, "Can't get the extent across files");582 return new InternalScriptExtent(l.PositionHelper, l.StartOffset, r.EndOffset);583 }584 585 internal static IScriptExtent Before(IScriptExtent extent)586 {587 Diagnostics.Assert(extent is InternalScriptExtent, "Private method expects internal position representation");588 InternalScriptExtent scriptExtent = (InternalScriptExtent)extent;589 int offset = scriptExtent.StartOffset - 1;590 if (offset < 0) offset = 0;591 return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset);592 }593 594 internal static IScriptExtent After(IScriptExtent extent)595 {596 Diagnostics.Assert(extent is InternalScriptExtent, "Private method expects internal position representation");597 InternalScriptExtent scriptExtent = (InternalScriptExtent)extent;598 int offset = scriptExtent.EndOffset;599 return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset);600 }601 602 internal static IScriptExtent LastCharacterOf(IScriptExtent extent)603 {604 Diagnostics.Assert(extent is InternalScriptExtent, "Private method expects internal position representation");605 InternalScriptExtent scriptExtent = (InternalScriptExtent)extent;606 int offset = scriptExtent.EndOffset - 1;607 if (offset < 0)608 {609 offset = 0;610 }611 612 return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset);613 }614 615 internal static IScriptExtent ExtentFromFirstOf(params object[] objs)616 {617 foreach (object obj in objs)618 {619 if (obj != null)620 {621 var token = obj as Token;622 if (token != null)623 {624 return token.Extent;625 }626 627 var ast = obj as Ast;628 if (ast != null)629 {630 return ast.Extent;631 }632 633 var typename = obj as ITypeName;634 if (typename != null)635 {636 return typename.Extent;637 }638 639 Diagnostics.Assert(obj is IScriptExtent, "Only accepts tokens, asts, and IScriptExtents");640 return (IScriptExtent)obj;641 }642 }643 644 Diagnostics.Assert(false, "One of the objects must not be null");645 return PositionUtilities.EmptyExtent;646 }647 648 internal static IScriptExtent ExtentOf(Token first, Token last) { return ExtentOf(first.Extent, last.Extent); }649 650 internal static IScriptExtent ExtentOf(Ast first, Ast last) { return ExtentOf(first.Extent, last.Extent); }651 652 internal static IScriptExtent ExtentOf(Ast first, Token last) { return ExtentOf(first.Extent, last.Extent); }653 654 internal static IScriptExtent ExtentOf(Token first, Ast last) { return ExtentOf(first.Extent, last.Extent); }655 656 internal static IScriptExtent ExtentOf(IScriptExtent first, Ast last) { return ExtentOf(first, last.Extent); }657 658 internal static IScriptExtent ExtentOf(IScriptExtent first, Token last) { return ExtentOf(first, last.Extent); }659 660 internal static IScriptExtent ExtentOf(Ast first, IScriptExtent last) { return ExtentOf(first.Extent, last); }661 662 internal static IScriptExtent ExtentOf(Token first, IScriptExtent last) { return ExtentOf(first.Extent, last); }663 // private static IScriptExtent Before(Ast ast) { return Before(ast.Extent); }664 665 internal static IScriptExtent Before(Token token) { return Before(token.Extent); }666 667 internal static IScriptExtent After(Ast ast) { return After(ast.Extent); }668 669 internal static IScriptExtent After(Token token) { return After(token.Extent); }670 671 private static IEnumerable<Ast> GetNestedErrorAsts(params object[] asts)672 {673 foreach (var obj in asts)674 {675 if (obj != null)676 {677 Ast ast = obj as Ast;678 if (ast != null)679 {680 yield return ast;681 }682 else683 {684 var enumerable = obj as IEnumerable<Ast>;685 if (enumerable != null)686 {687 foreach (var ast2 in enumerable)688 {689 if (ast2 != null)690 {691 yield return ast2;692 }693 }694 }695 else696 {697 Diagnostics.Assert(false, "Caller to pass only asts, or IEnumerable<Ast>");698 }699 }700 }701 }702 }703 704 /// <summary>705 /// Parses the specified constant hashtable string into a Hashtable object.706 /// </summary>707 /// <param name="input">The Hashtable string.</param>708 /// <param name="result">The Hashtable object.</param>709 /// <returns></returns>710 internal static bool TryParseAsConstantHashtable(string input, out Hashtable result)711 {712 result = null;713 714 if (string.IsNullOrWhiteSpace(input))715 {716 return false;717 }718 719 Token[] throwAwayTokens;720 ParseError[] parseErrors;721 var ast = Parser.ParseInput(input, out throwAwayTokens, out parseErrors);722 723 if (ast == null724 || parseErrors.Length > 0725 || ast.BeginBlock != null726 || ast.ProcessBlock != null727 || ast.CleanBlock != null728 || ast.DynamicParamBlock != null729 || ast.EndBlock.Traps != null)730 {731 return false;732 }733 734 var statements = ast.EndBlock.Statements;735 if (statements.Count != 1)736 {737 return false;738 }739 740 if (!(statements[0] is PipelineAst pipelineAst))741 {742 return false;743 }744 745 var expr = pipelineAst.GetPureExpression();746 if (expr == null)747 {748 return false;749 }750 751 if (!(expr is HashtableAst hashTableAst))752 {753 return false;754 }755 756 object hashtable;757 if (!IsConstantValueVisitor.IsConstant(hashTableAst, out hashtable, forRequires: true))758 {759 return false;760 }761 762 var data = hashtable as Hashtable;763 Diagnostics.Assert((data != null), "IsConstantValueVisitor.IsConstant() should return false when the specified HashtableAst is not a avalid Hashtable");764 result = data;765 return true;766 }767 768 #endregion Utilities769 770 #region Statements771 772 private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter)773 {774 return ScriptBlockRule(lCurly, isFilter, null);775 }776 777 private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter, StatementAst predefinedStatementAst)778 {779 // G script-block:780 // G using-statements:opt param-block:opt statement-terminators:opt script-block-body:opt781 // G782 // G using-statements:783 // G using-statement784 // G using-statements using-statement785 786 // We could set the mode here, but we can avoid rescanning keywords if the caller787 // sets the mode before skipping newlines.788 Diagnostics.Assert(_tokenizer.Mode == TokenizerMode.Command,789 "Caller to make sure the mode is correct.");790 791 // Skipping newlines here saves a more expensive resync if there is no parameter block.792 SkipNewlines();793 794 var usingStatements = lCurly == null ? UsingStatementsRule() : null;795 796 var restorePoint = _tokenizer.GetRestorePoint();797 ParamBlockAst paramBlock = ParamBlockRule();798 if (paramBlock == null)799 {800 // In case we scanned some attributes or type constraints but they didn't801 // belong to a param statement, we need to reparse them (because they will802 // mean something different, such as a type literal expression, or a cast.)803 Resync(restorePoint);804 }805 806 SkipNewlinesAndSemicolons();807 808 return ScriptBlockBodyRule(lCurly, usingStatements, paramBlock, isFilter, predefinedStatementAst);809 }810 811 private List<UsingStatementAst> UsingStatementsRule()812 {813 List<UsingStatementAst> result = null;814 815 while (true)816 {817 var token = PeekToken();818 if (token.Kind == TokenKind.Using)819 {820 SkipToken();821 var statement = UsingStatementRule(token);822 SkipNewlinesAndSemicolons();823 result ??= new List<UsingStatementAst>();824 825 var usingStatement = statement as UsingStatementAst;826 // otherwise returned statement is ErrorStatementAst.827 // We ignore it here, because error already reported to the parser.828 if (usingStatement != null)829 {830 result.Add(usingStatement);831 }832 833 continue;834 }835 836 // Normally we don't need to resync, but our caller may speculatively scan much more than837 // one token, so it needs the restore point to include this token.838 Resync(token);839 break;840 }841 842 return result;843 }844 845 private ParamBlockAst ParamBlockRule()846 {847 // G param-block:848 // G new-lines:opt attribute-list:opt new-lines:opt 'param' new-lines:opt849 // G '(' parameter-list:opt new-lines:opt ')'850 851 SkipNewlines();852 List<AttributeBaseAst> candidateAttributes = AttributeListRule(false);853 854 SkipNewlines();855 Token paramToken = PeekToken();856 if (paramToken.Kind != TokenKind.Param)857 {858 return null;859 }860 861 SkipToken();862 SkipNewlines();863 Token lparen = NextToken();864 if (lparen.Kind != TokenKind.LParen)865 {866 UngetToken(lparen);867 868 // This is not an error, we'll end up trying to invoke a command named 'param'.869 return null;870 }871 872 List<ParameterAst> parameters = ParameterListRule();873 874 SkipNewlines();875 Token rParen = NextToken();876 var endExtent = rParen.Extent;877 if (rParen.Kind != TokenKind.RParen)878 {879 // ErrorRecovery: assume we saw the closing paren and continue like normal.880 881 UngetToken(rParen);882 endExtent = Before(rParen);883 ReportIncompleteInput(After(parameters != null && parameters.Count > 0 ? parameters.Last().Extent : lparen.Extent),884 nameof(ParserStrings.MissingEndParenthesisInFunctionParameterList),885 ParserStrings.MissingEndParenthesisInFunctionParameterList);886 }887 888 List<AttributeAst> attributes = new List<AttributeAst>();889 if (candidateAttributes != null)890 {891 foreach (AttributeBaseAst attr in candidateAttributes)892 {893 AttributeAst attribute = attr as AttributeAst;894 if (attribute != null)895 {896 attributes.Add(attribute);897 }898 else899 {900 // ErrorRecovery: nothing to do, this is a semantic error that is caught in the parser901 // because the ast only allows attributes, no type constraints.902 903 ReportError(attr.Extent,904 nameof(ParserStrings.TypeNotAllowedBeforeParam),905 ParserStrings.TypeNotAllowedBeforeParam,906 attr.TypeName.FullName);907 }908 }909 }910 911 return new ParamBlockAst(ExtentOf(paramToken, endExtent), attributes, parameters);912 }913 914 private List<ParameterAst> ParameterListRule()915 {916 // G parameter-list:917 // G script-parameter918 // G parameter-list new-lines:opt ',' script-parameter919 920 List<ParameterAst> parameters = new List<ParameterAst>();921 Token commaToken = null;922 while (true)923 {924 ParameterAst parameter = ParameterRule();925 if (parameter == null)926 {927 if (commaToken != null)928 {929 // ErrorRecovery: ??930 931 ReportIncompleteInput(After(commaToken),932 nameof(ParserStrings.MissingExpressionAfterToken),933 ParserStrings.MissingExpressionAfterToken,934 commaToken.Kind.Text());935 }936 937 break;938 }939 940 parameters.Add(parameter);941 SkipNewlines();942 commaToken = PeekToken();943 if (commaToken.Kind != TokenKind.Comma)944 {945 break;946 }947 948 SkipToken();949 }950 951 return parameters;952 }953 954 private ParameterAst ParameterRule()955 {956 // G script-parameter:957 // G new-lines:opt attribute-list:opt new-lines:opt variable script-parameter-default:opt958 // G script-parameter-default:959 // G new-lines:opt '=' new-lines:opt expression960 961 List<AttributeBaseAst> attributes;962 VariableToken variableToken;963 ExpressionAst defaultValue = null;964 965 bool oldDisableCommaOperator = _disableCommaOperator;966 var oldTokenizerMode = _tokenizer.Mode;967 try968 {969 _disableCommaOperator = true;970 SetTokenizerMode(TokenizerMode.Expression);971 972 SkipNewlines();973 attributes = AttributeListRule(false);974 SkipNewlines();975 Token token = NextToken();976 if (token.Kind != TokenKind.Variable && token.Kind != TokenKind.SplattedVariable)977 {978 UngetToken(token);979 if (attributes != null)980 {981 // ErrorRecovery: skip to closing paren because returning null signals the last parameter.982 983 ReportIncompleteInput(After(attributes.Last()),984 nameof(ParserStrings.InvalidFunctionParameter),985 ParserStrings.InvalidFunctionParameter);986 SyncOnError(true, TokenKind.RParen);987 988 // Even though we don't have a complete parameter, we do have attributes. Intellisense989 // might want to complete something in the attributes, so we need to return something useful.990 var extent = ExtentOf(attributes[0].Extent, attributes[attributes.Count - 1].Extent);991 return new ParameterAst(extent, new VariableExpressionAst(extent, "__error__", false), attributes, null);992 }993 994 return null;995 }996 997 variableToken = ((VariableToken)token);998 999 SkipNewlines();1000 Token equalsToken = PeekToken();1001 if (equalsToken.Kind == TokenKind.Equals)1002 {1003 SkipToken();1004 SkipNewlines();1005 defaultValue = ExpressionRule();1006 if (defaultValue == null)1007 {1008 ReportIncompleteInput(After(equalsToken),1009 nameof(ParserStrings.MissingExpressionAfterToken),1010 ParserStrings.MissingExpressionAfterToken,1011 equalsToken.Kind.Text());1012 }1013 }1014 }1015 finally1016 {1017 _disableCommaOperator = oldDisableCommaOperator;1018 SetTokenizerMode(oldTokenizerMode);1019 }1020 1021 IScriptExtent startExtent = (attributes == null) ? variableToken.Extent : attributes[0].Extent;1022 IScriptExtent endExtent = (defaultValue == null) ? variableToken.Extent : defaultValue.Extent;1023 return new ParameterAst(ExtentOf(startExtent, endExtent),1024 new VariableExpressionAst(variableToken), attributes, defaultValue);1025 }1026 1027 private List<AttributeBaseAst> AttributeListRule(bool inExpressionMode)1028 {1029 // G attribute-list:1030 // G attribute1031 // G attribute-list attribute1032 1033 List<AttributeBaseAst> attributes = new List<AttributeBaseAst>();1034 AttributeBaseAst attribute = AttributeRule();1035 while (attribute != null)1036 {1037 attributes.Add(attribute);1038 if (!inExpressionMode || attribute is AttributeAst)1039 {1040 SkipNewlines();1041 }1042 1043 attribute = AttributeRule();1044 }1045 1046 if (attributes.Count == 0)1047 {1048 return null;1049 }1050 1051 return attributes;1052 }1053 1054 private AttributeBaseAst AttributeRule()1055 {1056 // G attribute:1057 // G '[' attribute-name '(' attribute-arguments ')' ']'1058 // G attribute-name:1059 // G type-spec1060 1061 var lBracket = NextLBracket();1062 if (lBracket == null)1063 {1064 return null;1065 }1066 1067 SkipNewlines();1068 1069 Token firstTypeNameToken;1070 ITypeName typeName = TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out firstTypeNameToken);1071 if (typeName == null)1072 {1073 // ErrorRecovery: Return null so we stop looking for attributes.1074 1075 Resync(lBracket); // TypeNameRule might have consumed some tokens1076 ReportIncompleteInput(After(lBracket),1077 nameof(ParserStrings.MissingTypename),1078 ParserStrings.MissingTypename);1079 return null;1080 }1081 1082 Token lParenOrRBracket = NextToken();1083 if (lParenOrRBracket.Kind == TokenKind.LParen)1084 {1085 SkipNewlines();1086 1087 List<ExpressionAst> positionalArguments = new List<ExpressionAst>();1088 List<NamedAttributeArgumentAst> namedArguments = new List<NamedAttributeArgumentAst>();1089 1090 IScriptExtent lastItemExtent = lParenOrRBracket.Extent;1091 var oldTokenizerMode = _tokenizer.Mode;1092 try1093 {1094 SetTokenizerMode(TokenizerMode.Expression);1095 AttributeArgumentsRule(positionalArguments, namedArguments, ref lastItemExtent);1096 }1097 finally1098 {1099 SetTokenizerMode(oldTokenizerMode);1100 }1101 1102 SkipNewlines();1103 Token rParen = NextToken();1104 if (rParen.Kind != TokenKind.RParen)1105 {1106 // ErrorRecovery: pretend we saw a ')', attempt to find an ']'.1107 1108 UngetToken(rParen);1109 rParen = null;1110 ReportIncompleteInput(After(lastItemExtent),1111 nameof(ParserStrings.MissingEndParenthesisInExpression),1112 ParserStrings.MissingEndParenthesisInExpression);1113 }1114 1115 SkipNewlines();1116 Token rBracket = NextToken();1117 if (rBracket.Kind != TokenKind.RBracket)1118 {1119 // ErrorRecovery: pretend we saw a ']', return our result.1120 1121 UngetToken(rBracket);1122 rBracket = null;1123 // Don't bother reporting a missing ']' if we reported a missing ')'.1124 if (rParen != null)1125 {1126 ReportIncompleteInput(After(rParen),1127 nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),1128 ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);1129 }1130 }1131 1132 firstTypeNameToken.TokenFlags |= TokenFlags.AttributeName;1133 return new AttributeAst(ExtentOf(lBracket, ExtentFromFirstOf(rBracket, rParen, lastItemExtent)), typeName, positionalArguments, namedArguments);1134 }1135 1136 if (ProduceV2Tokens)1137 {1138 var typeToken = new Token((InternalScriptExtent)ExtentOf(lBracket, lParenOrRBracket),1139 TokenKind.Identifier, TokenFlags.TypeName);1140 _tokenizer.ReplaceSavedTokens(lBracket, lParenOrRBracket, typeToken);1141 }1142 1143 if (lParenOrRBracket.Kind != TokenKind.RBracket)1144 {1145 UngetToken(lParenOrRBracket);1146 ReportError(Before(lParenOrRBracket),1147 nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),1148 ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);1149 lParenOrRBracket = null;1150 }1151 1152 return new TypeConstraintAst(ExtentOf(lBracket, ExtentFromFirstOf(lParenOrRBracket, typeName.Extent)), typeName);1153 }1154 1155 private void AttributeArgumentsRule(ICollection<ExpressionAst> positionalArguments,1156 ICollection<NamedAttributeArgumentAst> namedArguments,1157 ref IScriptExtent lastItemExtent)1158 {1159 // G attribute-arguments:1160 // G attribute-argument1161 // G attribute-argument new-lines:opt ',' attribute-arguments1162 // G attribute-argument:1163 // G new-lines:opt expression1164 // G new-lines:opt property-name '=' new-lines:opt expression1165 1166 bool oldDisableCommaOperator = _disableCommaOperator;1167 Token commaToken = null;1168 HashSet<string> keysSeen = new HashSet<string>();1169 try1170 {1171 _disableCommaOperator = true;1172 while (true)1173 {1174 SkipNewlines();1175 1176 StringConstantExpressionAst name = SimpleNameRule();1177 ExpressionAst expr;1178 bool expressionOmitted = false;1179 1180 if (name != null)1181 {1182 Token token = PeekToken();1183 if (token.Kind == TokenKind.Equals)1184 {1185 token = NextToken();1186 SkipNewlines();1187 1188 expr = ExpressionRule();1189 1190 if (expr == null)1191 {1192 // ErrorRecovery: ?1193 1194 IScriptExtent errorPosition = After(token);1195 ReportIncompleteInput(1196 errorPosition,1197 nameof(ParserStrings.MissingExpressionInNamedArgument),1198 ParserStrings.MissingExpressionInNamedArgument);1199 expr = new ErrorExpressionAst(errorPosition);1200 SyncOnError(true, TokenKind.Comma, TokenKind.RParen, TokenKind.RBracket, TokenKind.NewLine);