Files
PowerShell/src/System.Management.Automation/engine/parser/Parser.cs
T
Dongbo Wang b20dd31398 Fix parser to continue parsing key-value pairs after an If-Statement value in a HashExpression (#7002)
After parsing `if () { }`, new lines are skipped in order to see if either `elseif` or `else` is present. When neither is present, we should resync back to the pointer before skipping those possibly available new lines, so that the new line tokens can be utilized by the subsequent parsing.
2018-06-06 09:38:05 -07:00

7525 lines
314 KiB
C#

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.Tracing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Runspaces;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
#if !CORECLR
using Microsoft.CodeAnalysis;
#endif
namespace System.Management.Automation.Language
{
using KeyValuePair = Tuple<ExpressionAst, StatementAst>;
using IfClause = Tuple<PipelineBaseAst, StatementBlockAst>;
using SwitchClause = Tuple<ExpressionAst, StatementBlockAst>;
internal enum ParseMode
{
Default = 0,
ModuleAnalysis = 1
}
/// <summary>
/// The parser that parses PowerShell script and returns a <see cref="ScriptBlockAst"/>, tokens, and error messages
/// if the script cannot be parsed successfully.
/// </summary>
public sealed class Parser
{
private readonly Tokenizer _tokenizer;
internal Token _ungotToken;
private bool _disableCommaOperator;
private bool _savingTokens;
private bool _inConfiguration;
private ParseMode _parseMode;
//private bool _v3FeatureUsed;
internal string _fileName;
internal bool ProduceV2Tokens { get; set; }
internal const string VERBATIM_ARGUMENT = "--%";
internal const string VERBATIM_PARAMETERNAME = "-%"; // Same as VERBATIM_ARGUMENT w/o the first '-'.
internal Parser()
{
_tokenizer = new Tokenizer(this);
ErrorList = new List<ParseError>();
_fileName = null;
}
/// <summary>
/// Parse input from the specified file.
/// </summary>
/// <param name="fileName">The name of the file to parse.</param>
/// <param name="tokens">Returns the tokens from parsing the script.</param>
/// <param name="errors">Returns errors, if any, discovered while parsing the script.</param>
/// <returns>The <see cref="ScriptBlockAst"/> that represents the input script file.</returns>
public static ScriptBlockAst ParseFile(string fileName, out Token[] tokens, out ParseError[] errors)
{
const string scriptSchemaExtension = ".schema.psm1";
var parseDscResource = false;
// If the file has the 'schema.psm1' extension, then it is a 'DSC module file' however we don't actually load the
// module at parse time so we can't use the normal mechanisms to bind the module name for configuration commands.
// 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.
var parser = new Parser();
if (!string.IsNullOrEmpty(fileName) && fileName.Length > scriptSchemaExtension.Length && fileName.EndsWith(scriptSchemaExtension, StringComparison.OrdinalIgnoreCase))
{
parser._keywordModuleName = Path.GetFileName(fileName.Substring(0, fileName.Length - scriptSchemaExtension.Length));
parseDscResource = true;
}
string scriptContents;
try
{
var esi = new ExternalScriptInfo(fileName, fileName);
scriptContents = esi.ScriptContents;
}
catch (Exception e)
{
var emptyExtent = new EmptyScriptExtent();
var errorMsg = string.Format(CultureInfo.CurrentCulture, ParserStrings.FileReadError, e.Message);
errors = new[] { new ParseError(emptyExtent, "FileReadError", errorMsg) };
tokens = Utils.EmptyArray<Token>();
return new ScriptBlockAst(emptyExtent, null, new StatementBlockAst(emptyExtent, null, null), false);
}
var tokenList = new List<Token>();
ScriptBlockAst result;
try
{
if (!parseDscResource)
{
DynamicKeyword.Push();
}
result = parser.Parse(fileName, scriptContents, tokenList, out errors, ParseMode.Default);
}
catch (Exception e)
{
throw new ParseException(ParserStrings.UnrecoverableParserError, e);
}
finally
{
if (!parseDscResource)
{
DynamicKeyword.Pop();
}
}
tokens = tokenList.ToArray();
return result;
}
private string _keywordModuleName;
/// <summary>
/// Parse input that does not come from a file.
/// </summary>
/// <param name="input">The input to parse.</param>
/// <param name="tokens">Returns the tokens from parsing the script.</param>
/// <param name="errors">Returns errors, if any, discovered while parsing the script.</param>
/// <returns>The <see cref="ScriptBlockAst"/> that represents the input script file.</returns>
public static ScriptBlockAst ParseInput(string input, out Token[] tokens, out ParseError[] errors)
{
return ParseInput(input, null /* fileName */, out tokens, out errors);
}
/// <summary>
/// Parse input that does not come from a file.
/// </summary>
/// <param name="input">The input to parse.</param>
/// <param name="fileName">The fileName if present or null.</param>
/// <param name="tokens">Returns the tokens from parsing the script.</param>
/// <param name="errors">Returns errors, if any, discovered while parsing the script.</param>
/// <returns>The <see cref="ScriptBlockAst"/> that represents the input script file.</returns>
public static ScriptBlockAst ParseInput(string input, string fileName, out Token[] tokens, out ParseError[] errors)
{
Parser parser = new Parser();
List<Token> tokenList = new List<Token>();
ScriptBlockAst result;
try
{
result = parser.Parse(fileName, input, tokenList, out errors, ParseMode.Default);
}
catch (Exception e)
{
throw new ParseException(ParserStrings.UnrecoverableParserError, e);
}
tokens = tokenList.ToArray();
return result;
}
internal ScriptBlockAst Parse(string fileName, string input, List<Token> tokenList, out ParseError[] errors, ParseMode parseMode)
{
try
{
return ParseTask(fileName, input, tokenList, false, parseMode);
}
finally
{
errors = ErrorList.ToArray();
}
}
private ScriptBlockAst ParseTask(string fileName, string input, List<Token> tokenList, bool recursed, ParseMode parseMode)
{
bool etwEnabled = ParserEventSource.Log.IsEnabled();
if (etwEnabled) ParserEventSource.Log.ParseStart(ParserEventSource.GetFileOrScript(fileName, input), input.Length);
ScriptBlockAst ast = null;
_parseMode = parseMode;
_fileName = fileName;
_tokenizer.Initialize(fileName, input, tokenList);
_savingTokens = (tokenList != null);
ErrorList.Clear();
try
{
ast = ScriptBlockRule(null, isFilter: false);
#if DEBUG
ast.InternalVisit(new CheckAllParentsSet(ast));
#endif
ast.ScriptRequirements = _tokenizer.GetScriptRequirements();
if (parseMode == ParseMode.Default)
{
ast.PerformPostParseChecks(this);
}
#if DEBUG
ast.InternalVisit(new CheckTypeBuilder());
#endif
}
catch (InsufficientExecutionStackException)
{
if (recursed == false)
{
// We'll try parsing once more, this time on a new thread. The assumption here is
// that the stack was close to overflowing before we tried to parse, and that won't
// be a problem on a new thread.
var task = new Task<ScriptBlockAst>(() => ParseTask(fileName, input, tokenList, true, parseMode));
task.Start();
task.Wait();
ast = task.Result;
}
else
{
ReportError(_tokenizer.CurrentExtent(),
nameof(ParserStrings.ScriptTooComplicated),
ParserStrings.ScriptTooComplicated);
}
}
if (etwEnabled) ParserEventSource.Log.ParseStop();
return ast;
}
// This helper routine is used from the runtime to convert a string to a number.
internal static object ScanNumber(string str, Type toType)
{
str = str.Trim();
if (str.Length == 0)
{
// For backwards compatibility, we treat the empty string (w/ or w/o whitespace) as the
// integer constant 0. This is a slight change in semantics, the empty string was previously
// only considered a number if we committed to a numeric operation, e.g.:
// 5 - "" - this results in 5
// Returning 0 here means that
// "" - "" - will result in 0, previously was an error.
// This is mostly of interest for the operators -, /, %, -band, -bor, -bxor as these operators
// allow strings as the lval and still do a numeric operation, whereas + and * treat string
// lvals differently.
return 0;
}
var tokenizer = (new Parser())._tokenizer;
tokenizer.Initialize(null, str, null);
tokenizer.AllowSignedNumbers = true;
var token = tokenizer.NextToken() as NumberToken;
if (token == null || !tokenizer.IsAtEndOfScript(token.Extent))
{
// We call ConvertTo, primarily because we expect it will throw an exception,
// but it's possible it could succeed, e.g. if the string had commas, our lexer
// will fail, but Convert.ChangeType could succeed.
return LanguagePrimitives.ConvertTo(str, toType, CultureInfo.InvariantCulture);
}
return token.Value;
}
internal static ITypeName ScanType(string typename, bool ignoreErrors)
{
typename = typename.Trim();
if (typename.Length == 0)
{
return null;
}
var parser = new Parser();
var tokenizer = parser._tokenizer;
tokenizer.Initialize(null, typename, null);
Token unused;
var result = parser.TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out unused);
SemanticChecks.CheckArrayTypeNameDepth(result, PositionUtilities.EmptyExtent, parser);
if (!ignoreErrors && parser.ErrorList.Count > 0)
{
result = null;
}
return result;
}
internal static ExpressionAst ScanString(string str)
{
str = str.Replace("\"", "\"\"");
var parser = new Parser();
parser._tokenizer.Initialize(null, '"' + str + '"', null);
var strToken = (StringExpandableToken)parser._tokenizer.NextToken();
var ast = parser.ExpandableStringRule(strToken);
if (parser.ErrorList.Count > 0)
{
throw new ParseException(parser.ErrorList.ToArray());
}
return ast;
}
private string _previousFirstTokenText;
private string _previousLastTokenText;
private static bool IgnoreTokenWhenUpdatingPreviousFirstLast(Token token)
{
return (token.Kind == TokenKind.Variable || token.Kind == TokenKind.Generic) &&
(token.Text.Equals("$^", StringComparison.OrdinalIgnoreCase) ||
token.Text.Equals("$$", StringComparison.OrdinalIgnoreCase));
}
internal void SetPreviousFirstLastToken(ExecutionContext context)
{
var firstToken = _tokenizer.FirstToken;
if (firstToken != null)
{
context.SetVariable(SpecialVariables.FirstTokenVarPath, _previousFirstTokenText);
if (!IgnoreTokenWhenUpdatingPreviousFirstLast(firstToken))
{
var stringToken = firstToken as StringToken;
_previousFirstTokenText = stringToken != null
? stringToken.Value
: firstToken.Text;
}
context.SetVariable(SpecialVariables.LastTokenVarPath, _previousLastTokenText);
var lastToken = _tokenizer.LastToken;
if (!IgnoreTokenWhenUpdatingPreviousFirstLast(lastToken))
{
var stringToken = lastToken as StringToken;
_previousLastTokenText = stringToken != null
? stringToken.Value
: lastToken.Text;
}
}
}
//public bool V3FeatureUsed { get { return _v3FeatureUsed; } }
internal List<ParseError> ErrorList { get; }
#region Utilities
private void SkipNewlines()
{
if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine)
{
_ungotToken = null;
_tokenizer.SkipNewlines(false, false);
}
}
// Same as SkipNewlines, but remembers is used when we skip lines differently in
// V3.
private void V3SkipNewlines()
{
if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine)
{
_ungotToken = null;
_tokenizer.SkipNewlines(false, true);
}
}
private void SkipNewlinesAndSemicolons()
{
if (_ungotToken == null || _ungotToken.Kind == TokenKind.NewLine || _ungotToken.Kind == TokenKind.Semi)
{
_ungotToken = null;
_tokenizer.SkipNewlines(true, false);
}
}
private void SyncOnError(bool consumeClosingToken, params TokenKind[] syncTokens)
{
int parens = syncTokens.Contains(TokenKind.RParen) ? 1 : 0;
int curlies = syncTokens.Contains(TokenKind.RCurly) ? 1 : 0;
int braces = syncTokens.Contains(TokenKind.RBracket) ? 1 : 0;
while (true)
{
Token token = NextToken();
switch (token.Kind)
{
case TokenKind.LParen: ++parens; break;
case TokenKind.RParen:
--parens;
if (parens == 0 && syncTokens.Contains(TokenKind.RParen))
{
if (!consumeClosingToken)
{
UngetToken(token);
}
return;
}
break;
case TokenKind.LCurly: ++curlies; break;
case TokenKind.RCurly:
--curlies;
if (curlies == 0 && syncTokens.Contains(TokenKind.RCurly))
{
if (!consumeClosingToken)
{
UngetToken(token);
}
return;
}
break;
case TokenKind.LBracket: ++braces; break;
case TokenKind.RBracket:
--braces;
if (braces == 0 && syncTokens.Contains(TokenKind.RBracket))
{
if (!consumeClosingToken)
{
UngetToken(token);
}
return;
}
break;
case TokenKind.EndOfInput:
// Never consume <EOF>, but return it so caller
UngetToken(token);
return;
}
if (syncTokens.Contains(token.Kind) && parens == 0 && curlies == 0 && braces == 0)
{
break;
}
}
}
private Token NextToken()
{
Token token = _ungotToken ?? _tokenizer.NextToken();
_ungotToken = null;
return token;
}
private Token PeekToken()
{
Token token = _ungotToken ?? _tokenizer.NextToken();
if (_ungotToken == null)
_ungotToken = token;
return token;
}
private Token NextMemberAccessToken(bool allowLBracket)
{
// If _ungotToken is not null, we're in some sort of error state, don't return the token.
if (_ungotToken != null)
return null;
return _tokenizer.GetMemberAccessOperator(allowLBracket);
}
private Token NextInvokeMemberToken()
{
// If _ungotToken is not null, we're in some sort of error state, don't return the token.
if (_ungotToken != null)
return null;
return _tokenizer.GetInvokeMemberOpenParen();
}
private Token NextLBracket()
{
if (_ungotToken != null)
{
if (_ungotToken.Kind == TokenKind.LBracket) return NextToken();
// If _ungotToken is not null, we're in some sort of error state, don't return the token.
return null;
}
return _tokenizer.GetLBracket();
}
private StringToken GetVerbatimCommandArgumentToken()
{
if (_ungotToken == null || _ungotToken.Kind == TokenKind.Parameter)
{
_ungotToken = null;
return _tokenizer.GetVerbatimCommandArgument();
}
return null;
}
private void SkipToken()
{
Diagnostics.Assert(_ungotToken != null, "Don't skip a token you didn't unget");
_ungotToken = null;
}
private void UngetToken(Token token)
{
Diagnostics.Assert(_ungotToken == null, "Only 1 token lookahead is supported");
_ungotToken = token;
}
private void SetTokenizerMode(TokenizerMode mode)
{
if (mode != _tokenizer.Mode && _ungotToken != null)
{
// Only rescan tokens that differ b/w command and expression modes.
if (!_ungotToken.Kind.HasTrait(TokenFlags.ParseModeInvariant))
{
Resync(_ungotToken);
}
#if DEBUG
else if (_ungotToken.Kind != TokenKind.EndOfInput)
{
// Verify the comment above.
Token ungotToken = _ungotToken;
var oldTokenList = _tokenizer.TokenList;
_tokenizer.TokenList = null;
Resync(_ungotToken);
_tokenizer.Mode = mode;
Token rescan = _tokenizer.NextToken();
Diagnostics.Assert(ungotToken.Kind == rescan.Kind, "Rescan failed to return same kind");
Diagnostics.Assert(ungotToken.Text == rescan.Text, "Rescan failed to return same text");
IScriptPosition pos1 = ungotToken.Extent.StartScriptPosition;
IScriptPosition pos2 = rescan.Extent.StartScriptPosition;
Diagnostics.Assert(pos1.ColumnNumber == pos2.ColumnNumber, "Rescan failed to return same start column");
Diagnostics.Assert(pos1.LineNumber == pos2.LineNumber, "Rescan failed to return same start line#");
pos1 = ungotToken.Extent.EndScriptPosition;
pos2 = rescan.Extent.EndScriptPosition;
Diagnostics.Assert(pos1.ColumnNumber == pos2.ColumnNumber, "Rescan failed to return same end column");
Diagnostics.Assert(pos1.LineNumber == pos2.LineNumber, "Rescan failed to return same end line#");
// Make sure we leave things as they were - Resync clears _ungotToken.
_ungotToken = ungotToken;
_tokenizer.TokenList = oldTokenList;
}
#endif
}
_tokenizer.Mode = mode;
}
private void Resync(Token token)
{
_ungotToken = null;
_tokenizer.Resync(token);
}
private void Resync(int restorePoint)
{
_ungotToken = null;
_tokenizer.Resync(restorePoint);
}
private static bool IsSpecificParameter(Token token, string parameter)
{
Diagnostics.Assert(token.Kind == TokenKind.Parameter, "Token must be a ParameterToken");
var paramToken = (ParameterToken)token;
return parameter.StartsWith(paramToken.ParameterName, StringComparison.OrdinalIgnoreCase);
}
internal void NoteV3FeatureUsed()
{
//_v3FeatureUsed = true;
}
internal void RequireStatementTerminator()
{
var terminatorToken = PeekToken();
if (terminatorToken.Kind == TokenKind.NewLine || terminatorToken.Kind == TokenKind.Semi)
{
SkipToken();
}
else if (terminatorToken.Kind != TokenKind.EndOfInput)
{
ReportIncompleteInput(terminatorToken.Extent,
nameof(ParserStrings.MissingStatementTerminator),
ParserStrings.MissingStatementTerminator);
}
}
internal static IScriptExtent ExtentOf(IScriptExtent first, IScriptExtent last)
{
if (first is EmptyScriptExtent) return last;
if (last is EmptyScriptExtent) return first;
Diagnostics.Assert(first is InternalScriptExtent && last is InternalScriptExtent,
"Private method expects internal position representation");
InternalScriptExtent l = (InternalScriptExtent)first;
InternalScriptExtent r = (InternalScriptExtent)last;
Diagnostics.Assert(l.PositionHelper == r.PositionHelper, "Can't get the extent across files");
return new InternalScriptExtent(l.PositionHelper, l.StartOffset, r.EndOffset);
}
internal static IScriptExtent Before(IScriptExtent extent)
{
Diagnostics.Assert(extent is InternalScriptExtent, "Private method expects internal position representation");
InternalScriptExtent scriptExtent = (InternalScriptExtent)extent;
int offset = scriptExtent.StartOffset - 1;
if (offset < 0) offset = 0;
return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset);
}
internal static IScriptExtent After(IScriptExtent extent)
{
Diagnostics.Assert(extent is InternalScriptExtent, "Private method expects internal position representation");
InternalScriptExtent scriptExtent = (InternalScriptExtent)extent;
int offset = scriptExtent.EndOffset;
return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset);
}
internal static IScriptExtent LastCharacterOf(IScriptExtent extent)
{
Diagnostics.Assert(extent is InternalScriptExtent, "Private method expects internal position representation");
InternalScriptExtent scriptExtent = (InternalScriptExtent)extent;
int offset = scriptExtent.EndOffset - 1;
if (offset < 0)
{
offset = 0;
}
return new InternalScriptExtent(scriptExtent.PositionHelper, offset, offset);
}
internal static IScriptExtent ExtentFromFirstOf(params object[] objs)
{
foreach (object obj in objs)
{
if (obj != null)
{
var token = obj as Token;
if (token != null)
{
return token.Extent;
}
var ast = obj as Ast;
if (ast != null)
{
return ast.Extent;
}
var typename = obj as ITypeName;
if (typename != null)
{
return typename.Extent;
}
Diagnostics.Assert(obj is IScriptExtent, "Only accepts tokens, asts, and IScriptExtents");
return (IScriptExtent)obj;
}
}
Diagnostics.Assert(false, "One of the objects must not be null");
return PositionUtilities.EmptyExtent;
}
internal static IScriptExtent ExtentOf(Token first, Token last) { return ExtentOf(first.Extent, last.Extent); }
internal static IScriptExtent ExtentOf(Ast first, Ast last) { return ExtentOf(first.Extent, last.Extent); }
internal static IScriptExtent ExtentOf(Ast first, Token last) { return ExtentOf(first.Extent, last.Extent); }
internal static IScriptExtent ExtentOf(Token first, Ast last) { return ExtentOf(first.Extent, last.Extent); }
internal static IScriptExtent ExtentOf(IScriptExtent first, Ast last) { return ExtentOf(first, last.Extent); }
internal static IScriptExtent ExtentOf(IScriptExtent first, Token last) { return ExtentOf(first, last.Extent); }
internal static IScriptExtent ExtentOf(Ast first, IScriptExtent last) { return ExtentOf(first.Extent, last); }
internal static IScriptExtent ExtentOf(Token first, IScriptExtent last) { return ExtentOf(first.Extent, last); }
//private static IScriptExtent Before(Ast ast) { return Before(ast.Extent); }
internal static IScriptExtent Before(Token token) { return Before(token.Extent); }
internal static IScriptExtent After(Ast ast) { return After(ast.Extent); }
internal static IScriptExtent After(Token token) { return After(token.Extent); }
private static IEnumerable<Ast> GetNestedErrorAsts(params object[] asts)
{
foreach (var obj in asts)
{
if (obj != null)
{
Ast ast = obj as Ast;
if (ast != null)
{
yield return ast;
}
else
{
var enumerable = obj as IEnumerable<Ast>;
if (enumerable != null)
{
foreach (var ast2 in enumerable)
{
if (ast2 != null)
{
yield return ast2;
}
}
}
else
{
Diagnostics.Assert(false, "Caller to pass only asts, or IEnumerable<Ast>");
}
}
}
}
}
/// <summary>
/// Parses the specified constant hashtable string into a Hashtable object
/// </summary>
/// <param name="input">The Hashtable string</param>
/// <param name="result">the Hashtable object</param>
/// <returns></returns>
internal static bool TryParseAsConstantHashtable(string input, out Hashtable result)
{
result = null;
if (String.IsNullOrWhiteSpace(input))
{
return false;
}
Token[] throwAwayTokens;
ParseError[] parseErrors;
var ast = Parser.ParseInput(input, out throwAwayTokens, out parseErrors);
if ((ast == null) ||
parseErrors.Length > 0 ||
ast.BeginBlock != null ||
ast.ProcessBlock != null ||
ast.DynamicParamBlock != null ||
ast.EndBlock.Traps != null)
{
return false;
}
var statements = ast.EndBlock.Statements;
if (statements.Count != 1)
{
return false;
}
var pipelineAst = statements[0] as PipelineAst;
if (pipelineAst == null)
{
return false;
}
var expr = pipelineAst.GetPureExpression();
if (expr == null)
{
return false;
}
var hashTableAst = expr as HashtableAst;
if (hashTableAst == null)
{
return false;
}
object hashtable;
if (!IsConstantValueVisitor.IsConstant(hashTableAst, out hashtable, forRequires: true))
{
return false;
}
var data = hashtable as Hashtable;
Diagnostics.Assert((data != null), "IsConstantValueVisitor.IsConstant() should return false when the specified HashtableAst is not a avalid Hashtable");
result = data;
return true;
}
#endregion Utilities
#region Statements
private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter)
{
return ScriptBlockRule(lCurly, isFilter, null);
}
private ScriptBlockAst ScriptBlockRule(Token lCurly, bool isFilter, StatementAst predefinedStatementAst)
{
//G script-block:
//G using-statements:opt param-block:opt statement-terminators:opt script-block-body:opt
//G
//G using-statements:
//G using-statement
//G using-statements using-statement
// We could set the mode here, but we can avoid rescanning keywords if the caller
// sets the mode before skipping newlines.
Diagnostics.Assert(_tokenizer.Mode == TokenizerMode.Command,
"Caller to make sure the mode is correct.");
// Skipping newlines here saves a more expensive resync if there is no parameter block.
SkipNewlines();
var usingStatements = lCurly == null ? UsingStatementsRule() : null;
var restorePoint = _tokenizer.GetRestorePoint();
ParamBlockAst paramBlock = ParamBlockRule();
if (paramBlock == null)
{
// In case we scanned some attributes or type constraints but they didn't
// belong to a param statement, we need to reparse them (because they will
// mean something different, such as a type literal expression, or a cast.)
Resync(restorePoint);
}
SkipNewlinesAndSemicolons();
return ScriptBlockBodyRule(lCurly, usingStatements, paramBlock, isFilter, predefinedStatementAst);
}
private List<UsingStatementAst> UsingStatementsRule()
{
List<UsingStatementAst> result = null;
while (true)
{
var token = PeekToken();
if (token.Kind == TokenKind.Using)
{
SkipToken();
var statement = UsingStatementRule(token);
SkipNewlinesAndSemicolons();
if (result == null)
{
result = new List<UsingStatementAst>();
}
var usingStatement = statement as UsingStatementAst;
// otherwise returned statement is ErrorStatementAst.
// We ignore it here, because error already reported to the parser.
if (usingStatement != null)
{
result.Add(usingStatement);
}
continue;
}
// Normally we don't need to resync, but our caller may speculatively scan much more than
// one token, so it needs the restore point to include this token.
Resync(token);
break;
}
return result;
}
private ParamBlockAst ParamBlockRule()
{
//G param-block:
//G new-lines:opt attribute-list:opt new-lines:opt 'param' new-lines:opt
//G '(' parameter-list:opt new-lines:opt ')'
SkipNewlines();
List<AttributeBaseAst> candidateAttributes = AttributeListRule(false);
SkipNewlines();
Token paramToken = PeekToken();
if (paramToken.Kind != TokenKind.Param)
{
return null;
}
SkipToken();
SkipNewlines();
Token lparen = NextToken();
if (lparen.Kind != TokenKind.LParen)
{
UngetToken(lparen);
//This is not an error, we'll end up trying to invoke a command named 'param'.
return null;
}
List<ParameterAst> parameters = ParameterListRule();
SkipNewlines();
Token rParen = NextToken();
var endExtent = rParen.Extent;
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: assume we saw the closing paren and continue like normal.
UngetToken(rParen);
endExtent = Before(rParen);
ReportIncompleteInput(After(parameters != null && parameters.Any() ? parameters.Last().Extent : lparen.Extent),
nameof(ParserStrings.MissingEndParenthesisInFunctionParameterList),
ParserStrings.MissingEndParenthesisInFunctionParameterList);
}
List<AttributeAst> attributes = new List<AttributeAst>();
if (candidateAttributes != null)
{
foreach (AttributeBaseAst attr in candidateAttributes)
{
AttributeAst attribute = attr as AttributeAst;
if (attribute != null)
{
attributes.Add(attribute);
}
else
{
// ErrorRecovery: nothing to do, this is a semantic error that is caught in the parser
// because the ast only allows attributes, no type constraints.
ReportError(attr.Extent,
nameof(ParserStrings.TypeNotAllowedBeforeParam),
ParserStrings.TypeNotAllowedBeforeParam,
attr.TypeName.FullName);
}
}
}
return new ParamBlockAst(ExtentOf(paramToken, endExtent), attributes, parameters);
}
private List<ParameterAst> ParameterListRule()
{
//G parameter-list:
//G script-parameter
//G parameter-list new-lines:opt ',' script-parameter
List<ParameterAst> parameters = new List<ParameterAst>();
Token commaToken = null;
while (true)
{
ParameterAst parameter = ParameterRule();
if (parameter == null)
{
if (commaToken != null)
{
// ErrorRecovery: ??
ReportIncompleteInput(After(commaToken),
nameof(ParserStrings.MissingExpressionAfterToken),
ParserStrings.MissingExpressionAfterToken,
commaToken.Kind.Text());
}
break;
}
parameters.Add(parameter);
SkipNewlines();
commaToken = PeekToken();
if (commaToken.Kind != TokenKind.Comma)
{
break;
}
SkipToken();
}
return parameters;
}
private ParameterAst ParameterRule()
{
//G script-parameter:
//G new-lines:opt attribute-list:opt new-lines:opt variable script-parameter-default:opt
//G script-parameter-default:
//G new-lines:opt '=' new-lines:opt expression
List<AttributeBaseAst> attributes;
VariableToken variableToken;
ExpressionAst defaultValue = null;
bool oldDisableCommaOperator = _disableCommaOperator;
var oldTokenizerMode = _tokenizer.Mode;
try
{
_disableCommaOperator = true;
SetTokenizerMode(TokenizerMode.Expression);
SkipNewlines();
attributes = AttributeListRule(false);
SkipNewlines();
Token token = NextToken();
if (token.Kind != TokenKind.Variable && token.Kind != TokenKind.SplattedVariable)
{
UngetToken(token);
if (attributes != null)
{
// ErrorRecovery: skip to closing paren because returning null signals the last parameter.
ReportIncompleteInput(After(attributes.Last()),
nameof(ParserStrings.InvalidFunctionParameter),
ParserStrings.InvalidFunctionParameter);
SyncOnError(true, TokenKind.RParen);
// Even though we don't have a complete parameter, we do have attributes. Intellisense
// might want to complete something in the attributes, so we need to return something useful.
var extent = ExtentOf(attributes[0].Extent, attributes[attributes.Count - 1].Extent);
return new ParameterAst(extent, new VariableExpressionAst(extent, "__error__", false), attributes, null);
}
return null;
}
variableToken = ((VariableToken)token);
SkipNewlines();
Token equalsToken = PeekToken();
if (equalsToken.Kind == TokenKind.Equals)
{
SkipToken();
SkipNewlines();
defaultValue = ExpressionRule();
if (defaultValue == null)
{
ReportIncompleteInput(After(equalsToken),
nameof(ParserStrings.MissingExpressionAfterToken),
ParserStrings.MissingExpressionAfterToken,
equalsToken.Kind.Text());
}
}
}
finally
{
_disableCommaOperator = oldDisableCommaOperator;
SetTokenizerMode(oldTokenizerMode);
}
IScriptExtent startExtent = (attributes == null) ? variableToken.Extent : attributes[0].Extent;
IScriptExtent endExtent = (defaultValue == null) ? variableToken.Extent : defaultValue.Extent;
return new ParameterAst(ExtentOf(startExtent, endExtent),
new VariableExpressionAst(variableToken), attributes, defaultValue);
}
private List<AttributeBaseAst> AttributeListRule(bool inExpressionMode)
{
//G attribute-list:
//G attribute
//G attribute-list attribute
List<AttributeBaseAst> attributes = new List<AttributeBaseAst>();
AttributeBaseAst attribute = AttributeRule();
while (attribute != null)
{
attributes.Add(attribute);
if (!inExpressionMode || attribute is AttributeAst)
{
SkipNewlines();
}
attribute = AttributeRule();
}
if (attributes.Count == 0)
{
return null;
}
return attributes;
}
private AttributeBaseAst AttributeRule()
{
//G attribute:
//G '[' attribute-name '(' attribute-arguments ')' ']'
//G attribute-name:
//G type-spec
var lBracket = NextLBracket();
if (lBracket == null)
{
return null;
}
V3SkipNewlines();
Token firstTypeNameToken;
ITypeName typeName = TypeNameRule(allowAssemblyQualifiedNames: true, firstTypeNameToken: out firstTypeNameToken);
if (typeName == null)
{
// ErrorRecovery: Return null so we stop looking for attributes.
Resync(lBracket); // TypeNameRule might have consumed some tokens
ReportIncompleteInput(After(lBracket),
nameof(ParserStrings.MissingTypename),
ParserStrings.MissingTypename);
return null;
}
Token lParenOrRBracket = NextToken();
if (lParenOrRBracket.Kind == TokenKind.LParen)
{
SkipNewlines();
List<ExpressionAst> positionalArguments = new List<ExpressionAst>();
List<NamedAttributeArgumentAst> namedArguments = new List<NamedAttributeArgumentAst>();
IScriptExtent lastItemExtent = lParenOrRBracket.Extent;
var oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Expression);
AttributeArgumentsRule(positionalArguments, namedArguments, ref lastItemExtent);
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
SkipNewlines();
Token rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: pretend we saw a ')', attempt to find an ']'.
UngetToken(rParen);
rParen = null;
ReportIncompleteInput(After(lastItemExtent),
nameof(ParserStrings.MissingEndParenthesisInExpression),
ParserStrings.MissingEndParenthesisInExpression);
}
SkipNewlines();
Token rBracket = NextToken();
if (rBracket.Kind != TokenKind.RBracket)
{
// ErrorRecovery: pretend we saw a ']', return our result.
UngetToken(rBracket);
rBracket = null;
// Don't bother reporting a missing ']' if we reported a missing ')'.
if (rParen != null)
{
ReportIncompleteInput(After(rParen),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),
ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);
}
}
firstTypeNameToken.TokenFlags |= TokenFlags.AttributeName;
return new AttributeAst(ExtentOf(lBracket, ExtentFromFirstOf(rBracket, rParen, lastItemExtent)), typeName, positionalArguments, namedArguments);
}
if (ProduceV2Tokens)
{
var typeToken = new Token((InternalScriptExtent)ExtentOf(lBracket, lParenOrRBracket),
TokenKind.Identifier, TokenFlags.TypeName);
_tokenizer.ReplaceSavedTokens(lBracket, lParenOrRBracket, typeToken);
}
if (lParenOrRBracket.Kind != TokenKind.RBracket)
{
UngetToken(lParenOrRBracket);
ReportError(Before(lParenOrRBracket),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),
ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);
lParenOrRBracket = null;
}
return new TypeConstraintAst(ExtentOf(lBracket, ExtentFromFirstOf(lParenOrRBracket, typeName.Extent)), typeName);
}
private void AttributeArgumentsRule(ICollection<ExpressionAst> positionalArguments,
ICollection<NamedAttributeArgumentAst> namedArguments,
ref IScriptExtent lastItemExtent)
{
//G attribute-arguments:
//G attribute-argument
//G attribute-argument new-lines:opt ',' attribute-arguments
//G attribute-argument:
//G new-lines:opt expression
//G new-lines:opt property-name '=' new-lines:opt expression
bool oldDisableCommaOperator = _disableCommaOperator;
Token commaToken = null;
HashSet<string> keysSeen = new HashSet<string>();
try
{
_disableCommaOperator = true;
while (true)
{
SkipNewlines();
StringConstantExpressionAst name = SimpleNameRule();
ExpressionAst expr;
bool expressionOmitted = false;
if (name != null)
{
Token token = PeekToken();
if (token.Kind == TokenKind.Equals)
{
token = NextToken();
SkipNewlines();
expr = ExpressionRule();
if (expr == null)
{
// ErrorRecovery: ?
IScriptExtent errorPosition = After(token);
ReportIncompleteInput(errorPosition,
nameof(ParserStrings.MissingExpressionInNamedArgument),
ParserStrings.MissingExpressionInNamedArgument);
expr = new ErrorExpressionAst(errorPosition);
SyncOnError(true, TokenKind.Comma, TokenKind.RParen, TokenKind.RBracket, TokenKind.NewLine);
}
lastItemExtent = expr.Extent;
}
else
{
// If the expression is missing, assume the value true
// and record that it was defaulted for better error messages.
expr = new ConstantExpressionAst(name.Extent, true);
expressionOmitted = true;
NoteV3FeatureUsed();
}
}
else
{
expr = ExpressionRule();
}
if (name != null)
{
if (keysSeen.Contains(name.Value))
{
// ErrorRecovery: this is a semantic error, so just keep parsing.
ReportError(name.Extent,
nameof(ParserStrings.DuplicateNamedArgument),
ParserStrings.DuplicateNamedArgument,
name.Value);
}
else
{
namedArguments.Add(new NamedAttributeArgumentAst(ExtentOf(name, expr),
name.Value, expr, expressionOmitted));
}
}
else if (expr != null)
{
positionalArguments.Add(expr);
lastItemExtent = expr.Extent;
}
else if (commaToken != null)
{
// ErrorRecovery: Pretend we saw the argument and keep going.
IScriptExtent errorExtent = After(commaToken);
ReportIncompleteInput(errorExtent,
nameof(ParserStrings.MissingExpressionAfterToken),
ParserStrings.MissingExpressionAfterToken,
commaToken.Kind.Text());
positionalArguments.Add(new ErrorExpressionAst(errorExtent));
lastItemExtent = errorExtent;
}
SkipNewlines();
commaToken = PeekToken();
if (commaToken.Kind != TokenKind.Comma)
{
break;
}
lastItemExtent = commaToken.Extent;
SkipToken();
}
}
finally
{
_disableCommaOperator = oldDisableCommaOperator;
}
}
private ITypeName TypeNameRule(bool allowAssemblyQualifiedNames, out Token firstTypeNameToken)
{
//G type-spec:
//G array-type-name dimension:opt ']'
//G generic-type-name generic-type-arguments ']'
//G type-name
//G array-type-name:
//G type-name '['
//G generic-type-name:
//G type-name '['
//G dimension:
//G ','
//G dimension ','
//G generic-type-arguments:
//G type-spec
//G generic-type-arguments ',' type-spec
//G type-name:
//G namespace-type-name
//G namespace-type-name ',' assembly-name-spec
//G namespace-type-name:
//G nested-type-name
//G namespace-spec '.' nested-type-name
//G nested-type-name:
//G type-name-identifier
//G nested-type-name '+' type-name-identifier
//G namespace-spec:
//G type-name-identifier
//G type-name-identifier '.' type-name-identifier
//G type-name-identifier:
//G type-name-identifier-char
//G type-name-identifier type-name-identifier-char
//G type-name-identifier-char:
//G any letter, digit, '.', '`', or '_'.
// The above grammar is specified by the CLR. We don't attempt to implement the above grammar precisely, e.g.
// we would permit 'a+b.c'. The above grammar would disallow this.
var oldTokenizerMode = _tokenizer.Mode;
try
{
// Switch to typename mode to avoid aggressive argument tokenization.
SetTokenizerMode(TokenizerMode.TypeName);
Token typeName = NextToken();
if (typeName.Kind != TokenKind.Identifier)
{
UngetToken(typeName);
firstTypeNameToken = null;
return null;
}
// firstTypeNameToken is returned so we can mark it as an attribute if it turns out
// that we've got a attribute instead of a type literal.
firstTypeNameToken = typeName;
return FinishTypeNameRule(typeName, allowAssemblyQualifiedNames: allowAssemblyQualifiedNames);
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
}
private ITypeName FinishTypeNameRule(Token typeName, bool unBracketedGenericArg = false, bool allowAssemblyQualifiedNames = true)
{
Diagnostics.Assert(typeName.Kind == TokenKind.Identifier, "Caller must verify the argument.");
Token token = PeekToken();
if (token.Kind == TokenKind.LBracket)
{
var lbracket = token;
// Array or generic
SkipToken();
V3SkipNewlines();
token = NextToken();
switch (token.Kind)
{
case TokenKind.RBracket:
case TokenKind.Comma:
var elementType = new TypeName(typeName.Extent, typeName.Text);
return CompleteArrayTypeName(elementType, elementType, token);
case TokenKind.LBracket:
case TokenKind.Identifier:
return GenericTypeArgumentsRule(typeName, token, unBracketedGenericArg);
default:
// ErrorRecovery: sync to ']', and return non-null to avoid cascading errors.
if (token.Kind != TokenKind.EndOfInput)
{
ReportError(token.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
token.Text);
SyncOnError(true, TokenKind.RBracket);
}
else
{
UngetToken(token);
ReportIncompleteInput(After(lbracket),
nameof(ParserStrings.MissingTypename),
ParserStrings.MissingTypename);
}
return new TypeName(typeName.Extent, typeName.Text);
}
}
if (token.Kind == TokenKind.Comma && allowAssemblyQualifiedNames && !unBracketedGenericArg)
{
SkipToken();
string assemblyNameSpec = _tokenizer.GetAssemblyNameSpec();
if (string.IsNullOrWhiteSpace(assemblyNameSpec))
{
ReportError(After(token),
nameof(ParserStrings.MissingAssemblyNameSpecification),
ParserStrings.MissingAssemblyNameSpecification);
return new TypeName(typeName.Extent, typeName.Text);
}
return new TypeName(ExtentOf(typeName.Extent, _tokenizer.CurrentExtent()), typeName.Text, assemblyNameSpec);
}
return new TypeName(typeName.Extent, typeName.Text);
}
private ITypeName GetSingleGenericArgument(Token firstToken)
{
if (firstToken.Kind == TokenKind.Identifier)
{
return FinishTypeNameRule(firstToken, unBracketedGenericArg: true);
}
Diagnostics.Assert(firstToken.Kind == TokenKind.LBracket, "caller to verify correct token kind");
Token token = NextToken();
if (token.Kind != TokenKind.Identifier)
{
UngetToken(token);
ReportIncompleteInput(After(token),
nameof(ParserStrings.MissingTypename),
ParserStrings.MissingTypename);
return new TypeName(firstToken.Extent, ":ErrorTypeName:");
}
ITypeName typeName = FinishTypeNameRule(token);
if (typeName != null)
{
Token rBracket = NextToken();
if (rBracket.Kind != TokenKind.RBracket)
{
// ErrorRecovery: pretend we saw the closing bracket.
UngetToken(rBracket);
ReportIncompleteInput(Before(rBracket),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfType),
ParserStrings.EndSquareBracketExpectedAtEndOfType);
}
}
return typeName;
}
private ITypeName GenericTypeArgumentsRule(Token genericTypeName, Token firstToken, bool unBracketedGenericArg)
{
Diagnostics.Assert(firstToken.Kind == TokenKind.Identifier || firstToken.Kind == TokenKind.LBracket, "unexpected first token");
RuntimeHelpers.EnsureSufficientExecutionStack();
var genericArguments = new List<ITypeName>();
ITypeName typeName = GetSingleGenericArgument(firstToken);
genericArguments.Add(typeName);
Token commaOrRBracketToken;
Token token;
while (true)
{
V3SkipNewlines();
commaOrRBracketToken = NextToken();
if (commaOrRBracketToken.Kind != TokenKind.Comma)
{
break;
}
V3SkipNewlines();
token = PeekToken();
if (token.Kind == TokenKind.Identifier || token.Kind == TokenKind.LBracket)
{
SkipToken();
typeName = GetSingleGenericArgument(token);
}
else
{
ReportIncompleteInput(After(commaOrRBracketToken),
nameof(ParserStrings.MissingTypename),
ParserStrings.MissingTypename);
typeName = new TypeName(commaOrRBracketToken.Extent, ":ErrorTypeName:");
}
genericArguments.Add(typeName);
}
if (commaOrRBracketToken.Kind != TokenKind.RBracket)
{
// ErrorRecovery: pretend we had the closing bracket and just continue on.
UngetToken(commaOrRBracketToken);
ReportIncompleteInput(Before(commaOrRBracketToken),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),
ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);
commaOrRBracketToken = null;
}
var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text);
var result = new GenericTypeName(ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(commaOrRBracketToken, genericArguments.LastOrDefault(), firstToken)),
openGenericType, genericArguments);
token = PeekToken();
if (token.Kind == TokenKind.LBracket)
{
SkipToken();
return CompleteArrayTypeName(result, openGenericType, NextToken());
}
if (token.Kind == TokenKind.Comma && !unBracketedGenericArg)
{
SkipToken();
string assemblyNameSpec = _tokenizer.GetAssemblyNameSpec();
if (string.IsNullOrEmpty(assemblyNameSpec))
{
ReportError(After(token),
nameof(ParserStrings.MissingAssemblyNameSpecification),
ParserStrings.MissingAssemblyNameSpecification);
}
else
{
openGenericType.AssemblyName = assemblyNameSpec;
}
}
return result;
}
private ITypeName CompleteArrayTypeName(ITypeName elementType, TypeName typeForAssemblyQualification, Token firstTokenAfterLBracket)
{
while (true)
{
Token token;
switch (firstTokenAfterLBracket.Kind)
{
case TokenKind.Comma:
int dim = 1;
token = firstTokenAfterLBracket;
Token lastComma;
do
{
lastComma = token;
dim += 1;
token = NextToken();
} while (token.Kind == TokenKind.Comma);
if (token.Kind != TokenKind.RBracket)
{
// ErrorRecovery: just pretend we saw a ']'.
UngetToken(token);
ReportError(After(lastComma),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),
ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);
}
elementType = new ArrayTypeName(ExtentOf(elementType.Extent, token.Extent), elementType, dim);
break;
case TokenKind.RBracket:
elementType = new ArrayTypeName(ExtentOf(elementType.Extent, firstTokenAfterLBracket.Extent), elementType, 1);
break;
case TokenKind.EndOfInput:
UngetToken(firstTokenAfterLBracket);
ReportError(Before(firstTokenAfterLBracket),
nameof(ParserStrings.EndSquareBracketExpectedAtEndOfAttribute),
ParserStrings.EndSquareBracketExpectedAtEndOfAttribute);
break;
default:
// ErrorRecovery: sync to ']', and return null to avoid cascading errors.
ReportError(firstTokenAfterLBracket.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
firstTokenAfterLBracket.Text);
SyncOnError(true, TokenKind.RBracket);
break;
}
token = PeekToken();
if (token.Kind == TokenKind.Comma)
{
SkipToken();
var assemblyName = _tokenizer.GetAssemblyNameSpec();
if (string.IsNullOrEmpty(assemblyName))
{
ReportError(After(token),
nameof(ParserStrings.MissingAssemblyNameSpecification),
ParserStrings.MissingAssemblyNameSpecification);
}
else
{
typeForAssemblyQualification.AssemblyName = assemblyName;
}
break;
}
if (token.Kind != TokenKind.LBracket)
{
break;
}
// Jagged array, skip the '[' and keep parsing.
SkipToken();
firstTokenAfterLBracket = NextToken();
}
return elementType;
}
private bool CompleteScriptBlockBody(Token lCurly, ref IScriptExtent bodyExtent, out IScriptExtent fullBodyExtent)
{
// If the caller passed in the open curly, then they expect us to consume the closing curly
// and include that in the extent.
if (lCurly != null)
{
Token rCurly = NextToken();
IScriptExtent endScriptBlock;
if (rCurly.Kind != TokenKind.RCurly)
{
// ErrorRecovery: pretend we saw the closing curly.
UngetToken(rCurly);
endScriptBlock = bodyExtent ?? lCurly.Extent;
ReportIncompleteInput(lCurly.Extent,
rCurly.Extent,
nameof(ParserStrings.MissingEndCurlyBrace),
ParserStrings.MissingEndCurlyBrace);
}
else
{
endScriptBlock = rCurly.Extent;
// If body was empty, use the extent b/w (but not including) the curlies, but only
// if that region has at least 1 character.
if (bodyExtent == null && (lCurly.Extent.EndColumnNumber != rCurly.Extent.StartColumnNumber))
{
bodyExtent = ExtentOf(After(lCurly), Before(rCurly));
}
}
fullBodyExtent = ExtentOf(lCurly, endScriptBlock);
}
else
{
fullBodyExtent = _tokenizer.GetScriptExtent();
Token token = NextToken();
if (token.Kind != TokenKind.EndOfInput)
{
// ErrorRecovery: eat the unexpected token, and continue parsing to find more
// of the script.
ReportError(token.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
token.Text);
return false;
}
}
return true;
}
private ScriptBlockAst ScriptBlockBodyRule(Token lCurly, List<UsingStatementAst> usingStatements, ParamBlockAst paramBlockAst, bool isFilter, StatementAst predefinedStatementAst)
{
//G script-block-body:
//G named-block-list
//G statement-list
Token token = PeekToken();
if ((token.TokenFlags & TokenFlags.ScriptBlockBlockName) == TokenFlags.ScriptBlockBlockName)
{
return NamedBlockListRule(lCurly, usingStatements, paramBlockAst);
}
List<TrapStatementAst> traps = new List<TrapStatementAst>();
List<StatementAst> statements = new List<StatementAst>();
if (predefinedStatementAst != null)
{
statements.Add(predefinedStatementAst);
}
IScriptExtent statementListExtent = paramBlockAst != null ? paramBlockAst.Extent : null;
IScriptExtent scriptBlockExtent;
while (true)
{
IScriptExtent extent = StatementListRule(statements, traps);
if (statementListExtent == null)
{
statementListExtent = extent;
}
else if (extent != null)
{
statementListExtent = ExtentOf(statementListExtent, extent);
}
if (CompleteScriptBlockBody(lCurly, ref statementListExtent, out scriptBlockExtent))
{
break;
}
}
return new ScriptBlockAst(scriptBlockExtent, usingStatements, paramBlockAst,
new StatementBlockAst(statementListExtent ?? PositionUtilities.EmptyExtent, statements, traps), isFilter);
}
private ScriptBlockAst NamedBlockListRule(Token lCurly, List<UsingStatementAst> usingStatements, ParamBlockAst paramBlockAst)
{
//G named-block-list:
//G named-block
//G named-block-list named-block
//G named-block:
//G statement-terminators:opt block-name statement-block
//G block-name: one of
//G 'dynamicparam' 'begin' 'process' 'end'
NamedBlockAst dynamicParamBlock = null;
NamedBlockAst beginBlock = null;
NamedBlockAst processBlock = null;
NamedBlockAst endBlock = null;
IScriptExtent startExtent = lCurly != null
? lCurly.Extent
: (paramBlockAst != null) ? paramBlockAst.Extent : null;
IScriptExtent endExtent = null;
IScriptExtent extent = null;
IScriptExtent scriptBlockExtent = null;
while (true)
{
Token blockNameToken = NextToken();
switch (blockNameToken.Kind)
{
default:
// Next token is unexpected.
// ErrorRecovery: if 'lCurly' is present, pretend we saw a closing curly; otherwise, eat the unexpected token.
if (lCurly != null)
{
UngetToken(blockNameToken);
scriptBlockExtent = ExtentOf(startExtent, endExtent);
}
else
{
// If "lCurly == null", then it's a ps1/psm1 file, and thus the extent is the whole file.
scriptBlockExtent = _tokenizer.GetScriptExtent();
}
// Report error about the unexpected token.
ReportError(blockNameToken.Extent,
nameof(ParserStrings.MissingNamedBlocks),
ParserStrings.MissingNamedBlocks,
blockNameToken.Text);
goto return_script_block_ast;
case TokenKind.RCurly:
case TokenKind.EndOfInput:
// If the next token is RCurly or <eof>, handle it in 'CompleteScriptBlockBody'.
UngetToken(blockNameToken);
extent = ExtentOf(startExtent, endExtent);
goto finished_named_block_list;
case TokenKind.Dynamicparam:
case TokenKind.Begin:
case TokenKind.Process:
case TokenKind.End:
break;
}
if (startExtent == null)
{
startExtent = blockNameToken.Extent;
}
endExtent = blockNameToken.Extent;
StatementBlockAst statementBlock = StatementBlockRule();
if (statementBlock == null)
{
// ErrorRecovery: Eat the block name and keep going, there might be a valid block next.
ReportIncompleteInput(After(blockNameToken.Extent),
nameof(ParserStrings.MissingNamedStatementBlock),
ParserStrings.MissingNamedStatementBlock,
blockNameToken.Kind.Text());
statementBlock = new StatementBlockAst(blockNameToken.Extent, Utils.EmptyArray<StatementAst>(), null);
}
else
{
endExtent = statementBlock.Extent;
}
extent = ExtentOf(blockNameToken, endExtent);
if (blockNameToken.Kind == TokenKind.Begin && beginBlock == null)
{
beginBlock = new NamedBlockAst(extent, TokenKind.Begin, statementBlock, false);
}
else if (blockNameToken.Kind == TokenKind.Process && processBlock == null)
{
processBlock = new NamedBlockAst(extent, TokenKind.Process, statementBlock, false);
}
else if (blockNameToken.Kind == TokenKind.End && endBlock == null)
{
endBlock = new NamedBlockAst(extent, TokenKind.End, statementBlock, false);
}
else if (blockNameToken.Kind == TokenKind.Dynamicparam && dynamicParamBlock == null)
{
dynamicParamBlock = new NamedBlockAst(extent, TokenKind.Dynamicparam, statementBlock, false);
}
else
{
// ErrorRecovery: this is a semantic error, we can keep parsing w/o trouble.
ReportError(extent,
nameof(ParserStrings.DuplicateScriptCommandClause),
ParserStrings.DuplicateScriptCommandClause,
blockNameToken.Kind.Text());
}
SkipNewlinesAndSemicolons();
}
finished_named_block_list:
CompleteScriptBlockBody(lCurly, ref extent, out scriptBlockExtent);
return_script_block_ast:
return new ScriptBlockAst(scriptBlockExtent, usingStatements, paramBlockAst, beginBlock, processBlock, endBlock,
dynamicParamBlock);
}
private StatementBlockAst StatementBlockRule()
{
//G statement-block:
//G new-lines:opt '{' statement-list:opt new-lines:opt '}'
SkipNewlines();
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
UngetToken(lCurly);
return null;
}
List<TrapStatementAst> traps = new List<TrapStatementAst>();
List<StatementAst> statements = new List<StatementAst>();
IScriptExtent statementListExtent = StatementListRule(statements, traps);
Token rCurly = NextToken();
IScriptExtent endBlock;
if (rCurly.Kind != TokenKind.RCurly)
{
// ErrorRecovery: Pretend we saw the missing curly and keep parsing.
UngetToken(rCurly);
endBlock = statementListExtent ?? lCurly.Extent;
ReportIncompleteInput(lCurly.Extent,
rCurly.Extent,
nameof(ParserStrings.MissingEndCurlyBrace),
ParserStrings.MissingEndCurlyBrace);
}
else
{
endBlock = rCurly.Extent;
}
return new StatementBlockAst(ExtentOf(lCurly, endBlock), statements, traps);
}
private IScriptExtent StatementListRule(List<StatementAst> statements, List<TrapStatementAst> traps)
{
//G statement-list:
//G statement
//G statement-list statement
StatementAst firstStatement = null;
StatementAst lastStatement = null;
SkipNewlinesAndSemicolons();
while (true)
{
StatementAst statement = StatementRule();
if (statement == null)
{
break;
}
_tokenizer.CheckAstIsBeforeSignature(statement);
var trapStatementAst = statement as TrapStatementAst;
if (trapStatementAst != null)
{
traps.Add(trapStatementAst);
}
else
{
statements.Add(statement);
}
// Track the last statement inside our loop so we don't use the EmptyPipeline
// as our last statement. The last statement is used to track the extent of
// this statement list.
if (firstStatement == null)
{
firstStatement = statement;
}
lastStatement = statement;
SkipNewlinesAndSemicolons();
Token token = PeekToken();
if (token.Kind == TokenKind.RParen || token.Kind == TokenKind.RCurly)
{
break;
}
}
return (firstStatement == null) ? null : ExtentOf(firstStatement, lastStatement);
}
/// <summary>
/// Parse a single statement.
/// </summary>
/// <returns>A statement ast. Never returns null, always returns PipelineAst.EmptyPipeline if there was no statement.</returns>
private StatementAst StatementRule()
{
//G statement:
//G if-statement
//G label:opt labeled-statement
//G function-statement
//G flow-control-statement statement-terminator
//G trap-statement
//G try-statement
//G data-statement
//G pipeline statement-terminator
//G
//G labeled-statement:
//G switch-statement
//G foreach-statement
//G for-statement
//G while-statement
//G do-statement
//G
//G flow-control-statement:
//G 'break' label-expression:opt
//G 'continue' label-expression:opt
//G 'throw' pipeline:opt
//G 'return' pipeline:opt
//G 'exit' pipeline:opt
//G
//G statement-terminator:
//G ';'
//G new-line-character
RuntimeHelpers.EnsureSufficientExecutionStack();
int restorePoint = 0;
StatementAst statement;
Token token = NextToken();
List<AttributeBaseAst> attributes = null;
if (token.Kind == TokenKind.Generic && token.Text[0] == '[')
{
restorePoint = token.Extent.StartOffset;
Resync(token);
attributes = AttributeListRule(false);
token = NextToken();
if (attributes != null
&& attributes.Count > 0)
{
if ((token.TokenFlags & TokenFlags.StatementDoesntSupportAttributes) != 0)
{
if (attributes.OfType<TypeConstraintAst>().Any())
{
Resync(restorePoint);
token = NextToken();
}
else
{
ReportError(attributes[0].Extent,
nameof(ParserStrings.UnexpectedAttribute),
ParserStrings.UnexpectedAttribute,
attributes[0].TypeName.FullName);
}
}
else if ((token.TokenFlags & TokenFlags.Keyword) != 0)
{
foreach (var attr in attributes.Where(attr => !(attr is AttributeAst)))
{
ReportError(attr.Extent,
nameof(ParserStrings.TypeNotAllowedBeforeStatement),
ParserStrings.TypeNotAllowedBeforeStatement,
attr.TypeName.FullName);
break;
}
}
else
{
Resync(restorePoint);
token = NextToken();
}
}
}
switch (token.Kind)
{
case TokenKind.If:
statement = IfStatementRule(token);
break;
case TokenKind.Switch:
statement = SwitchStatementRule(null, token);
break;
case TokenKind.Foreach:
statement = ForeachStatementRule(null, token);
break;
case TokenKind.For:
statement = ForStatementRule(null, token);
break;
case TokenKind.While:
statement = WhileStatementRule(null, token);
break;
case TokenKind.Do:
statement = DoWhileStatementRule(null, token);
break;
case TokenKind.Function:
case TokenKind.Filter:
case TokenKind.Workflow:
statement = FunctionDeclarationRule(token);
break;
case TokenKind.Return:
statement = ReturnStatementRule(token);
break;
case TokenKind.Throw:
statement = ThrowStatementRule(token);
break;
case TokenKind.Exit:
statement = ExitStatementRule(token);
break;
case TokenKind.Break:
statement = BreakStatementRule(token);
break;
case TokenKind.Continue:
statement = ContinueStatementRule(token);
break;
case TokenKind.Trap:
statement = TrapStatementRule(token);
break;
case TokenKind.Try:
statement = TryStatementRule(token);
break;
case TokenKind.Data:
statement = DataStatementRule(token);
break;
case TokenKind.Parallel:
case TokenKind.Sequence:
statement = BlockStatementRule(token);
break;
case TokenKind.Configuration:
statement = ConfigurationStatementRule(attributes != null ? attributes.OfType<AttributeAst>() : null, token);
break;
case TokenKind.From:
case TokenKind.Define:
case TokenKind.Var:
ReportError(token.Extent,
nameof(ParserStrings.ReservedKeywordNotAllowed),
ParserStrings.ReservedKeywordNotAllowed,
token.Kind.Text());
statement = new ErrorStatementAst(token.Extent);
break;
case TokenKind.Label:
SkipNewlines();
statement = LabeledStatementRule((LabelToken)token);
break;
case TokenKind.EndOfInput:
if (attributes != null)
{
Resync(restorePoint);
statement = PipelineRule();
}
else
{
UngetToken(token);
statement = null;
}
break;
case TokenKind.Else:
case TokenKind.ElseIf:
case TokenKind.Catch:
case TokenKind.Until:
if (ErrorList.Count > 0)
{
// If we have already seen an error, just eat these tokens. By eating the token, we won't
// generate an odd pipeline with the keyword as a command name, which should provider a better
// user experience (e.g., better syntax coloring.)
SkipNewlines();
return StatementRule();
}
goto default;
case TokenKind.DynamicKeyword:
DynamicKeyword keywordData = DynamicKeyword.GetKeyword(token.Text);
statement = DynamicKeywordStatementRule(token, keywordData);
break;
case TokenKind.Class:
statement = ClassDefinitionRule(attributes, token);
break;
case TokenKind.Enum:
statement = EnumDefinitionRule(attributes, token);
break;
case TokenKind.Using:
statement = UsingStatementRule(token);
// Report an error - usings must appear before anything else in the script, but parse it anyway
ReportError(statement.Extent,
nameof(ParserStrings.UsingMustBeAtStartOfScript),
ParserStrings.UsingMustBeAtStartOfScript);
break;
default:
if (attributes != null)
{
Resync(restorePoint);
}
else
{
UngetToken(token);
}
statement = PipelineRule();
break;
}
return statement;
}
private StringConstantExpressionAst SimpleNameRule()
{
Token token;
return SimpleNameRule(out token);
}
private StringConstantExpressionAst SimpleNameRule(out Token token)
{
try
{
_tokenizer.WantSimpleName = true;
token = PeekToken();
}
finally
{
_tokenizer.WantSimpleName = false;
}
if (token.Kind == TokenKind.Identifier)
{
// We mark the token as a member name even though it may be a loop name or data variable name.
// V2 did this, so it's at least backwards compatible.
token.TokenFlags |= TokenFlags.MemberName;
SkipToken();
return new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord);
}
return null;
}
private ExpressionAst LabelOrKeyRule()
{
//G label-expression:
//G simple-name
//G unary-expression
//G key-expression:
//G simple-name
//G unary-expression
var simpleName = SimpleNameRule();
if (simpleName != null)
{
return simpleName;
}
Token token = PeekToken();
if (token.Kind != TokenKind.NewLine && token.Kind != TokenKind.Semi)
{
ExpressionAst labelExpr;
bool disableCommaOperator = _disableCommaOperator;
try
{
_disableCommaOperator = true;
labelExpr = UnaryExpressionRule();
}
finally
{
_disableCommaOperator = disableCommaOperator;
}
if (labelExpr != null)
{
return labelExpr;
}
// If this is a label, then any token other than EOF here is effectively unreachable code. E.g.
// break & "cmd"
// Gets parsed as:
// break ; & "cmd"
}
return null;
}
private BreakStatementAst BreakStatementRule(Token breakToken)
{
//G flow-control-statement:
//G 'break' label-expression:opt
ExpressionAst labelExpr = LabelOrKeyRule();
IScriptExtent extent = (labelExpr != null)
? ExtentOf(breakToken, labelExpr)
: breakToken.Extent;
return new BreakStatementAst(extent, labelExpr);
}
private ContinueStatementAst ContinueStatementRule(Token continueToken)
{
//G flow-control-statement:
//G 'continue' label-expression:opt
ExpressionAst labelExpr = LabelOrKeyRule();
IScriptExtent extent = (labelExpr != null)
? ExtentOf(continueToken, labelExpr)
: continueToken.Extent;
return new ContinueStatementAst(extent, labelExpr);
}
private ReturnStatementAst ReturnStatementRule(Token token)
{
//G flow-control-statement:
//G 'return' pipeline:opt
PipelineBaseAst pipeline = PipelineRule();
IScriptExtent extent = (pipeline != null)
? ExtentOf(token, pipeline)
: token.Extent;
return new ReturnStatementAst(extent, pipeline);
}
private ExitStatementAst ExitStatementRule(Token token)
{
//G flow-control-statement:
//G 'exit' pipeline:opt
PipelineBaseAst pipeline = PipelineRule();
IScriptExtent extent = (pipeline != null)
? ExtentOf(token, pipeline)
: token.Extent;
return new ExitStatementAst(extent, pipeline);
}
private ThrowStatementAst ThrowStatementRule(Token token)
{
//G flow-control-statement:
//G 'throw' pipeline:opt
PipelineBaseAst pipeline = PipelineRule();
IScriptExtent extent = (pipeline != null)
? ExtentOf(token, pipeline)
: token.Extent;
return new ThrowStatementAst(extent, pipeline);
}
private StatementAst LabeledStatementRule(LabelToken label)
{
//G statement:
//G label:opt labeled-statement
//G
//G labeled-statement:
//G switch-statement
//G foreach-statement
//G for-statement
//G while-statement
//G do-statement
StatementAst statement;
Token token = NextToken();
switch (token.Kind)
{
case TokenKind.Switch:
statement = SwitchStatementRule(label, token);
break;
case TokenKind.Foreach:
statement = ForeachStatementRule(label, token);
break;
case TokenKind.For:
statement = ForStatementRule(label, token);
break;
case TokenKind.While:
statement = WhileStatementRule(label, token);
break;
case TokenKind.Do:
statement = DoWhileStatementRule(label, token);
break;
default:
// We can only unget 1 token, but have 2 to unget, so resync on the label.
Resync(label);
statement = PipelineRule();
break;
}
return statement;
}
private StatementAst BlockStatementRule(Token kindToken)
{
//G block-statement
//G keyword statement-block
StatementBlockAst body = StatementBlockRule();
// ErrorRecovery: nothing more to look for, so just return the error statement.
if (body == null)
{
ReportIncompleteInput(After(kindToken.Extent),
nameof(ParserStrings.MissingStatementAfterKeyword),
ParserStrings.MissingStatementAfterKeyword,
kindToken.Text);
return new ErrorStatementAst(ExtentOf(kindToken, kindToken));
}
return new BlockStatementAst(ExtentOf(kindToken, body), kindToken, body);
}
/// <summary>
/// Handle the InlineScript syntax in the script workflow
/// </summary>
/// <param name="inlineScriptToken"></param>
/// <param name="elements"></param>
/// <returns>
/// true -- InlineScript parsing successful
/// false -- InlineScript parsing unsuccessful
/// </returns>
private bool InlineScriptRule(Token inlineScriptToken, List<CommandElementAst> elements)
{
//G Command
//G InlineScript scriptblock-expression
Diagnostics.Assert(elements != null && elements.Count == 0, "The CommandElement list should be empty");
var commandName = new StringConstantExpressionAst(inlineScriptToken.Extent, inlineScriptToken.Text, StringConstantType.BareWord);
inlineScriptToken.TokenFlags |= TokenFlags.CommandName;
elements.Add(commandName);
SkipNewlines();
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
UngetToken(lCurly);
ReportIncompleteInput(After(inlineScriptToken),
nameof(ParserStrings.MissingStatementAfterKeyword),
ParserStrings.MissingStatementAfterKeyword,
inlineScriptToken.Text);
return false;
}
var expr = ScriptBlockExpressionRule(lCurly);
elements.Add(expr);
return true;
}
private StatementAst IfStatementRule(Token ifToken)
{
//G if-statement:
//G 'if' new-lines:opt '(' pipeline-statement ')' statement-block elseif-clauses:opt else-clause:opt
//G elseif-clauses:
//G elseif-clause
//G elseif-clauses elseif-clause
//G elseif-clause:
//G 'elseif' new-lines:opt '(' pipeline-statement ')' statement-block
//G else-clause:
//G 'else' statement-block
List<IfClause> clauses = new List<IfClause>();
List<Ast> componentAsts = new List<Ast>();
StatementBlockAst elseClause = null;
Token keyword = ifToken;
while (true)
{
SkipNewlines();
Token lParen = NextToken();
if (lParen.Kind != TokenKind.LParen)
{
// ErrorRecovery: assume user just typed 'if' or 'elseif' and hadn't started typing anything
// else yet. Next token is likely a newline, so just put it back and keep parsing.
UngetToken(lParen);
ReportIncompleteInput(After(keyword),
nameof(ParserStrings.MissingOpenParenthesisInIfStatement),
ParserStrings.MissingOpenParenthesisInIfStatement,
keyword.Text);
return new ErrorStatementAst(ExtentOf(ifToken, keyword), componentAsts);
}
SkipNewlines();
PipelineBaseAst condition = PipelineRule();
if (condition == null)
{
// ErrorRecovery: assume pipeline just hasn't been entered yet, continue hoping
// to find a close paren and statement block.
IScriptExtent errorPosition = After(lParen);
ReportIncompleteInput(errorPosition,
nameof(ParserStrings.IfStatementMissingCondition),
ParserStrings.IfStatementMissingCondition,
keyword.Text);
condition = new ErrorStatementAst(errorPosition);
}
else
{
componentAsts.Add(condition);
}
SkipNewlines();
Token rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: assume the next token is a newline or part of something else,
// so stop parsing the statement and try parsing something else if possible.
UngetToken(rParen);
// Don't bother reporting this error if we already reported an empty condition error.
if (!(condition is ErrorStatementAst))
{
ReportIncompleteInput(rParen.Extent,
nameof(ParserStrings.MissingEndParenthesisAfterStatement),
ParserStrings.MissingEndParenthesisAfterStatement,
keyword.Text);
}
return new ErrorStatementAst(ExtentOf(ifToken, Before(rParen)), componentAsts);
}
SkipNewlines();
StatementBlockAst body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: assume the next token is a newline or part of something else,
// so stop parsing the statement and try parsing something else.
ReportIncompleteInput(rParen.Extent,
nameof(ParserStrings.MissingStatementBlock),
ParserStrings.MissingStatementBlock,
keyword.Text);
return new ErrorStatementAst(ExtentOf(ifToken, rParen), componentAsts);
}
componentAsts.Add(body);
clauses.Add(new IfClause(condition, body));
// Save a restore point here. In case there is no 'elseif' or 'else' following,
// we should resync back here to preserve the possible new lines. The new lines
// could be important for the following parsing. For example, in case we are in
// a HashExpression, a new line might be needed for parsing the key-value that
// is following the if statement:
// @{
// a = if (1) {}
// b = 10
// }
int restorePoint = _ungotToken == null ? _tokenizer.GetRestorePoint() : _ungotToken.Extent.StartOffset;
SkipNewlines();
keyword = PeekToken();
if (keyword.Kind == TokenKind.ElseIf)
{
SkipToken();
continue;
}
else if (keyword.Kind == TokenKind.Else)
{
SkipToken();
SkipNewlines();
elseClause = StatementBlockRule();
if (elseClause == null)
{
// ErrorRecovery: assume the next token is a newline or part of something else,
// so stop parsing the statement and try parsing something else.
ReportIncompleteInput(After(keyword),
nameof(ParserStrings.MissingStatementBlockAfterElse),
ParserStrings.MissingStatementBlockAfterElse);
return new ErrorStatementAst(ExtentOf(ifToken, keyword), componentAsts);
}
}
else
{
// There is no 'elseif' or 'else' following, so resync back to the possible new lines.
Resync(restorePoint);
}
break;
}
IScriptExtent endExtent = (elseClause != null)
? elseClause.Extent
: clauses[clauses.Count - 1].Item2.Extent;
IScriptExtent extent = ExtentOf(ifToken, endExtent);
return new IfStatementAst(extent, clauses, elseClause);
}
private StatementAst SwitchStatementRule(LabelToken labelToken, Token switchToken)
{
//G switch-statement:
//G 'switch' new-lines:opt switch-parameters:opt switch-condition switch-body
//G switch-parameters:
//G switch-parameter
//G switch-parameters switch-parameter
//G switch-parameter:
//G '-regex'
//G '-wildcard'
//G '-exact'
//G '-casesensitive'
//G '-parallel'
//G switch-condition:
//G '(' new-lines:opt pipeline new-lines:opt ')'
//G -file new-lines:opt switch-filename
//G switch-filename:
//G command-argument
//G primary-expression
//G switch-body:
//G new-lines:opt '{' new-lines:opt switch-clauses '}'
//G switch-clauses:
//G switch-clause
//G switch-clauses switch-clause
//G switch-clause:
//G switch-clause-condition statement-block statement-terminators:opt
//G switch-clause-condition:
//G command-argument
//G primary-expression
IScriptExtent startExtent = (labelToken ?? switchToken).Extent;
IScriptExtent endErrorStatement = startExtent;
bool isError = false;
bool isIncompleteError = false;
SkipNewlines();
bool needErrorCondition = false; // Only used to track if we need to include (condition) ast for the error statement.
PipelineBaseAst condition = null;
Dictionary<string, Tuple<Token, Ast>> specifiedFlags = null; // Only used to track all flags specified for the error ast
Token switchParameterToken = PeekToken();
SwitchFlags flags = SwitchFlags.None;
while (switchParameterToken.Kind == TokenKind.Parameter)
{
SkipToken();
endErrorStatement = switchParameterToken.Extent;
specifiedFlags = specifiedFlags ?? new Dictionary<string, Tuple<Token, Ast>>();
if (IsSpecificParameter(switchParameterToken, "regex"))
{
flags |= SwitchFlags.Regex;
flags &= ~SwitchFlags.Wildcard;
if (!specifiedFlags.ContainsKey("regex"))
{
specifiedFlags.Add("regex", new Tuple<Token, Ast>(switchParameterToken, null));
}
}
else if (IsSpecificParameter(switchParameterToken, "wildcard"))
{
flags |= SwitchFlags.Wildcard;
flags &= ~SwitchFlags.Regex;
if (!specifiedFlags.ContainsKey("wildcard"))
{
specifiedFlags.Add("wildcard", new Tuple<Token, Ast>(switchParameterToken, null));
}
}
else if (IsSpecificParameter(switchParameterToken, "exact"))
{
flags &= ~SwitchFlags.Regex;
flags &= ~SwitchFlags.Wildcard;
if (!specifiedFlags.ContainsKey("exact"))
{
specifiedFlags.Add("exact", new Tuple<Token, Ast>(switchParameterToken, null));
}
}
else if (IsSpecificParameter(switchParameterToken, "casesensitive"))
{
flags |= SwitchFlags.CaseSensitive;
if (!specifiedFlags.ContainsKey("casesensitive"))
{
specifiedFlags.Add("casesensitive", new Tuple<Token, Ast>(switchParameterToken, null));
}
}
else if (IsSpecificParameter(switchParameterToken, "parallel"))
{
flags |= SwitchFlags.Parallel;
if (!specifiedFlags.ContainsKey("parallel"))
{
specifiedFlags.Add("parallel", new Tuple<Token, Ast>(switchParameterToken, null));
}
}
else if (IsSpecificParameter(switchParameterToken, "file"))
{
flags |= SwitchFlags.File;
SkipNewlines();
ExpressionAst fileNameExpr = GetSingleCommandArgument(CommandArgumentContext.FileName);
if (fileNameExpr == null)
{
// ErrorRecovery: pretend we saw the filename and continue.
isError = true;
isIncompleteError = ReportIncompleteInput(After(switchParameterToken),
nameof(ParserStrings.MissingFilenameOption),
ParserStrings.MissingFilenameOption);
if (!specifiedFlags.ContainsKey("file"))
{
specifiedFlags.Add("file", new Tuple<Token, Ast>(switchParameterToken, null));
}
}
else
{
endErrorStatement = fileNameExpr.Extent;
condition = new PipelineAst(fileNameExpr.Extent,
new CommandExpressionAst(fileNameExpr.Extent, fileNameExpr, null), background: false);
if (!specifiedFlags.ContainsKey("file"))
{
specifiedFlags.Add("file", new Tuple<Token, Ast>(switchParameterToken, condition));
}
}
}
else
{
// ErrorRecovery: just ignore the token, continue parsing.
isError = true;
ReportError(switchParameterToken.Extent,
nameof(ParserStrings.InvalidSwitchFlag),
ParserStrings.InvalidSwitchFlag,
((ParameterToken)switchParameterToken).ParameterName);
}
switchParameterToken = PeekToken();
}
if (switchParameterToken.Kind == TokenKind.Minus)
{
specifiedFlags = specifiedFlags ?? new Dictionary<string, Tuple<Token, Ast>>();
specifiedFlags.Add(VERBATIM_ARGUMENT, new Tuple<Token, Ast>(switchParameterToken, null));
}
Token lParen = PeekToken();
if (lParen.Kind == TokenKind.LParen)
{
endErrorStatement = lParen.Extent;
SkipToken();
if ((flags & SwitchFlags.File) == SwitchFlags.File)
{
// ErrorRecovery: nothing special this is a semantic error.
isError = true;
ReportError(lParen.Extent,
nameof(ParserStrings.PipelineValueRequired),
ParserStrings.PipelineValueRequired);
}
needErrorCondition = true; // need to add condition ast to the error statement if the parsing fails
SkipNewlines();
condition = PipelineRule();
if (condition == null)
{
// ErrorRecovery: pretend we saw the condition and keep parsing.
isError = true;
isIncompleteError = ReportIncompleteInput(After(lParen),
nameof(ParserStrings.PipelineValueRequired),
ParserStrings.PipelineValueRequired);
}
else
{
endErrorStatement = condition.Extent;
}
SkipNewlines();
Token rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: Try to parse the switch body, if we don't find a body, then bail.
UngetToken(rParen);
if (!isIncompleteError)
{
isError = true;
isIncompleteError =
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingEndParenthesisInSwitchStatement),
ParserStrings.MissingEndParenthesisInSwitchStatement);
}
}
else
{
endErrorStatement = rParen.Extent;
}
}
else if (condition == null)
{
if ((flags & SwitchFlags.File) == 0)
{
isError = true;
isIncompleteError = ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.PipelineValueRequired),
ParserStrings.PipelineValueRequired);
}
else
{
Diagnostics.Assert(isError, "An error should already have been issued");
}
}
SkipNewlines();
Token lCurly = NextToken();
StatementBlockAst @default = null;
List<SwitchClause> clauses = new List<SwitchClause>();
List<Ast> errorAsts = new List<Ast>(); // in case there is an error, we want the asts parsed up to the error.
Token rCurly = null;
if (lCurly.Kind != TokenKind.LCurly)
{
// ErrorRecovery: Assume we don't have any switch body to parse.
UngetToken(lCurly);
if (!isIncompleteError)
{
isError = true;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingCurlyBraceInSwitchStatement),
ParserStrings.MissingCurlyBraceInSwitchStatement);
}
}
else
{
endErrorStatement = lCurly.Extent;
SkipNewlines();
while (true)
{
ExpressionAst clauseCondition = GetSingleCommandArgument(CommandArgumentContext.SwitchCondition);
if (clauseCondition == null)
{
// ErrorRecovery: if we don't have anything that looks like a condition, we won't
// find a body (because a body is just a script block, which works as a condition.)
// So don't look for a body, hope we find the '}' next.
isError = true;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingSwitchConditionExpression),
ParserStrings.MissingSwitchConditionExpression);
// Consume a closing curly, if there is one, to avoid an extra error
if (PeekToken().Kind == TokenKind.RCurly)
{
SkipToken();
}
break;
}
errorAsts.Add(clauseCondition);
endErrorStatement = clauseCondition.Extent;
StatementBlockAst clauseBody = StatementBlockRule();
if (clauseBody == null)
{
// ErrorRecovery: We might find another condition/body pair, so keep going.
isError = true;
isIncompleteError = ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingSwitchStatementClause),
ParserStrings.MissingSwitchStatementClause);
}
else
{
errorAsts.Add(clauseBody);
endErrorStatement = clauseBody.Extent;
var clauseConditionString = clauseCondition as StringConstantExpressionAst;
if (clauseConditionString != null &&
clauseConditionString.StringConstantType == StringConstantType.BareWord &&
clauseConditionString.Value.Equals("default", StringComparison.OrdinalIgnoreCase))
{
if (@default != null)
{
// ErrorRecovery: just report the error and continue, forget the previous default clause.
isError = true;
ReportError(clauseCondition.Extent,
nameof(ParserStrings.MultipleSwitchDefaultClauses),
ParserStrings.MultipleSwitchDefaultClauses);
}
@default = clauseBody;
}
else
{
clauses.Add(new SwitchClause(clauseCondition, clauseBody));
}
}
SkipNewlinesAndSemicolons();
Token token = PeekToken();
if (token.Kind == TokenKind.RCurly)
{
rCurly = token;
SkipToken();
break;
}
if (token.Kind == TokenKind.EndOfInput)
{
if (!isIncompleteError)
{
isError = true;
ReportIncompleteInput(lCurly.Extent,
token.Extent,
nameof(ParserStrings.MissingEndCurlyBrace),
ParserStrings.MissingEndCurlyBrace);
}
break;
}
}
}
if (isError)
{
return new ErrorStatementAst(ExtentOf(startExtent, endErrorStatement), switchToken, specifiedFlags,
needErrorCondition ? GetNestedErrorAsts(condition) : null, GetNestedErrorAsts(errorAsts));
}
return new SwitchStatementAst(ExtentOf(labelToken ?? switchToken, rCurly),
labelToken != null ? labelToken.LabelText : null, condition, flags, clauses, @default);
}
private StatementAst ConfigurationStatementRule(IEnumerable<AttributeAst> customAttributes, Token configurationToken)
{
//G configuration-statement:
//G 'configuration' new-lines:opt singleNameExpression new-lines:opt statement-block
//G singleNameExpression:
//G command-argument
//G primary-expression
IScriptExtent startExtent = configurationToken.Extent;
IScriptExtent endErrorStatement = startExtent;
bool isError = false;
// The expression that returns the configuration name.
ExpressionAst configurationName;
string simpleConfigurationNameValue = null;
SkipNewlines();
Token configurationNameToken = NextToken();
Token configurationKeywordToken = configurationNameToken;
if (configurationNameToken.Kind == TokenKind.LCurly)
{
ReportError(After(startExtent),
nameof(ParserStrings.MissingConfigurationName),
ParserStrings.MissingConfigurationName);
// Try reading the configuration body - this should keep the parse in sync - but we won't return it
ScriptBlockExpressionRule(configurationNameToken);
return null;
}
if (configurationNameToken.Kind == TokenKind.EndOfInput)
{
UngetToken(configurationNameToken);
ReportIncompleteInput(After(configurationNameToken.Extent),
nameof(ParserStrings.MissingConfigurationName),
ParserStrings.MissingConfigurationName);
return null;
}
// Unget the configuration token so it can possibly be re-read as part of an expression
UngetToken(configurationNameToken);
// Finally read the name for this configuration
configurationName = GetWordOrExpression(configurationNameToken);
if (configurationName == null)
{
isError = true;
ReportIncompleteInput(configurationNameToken.Extent,
nameof(ParserStrings.MissingConfigurationName),
ParserStrings.MissingConfigurationName);
}
else
{
object outValue;
if (IsConstantValueVisitor.IsConstant(configurationName, out outValue))
{
simpleConfigurationNameValue = outValue as string;
if (simpleConfigurationNameValue == null ||
!System.Text.RegularExpressions.Regex.IsMatch(simpleConfigurationNameValue, "^[A-Za-z][A-Za-z0-9_./-]*$"))
{
// This is actually a semantics check, the syntax is fine at this point.
// Continue parsing to get as much information as possible
isError = true;
ReportError(configurationName.Extent,
nameof(ParserStrings.InvalidConfigurationName),
ParserStrings.InvalidConfigurationName,
simpleConfigurationNameValue ?? string.Empty);
}
}
}
SkipNewlines();
//
// Load the system classes and import them as keywords
//
Runspaces.Runspace localRunspace = null;
bool topLevel = false;
try
{
// At this point, we'll need a runspace to use to hold the metadata for the parse. If there is no
// current runspace to use, we create one and set it to be the default for this thread...
if (Runspaces.Runspace.DefaultRunspace == null)
{
localRunspace =
Runspaces.RunspaceFactory.CreateRunspace(Runspaces.InitialSessionState.CreateDefault2());
localRunspace.ThreadOptions = Runspaces.PSThreadOptions.UseCurrentThread;
localRunspace.Open();
Runspaces.Runspace.DefaultRunspace = localRunspace;
}
// Configuration is not supported on ARM or in ConstrainedLanguage
if (PsUtils.IsRunningOnProcessorArchitectureARM() || Runspace.DefaultRunspace.ExecutionContext.LanguageMode == PSLanguageMode.ConstrainedLanguage)
{
ReportError(configurationToken.Extent,
nameof(ParserStrings.ConfigurationNotAllowedInConstrainedLanguage),
ParserStrings.ConfigurationNotAllowedInConstrainedLanguage,
configurationToken.Kind.Text());
return null;
}
// Configuration is not supported on WinPE
if (Utils.IsWinPEHost())
{
ReportError(configurationToken.Extent,
nameof(ParserStrings.ConfigurationNotAllowedOnWinPE),
ParserStrings.ConfigurationNotAllowedOnWinPE,
configurationToken.Kind.Text());
return null;
}
ExpressionAst configurationBodyScriptBlock = null;
// Automatically import the PSDesiredStateConfiguration module at this point.
PowerShell p = null;
// Save the parser we're using so we can resume the current parse when we're done.
var currentParser = Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser;
Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser = new Parser();
try
{
if (localRunspace != null)
{
p = PowerShell.Create();
p.Runspace = localRunspace;
}
else
{
p = PowerShell.Create(RunspaceMode.CurrentRunspace);
}
try
{
// See of the default CIM keywords are already loaded. If they haven't been
// then this is the top level. Record that information and then load the defaults
// keywords.
if (DynamicKeyword.GetKeyword("OMI_ConfigurationDocument") == null)
{
// Load the default CIM keywords
Collection<Exception> CIMKeywordErrors = new Collection<Exception>();
Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.LoadDefaultCimKeywords(CIMKeywordErrors);
// Report any errors encountered while loading CIM dynamic keywords.
if (CIMKeywordErrors.Count > 0)
{
ReportErrorsAsWarnings(CIMKeywordErrors);
}
// Load any keywords that have been defined earlier in the script.
if (_configurationKeywordsDefinedInThisFile != null)
{
foreach (var kw in _configurationKeywordsDefinedInThisFile.Values)
{
if (!DynamicKeyword.ContainsKeyword(kw.Keyword))
{
DynamicKeyword.AddKeyword(kw);
}
}
}
topLevel = true;
}
}
catch (Exception e)
{
// This shouldn't happen - the system classes should always be good, but just in case,
// we'll catch the exception and report it as an error.
ReportError(configurationKeywordToken.Extent,
nameof(ParserStrings.ParserError),
ParserStrings.ParserError,
e.ToString());
return null;
}
}
finally
{
if (p != null)
{
p.Dispose();
}
//
// Put the parser back...
//
Runspaces.Runspace.DefaultRunspace.ExecutionContext.Engine.EngineParser = currentParser;
}
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
ReportIncompleteInput(After(lCurly.Extent),
nameof(ParserStrings.MissingCurlyInConfigurationStatement),
ParserStrings.MissingCurlyInConfigurationStatement);
isError = true;
UngetToken(lCurly);
}
else
{
var oldInConfiguration = _inConfiguration;
try
{
_inConfiguration = true;
configurationBodyScriptBlock = ScriptBlockExpressionRule(lCurly);
}
finally
{
_inConfiguration = oldInConfiguration;
}
if (configurationBodyScriptBlock == null)
{
ReportError(After(lCurly.Extent),
nameof(ParserStrings.ConfigurationBodyEmpty),
ParserStrings.ConfigurationBodyEmpty);
return null;
}
}
if (isError)
{
return new ErrorStatementAst(ExtentOf(startExtent, endErrorStatement), configurationToken);
}
#region "Add Configuration Keywords"
// If the configuration name is a constant string, then
// if we're not at the top level, we'll add it to the list of configuration resource keywords.
// If we are at the top level, then we'll add it to the list of keywords defined in this
// parse so it can be used as a resource in subsequent config statements.
var scAst = configurationName as StringConstantExpressionAst;
if (scAst != null)
{
var keywordToAddForThisConfigurationStatement = new System.Management.Automation.Language.DynamicKeyword
{
BodyMode = DynamicKeywordBodyMode.Hashtable,
ImplementingModule = _keywordModuleName,
Keyword = scAst.Value,
NameMode = DynamicKeywordNameMode.NameRequired,
DirectCall = true,
};
// Add the DependsOn property.
var dependsOnProp = new DynamicKeywordProperty
{
Mandatory = true,
Name = "DependsOn",
};
keywordToAddForThisConfigurationStatement.Properties.Add(dependsOnProp.Name, dependsOnProp);
// Add the PsDscRunAsCredential property.
var RunAsProp = new DynamicKeywordProperty
{
Mandatory = true,
Name = "PsDscRunAsCredential",
};
keywordToAddForThisConfigurationStatement.Properties.Add(RunAsProp.Name, RunAsProp);
// Extract the parameters, if any and them to the keyword definition.
var sbeAst = configurationBodyScriptBlock as ScriptBlockExpressionAst;
if (sbeAst != null)
{
var pList = sbeAst.ScriptBlock.ParamBlock;
if (pList != null)
{
foreach (var parm in pList.Parameters)
{
var keywordProp = new DynamicKeywordProperty();
keywordProp.Name = parm.Name.VariablePath.UserPath;
if (parm.Attributes != null)
{
foreach (var attr in parm.Attributes)
{
var typeConstraint = attr as TypeConstraintAst;
if (typeConstraint != null)
{
keywordProp.TypeConstraint = typeConstraint.TypeName.Name;
continue;
}
var aAst = attr as AttributeAst;
if (aAst != null)
{
if (string.Equals(aAst.TypeName.Name, "Parameter", StringComparison.OrdinalIgnoreCase))
{
if (aAst.NamedArguments != null)
{
foreach (var na in aAst.NamedArguments)
{
if (string.Equals(na.ArgumentName, "Mandatory", StringComparison.OrdinalIgnoreCase))
{
if (na.ExpressionOmitted)
{
keywordProp.Mandatory = true;
}
else if (na.Argument != null)
{
ConstantExpressionAst ceAst = na.Argument as ConstantExpressionAst;
if (ceAst != null)
{
keywordProp.Mandatory = System.Management.Automation.LanguagePrimitives.IsTrue(ceAst.Value);
}
}
}
}
}
}
}
}
}
keywordToAddForThisConfigurationStatement.Properties.Add(keywordProp.Name, keywordProp);
}
}
}
if (topLevel)
{
if (_configurationKeywordsDefinedInThisFile == null)
{
_configurationKeywordsDefinedInThisFile = new Dictionary<string, DynamicKeyword>();
}
_configurationKeywordsDefinedInThisFile[keywordToAddForThisConfigurationStatement.Keyword] = keywordToAddForThisConfigurationStatement;
}
else
{
System.Management.Automation.Language.DynamicKeyword.AddKeyword(keywordToAddForThisConfigurationStatement);
}
}
#endregion
// End of dynamic keyword definition for this function...
//############################################################
//############################################################
bool isMetaConfiguration = false;
if (customAttributes != null)
{
isMetaConfiguration = customAttributes.Any(
attribute => (attribute.TypeName.GetReflectionAttributeType() != null) &&
(attribute.TypeName.GetReflectionAttributeType() == typeof(DscLocalConfigurationManagerAttribute)));
}
ScriptBlockExpressionAst bodyAst = configurationBodyScriptBlock as ScriptBlockExpressionAst;
IScriptExtent configurationExtent = ExtentOf(startExtent, bodyAst);
return new ConfigurationDefinitionAst(configurationExtent,
bodyAst,
isMetaConfiguration ? ConfigurationType.Meta : ConfigurationType.Resource,
configurationName)
{
LCurlyToken = lCurly,
ConfigurationToken = configurationToken,
CustomAttributes = customAttributes,
DefinedKeywords = DynamicKeyword.GetKeyword()
};
}
catch (Exception e)
{
// In theory this should never happen so if it does, we'll report the actual exception rather than introducing a new message
ReportError(configurationKeywordToken.Extent,
nameof(ParserStrings.ParserError),
ParserStrings.ParserError,
"ConfigurationStatementToken: " + e);
return null;
}
finally
{
// If we had to allocate a runspace for the parser, we'll free it now.
if (localRunspace != null)
{
// If a runspace was created for this operation, close it and reset the default runspace to null.
localRunspace.Close();
Runspaces.Runspace.DefaultRunspace = null;
}
if (topLevel)
{
//
// Clear out all of the cached classes and keywords.
// They will need to be reloaded when the generated function is actually run.
//
Microsoft.PowerShell.DesiredStateConfiguration.Internal.DscClassCache.ClearCache();
System.Management.Automation.Language.DynamicKeyword.Reset();
}
// Finally resync the tokenizer at the current position
// this will flush any cached dynamic keyword tokens.
var restorePoint = _tokenizer.GetRestorePoint();
Resync(restorePoint);
}
}
private Dictionary<string, DynamicKeyword> _configurationKeywordsDefinedInThisFile;
/// <summary>
/// Reads an argument expression for a keyword or keyword parameter.
/// This can be either a bare word or an expression
/// </summary>
/// <param name="keywordToken">the token of the associated keyword</param>
private ExpressionAst GetWordOrExpression(Token keywordToken)
{
Token nameToken = NextToken();
if (nameToken.Kind == TokenKind.EndOfInput)
{
// ErrorRecovery: report incomplete statement and return
UngetToken(nameToken);
ReportIncompleteInput(After(keywordToken),
nameof(ParserStrings.RequiredNameOrExpressionMissing),
ParserStrings.RequiredNameOrExpressionMissing);
return null;
}
ExpressionAst argument = GetCommandArgument(CommandArgumentContext.CommandArgument, nameToken);
if (argument == null)
{
var extent = keywordToken.Extent;
ReportError(After(extent),
nameof(ParserStrings.ParameterRequiresArgument),
ParserStrings.ParameterRequiresArgument,
keywordToken.Text);
}
return argument;
}
private StatementAst ForeachStatementRule(LabelToken labelToken, Token forEachToken)
{
//G foreach-statement:
//G 'foreach' new-lines:opt foreach-parameters:opt new-lines:opt '('
//G new-lines:opt variable new-lines:opt 'in' new-lines:opt pipeline
//G new-lines:opt ')' statement-block
//G foreach-parameters:
//G foreach-parameter
//G foreach-parameters foreach-parameter
//G foreach-parameter:
//G '-parallel'
//G '-throttlelimit' new-lines:opt foreach-throttlelimit
//G foreach-throttlelimit:
//G command-argument
//G primary-expression
IScriptExtent startOfStatement = labelToken != null ? labelToken.Extent : forEachToken.Extent;
IScriptExtent endErrorStatement = null;
SkipNewlines();
// Process parameters on foreach
Token foreachParameterToken = PeekToken();
ForEachFlags flags = ForEachFlags.None;
ExpressionAst throttleLimit = null;
while (foreachParameterToken.Kind == TokenKind.Parameter)
{
SkipToken();
if (IsSpecificParameter(foreachParameterToken, "parallel"))
{
flags |= ForEachFlags.Parallel;
}
else if (IsSpecificParameter(foreachParameterToken, "throttlelimit"))
{
SkipNewlines();
throttleLimit = GetSingleCommandArgument(CommandArgumentContext.CommandArgument);
if (throttleLimit == null)
{
// ErrorRecovery: pretend we saw the throttle limit and continue.
ReportIncompleteInput(After(foreachParameterToken),
nameof(ParserStrings.MissingThrottleLimit),
ParserStrings.MissingThrottleLimit);
}
}
else
{
// ErrorRecovery: just ignore the token, continue parsing.
endErrorStatement = foreachParameterToken.Extent;
ReportError(foreachParameterToken.Extent,
nameof(ParserStrings.InvalidForeachFlag),
ParserStrings.InvalidForeachFlag,
((ParameterToken)foreachParameterToken).ParameterName);
}
SkipNewlines();
foreachParameterToken = PeekToken();
}
Token lParen = NextToken();
if (lParen.Kind != TokenKind.LParen)
{
// ErrorRecovery: assume the rest of the statement is missing.
UngetToken(lParen);
endErrorStatement = forEachToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingOpenParenthesisAfterKeyword),
ParserStrings.MissingOpenParenthesisAfterKeyword,
forEachToken.Kind.Text());
return new ErrorStatementAst(ExtentOf(startOfStatement, endErrorStatement));
}
SkipNewlines();
Token token = NextToken();
if (token.Kind != TokenKind.Variable && token.Kind != TokenKind.SplattedVariable)
{
// ErrorRecovery: assume the rest of the statement is missing.
UngetToken(token);
endErrorStatement = lParen.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingVariableNameAfterForeach),
ParserStrings.MissingVariableNameAfterForeach);
return new ErrorStatementAst(ExtentOf(startOfStatement, endErrorStatement));
}
var variableAst = new VariableExpressionAst((VariableToken)token);
SkipNewlines();
PipelineBaseAst pipeline = null;
StatementBlockAst body = null;
Token inToken = NextToken();
if (inToken.Kind != TokenKind.In)
{
// ErrorRecovery: assume the rest of the statement is missing.
UngetToken(inToken);
endErrorStatement = variableAst.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingInInForeach),
ParserStrings.MissingInInForeach);
}
else
{
SkipNewlines();
pipeline = PipelineRule();
if (pipeline == null)
{
// ErrorRecovery: assume the rest of the statement is missing.
endErrorStatement = inToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingForeachExpression),
ParserStrings.MissingForeachExpression);
}
else
{
SkipNewlines();
Token rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: assume the rest of the statement is missing.
UngetToken(rParen);
endErrorStatement = pipeline.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingEndParenthesisAfterForeach),
ParserStrings.MissingEndParenthesisAfterForeach);
}
else
{
body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: nothing more to look for, so just return the error statement.
endErrorStatement = rParen.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingForeachStatement),
ParserStrings.MissingForeachStatement);
}
}
}
}
if (endErrorStatement != null)
{
return new ErrorStatementAst(ExtentOf(startOfStatement, endErrorStatement),
GetNestedErrorAsts(variableAst, pipeline, body));
}
return new ForEachStatementAst(ExtentOf(startOfStatement, body),
labelToken != null ? labelToken.LabelText : null,
flags,
throttleLimit, variableAst, pipeline, body);
}
private StatementAst ForStatementRule(LabelToken labelToken, Token forToken)
{
//G for-statement:
//G 'for' new-lines:opt '('
//G new-lines:opt for-initializer:opt statement-terminator
//G new-lines:opt for-condition:opt statement-terminator
//G new-lines:opt for-iterator:opt
//G new-lines:opt ')' statement-block
//G 'for' new-lines:opt '('
//G new-lines:opt for-initializer:opt statement-terminator
//G new-lines:opt for-condition:opt
//G new-lines:opt ')' statement-block
//G 'for' new-lines:opt '('
//G new-lines:opt for-initializer:opt
//G new-lines:opt ')' statement-block
//G for-initializer:
//G pipeline
//G for-condition:
//G pipeline
//G for-iterator:
//G pipeline
IScriptExtent endErrorStatement = null;
SkipNewlines();
Token lParen = NextToken();
if (lParen.Kind != TokenKind.LParen)
{
// ErrorRecovery: don't continue parsing the for statement.
UngetToken(lParen);
endErrorStatement = forToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingOpenParenthesisAfterKeyword),
ParserStrings.MissingOpenParenthesisAfterKeyword,
forToken.Kind.Text());
return new ErrorStatementAst(ExtentOf(labelToken ?? forToken, endErrorStatement));
}
SkipNewlines();
PipelineBaseAst initializer = PipelineRule();
if (initializer != null)
{
endErrorStatement = initializer.Extent;
}
if (PeekToken().Kind == TokenKind.Semi)
{
endErrorStatement = NextToken().Extent;
}
SkipNewlines();
PipelineBaseAst condition = PipelineRule();
if (condition != null)
{
endErrorStatement = condition.Extent;
}
if (PeekToken().Kind == TokenKind.Semi)
{
endErrorStatement = NextToken().Extent;
}
SkipNewlines();
PipelineBaseAst iterator = PipelineRule();
if (iterator != null)
{
endErrorStatement = iterator.Extent;
}
SkipNewlines();
Token rParen = NextToken();
StatementBlockAst body = null;
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: don't continue parsing the for statement.
UngetToken(rParen);
if (endErrorStatement == null)
{
endErrorStatement = lParen.Extent;
}
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingEndParenthesisAfterStatement),
ParserStrings.MissingEndParenthesisAfterStatement,
forToken.Kind.Text());
}
else
{
body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: return an error statement.
endErrorStatement = rParen.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingLoopStatement),
ParserStrings.MissingLoopStatement,
forToken.Kind.Text());
}
}
if (body == null)
{
return new ErrorStatementAst(ExtentOf(labelToken ?? forToken, endErrorStatement),
GetNestedErrorAsts(initializer, condition, iterator));
}
return new ForStatementAst(ExtentOf(labelToken ?? forToken, body),
labelToken != null ? labelToken.LabelText : null, initializer, condition, iterator, body);
}
private StatementAst WhileStatementRule(LabelToken labelToken, Token whileToken)
{
//G while-statement:
//G 'while ' new-lines:opt '(' new-lines:opt while-condition new-lines:opt ')' statement-block
SkipNewlines();
Token lParen = NextToken();
if (lParen.Kind != TokenKind.LParen)
{
// ErrorRecovery: assume user just typed 'while' and hadn't started typing anything
// else yet. Next token is likely a newline, so just put it back and keep parsing.
UngetToken(lParen);
ReportIncompleteInput(After(whileToken),
nameof(ParserStrings.MissingOpenParenthesisAfterKeyword),
ParserStrings.MissingOpenParenthesisAfterKeyword,
whileToken.Text);
return new ErrorStatementAst(ExtentOf(labelToken ?? whileToken, whileToken));
}
SkipNewlines();
PipelineBaseAst condition = PipelineRule();
PipelineBaseAst errorCondition = null;
if (condition == null)
{
// ErrorRecovery: assume pipeline just hasn't been entered yet, continue hoping
// to find a close paren and statement block.
IScriptExtent errorPosition = After(lParen);
ReportIncompleteInput(errorPosition,
nameof(ParserStrings.MissingExpressionAfterKeyword),
ParserStrings.MissingExpressionAfterKeyword,
whileToken.Kind.Text());
condition = new ErrorStatementAst(errorPosition);
}
else
{
errorCondition = condition;
}
SkipNewlines();
Token rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: assume the next token is a newline or part of something else,
// so stop parsing the statement and try parsing something else if possible.
UngetToken(rParen);
if (!(condition is ErrorStatementAst))
{
ReportIncompleteInput(After(condition),
nameof(ParserStrings.MissingEndParenthesisAfterStatement),
ParserStrings.MissingEndParenthesisAfterStatement,
whileToken.Kind.Text());
}
return new ErrorStatementAst(ExtentOf(labelToken ?? whileToken, condition), GetNestedErrorAsts(errorCondition));
}
SkipNewlines();
StatementBlockAst body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: assume the next token is a newline or part of something else.
ReportIncompleteInput(After(rParen),
nameof(ParserStrings.MissingLoopStatement),
ParserStrings.MissingLoopStatement,
whileToken.Kind.Text());
return new ErrorStatementAst(ExtentOf(labelToken ?? whileToken, rParen), GetNestedErrorAsts(errorCondition));
}
return new WhileStatementAst(ExtentOf(labelToken ?? whileToken, body),
labelToken != null ? labelToken.LabelText : null, condition, body);
}
/// <summary>
/// Parse a dynamic keyword statement which will be either of the form
/// keyword [parameters] [name] { a=1; b=2; } # constructor with properties
/// or
/// keyword [parameters] [name] { ... } # constructor with a simple body.
/// or keywordcommand parameters
/// This custom keyword does not introduce a new AST node type. Instead it generates a
/// CommandAst that calls a PowerShell command to implement the keyword's logic.
/// This command has one of two signatures:
/// keywordImplCommand
/// </summary>
/// <param name="functionName">The name of the function to invoke</param>
/// <param name="keywordData">The data for this keyword definition</param>
/// <returns></returns>
private StatementAst DynamicKeywordStatementRule(Token functionName, DynamicKeyword keywordData)
{
//////////////////////////////////////////////////////////////////////////////////
// If a custom action was provided. then invoke it
//////////////////////////////////////////////////////////////////////////////////
if (keywordData.PreParse != null)
{
try
{
ParseError[] errors = keywordData.PreParse(keywordData);
if (errors != null && errors.Length > 0)
{
foreach (var e in errors)
{
ReportError(e);
}
}
}
catch (Exception e)
{
ReportError(functionName.Extent,
nameof(ParserStrings.DynamicKeywordPreParseException),
ParserStrings.DynamicKeywordPreParseException,
keywordData.ResourceName, e.ToString());
return null;
}
}
if (keywordData.IsReservedKeyword)
{
// ErrorRecovery: eat the token
ReportError(functionName.Extent,
nameof(ParserStrings.UnsupportedReservedKeyword),
ParserStrings.UnsupportedReservedKeyword,
keywordData.Keyword);
return null;
}
if (keywordData.HasReservedProperties)
{
// ErrorRecovery: eat the token
ReportError(functionName.Extent,
nameof(ParserStrings.UnsupportedReservedProperty),
ParserStrings.UnsupportedReservedProperty,
"'Require', 'Trigger', 'Notify', 'Before', 'After' and 'Subscribe'");
return null;
}
string elementName = string.Empty;
DynamicKeywordStatementAst dynamicKeywordAst;
if (keywordData.BodyMode == DynamicKeywordBodyMode.Command)
{
UngetToken(functionName);
dynamicKeywordAst = (DynamicKeywordStatementAst)CommandRule(forDynamicKeyword: true);
dynamicKeywordAst.Keyword = keywordData;
dynamicKeywordAst.FunctionName = functionName;
}
else
{
SkipNewlines();
// The expression that returns the resource name or names.
ExpressionAst instanceName = null;
Token nameToken = NextToken();
if (nameToken.Kind == TokenKind.EndOfInput)
{
UngetToken(nameToken);
if (keywordData.NameMode == DynamicKeywordNameMode.NameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired)
{
ReportIncompleteInput(After(functionName),
nameof(ParserStrings.RequiredNameOrExpressionMissing),
ParserStrings.RequiredNameOrExpressionMissing);
}
else
{
// Name not required so report missing brace
ReportIncompleteInput(After(functionName.Extent),
nameof(ParserStrings.MissingBraceInObjectDefinition),
ParserStrings.MissingBraceInObjectDefinition);
}
return null;
}
// If it's an lcurly, then no name was provided, and we skip to the body processing
Token lCurly = null;
if (nameToken.Kind == TokenKind.LCurly)
{
lCurly = nameToken;
if (keywordData.NameMode == DynamicKeywordNameMode.NameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired)
{
ReportError(After(functionName),
nameof(ParserStrings.RequiredNameOrExpressionMissing),
ParserStrings.RequiredNameOrExpressionMissing);
UngetToken(nameToken);
return null;
}
}
else if (nameToken.Kind == TokenKind.Identifier || nameToken.Kind == TokenKind.DynamicKeyword)
{
if (keywordData.NameMode == DynamicKeywordNameMode.NoName)
{
ReportError(After(functionName),
nameof(ParserStrings.UnexpectedNameForType),
ParserStrings.UnexpectedNameForType,
functionName.Text,
nameToken.Text);
UngetToken(nameToken);
return null;
}
// If it's an identifier then this is the name for the data object
elementName = nameToken.Text;
// If only a simple name is allowed, then the string must be non-null.
if ((keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName) && string.IsNullOrEmpty(elementName))
{
ReportIncompleteInput(After(functionName),
nameof(ParserStrings.RequiredNameOrExpressionMissing),
ParserStrings.RequiredNameOrExpressionMissing);
UngetToken(nameToken);
return null;
}
}
else
{
// see if an expression was provided instead of a bare word...
UngetToken(nameToken);
instanceName = GetSingleCommandArgument(CommandArgumentContext.CommandName);
if (instanceName == null)
{
if (keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName)
{
ReportError(After(functionName),
nameof(ParserStrings.RequiredNameOrExpressionMissing),
ParserStrings.RequiredNameOrExpressionMissing);
}
else
{
// It wasn't an '{' and it wasn't a name expression so it's a unexpected token.
ReportError(After(functionName),
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
nameToken.Text);
}
return null;
}
// Ok, we got a name expression, but we're expecting no name, so it's and error.
if (keywordData.NameMode == DynamicKeywordNameMode.NoName)
{
ReportError(After(functionName),
nameof(ParserStrings.UnexpectedNameForType),
ParserStrings.UnexpectedNameForType,
functionName.Text,
instanceName.ToString());
return null;
}
// We were expecting a simple name so report an error
if (keywordData.NameMode == DynamicKeywordNameMode.SimpleNameRequired || keywordData.NameMode == DynamicKeywordNameMode.SimpleOptionalName)
{
// If no match, then this is an incomplete token BUGBUG fix message
ReportError(nameToken.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
nameToken.Text);
return null;
}
}
// If we didn't get a resource expression AST, then we need to build one out of the
// name that was specified. It may be the case that we don't have
// a resource name in which case it will be the empty string. Even in the cases were
// we aren't expecting a name, we still do this so that the signature of the implementing function remains
// the same.
ExpressionAst originalInstanceName = instanceName;
if (instanceName == null)
{
instanceName = new StringConstantExpressionAst(nameToken.Extent, elementName, StringConstantType.BareWord);
}
SkipNewlines();
//
// Now look for the body of the data statement.
//
if (lCurly == null)
{
lCurly = NextToken();
if (lCurly.Kind == TokenKind.EndOfInput)
{
UngetToken(lCurly);
ReportIncompleteInput(After(functionName.Extent),
nameof(ParserStrings.MissingBraceInObjectDefinition),
ParserStrings.MissingBraceInObjectDefinition);
// Preserve the name expression for tab completion
return originalInstanceName == null
? null
: new ErrorStatementAst(ExtentOf(functionName, originalInstanceName),
GetNestedErrorAsts(originalInstanceName));
}
if (lCurly.Kind != TokenKind.LCurly)
{
// We need to generate a reasonable error message for this case:
//
// Configuration C {
// node $AllNode.NodeName{ # There is no space before curly, and we are converting scriptblock to and argument to call 'NodeName'
// ...
// }
// } # we don't want to simple report an unexpected token here, it would be super-confusing.
InvokeMemberExpressionAst instanceInvokeMemberExpressionAst = instanceName as InvokeMemberExpressionAst;
if (instanceInvokeMemberExpressionAst != null &&
instanceInvokeMemberExpressionAst.Arguments.Count == 1 &&
instanceInvokeMemberExpressionAst.Arguments[0] is ScriptBlockExpressionAst &&
// the last condition checks that there is no space between "method" name and '{'
instanceInvokeMemberExpressionAst.Member.Extent.EndOffset == instanceInvokeMemberExpressionAst.Arguments[0].Extent.StartOffset)
{
ReportError(LastCharacterOf(instanceInvokeMemberExpressionAst.Member.Extent),
nameof(ParserStrings.UnexpectedTokenInDynamicKeyword),
ParserStrings.UnexpectedTokenInDynamicKeyword,
functionName.Text);
}
else
{
ReportError(lCurly.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
lCurly.Text);
}
if (lCurly.Kind == TokenKind.Dot && originalInstanceName != null && lCurly.Extent.StartOffset == originalInstanceName.Extent.EndOffset)
{
// Generate more useful ast for tab-completing extension methods on special DSC collection variables
// e.g. configuration foo { node $AllNodes.<tab>
IScriptExtent errorExtent = ExtentOf(originalInstanceName, lCurly);
var errorExpr = new ErrorExpressionAst(errorExtent);
var memberExpr = new MemberExpressionAst(originalInstanceName.Extent, originalInstanceName, errorExpr, @static: false);
return new ErrorStatementAst(errorExtent, new[] { memberExpr });
}
UngetToken(lCurly);
// Preserve the name expression for tab completion
return originalInstanceName == null
? null
: new ErrorStatementAst(ExtentOf(functionName, originalInstanceName),
GetNestedErrorAsts(originalInstanceName));
}
}
//
// The keyword data is used to see
// if a scriptblock or a hashtable is expected.
//
ExpressionAst body = null;
if (keywordData.BodyMode == DynamicKeywordBodyMode.ScriptBlock)
{
var oldInConfiguration = _inConfiguration;
try
{
_inConfiguration = false;
body = ScriptBlockExpressionRule(lCurly);
}
finally
{
_inConfiguration = oldInConfiguration;
}
}
else if (keywordData.BodyMode == DynamicKeywordBodyMode.Hashtable)
{
// Resource property value could be set to nested DSC resources except Script resource
bool isScriptResource = String.Compare(functionName.Text, @"Script", StringComparison.OrdinalIgnoreCase) == 0;
try
{
if (isScriptResource)
DynamicKeyword.Push();
body = HashExpressionRule(lCurly, true /* parsingSchemaElement */);
}
finally
{
if (isScriptResource)
DynamicKeyword.Pop();
}
}
// commandast
// elements: instancename/dynamickeyword/hashtable or scripblockexpress
if (body == null)
{
// Failed to read the statement body
ReportIncompleteInput(After(lCurly),
nameof(ParserStrings.MissingStatementAfterKeyword),
ParserStrings.MissingStatementAfterKeyword,
keywordData.Keyword);
// Preserve the name expression for tab completion
return originalInstanceName == null
? null
: new ErrorStatementAst(ExtentOf(functionName, originalInstanceName),
GetNestedErrorAsts(originalInstanceName));
}
//////////////////////////////////////////////////////////////////////////
// The statement is now fully parsed
//////////////////////////////////////////////////////////////////////////
//
// Create DynamicKeywordStatementAst
//
Collection<CommandElementAst> commandElements = new Collection<CommandElementAst>
{
new StringConstantExpressionAst(functionName.Extent, functionName.Text, StringConstantType.BareWord),
(ExpressionAst)instanceName.Copy(),
(ExpressionAst)body.Copy()
};
Token nextToken = NextToken();
IScriptExtent dynamicKeywordExtent = ExtentOf(functionName, Before(nextToken));
UngetToken(nextToken);
dynamicKeywordAst = new DynamicKeywordStatementAst(dynamicKeywordExtent, commandElements)
{
Keyword = keywordData,
LCurly = lCurly,
FunctionName = functionName,
InstanceName = instanceName,
OriginalInstanceName = originalInstanceName,
BodyExpression = body,
ElementName = elementName,
};
}
//////////////////////////////////////////////////////////////////////////////////
// If a custom action was provided. then invoke it
//////////////////////////////////////////////////////////////////////////////////
if (keywordData.PostParse != null)
{
try
{
ParseError[] errors = keywordData.PostParse(dynamicKeywordAst);
if (errors != null && errors.Length > 0)
{
foreach (var e in errors)
{
ReportError(e);
}
}
}
catch (Exception e)
{
ReportError(functionName.Extent,
nameof(ParserStrings.DynamicKeywordPostParseException),
ParserStrings.DynamicKeywordPostParseException,
keywordData.Keyword,
e.ToString());
return null;
}
}
return dynamicKeywordAst;
}
internal StatementAst CreateErrorStatementAst(
Token functionName,
ExpressionAst instanceName,
ExpressionAst bodyExpression)
{
return new ErrorStatementAst(ExtentOf(functionName, bodyExpression), GetNestedErrorAsts(instanceName, bodyExpression));
}
private StatementAst DoWhileStatementRule(LabelToken labelToken, Token doToken)
{
//G do-statement:
//G 'do' statement-block new-lines:opt 'while' new-lines:opt '(' while-condition new-lines:opt ')'
//G 'do' statement-block new-lines:opt 'until' new-lines:opt '(' while-condition new-lines:opt ')'
//G
//G while-condition:
//G new-lines:opt pipeline
IScriptExtent startExtent = (labelToken ?? doToken).Extent;
IScriptExtent endErrorStatement = null;
Token rParen = null;
Token whileOrUntilToken = null;
PipelineBaseAst condition = null;
StatementBlockAst body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: Skip the keyword and stop trying to parse this statement, continue on whatever
// comes next.
endErrorStatement = doToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingLoopStatement),
ParserStrings.MissingLoopStatement,
TokenKind.Do.Text());
}
else
{
SkipNewlines();
whileOrUntilToken = NextToken();
if (whileOrUntilToken.Kind != TokenKind.While && whileOrUntilToken.Kind != TokenKind.Until)
{
// ErrorRecovery: Skip looking for a condition, continue on whatever comes next.
UngetToken(whileOrUntilToken);
endErrorStatement = body.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingWhileOrUntilInDoWhile),
ParserStrings.MissingWhileOrUntilInDoWhile);
}
else
{
SkipNewlines();
Token lParen = NextToken();
if (lParen.Kind != TokenKind.LParen)
{
// ErrorRecovery: Skip looking for the condition, return an error statement.
UngetToken(lParen);
endErrorStatement = whileOrUntilToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingOpenParenthesisAfterKeyword),
ParserStrings.MissingOpenParenthesisAfterKeyword,
whileOrUntilToken.Kind.Text());
}
else
{
SkipNewlines();
condition = PipelineRule();
if (condition == null)
{
// ErrorRecovery: try to get the matching close paren, then return an error statement.
endErrorStatement = lParen.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingExpressionAfterKeyword),
ParserStrings.MissingExpressionAfterKeyword,
whileOrUntilToken.Kind.Text());
}
SkipNewlines();
rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: this is it, so just pretend we saw the paren, return an error statement.
UngetToken(rParen);
// If condition == null, we issue an error message already, don't bother with this one.
if (condition != null)
{
endErrorStatement = condition.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingEndParenthesisAfterStatement),
ParserStrings.MissingEndParenthesisAfterStatement,
whileOrUntilToken.Kind.Text());
}
}
}
}
}
if (endErrorStatement != null)
{
return new ErrorStatementAst(ExtentOf(startExtent, endErrorStatement), GetNestedErrorAsts(body, condition));
}
IScriptExtent extent = ExtentOf(startExtent, rParen);
string label = (labelToken != null) ? labelToken.LabelText : null;
if (whileOrUntilToken.Kind == TokenKind.Until)
{
return new DoUntilStatementAst(extent, label, condition, body);
}
return new DoWhileStatementAst(extent, label, condition, body);
}
private StatementAst ClassDefinitionRule(List<AttributeBaseAst> customAttributes, Token classToken)
{
//G class-statement:
//G attribute-list:opt 'class' new-lines:opt class-name new-lines:opt '{' class-member-list '}'
//G attribute-list:opt 'class' new-lines:opt class-name new-lines:opt ':' base-type-list '{' new-lines:opt class-member-list:opt '}'
//G
//G class-name:
//G simple-name
//G
//G base-type-list:
//G new-lines:opt type-name new-lines:opt
//G base-class-list ',' new-lines:opt type-name new-lines:opt
//G
//G class-member-list:
//G class-member new-lines:opt
//G class-member-list class-member
SkipNewlines();
Token classNameToken;
var name = SimpleNameRule(out classNameToken);
if (name == null)
{
ReportIncompleteInput(After(classToken),
nameof(ParserStrings.MissingNameAfterKeyword),
ParserStrings.MissingNameAfterKeyword,
classToken.Text);
return new ErrorStatementAst(classToken.Extent);
}
// Class name token represents a name of type, not a member. Highlight it as a type name.
classNameToken.TokenFlags &= ~TokenFlags.MemberName;
classNameToken.TokenFlags |= TokenFlags.TypeName;
SkipNewlines();
// handle inheritance constraint.
var oldTokenizerMode = _tokenizer.Mode;
var superClassesList = new List<TypeConstraintAst>();
try
{
SetTokenizerMode(TokenizerMode.Signature);
Token colonToken = PeekToken();
if (colonToken.Kind == TokenKind.Colon)
{
this.SkipToken();
SkipNewlines();
ITypeName superClass;
Token unused;
Token commaToken = null;
while (true)
{
superClass = this.TypeNameRule(allowAssemblyQualifiedNames: false, firstTypeNameToken: out unused);
if (superClass == null)
{
ReportIncompleteInput(After(ExtentFromFirstOf(commaToken, colonToken)),
nameof(ParserStrings.TypeNameExpected),
ParserStrings.TypeNameExpected);
break;
}
superClassesList.Add(new TypeConstraintAst(superClass.Extent, superClass));
SkipNewlines();
commaToken = this.PeekToken();
if (commaToken.Kind == TokenKind.Comma)
{
this.SkipToken();
this.SkipNewlines();
}
else
{
break;
}
}
}
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
UngetToken(lCurly);
var lastElement = (superClassesList.Count > 0) ? (Ast)superClassesList[superClassesList.Count - 1] : name;
ReportIncompleteInput(After(lastElement),
nameof(ParserStrings.MissingTypeBody),
ParserStrings.MissingTypeBody,
classToken.Kind.Text());
return new ErrorStatementAst(ExtentOf(classToken, lastElement), superClassesList);
}
IScriptExtent lastExtent = lCurly.Extent;
List<Ast> nestedAsts = null;
MemberAst member;
List<MemberAst> members = new List<MemberAst>();
List<Ast> astsOnError = null;
while ((member = ClassMemberRule(name.Value, out astsOnError)) != null || astsOnError != null)
{
if (member != null)
{
members.Add(member);
lastExtent = member.Extent;
}
if (astsOnError != null && astsOnError.Count > 0)
{
if (nestedAsts == null)
{
nestedAsts = new List<Ast>();
}
nestedAsts.AddRange(astsOnError);
lastExtent = astsOnError.Last().Extent;
}
}
var rCurly = NextToken();
if (rCurly.Kind != TokenKind.RCurly)
{
UngetToken(rCurly);
ReportIncompleteInput(After(lCurly),
rCurly.Extent,
nameof(ParserStrings.MissingEndCurlyBrace),
ParserStrings.MissingEndCurlyBrace);
}
else
{
lastExtent = rCurly.Extent;
}
var startExtent = customAttributes != null && customAttributes.Count > 0
? customAttributes[0].Extent
: classToken.Extent;
var extent = ExtentOf(startExtent, lastExtent);
var classDefn = new TypeDefinitionAst(extent, name.Value, customAttributes == null ? null : customAttributes.OfType<AttributeAst>(), members, TypeAttributes.Class, superClassesList);
if (customAttributes != null && customAttributes.OfType<TypeConstraintAst>().Any())
{
if (nestedAsts == null)
{
nestedAsts = new List<Ast>();
}
//no need to report error since the error is reported in method StatementRule
nestedAsts.AddRange(customAttributes.OfType<TypeConstraintAst>());
nestedAsts.Add(classDefn);
}
if (nestedAsts != null && nestedAsts.Count > 0)
{
return new ErrorStatementAst(extent, nestedAsts);
}
return classDefn;
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
}
private MemberAst ClassMemberRule(string className, out List<Ast> astsOnError)
{
//G class-member:
//G method-member
//G property-member
//G
//G method-member:
//G member-attribute-list:opt function-statement
//G
//G property-member:
//G member-attribute-list:opt variable
//G member-attribute-list:opt variable '=' expression
//G
//G member-attribute-list:
//G member-attribute
//G member-attribute-list member-attribute
//G
//G member-attribute:
//G attribute
//G 'static'
//G 'hidden'
IScriptExtent startExtent = null;
var attributeList = new List<AttributeAst>();
TypeConstraintAst typeConstraint = null;
bool scanningAttributes = true;
Token staticToken = null;
Token hiddenToken = null;
Token token = null;
object lastAttribute = null;
astsOnError = null;
#if SUPPORT_PUBLIC_PRIVATE
// Public/private not yet supported
Token publicToken = null;
Token privateToken = null;
#endif
while (scanningAttributes)
{
SkipNewlines();
var attribute = AttributeRule();
if (attribute != null)
{
lastAttribute = attribute;
if (startExtent == null)
{
startExtent = attribute.Extent;
}
var attributeAst = attribute as AttributeAst;
if (attributeAst != null)
{
attributeList.Add(attributeAst);
}
else if (typeConstraint == null)
{
typeConstraint = (TypeConstraintAst)attribute;
}
else
{
ReportError(attribute.Extent, nameof(ParserStrings.TooManyTypes), ParserStrings.TooManyTypes);
}
continue;
}
token = PeekToken();
if (startExtent == null)
{
startExtent = token.Extent;
}
switch (token.Kind)
{
#if SUPPORT_PUBLIC_PRIVATE
case TokenKind.Public:
if (publicToken != null)
{
ReportError(token.Extent,
nameof(ParserStrings.DuplicateQualifier),
ParserStrings.DuplicateQualifier,
token.Text);
}
if (privateToken != null)
{
ReportError(token.Extent,
nameof(ParserStrings.ModifiersCannotBeCombined),
ParserStrings.ModifiersCannotBeCombined,
privateToken.Text,
token.Text);
}
publicToken = token;
SkipToken();
break;
case TokenKind.Private:
if (privateToken != null)
{
ReportError(token.Extent,
nameof(ParserStrings.DuplicateQualifier),
ParserStrings.DuplicateQualifier,
token.Text);
}
if (publicToken != null)
{
ReportError(token.Extent,
nameof(ParserStrings.ModifiersCannotBeCombined),
ParserStrings.ModifiersCannotBeCombined,
publicToken.Text,
token.Text);
}
privateToken = token;
SkipToken();
break;
#endif
case TokenKind.Hidden:
if (hiddenToken != null)
{
ReportError(token.Extent,
nameof(ParserStrings.DuplicateQualifier),
ParserStrings.DuplicateQualifier,
token.Text);
}
hiddenToken = token;
lastAttribute = token;
SkipToken();
break;
case TokenKind.Static:
if (staticToken != null)
{
ReportError(token.Extent,
nameof(ParserStrings.DuplicateQualifier),
ParserStrings.DuplicateQualifier,
token.Text);
}
staticToken = token;
lastAttribute = token;
SkipToken();
break;
default:
scanningAttributes = false;
break;
}
}
if (token.Kind == TokenKind.Variable)
{
SkipToken();
var varToken = token as VariableToken;
ExpressionAst initialValueAst = null;
var assignToken = PeekToken();
if (assignToken.Kind == TokenKind.Equals)
{
SkipToken();
SkipNewlines();
initialValueAst = ExpressionRule();
}
#if SUPPORT_PUBLIC_PRIVATE
PropertyAttributes attributes = privateToken != null ? PropertyAttributes.Private : PropertyAttributes.Public;
#else
PropertyAttributes attributes = PropertyAttributes.Public;
#endif
if (staticToken != null)
{
attributes |= PropertyAttributes.Static;
}
if (hiddenToken != null)
{
attributes |= PropertyAttributes.Hidden;
}
var endExtent = initialValueAst != null ? initialValueAst.Extent : varToken.Extent;
Token terminatorToken = PeekToken();
if (terminatorToken.Kind != TokenKind.NewLine && terminatorToken.Kind != TokenKind.Semi && terminatorToken.Kind != TokenKind.RCurly)
{
ReportIncompleteInput(After(endExtent),
nameof(ParserStrings.MissingPropertyTerminator),
ParserStrings.MissingPropertyTerminator);
}
SkipNewlinesAndSemicolons();
// Include the semicolon in the extent but not newline or rcurly as that will look weird, e.g. if an error is reported on the full extent
if (terminatorToken.Kind == TokenKind.Semi)
{
endExtent = terminatorToken.Extent;
}
if (!String.IsNullOrEmpty(varToken.Name))
{
return new PropertyMemberAst(ExtentOf(startExtent, endExtent), varToken.Name,
typeConstraint, attributeList, attributes, initialValueAst);
}
else
{
// Incompleted input like:
// class foo { $private: }
// Error message already emitted by tokenizer ScanVariable
RecordErrorAsts(attributeList, ref astsOnError);
RecordErrorAsts(typeConstraint, ref astsOnError);
RecordErrorAsts(initialValueAst, ref astsOnError);
return null;
}
}
if (token.Kind == TokenKind.Identifier)
{
SkipToken();
var functionDefinition = MethodDeclarationRule(token, className, staticToken != null) as FunctionDefinitionAst;
if (functionDefinition == null)
{
// TODO: better error recovery - shouldn't assume this was the last class member
Diagnostics.Assert(ErrorList.Count > 0, "Should be an error if we don't have a function");
SyncOnError(false, TokenKind.RCurly);
RecordErrorAsts(attributeList, ref astsOnError);
RecordErrorAsts(typeConstraint, ref astsOnError);
return null;
}
#if FALSE
MethodAttributes attributes = privateToken != null ? MethodAttributes.Private : MethodAttributes.Public;
#else
MethodAttributes attributes = MethodAttributes.Public;
#endif
if (staticToken != null)
{
attributes |= MethodAttributes.Static;
}
if (hiddenToken != null)
{
attributes |= MethodAttributes.Hidden;
}
return new FunctionMemberAst(ExtentOf(startExtent, functionDefinition), functionDefinition, typeConstraint, attributeList, attributes);
}
if (lastAttribute != null)
{
// We have the start of a member, but didn't see a variable or 'def'.
ReportIncompleteInput(After(ExtentFromFirstOf(lastAttribute)),
nameof(ParserStrings.IncompleteMemberDefinition),
ParserStrings.IncompleteMemberDefinition);
RecordErrorAsts(attributeList, ref astsOnError);
RecordErrorAsts(typeConstraint, ref astsOnError);
}
return null;
}
private void RecordErrorAsts(Ast errAst, ref List<Ast> astsOnError)
{
if (errAst == null)
{
return;
}
if (astsOnError == null)
{
astsOnError = new List<Ast>();
}
astsOnError.Add(errAst);
}
private void RecordErrorAsts(IEnumerable<Ast> errAsts, ref List<Ast> astsOnError)
{
if (errAsts == null || !errAsts.Any())
{
return;
}
if (astsOnError == null)
{
astsOnError = new List<Ast>();
}
astsOnError.AddRange(errAsts);
}
private Token NextTypeIdentifierToken()
{
var oldTokenizerMode = _tokenizer.Mode;
try
{
// Switch to typename mode to avoid aggressive argument tokenization.
SetTokenizerMode(TokenizerMode.TypeName);
Token typeName = NextToken();
if (typeName.Kind != TokenKind.Identifier)
{
UngetToken(typeName);
return null;
}
return typeName;
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
}
private StatementAst EnumDefinitionRule(List<AttributeBaseAst> customAttributes, Token enumToken)
{
//G enum-statement:
//G 'enum' new-lines:opt enum-name '{' enum-member-list '}'
//G
//G enum-name:
//G simple-name
//G
//G enum-member-list:
//G enum-member new-lines:opt
//G enum-member-list enum-member
SkipNewlines();
var name = SimpleNameRule();
if (name == null)
{
ReportIncompleteInput(After(enumToken),
nameof(ParserStrings.MissingNameAfterKeyword),
ParserStrings.MissingNameAfterKeyword,
enumToken.Text);
return new ErrorStatementAst(enumToken.Extent);
}
SkipNewlines();
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
UngetToken(lCurly);
ReportIncompleteInput(After(name),
nameof(ParserStrings.MissingTypeBody),
ParserStrings.MissingTypeBody,
enumToken.Kind.Text());
return new ErrorStatementAst(ExtentOf(enumToken, name));
}
IScriptExtent lastExtent = lCurly.Extent;
MemberAst member;
List<MemberAst> members = new List<MemberAst>();
while ((member = EnumMemberRule()) != null)
{
members.Add(member);
lastExtent = member.Extent;
}
var rCurly = NextToken();
if (rCurly.Kind != TokenKind.RCurly)
{
UngetToken(rCurly);
ReportIncompleteInput(After(lCurly),
rCurly.Extent,
nameof(ParserStrings.MissingEndCurlyBrace),
ParserStrings.MissingEndCurlyBrace);
}
var startExtent = customAttributes != null && customAttributes.Count > 0
? customAttributes[0].Extent
: enumToken.Extent;
var extent = ExtentOf(startExtent, rCurly);
var enumDefn = new TypeDefinitionAst(extent, name.Value, customAttributes == null ? null : customAttributes.OfType<AttributeAst>(), members, TypeAttributes.Enum, null);
if (customAttributes != null && customAttributes.OfType<TypeConstraintAst>().Any())
{
//no need to report error since there is error reported in method StatementRule
List<Ast> nestedAsts = new List<Ast>();
nestedAsts.AddRange(customAttributes.OfType<TypeConstraintAst>());
nestedAsts.Add(enumDefn);
return new ErrorStatementAst(startExtent, nestedAsts);
}
return enumDefn;
}
private MemberAst EnumMemberRule()
{
//G enum-member:
//G enum-member-name
//G enum-member-name '=' expression
//G
//G enum-member-name:
//G simple-name
const PropertyAttributes enumMemberAttributes =
PropertyAttributes.Public | PropertyAttributes.Static | PropertyAttributes.Literal;
SkipNewlines();
var enumeratorName = SimpleNameRule();
if (enumeratorName == null)
{
return null;
}
IScriptExtent endExtent = enumeratorName.Extent;
ExpressionAst initialValueAst = null;
var missingInitializer = false;
var oldTokenizerMode = _tokenizer.Mode;
Token assignToken = null;
try
{
SetTokenizerMode(TokenizerMode.Expression);
assignToken = PeekToken();
if (assignToken.Kind == TokenKind.Equals)
{
SkipToken();
initialValueAst = ExpressionRule();
if (initialValueAst == null)
{
ReportError(After(assignToken),
nameof(ParserStrings.ExpectedValueExpression),
ParserStrings.ExpectedValueExpression,
assignToken.Kind.Text());
endExtent = assignToken.Extent;
missingInitializer = true;
}
else
{
endExtent = initialValueAst.Extent;
}
}
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
Token terminatorToken = PeekToken();
if (terminatorToken.Kind != TokenKind.NewLine && terminatorToken.Kind != TokenKind.Semi && terminatorToken.Kind != TokenKind.RCurly)
{
// If the initializer is missing, no sense in reporting another error about a missing terminator
if (!missingInitializer)
{
ReportIncompleteInput(After(endExtent),
nameof(ParserStrings.MissingPropertyTerminator),
ParserStrings.MissingPropertyTerminator);
}
}
SkipNewlinesAndSemicolons();
// Include the semicolon in the extent but not newline or rcurly as that will look weird, e.g. if an error is reported on the full extent
if (terminatorToken.Kind == TokenKind.Semi)
{
endExtent = terminatorToken.Extent;
}
return new PropertyMemberAst(ExtentOf(enumeratorName, endExtent), enumeratorName.Value, null, null, enumMemberAttributes, initialValueAst);
}
private StatementAst UsingStatementRule(Token usingToken)
{
//G using-statement:
//G 'using' 'namespace' identifier
//G 'using' 'namespace' identifier '=' identifier
//G 'using' 'module' identifier
//G 'using' 'module' identifier '=' identifier
//G 'using' 'module' hashtable
//G 'using' 'module' identifier '=' hashtable
//G 'using' 'type' identifier '=' identifier
//G 'using' 'assembly' identifier
//G 'using' 'command' identifier '=' identifier
// in case of aliasing (=), first identifier is alias name, second identifier is alias value (the real name).
var directiveToken = NextToken();
UsingStatementKind kind;
bool aliasAllowed = false;
bool aliasRequired = false;
switch (directiveToken.Kind)
{
case TokenKind.Namespace:
kind = UsingStatementKind.Namespace;
aliasAllowed = true;
break;
case TokenKind.Type:
kind = UsingStatementKind.Type;
aliasRequired = true;
break;
case TokenKind.Module:
kind = UsingStatementKind.Module;
aliasAllowed = true;
break;
case TokenKind.Command:
kind = UsingStatementKind.Command;
aliasRequired = true;
break;
case TokenKind.Assembly:
kind = UsingStatementKind.Assembly;
break;
default:
UngetToken(directiveToken);
ReportIncompleteInput(After(usingToken),
nameof(ParserStrings.MissingUsingStatementDirective),
ParserStrings.MissingUsingStatementDirective);
return new ErrorStatementAst(usingToken.Extent);
}
var itemToken = NextToken();
switch (itemToken.Kind)
{
case TokenKind.EndOfInput:
case TokenKind.NewLine:
// Example: 'using module ,FooBar'
// GetCommandArgument will successfully return an argument for a unary array argument
// but we don't want to allow that syntax with a using statement.
case TokenKind.Comma:
case TokenKind.Semi:
{
ReportIncompleteInput(After(directiveToken),
nameof(ParserStrings.MissingUsingItemName),
ParserStrings.MissingUsingItemName);
return new ErrorStatementAst(ExtentOf(usingToken, directiveToken));
}
}
var itemAst = GetCommandArgument(CommandArgumentContext.CommandArgument, itemToken);
if (itemAst == null)
{
ReportError(itemToken.Extent,
nameof(ParserStrings.InvalidValueForUsingItemName),
ParserStrings.InvalidValueForUsingItemName,
itemToken.Text);
// ErrorRecovery: If there is no identifier, skip whole 'using' line
SyncOnError(true, TokenKind.Semi, TokenKind.NewLine);
return new ErrorStatementAst(ExtentOf(usingToken, itemToken.Extent));
}
if (!(itemAst is StringConstantExpressionAst) && (kind != UsingStatementKind.Module || !(itemAst is HashtableAst)))
{
ReportError(ExtentFromFirstOf(itemAst, itemToken),
nameof(ParserStrings.InvalidValueForUsingItemName),
ParserStrings.InvalidValueForUsingItemName,
itemAst.Extent.Text);
return new ErrorStatementAst(ExtentOf(usingToken, ExtentFromFirstOf(itemAst, itemToken)));
}
HashtableAst htAst = null;
switch (kind)
{
case UsingStatementKind.Assembly:
itemAst = ResolveUsingAssembly((StringConstantExpressionAst)itemAst);
break;
case UsingStatementKind.Module:
htAst = itemAst as HashtableAst;
break;
}
// if htAst is not null, then we don't expect alias
if ((aliasAllowed || aliasRequired) && (htAst == null))
{
var equalsToken = PeekToken();
if (equalsToken.Kind == TokenKind.Equals)
{
SkipToken();
var aliasToken = NextToken();
if (aliasToken.Kind == TokenKind.EndOfInput)
{
UngetToken(aliasToken);
ReportIncompleteInput(After(equalsToken),
nameof(ParserStrings.MissingNamespaceAlias),
ParserStrings.MissingNamespaceAlias);
return new ErrorStatementAst(ExtentOf(usingToken, equalsToken));
}
var aliasAst = GetCommandArgument(CommandArgumentContext.CommandArgument, aliasToken);
if (kind == UsingStatementKind.Module && aliasAst is HashtableAst)
{
htAst = (HashtableAst)aliasAst;
}
else if (!(aliasAst is StringConstantExpressionAst))
{
return new ErrorStatementAst(ExtentOf(usingToken, aliasAst), new Ast[] { itemAst, aliasAst });
}
RequireStatementTerminator();
if (htAst == null)
{
return new UsingStatementAst(
ExtentOf(usingToken, aliasToken),
kind,
(StringConstantExpressionAst)itemAst,
(StringConstantExpressionAst)aliasAst);
}
else
{
return new UsingStatementAst(
ExtentOf(usingToken, aliasToken),
(StringConstantExpressionAst)itemAst,
htAst);
}
}
if (aliasRequired)
{
ReportIncompleteInput(After(itemToken),
nameof(ParserStrings.MissingEqualsInUsingAlias),
ParserStrings.MissingEqualsInUsingAlias);
return new ErrorStatementAst(ExtentOf(usingToken, itemAst), new Ast[] { itemAst });
}
}
RequireStatementTerminator();
if (htAst == null)
{
return new UsingStatementAst(ExtentOf(usingToken, itemAst), kind, (StringConstantExpressionAst)itemAst);
}
else
{
return new UsingStatementAst(ExtentOf(usingToken, itemAst), htAst);
}
}
private StringConstantExpressionAst ResolveUsingAssembly(StringConstantExpressionAst name)
{
var assemblyName = name.Value;
Uri uri;
// assemblyName can be invalid, i.e. during typing.
// We only use uri to check that we don't allow UNC paths.
if (Uri.TryCreate(assemblyName, UriKind.Absolute, out uri))
{
if (uri.IsUnc)
{
ReportError(name.Extent,
nameof(ParserStrings.CannotLoadAssemblyFromUncPath),
ParserStrings.CannotLoadAssemblyFromUncPath,
assemblyName);
}
// don't allow things like 'using assembly http://microsoft.com'
if (uri.Scheme != "file")
{
ReportError(name.Extent,
nameof(ParserStrings.CannotLoadAssemblyWithUriSchema),
ParserStrings.CannotLoadAssemblyWithUriSchema,
uri.Scheme);
}
}
else
{
string assemblyFileName = assemblyName;
try
{
var scriptFileName = name.Extent.File;
if (!Path.IsPathRooted(assemblyFileName))
{
string workingDirectory;
if (string.IsNullOrEmpty(scriptFileName))
{
// We are in REPL, or Invoke-Expression, or ScriptBlock.Create, etc.
// It's legal to use '.\foo.dll', and we should do lookup in $pwd.
// We resolving it in parse time to avoid difference in script behavior,
// when script execution is delayed from parsing, i.e.
// $script = [scriptblock]::Create('using assembly .\foo.dll; [Foo].foo()')
// # using assembly behavior should be the same in these two cases:
// cd A; $script.Invoke()
// cd B; $script.Invoke()
if (Runspaces.Runspace.DefaultRunspace != null)
{
workingDirectory = Runspaces.Runspace.DefaultRunspace.ExecutionContext.EngineIntrinsics.SessionState.Path.CurrentLocation.Path;
}
else
{
// In this case, our best guess is process working directory.
workingDirectory = Directory.GetCurrentDirectory();
}
}
else
{
workingDirectory = Path.GetDirectoryName(scriptFileName);
}
assemblyFileName = workingDirectory + @"\" + assemblyFileName;
}
#if !CORECLR
if (!File.Exists(assemblyFileName))
{
GlobalAssemblyCache.ResolvePartialName(assemblyName, out assemblyFileName);
}
#endif
}
catch
{
}
if (assemblyFileName == null || !File.Exists(assemblyFileName))
{
ReportError(name.Extent,
nameof(ParserStrings.ErrorLoadingAssembly),
ParserStrings.ErrorLoadingAssembly,
assemblyName);
}
else
{
return new StringConstantExpressionAst(name.Extent, assemblyFileName, name.StringConstantType);
}
}
return name;
}
private StatementAst MethodDeclarationRule(Token functionNameToken, string className, bool isStaticMethod)
{
//G method-statement:
//G new-lines:opt function-name function-parameter-declaration base-ctor-call:opt '{' script-block '}'
//G
//G base-ctor-call: // can be present only if function-name == className
//G ':' new-lines:opt 'base' new-lines:opt parenthesized-expression new-lines:opt
//G
//G function-name:
//G command-argument
var functionName = functionNameToken.Text;
List<ParameterAst> parameters;
Token lParen = this.PeekToken();
IScriptExtent endErrorStatement = null;
Token rParen = null;
if (lParen.Kind == TokenKind.LParen)
{
parameters = this.FunctionParameterDeclarationRule(out endErrorStatement, out rParen);
}
else
{
this.ReportIncompleteInput(After(functionNameToken),
nameof(ParserStrings.MissingMethodParameterList),
ParserStrings.MissingMethodParameterList);
parameters = new List<ParameterAst>();
}
bool isCtor = functionName.Equals(className, StringComparison.OrdinalIgnoreCase);
List<ExpressionAst> baseCtorCallParams = null;
Token baseToken = null;
IScriptExtent baseCallLastExtent = null;
TokenizerMode oldTokenizerMode;
if (isCtor && !isStaticMethod)
{
this.SkipNewlines();
oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Signature);
Token colonToken = PeekToken();
if (colonToken.Kind == TokenKind.Colon)
{
SkipToken();
this.SkipNewlines();
baseToken = PeekToken();
if (baseToken.Kind == TokenKind.Base)
{
SkipToken();
this.SkipNewlines();
lParen = PeekToken();
if (lParen.Kind == TokenKind.LParen)
{
SkipToken();
// we don't allow syntax
// : base{ script }
// as a short for for
// : base( { script } )
baseCtorCallParams = InvokeParamParenListRule(lParen, out baseCallLastExtent);
this.SkipNewlines();
}
else
{
endErrorStatement = baseToken.Extent;
ReportIncompleteInput(After(baseToken),
nameof(ParserStrings.MissingMethodParameterList),
ParserStrings.MissingMethodParameterList);
}
}
else
{
endErrorStatement = colonToken.Extent;
ReportIncompleteInput(After(colonToken),
nameof(ParserStrings.MissingBaseCtorCall),
ParserStrings.MissingBaseCtorCall);
}
}
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
if (baseCtorCallParams == null)
{
// Assuming implicit default ctor
baseCtorCallParams = new List<ExpressionAst>();
}
}
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
UngetToken(lCurly);
if (endErrorStatement == null)
{
endErrorStatement = ExtentFromFirstOf(rParen, functionNameToken);
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingFunctionBody),
ParserStrings.MissingFunctionBody);
}
}
if (endErrorStatement != null)
{
return new ErrorStatementAst(ExtentOf(functionNameToken, endErrorStatement), parameters);
}
StatementAst baseCtorCallStatement = null;
if (isCtor && !isStaticMethod)
{
IScriptExtent baseCallExtent;
IScriptExtent baseKeywordExtent;
if (baseToken != null)
{
baseCallExtent = ExtentOf(baseToken, baseCallLastExtent);
baseKeywordExtent = baseToken.Extent;
}
else
{
baseCallExtent = PositionUtilities.EmptyExtent;
baseKeywordExtent = PositionUtilities.EmptyExtent;
}
var invokeMemberAst = new BaseCtorInvokeMemberExpressionAst(baseKeywordExtent, baseCallExtent, baseCtorCallParams);
baseCtorCallStatement = new CommandExpressionAst(invokeMemberAst.Extent, invokeMemberAst, null);
}
oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Command);
ScriptBlockAst scriptBlock = ScriptBlockRule(lCurly, false, baseCtorCallStatement);
var result = new FunctionDefinitionAst(ExtentOf(functionNameToken, scriptBlock),
/*isFilter:*/false, /*isWorkflow:*/false, functionNameToken, parameters, scriptBlock);
return result;
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
}
private StatementAst FunctionDeclarationRule(Token functionToken)
{
//G function-statement:
//G 'function' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}'
//G 'filter' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}'
//G 'workflow' new-lines:opt function-name function-parameter-declaration:opt '{' script-block '}'
//G
//G function-name:
//G command-argument
SkipNewlines();
Token functionNameToken = NextToken();
switch (functionNameToken.Kind)
{
// The following are not allowed as function names, anything else is allowed:
// The general rule is - punctuators and unary operators are not allowed
// We want to allow as much as reasonably possible, e.g. if it could be a
// valid command name, we want to allow it with very limited exceptions.
case TokenKind.Pipe:
case TokenKind.LParen:
case TokenKind.LCurly:
case TokenKind.AtParen:
case TokenKind.AtCurly:
case TokenKind.RCurly:
case TokenKind.RParen:
case TokenKind.EndOfInput:
case TokenKind.Semi:
case TokenKind.Redirection:
case TokenKind.RedirectInStd:
case TokenKind.AndAnd:
case TokenKind.OrOr:
case TokenKind.Ampersand:
case TokenKind.Variable:
case TokenKind.SplattedVariable:
case TokenKind.HereStringExpandable:
case TokenKind.HereStringLiteral:
case TokenKind.StringLiteral:
case TokenKind.StringExpandable:
// ErrorRecovery: Don't continue, assume the function isn't there yet.
UngetToken(functionNameToken);
ReportIncompleteInput(After(functionToken),
nameof(ParserStrings.MissingNameAfterKeyword),
ParserStrings.MissingNameAfterKeyword,
functionToken.Text);
return new ErrorStatementAst(functionToken.Extent);
}
// A function name that matches a keyword isn't really a keyword, so don't color it that way
functionNameToken.TokenFlags &= ~TokenFlags.Keyword;
IScriptExtent endErrorStatement;
Token rParen;
var parameters = this.FunctionParameterDeclarationRule(out endErrorStatement, out rParen);
Token lCurly = NextToken();
if (lCurly.Kind != TokenKind.LCurly)
{
// ErrorRecovery: If there is no opening curly, assume it hasn't been entered yet and don't consume anything.
UngetToken(lCurly);
if (endErrorStatement == null)
{
endErrorStatement = ExtentFromFirstOf(rParen, functionNameToken);
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingFunctionBody),
ParserStrings.MissingFunctionBody);
}
}
if (endErrorStatement != null)
{
return new ErrorStatementAst(ExtentOf(functionToken, endErrorStatement), parameters);
}
bool isFilter = functionToken.Kind == TokenKind.Filter;
bool isWorkflow = functionToken.Kind == TokenKind.Workflow;
bool oldTokenizerWorkflowContext = _tokenizer.InWorkflowContext;
try
{
_tokenizer.InWorkflowContext = isWorkflow;
ScriptBlockAst scriptBlock = ScriptBlockRule(lCurly, isFilter);
var functionName = (functionNameToken.Kind == TokenKind.Generic)
? ((StringToken)functionNameToken).Value
: functionNameToken.Text;
FunctionDefinitionAst result = new FunctionDefinitionAst(ExtentOf(functionToken, scriptBlock),
isFilter, isWorkflow, functionNameToken, parameters, scriptBlock);
return result;
}
finally
{
_tokenizer.InWorkflowContext = oldTokenizerWorkflowContext;
}
}
private List<ParameterAst> FunctionParameterDeclarationRule(out IScriptExtent endErrorStatement, out Token rParen)
{
//G function-parameter-declaration:
//G new-lines:opt '(' parameter-list new-lines:opt ')'
List<ParameterAst> parameters = null;
endErrorStatement = null;
SkipNewlines();
rParen = null;
Token lParen = PeekToken();
if (lParen.Kind == TokenKind.LParen)
{
SkipToken();
parameters = ParameterListRule();
SkipNewlines();
rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: assume a body follows, so just keep parsing.
UngetToken(rParen);
endErrorStatement = parameters.Any() ? parameters.Last().Extent : lParen.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingEndParenthesisInFunctionParameterList),
ParserStrings.MissingEndParenthesisInFunctionParameterList);
}
SkipNewlines();
}
return parameters;
}
private StatementAst TrapStatementRule(Token trapToken)
{
//G trap-statement:
//G 'trap' new-lines:opt type-literal:opt statement-block
var restorePoint = _tokenizer.GetRestorePoint();
SkipNewlines();
AttributeBaseAst type = AttributeRule();
var typeConstraintAst = type as TypeConstraintAst;
if (type != null && typeConstraintAst == null)
{
// Presumably we parsed an attribute instead of a type. Put it back and let the code
// below report a missing trap body. The attribute might belong to something that
// is after the trap, something like:
// trap
// [ValidateNotNullOrEmpty()]$var
Resync(restorePoint);
}
var body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: just return an error statement.
IScriptExtent endErrorStatement = ExtentFromFirstOf(typeConstraintAst, trapToken);
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingTrapStatement),
ParserStrings.MissingTrapStatement);
return new ErrorStatementAst(ExtentOf(trapToken, endErrorStatement), GetNestedErrorAsts(typeConstraintAst));
}
return new TrapStatementAst(ExtentOf(trapToken, body), typeConstraintAst, body);
}
/// <summary>
/// Parse a catch block.
/// </summary>
/// <param name="endErrorStatement">
/// Set to the last thing scanned that is definitely part of the catch, but only set after issuing an error.
/// </param>
/// <param name="errorAsts">
/// If there are any errors and CatchBlockRule is returning null, this list is used to return back any asts
/// consumed here (essentially the type constraints.)
/// </param>
/// <returns>A catch clause, or null there is no catch or there was some error.</returns>
private CatchClauseAst CatchBlockRule(ref IScriptExtent endErrorStatement, ref List<TypeConstraintAst> errorAsts)
{
//G catch-clause:
//G new-lines:opt 'catch' catch-type-list:opt statement-block
//G catch-type-list:
//G new-lines:opt type-literal
//G catch-type-list new-lines:opt ',' new-lines:opt type-literal
SkipNewlines();
Token catchToken = NextToken();
if (catchToken.Kind != TokenKind.Catch)
{
UngetToken(catchToken);
return null;
}
List<TypeConstraintAst> exceptionTypes = null;
Token commaToken = null;
do
{
var restorePoint = _tokenizer.GetRestorePoint();
SkipNewlines();
AttributeBaseAst type = AttributeRule();
if (type == null)
{
if (commaToken != null)
{
// ErrorRecovery: Just consume the comma, pretend it wasn't there and look for the handler block.
endErrorStatement = commaToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingTypeLiteralToken),
ParserStrings.MissingTypeLiteralToken);
}
break;
}
var typeConstraintAst = type as TypeConstraintAst;
if (typeConstraintAst == null)
{
// Presumably we parsed an attribute instead of a type. Put it back and let the code
// below report a missing catch clause body. The attribute might belong to something that
// is after the try/catch, something like:
// catch
// [ValidateNotNullOrEmpty()]$var
Resync(restorePoint);
break;
}
if (exceptionTypes == null)
{
exceptionTypes = new List<TypeConstraintAst>();
}
exceptionTypes.Add(typeConstraintAst);
SkipNewlines();
commaToken = PeekToken();
if (commaToken.Kind != TokenKind.Comma)
{
break;
}
SkipToken();
} while (true);
StatementBlockAst handler = StatementBlockRule();
if (handler == null)
{
// Avoid issuing an extra error if we've already issued one, plus the end of the ErrorStatementAst
// shouldn't be updated.
if (commaToken == null || endErrorStatement != commaToken.Extent)
{
// ErrorRecovery: just use the "missing" block in the result ast.
endErrorStatement = exceptionTypes != null ? exceptionTypes.Last().Extent : catchToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingCatchHandlerBlock),
ParserStrings.MissingCatchHandlerBlock);
}
if (exceptionTypes != null)
{
if (errorAsts == null)
{
errorAsts = exceptionTypes;
}
else
{
errorAsts.AddRange(exceptionTypes);
}
}
return null;
}
return new CatchClauseAst(ExtentOf(catchToken, handler), exceptionTypes, handler);
}
private StatementAst TryStatementRule(Token tryToken)
{
//G try-statement:
//G 'try' statement-block catch-clauses
//G 'try' statement-block finally-clause
//G 'try' statement-block catch-clauses finally-clause
//G catch-clauses:
//G catch-clause
//G catch-clauses catch-clause
//G catch-clause:
//G new-lines:opt 'catch' catch-type-list:opt statement-block
//G catch-type-list:
//G new-lines:opt type-literal
//G catch-type-list new-lines:opt ',' new-lines:opt type-literal
//G finally-clause:
//G new-lines:opt 'finally' statement-block
SkipNewlines();
StatementBlockAst body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: don't parse more, return an error statement.
ReportIncompleteInput(After(tryToken),
nameof(ParserStrings.MissingTryStatementBlock),
ParserStrings.MissingTryStatementBlock);
return new ErrorStatementAst(tryToken.Extent);
}
IScriptExtent endErrorStatement = null;
CatchClauseAst catchClause;
List<CatchClauseAst> catches = new List<CatchClauseAst>();
List<TypeConstraintAst> errorAsts = null;
while ((catchClause = CatchBlockRule(ref endErrorStatement, ref errorAsts)) != null)
{
catches.Add(catchClause);
}
SkipNewlines();
Token finallyToken = PeekToken();
StatementBlockAst finallyBlock = null;
if (finallyToken.Kind == TokenKind.Finally)
{
SkipToken();
finallyBlock = StatementBlockRule();
if (finallyBlock == null)
{
// ErrorRecovery: just return null, a null finally block is fine in the resulting ast (but maybe consider
// marking the resulting ast as having an error.)
endErrorStatement = finallyToken.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingFinallyStatementBlock),
ParserStrings.MissingFinallyStatementBlock,
finallyToken.Kind.Text());
}
}
if (catches.Count == 0 && finallyBlock == null && endErrorStatement == null)
{
// ErrorRecovery: don't parse more, return an error statement.
endErrorStatement = body.Extent;
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingCatchOrFinally),
ParserStrings.MissingCatchOrFinally);
}
if (endErrorStatement != null)
{
return new ErrorStatementAst(ExtentOf(tryToken, endErrorStatement), GetNestedErrorAsts(body, catches, errorAsts));
}
return new TryStatementAst(ExtentOf(tryToken, finallyBlock != null ? finallyBlock.Extent : catches.Last().Extent),
body, catches, finallyBlock);
}
private StatementAst DataStatementRule(Token dataToken)
{
//G data-statement:
//G 'data' new-lines:opt data-name data-commands-allowed:opt statement-block
//G data-name:
//G simple-name
//G data-commands-allowed:
//G new-lines:opt '-supportedcommand' data-commands-list
//G data-commands-list:
//G new-lines:opt data-command
//G data-commands-list ',' new-lines:opt data-command
//G data-command:
//G command-name-expr
IScriptExtent endErrorStatement = null;
SkipNewlines();
var dataVariableNameAst = SimpleNameRule();
string dataVariableName = (dataVariableNameAst != null) ? dataVariableNameAst.Value : null;
SkipNewlines();
Token supportedCommandToken = PeekToken();
List<ExpressionAst> commands = null;
if (supportedCommandToken.Kind == TokenKind.Parameter)
{
SkipToken();
if (!IsSpecificParameter(supportedCommandToken, "SupportedCommand"))
{
// ErrorRecovery: Assume the parameter is just misspelled, look for command names next
endErrorStatement = supportedCommandToken.Extent;
ReportError(endErrorStatement,
nameof(ParserStrings.InvalidParameterForDataSectionStatement),
ParserStrings.InvalidParameterForDataSectionStatement,
((ParameterToken)supportedCommandToken).ParameterName);
}
Token commaToken = null;
commands = new List<ExpressionAst>();
while (true)
{
SkipNewlines();
ExpressionAst ast = GetSingleCommandArgument(CommandArgumentContext.CommandName);
if (ast == null)
{
// ErrorRecovery: Look for the data statement body.
// Only report an error if an error hasn't already been issued.
if (endErrorStatement == null)
{
ReportIncompleteInput(After(commaToken ?? supportedCommandToken),
nameof(ParserStrings.MissingValueForSupportedCommandInDataSectionStatement),
ParserStrings.MissingValueForSupportedCommandInDataSectionStatement);
}
endErrorStatement = commaToken != null ? commaToken.Extent : supportedCommandToken.Extent;
break;
}
commands.Add(ast);
commaToken = PeekToken();
if (commaToken.Kind == TokenKind.Comma)
{
SkipToken();
continue;
}
break;
}
}
StatementBlockAst body = null;
if (endErrorStatement == null)
{
body = StatementBlockRule();
if (body == null)
{
// ErrorRecovery: return an error statement.
endErrorStatement = commands != null
? commands.Last().Extent
: ExtentFromFirstOf(dataVariableNameAst, dataToken);
ReportIncompleteInput(After(endErrorStatement),
nameof(ParserStrings.MissingStatementBlockForDataSection),
ParserStrings.MissingStatementBlockForDataSection);
}
}
if (endErrorStatement != null)
{
return new ErrorStatementAst(ExtentOf(dataToken, endErrorStatement), GetNestedErrorAsts(commands));
}
return new DataStatementAst(ExtentOf(dataToken, body), dataVariableName, commands, body);
}
#endregion Statements
#region Pipelines
private PipelineBaseAst PipelineRule()
{
//G pipeline:
//G assignment-expression
//G expression redirections:opt pipeline-tail:opt
//G command pipeline-tail:opt
//G
//G assignment-expression:
//G expression assignment-operator statement
//G
//G pipeline-tail:
//G '|' new-lines:opt command
//G '|' new-lines:opt command pipeline-tail
var pipelineElements = new List<CommandBaseAst>();
IScriptExtent startExtent = null;
Token pipeToken = null;
bool scanning = true;
bool background = false;
while (scanning)
{
CommandBaseAst commandAst;
Token assignToken = null;
ExpressionAst expr;
var oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Expression);
expr = ExpressionRule();
if (expr != null)
{
// We peek here because we are in expression mode, otherwise =0 will get scanned
// as a single token.
var token = PeekToken();
if (token.Kind.HasTrait(TokenFlags.AssignmentOperator))
{
SkipToken();
assignToken = token;
}
}
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
if (expr != null)
{
if (pipelineElements.Count > 0)
{
// ErrorRecovery: this is a semantic error, so just keep parsing.
ReportError(expr.Extent,
nameof(ParserStrings.ExpressionsMustBeFirstInPipeline),
ParserStrings.ExpressionsMustBeFirstInPipeline);
}
if (assignToken != null)
{
SkipNewlines();
StatementAst statement = StatementRule();
if (statement == null)
{
// ErrorRecovery: we are very likely at EOF because pretty much anything should result in some
// pipeline, so just keep parsing.
IScriptExtent errorExtent = After(assignToken);
ReportIncompleteInput(errorExtent,
nameof(ParserStrings.ExpectedValueExpression),
ParserStrings.ExpectedValueExpression,
assignToken.Kind.Text());
statement = new ErrorStatementAst(errorExtent);
}
return new AssignmentStatementAst(ExtentOf(expr, statement),
expr, assignToken.Kind, statement, assignToken.Extent);
}
RedirectionAst[] redirections = null;
var redirectionToken = PeekToken() as RedirectionToken;
RedirectionAst lastRedirection = null;
while (redirectionToken != null)
{
SkipToken();
if (redirections == null)
{
redirections = new RedirectionAst[CommandBaseAst.MaxRedirections];
}
IScriptExtent unused = null;
lastRedirection = RedirectionRule(redirectionToken, redirections, ref unused);
redirectionToken = PeekToken() as RedirectionToken;
}
var exprExtent = lastRedirection != null ? ExtentOf(expr, lastRedirection) : expr.Extent;
commandAst = new CommandExpressionAst(exprExtent, expr,
redirections != null ? redirections.Where(r => r != null) : null);
}
else
{
commandAst = (CommandAst)CommandRule(forDynamicKeyword: false);
}
if (commandAst != null)
{
if (startExtent == null)
{
startExtent = commandAst.Extent;
}
pipelineElements.Add(commandAst);
}
else if (pipelineElements.Count > 0 || PeekToken().Kind == TokenKind.Pipe)
{
// ErrorRecovery: just fall through
// If the first pipe element is null, the position points to the pipe (ideally it would
// point before, but the pipe could be the first character), otherwise the empty element
// is after the pipe character.
IScriptExtent errorPosition = pipeToken != null ? After(pipeToken) : PeekToken().Extent;
ReportIncompleteInput(errorPosition,
nameof(ParserStrings.EmptyPipeElement),
ParserStrings.EmptyPipeElement);
}
pipeToken = PeekToken();
switch (pipeToken.Kind)
{
case TokenKind.Semi:
case TokenKind.NewLine:
case TokenKind.RParen:
case TokenKind.RCurly:
case TokenKind.EndOfInput:
scanning = false;
continue;
case TokenKind.Ampersand:
SkipToken();
scanning = false;
background = true;
break;
case TokenKind.Pipe:
SkipToken();
SkipNewlines();
if (PeekToken().Kind == TokenKind.EndOfInput)
{
scanning = false;
ReportIncompleteInput(After(pipeToken),
nameof(ParserStrings.EmptyPipeElement),
ParserStrings.EmptyPipeElement);
}
break;
case TokenKind.AndAnd:
case TokenKind.OrOr:
// Parse in a manner similar to a pipe, but issue an error (for now, but should implement this for V3.)
SkipToken();
SkipNewlines();
ReportError(pipeToken.Extent,
nameof(ParserStrings.InvalidEndOfLine),
ParserStrings.InvalidEndOfLine,
pipeToken.Text);
if (PeekToken().Kind == TokenKind.EndOfInput)
{
scanning = false;
}
break;
default:
// ErrorRecovery: don't eat the token, assume it belongs to something else.
ReportError(pipeToken.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
pipeToken.Text);
scanning = false;
break;
}
}
if (pipelineElements.Count == 0)
{
return null;
}
return new PipelineAst(ExtentOf(startExtent, pipelineElements[pipelineElements.Count - 1]), pipelineElements, background);
}
private RedirectionAst RedirectionRule(RedirectionToken redirectionToken, RedirectionAst[] redirections, ref IScriptExtent extent)
{
//G redirections:
//G redirection
//G redirections redirection
//G redirection:
//G merging-redirection-operator
//G file-redirection-operator redirected-file-name
//G redirected-file-name:
//G command-argument
//G primary-expression
RedirectionAst result;
var fileRedirectionToken = redirectionToken as FileRedirectionToken;
if (fileRedirectionToken != null || (redirectionToken is InputRedirectionToken))
{
// get location
var filename = GetSingleCommandArgument(CommandArgumentContext.FileName);
if (filename == null)
{
// ErrorRecovery: Just pretend we have a filename and continue parsing.
ReportError(After(redirectionToken),
nameof(ParserStrings.MissingFileSpecification),
ParserStrings.MissingFileSpecification);
filename = new ErrorExpressionAst(redirectionToken.Extent);
}
if (fileRedirectionToken == null)
{
// Must be an input redirection
ReportError(redirectionToken.Extent,
nameof(ParserStrings.RedirectionNotSupported),
ParserStrings.RedirectionNotSupported,
redirectionToken.Text);
extent = ExtentOf(redirectionToken, filename);
return null;
}
result = new FileRedirectionAst(ExtentOf(fileRedirectionToken, filename), fileRedirectionToken.FromStream,
filename, fileRedirectionToken.Append);
}
else
{
var mergingRedirectionToken = (MergingRedirectionToken)redirectionToken;
RedirectionStream fromStream = mergingRedirectionToken.FromStream;
RedirectionStream toStream = mergingRedirectionToken.ToStream;
if (toStream != RedirectionStream.Output)
{
// Have we seen something like 1>&2 or 2>&3
// ErrorRecovery: This is just a semantic error, so no special recovery.
ReportError(redirectionToken.Extent,
nameof(ParserStrings.RedirectionNotSupported),
ParserStrings.RedirectionNotSupported,
mergingRedirectionToken.Text);
toStream = RedirectionStream.Output;
}
else if (fromStream == toStream)
{
// Make sure 1>&1, 2>&2, etc. is an error.
// ErrorRecovery: This is just a semantic error, so no special recovery.
ReportError(redirectionToken.Extent,
nameof(ParserStrings.RedirectionNotSupported),
ParserStrings.RedirectionNotSupported,
mergingRedirectionToken.Text);
}
result = new MergingRedirectionAst(mergingRedirectionToken.Extent, mergingRedirectionToken.FromStream, toStream);
}
if (redirections[(int)result.FromStream] == null)
{
redirections[(int)result.FromStream] = result;
}
else
{
string errorStream;
switch (result.FromStream)
{
case RedirectionStream.All: errorStream = ParserStrings.AllStream; break;
case RedirectionStream.Output: errorStream = ParserStrings.OutputStream; break;
case RedirectionStream.Error: errorStream = ParserStrings.ErrorStream; break;
case RedirectionStream.Warning: errorStream = ParserStrings.WarningStream; break;
case RedirectionStream.Verbose: errorStream = ParserStrings.VerboseStream; break;
case RedirectionStream.Debug: errorStream = ParserStrings.DebugStream; break;
case RedirectionStream.Information: errorStream = ParserStrings.InformationStream; break;
default:
throw PSTraceSource.NewArgumentOutOfRangeException("result.FromStream", result.FromStream);
}
ReportError(result.Extent,
nameof(ParserStrings.StreamAlreadyRedirected),
ParserStrings.StreamAlreadyRedirected,
errorStream);
}
extent = result.Extent;
return result;
}
private ExpressionAst GetSingleCommandArgument(CommandArgumentContext context)
{
if (PeekToken().Kind == TokenKind.Comma || PeekToken().Kind == TokenKind.EndOfInput)
{
return null;
}
var oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Command);
return GetCommandArgument(context, NextToken());
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
}
[Flags]
private enum CommandArgumentContext
{
CommandName = 0x01,
CommandNameAfterInvocationOperator = 0x02 | CommandName,
FileName = 0x04,
CommandArgument = 0x08,
SwitchCondition = 0x10,
}
private ExpressionAst GetCommandArgument(CommandArgumentContext context, Token token)
{
Diagnostics.Assert(token.Kind != TokenKind.Comma, "A unary comma is an error in command mode, and should have already been reported.");
ExpressionAst exprAst;
List<ExpressionAst> commandArgs = null;
Token commaToken = null;
bool foundVerbatimArgument = false;
while (true)
{
switch (token.Kind)
{
// The following tokens are never allowed as command arguments.
case TokenKind.Pipe:
case TokenKind.RCurly:
case TokenKind.RParen:
case TokenKind.EndOfInput:
case TokenKind.NewLine:
case TokenKind.Semi:
case TokenKind.Redirection:
case TokenKind.RedirectInStd:
case TokenKind.AndAnd:
case TokenKind.OrOr:
case TokenKind.Ampersand:
case TokenKind.MinusMinus:
case TokenKind.Comma:
UngetToken(token);
// If we haven't seen an argument, the caller must issue an error. If we've seen at least one
// argument, then we will issue the error and return back the arguments seen so far.
if (commaToken == null)
{
return null;
}
// ErrorRecovery: stop looking for additional arguments, exclude the trailing comma
ReportIncompleteInput(After(commaToken),
nameof(ParserStrings.MissingExpression),
ParserStrings.MissingExpression,
",");
return new ErrorExpressionAst(ExtentOf(commandArgs.First(), commaToken), commandArgs);
case TokenKind.SplattedVariable:
case TokenKind.Variable:
case TokenKind.Number:
case TokenKind.HereStringExpandable:
case TokenKind.StringExpandable:
case TokenKind.HereStringLiteral:
case TokenKind.StringLiteral:
case TokenKind.LParen:
case TokenKind.DollarParen:
case TokenKind.AtParen:
case TokenKind.AtCurly:
case TokenKind.LCurly:
UngetToken(token);
exprAst = PrimaryExpressionRule(withMemberAccess: true);
Diagnostics.Assert(exprAst != null, "PrimaryExpressionRule should never return null");
break;
case TokenKind.Generic:
if ((context & CommandArgumentContext.CommandName) != 0)
{
token.TokenFlags |= TokenFlags.CommandName;
}
var genericToken = (StringToken)token;
var expandableToken = genericToken as StringExpandableToken;
// A command name w/o invocation operator is not expandable even if the token has expandable parts
// If we have seen an invocation operator, the command name is expandable.
if (expandableToken != null && context != CommandArgumentContext.CommandName)
{
var nestedExpressions = ParseNestedExpressions(expandableToken);
exprAst = new ExpandableStringExpressionAst(expandableToken, expandableToken.Value,
expandableToken.FormatString, nestedExpressions);
}
else
{
exprAst = new StringConstantExpressionAst(genericToken.Extent, genericToken.Value, StringConstantType.BareWord);
// If this is a verbatim argument, then don't continue peeking
if (String.Equals(genericToken.Value, VERBATIM_ARGUMENT, StringComparison.OrdinalIgnoreCase))
{
foundVerbatimArgument = true;
}
}
break;
default:
exprAst = new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord);
// A command/argument that matches a keyword isn't really a keyword, so don't color it that way
token.TokenFlags &= ~TokenFlags.Keyword;
switch (context)
{
case CommandArgumentContext.CommandName:
case CommandArgumentContext.CommandNameAfterInvocationOperator:
token.TokenFlags |= TokenFlags.CommandName;
break;
case CommandArgumentContext.FileName:
case CommandArgumentContext.CommandArgument:
case CommandArgumentContext.SwitchCondition:
token.SetIsCommandArgument();
break;
}
break;
}
if (context != CommandArgumentContext.CommandArgument)
{
break;
}
if (foundVerbatimArgument)
{
break;
}
token = PeekToken();
if (token.Kind != TokenKind.Comma)
{
break;
}
commaToken = token;
if (commandArgs == null)
{
commandArgs = new List<ExpressionAst>();
}
commandArgs.Add(exprAst);
SkipToken();
SkipNewlines();
token = NextToken();
}
Diagnostics.Assert(commandArgs != null || exprAst != null, "How did that happen?");
if (commandArgs != null)
{
commandArgs.Add(exprAst);
return new ArrayLiteralAst(ExtentOf(commandArgs[0], commandArgs[commandArgs.Count - 1]), commandArgs);
}
return exprAst;
}
internal Ast CommandRule(bool forDynamicKeyword)
{
//G command:
//G command-name command-elements:opt
//G command-invocation-operator command-module:opt command-name-expr command-elements:opt
//G command-invocation-operator: one of
//G '&' '.'
//G command-module:
//G primary-expression
//G command-name:
//G generic-token
//G generic-token-with-subexpr
//G generic-token-with-subexpr: No whitespace is allowed between ) and command-name.
//G generic-token-with-subexpr-start statement-list:opt ) command-name
//G command-name-expr:
//G command-name
//G primary-expression
//G command-elements:
//G command-element
//G command-elements command-element
//G command-element:
//G command-parameter
//G command-argument
//G redirection
//G command-argument:
//G command-name-expr
Token firstToken;
bool dotSource, ampersand;
bool sawDashDash = false;
bool foundVerbatimArgument = false;
RedirectionAst[] redirections = null;
IScriptExtent endExtent;
var elements = new List<CommandElementAst>();
var oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Command);
Token token = NextToken();
firstToken = token;
endExtent = token.Extent;
dotSource = false;
ampersand = false;
CommandArgumentContext context;
if (token.Kind == TokenKind.Dot)
{
dotSource = true;
token = NextToken();
context = CommandArgumentContext.CommandNameAfterInvocationOperator;
}
else if (token.Kind == TokenKind.Ampersand)
{
ampersand = true;
token = NextToken();
context = CommandArgumentContext.CommandNameAfterInvocationOperator;
}
else
{
context = CommandArgumentContext.CommandName;
}
bool scanning = true;
while (scanning)
{
switch (token.Kind)
{
case TokenKind.Pipe:
case TokenKind.RCurly:
case TokenKind.RParen:
case TokenKind.EndOfInput:
case TokenKind.NewLine:
case TokenKind.Semi:
case TokenKind.AndAnd:
case TokenKind.OrOr:
case TokenKind.Ampersand:
UngetToken(token);
scanning = false;
continue;
case TokenKind.MinusMinus:
endExtent = token.Extent;
// Add the first -- as a parameter, which is then ignored when constructing the command processor unless it's a native
// command. All subsequent -- are added as arguments.
elements.Add(sawDashDash
? (CommandElementAst)new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord)
: new CommandParameterAst(token.Extent, "-", null, token.Extent));
sawDashDash = true;
break;
case TokenKind.Comma:
endExtent = token.Extent;
ReportError(token.Extent,
nameof(ParserStrings.MissingArgument),
ParserStrings.MissingArgument);
SkipNewlines();
break;
case TokenKind.Parameter:
if ((context & CommandArgumentContext.CommandName) != 0 || sawDashDash)
{
endExtent = token.Extent;
token.TokenFlags |= TokenFlags.CommandName;
var commandName = new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord);
elements.Add(commandName);
break;
}
var parameterToken = (ParameterToken)token;
ExpressionAst parameterArgs;
IScriptExtent extent;
// If the next token is a comma, don't grab it as part of the argument. The next time through this
// loop will issue an error.
if (parameterToken.UsedColon && PeekToken().Kind != TokenKind.Comma)
{
parameterArgs = GetCommandArgument(CommandArgumentContext.CommandArgument, NextToken());
if (parameterArgs == null)
{
extent = parameterToken.Extent;
ReportError(After(extent),
nameof(ParserStrings.ParameterRequiresArgument),
ParserStrings.ParameterRequiresArgument,
parameterToken.Text);
}
else
{
extent = ExtentOf(token, parameterArgs);
}
}
else
{
parameterArgs = null;
extent = token.Extent;
}
endExtent = extent;
var paramAst = new CommandParameterAst(extent, parameterToken.ParameterName, parameterArgs, token.Extent);
elements.Add(paramAst);
break;
case TokenKind.Redirection:
case TokenKind.RedirectInStd:
if ((context & CommandArgumentContext.CommandName) == 0)
{
if (redirections == null)
{
redirections = new RedirectionAst[CommandBaseAst.MaxRedirections];
}
RedirectionRule((RedirectionToken)token, redirections, ref endExtent);
}
else
{
// For backwards compatibility, we allow redirection operators as command names.
// V2 did not allow:
// & <<
// but V3 and on will because it falls out rather naturally here.
endExtent = token.Extent;
elements.Add(new StringConstantExpressionAst(token.Extent, token.Text, StringConstantType.BareWord));
}
break;
default:
if (token.Kind == TokenKind.InlineScript && context == CommandArgumentContext.CommandName)
{
scanning = InlineScriptRule(token, elements);
Diagnostics.Assert(elements.Count >= 1, "We should at least have the command name: inlinescript");
endExtent = elements.Last().Extent;
if (!scanning) { continue; }
}
else
{
var ast = GetCommandArgument(context, token);
// If this is the special verbatim argument syntax, look for the next element
StringToken argumentToken = token as StringToken;
if ((argumentToken != null) && String.Equals(argumentToken.Value, VERBATIM_ARGUMENT, StringComparison.OrdinalIgnoreCase))
{
elements.Add(ast);
endExtent = ast.Extent;
var verbatimToken = GetVerbatimCommandArgumentToken();
if (verbatimToken != null)
{
foundVerbatimArgument = true;
scanning = false;
ast = new StringConstantExpressionAst(verbatimToken.Extent, verbatimToken.Value, StringConstantType.BareWord);
elements.Add(ast);
endExtent = ast.Extent;
}
break;
}
endExtent = ast.Extent;
elements.Add(ast);
}
break;
}
if (!foundVerbatimArgument)
{
context = CommandArgumentContext.CommandArgument;
token = NextToken();
}
}
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
if (elements.Count == 0)
{
Diagnostics.Assert(!forDynamicKeyword, "DynamicKeyword should have command name at least");
if (dotSource || ampersand)
{
IScriptExtent extent = firstToken.Extent;
ReportError(extent,
nameof(ParserStrings.MissingExpression),
ParserStrings.MissingExpression,
firstToken.Text);
}
return null;
}
if (forDynamicKeyword)
{
// TODO: report error if any redirections
return new DynamicKeywordStatementAst(ExtentOf(firstToken, endExtent), elements);
}
return new CommandAst(ExtentOf(firstToken, endExtent), elements,
dotSource || ampersand ? firstToken.Kind : TokenKind.Unknown,
redirections != null ? redirections.Where(r => r != null) : null);
}
#endregion Pipelines
#region Expressions
private ExpressionAst ExpressionRule()
{
//G expression:
//G logical-expression
//G
//G logical-expression:
//G bitwise-expression
//G logical-expression '-and' new-lines:opt bitwise-expression
//G logical-expression '-or' new-lines:opt bitwise-expression
//G logical-expression '-xor' new-lines:opt bitwise-expression
//G
//G bitwise-expression:
//G comparison-expression
//G bitwise-expression '-band' new-lines:opt comparison-expression
//G bitwise-expression '-bor' new-lines:opt comparison-expression
//G bitwise-expression '-bxor' new-lines:opt comparison-expression
//G
//G comparison-expression:
//G additive-expression
//G comparison-expression comparison-operator new-lines:opt additive-expression
//G
//G additive-expression:
//G multiplicative-expression
//G additive-expression '+' new-lines:opt multiplicative-expression
//G additive-expression dash new-lines:opt multiplicative-expression
//G
//G multiplicative-expression:
//G format-expression
//G multiplicative-expression '*' new-lines:opt format-expression
//G multiplicative-expression '/' new-lines:opt format-expression
//G multiplicative-expression '%' new-lines:opt format-expression
//G
//G format-expression:
//G range-expression
//G format-expression format-operator new-lines:opt range-expression
//G
//G range-expression:
//G array-literal-expression
//G range-expression '..' new-lines:opt array-literal-expression
RuntimeHelpers.EnsureSufficientExecutionStack();
var oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Expression);
ExpressionAst lhs, rhs;
ExpressionAst expr = ArrayLiteralRule();
if (expr == null)
{
return null;
}
ParameterToken paramToken;
Token token = PeekToken();
if (!token.Kind.HasTrait(TokenFlags.BinaryOperator))
{
paramToken = token as ParameterToken;
if (paramToken != null)
{
return ErrorRecoveryParameterInExpression(paramToken, expr);
}
return expr;
}
SkipToken();
Stack<ExpressionAst> operandStack = new Stack<ExpressionAst>();
Stack<Token> operatorStack = new Stack<Token>();
operandStack.Push(expr);
operatorStack.Push(token);
int precedence = token.Kind.GetBinaryPrecedence();
while (true)
{
SkipNewlines();
expr = ArrayLiteralRule();
if (expr == null)
{
// ErrorRecovery: create an error expression to fill out the ast and keep parsing.
IScriptExtent extent = After(token);
// Use token.Text, not token.Kind.Text() b/c the kind might not match the actual operator used
// when a case insensitive operator is used.
ReportIncompleteInput(extent,
nameof(ParserStrings.ExpectedValueExpression),
ParserStrings.ExpectedValueExpression,
token.Text);
expr = new ErrorExpressionAst(extent);
}
operandStack.Push(expr);
token = NextToken();
if (!token.Kind.HasTrait(TokenFlags.BinaryOperator))
{
// Remember the token that we stopped on, but only parameters, used for error recovery
paramToken = token as ParameterToken;
UngetToken(token);
break;
}
int newPrecedence = token.Kind.GetBinaryPrecedence();
while (newPrecedence <= precedence)
{
rhs = operandStack.Pop();
lhs = operandStack.Pop();
Token op = operatorStack.Pop();
operandStack.Push(new BinaryExpressionAst(ExtentOf(lhs, rhs), lhs, op.Kind, rhs, op.Extent));
if (operatorStack.Count == 0)
break;
precedence = operatorStack.Peek().Kind.GetBinaryPrecedence();
}
operatorStack.Push(token);
precedence = newPrecedence;
}
rhs = operandStack.Pop();
Diagnostics.Assert(operandStack.Count == operatorStack.Count, "Stacks out of sync");
while (operandStack.Count > 0)
{
lhs = operandStack.Pop();
token = operatorStack.Pop();
rhs = new BinaryExpressionAst(ExtentOf(lhs, rhs), lhs, token.Kind, rhs, token.Extent);
}
if (paramToken != null)
{
return ErrorRecoveryParameterInExpression(paramToken, rhs);
}
return rhs;
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
}
private ExpressionAst ErrorRecoveryParameterInExpression(ParameterToken paramToken, ExpressionAst expr)
{
// ErrorRecovery - when we have a parameter in an expression, eat it under the assumption
// that it's an incomplete operator. This simplifies analysis later, e.g. trying to autocomplete
// operators.
ReportError(paramToken.Extent,
nameof(ParserStrings.UnexpectedToken),
ParserStrings.UnexpectedToken,
paramToken.Text);
SkipToken();
return new ErrorExpressionAst(ExtentOf(expr, paramToken),
new Ast[] {
expr,
new CommandParameterAst(paramToken.Extent, paramToken.ParameterName, null, paramToken.Extent)});
}
private ExpressionAst ArrayLiteralRule()
{
//G array-literal-expression:
//G unary-expression
//G unary-expression ',' new-lines:opt array-literal-expression
ExpressionAst lastExpr = UnaryExpressionRule();
ExpressionAst firstExpr = lastExpr;
Token commaToken = PeekToken();
if (commaToken.Kind != TokenKind.Comma || _disableCommaOperator)
{
return lastExpr;
}
var arrayValues = new List<ExpressionAst> { lastExpr };
while (commaToken.Kind == TokenKind.Comma)
{
SkipToken();
SkipNewlines();
lastExpr = UnaryExpressionRule();
if (lastExpr == null)
{
// ErrorRecovery: create an error expression for the ast and break.
ReportIncompleteInput(After(commaToken),
nameof(ParserStrings.MissingExpressionAfterToken),
ParserStrings.MissingExpressionAfterToken,
commaToken.Text);
lastExpr = new ErrorExpressionAst(commaToken.Extent);
arrayValues.Add(lastExpr);
break;
}
arrayValues.Add(lastExpr);
commaToken = PeekToken();
}
return new ArrayLiteralAst(ExtentOf(firstExpr, lastExpr), arrayValues);
}
private ExpressionAst UnaryExpressionRule()
{
//G unary-expression:
//G primary-expression
//G expression-with-unary-operator
//G
//G expression-with-unary-operator:
//G ',' new-lines:opt unary-expression
//G '-not' new-lines:opt unary-expression
//G '!' new-lines:opt unary-expression
//G '-bnot' new-lines:opt unary-expression
//G '+' new-lines:opt unary-expression
//G dash new-lines:opt unary-expression
//G pre-increment-expression
//G pre-decrement-expression
//G cast-expression
//G '-split' new-lines:opt unary-expression
//G '-join' new-lines:opt unary-expression
//G
//G pre-increment-expression:
//G '++' new-lines:opt unary-expression
//G
//G pre-decrement-expression:
//G dashdash new-lines:opt unary-expression
//G
//G cast-expression:
//G type-literal unary-expression
RuntimeHelpers.EnsureSufficientExecutionStack();
ExpressionAst expr = null;
Token token;
bool oldAllowSignedNumbers = _tokenizer.AllowSignedNumbers;
try
{
_tokenizer.AllowSignedNumbers = true;
if (_ungotToken != null && _ungotToken.Kind == TokenKind.Minus)
{
Resync(_ungotToken);
}
token = PeekToken();
}
finally
{
_tokenizer.AllowSignedNumbers = oldAllowSignedNumbers;
}
ExpressionAst child;
if (token.Kind.HasTrait(TokenFlags.UnaryOperator))
{
if (_disableCommaOperator && token.Kind == TokenKind.Comma)
{
return null;
}
SkipToken();
SkipNewlines();
child = UnaryExpressionRule();
if (child != null)
{
if (token.Kind == TokenKind.Comma)
{
expr = new ArrayLiteralAst(ExtentOf(token, child), new ExpressionAst[] { child });
}
else
{
expr = new UnaryExpressionAst(ExtentOf(token, child), token.Kind, child);
}
}
else
{
// ErrorRecovery: don't bother constructing a unary expression, but we know we must have
// some sort of expression, so return an error expression.
// Use token.Text, not token.Kind.Text() b/c the kind might not match the actual operator used
// when a case insensitive operator is used.
ReportIncompleteInput(After(token),
nameof(ParserStrings.MissingExpressionAfterOperator),
ParserStrings.MissingExpressionAfterOperator,
token.Text);
return new ErrorExpressionAst(token.Extent);
}
}
else if (token.Kind == TokenKind.LBracket)
{
// Possibly a type literal or attribute.
var attributes = AttributeListRule(true);
if (attributes == null)
{
return null;
}
AttributeBaseAst lastAttribute = attributes.Last();
if (lastAttribute is AttributeAst)
{
SkipNewlines();
child = UnaryExpressionRule();
if (child == null)
{
// ErrorRecovery: We have a list of attributes, and we know it's not before a param statement,
// so we know we must have some sort of expression. Return an error expression then.
ReportIncompleteInput(lastAttribute.Extent,
nameof(ParserStrings.UnexpectedAttribute),
ParserStrings.UnexpectedAttribute,
lastAttribute.TypeName.FullName);
return new ErrorExpressionAst(ExtentOf(token, lastAttribute));
}
expr = new AttributedExpressionAst(ExtentOf(lastAttribute, child), lastAttribute, child);
}
else
{
Diagnostics.Assert(_ungotToken == null || ErrorList.Count > 0,
"Unexpected lookahead from AttributeListRule.");
// If we've looked ahead, don't go looking for a member access token, we've already issued an error,
// just assume we're not trying to access a member.
var memberAccessToken = _ungotToken != null ? null : NextMemberAccessToken(false);
if (memberAccessToken != null)
{
expr = CheckPostPrimaryExpressionOperators(memberAccessToken,
new TypeExpressionAst(lastAttribute.Extent,
lastAttribute.TypeName));
}
else
{
token = PeekToken();
if (token.Kind != TokenKind.NewLine && token.Kind != TokenKind.Comma)
{
child = UnaryExpressionRule();
if (child != null)
{
expr = new ConvertExpressionAst(ExtentOf(lastAttribute, child),
(TypeConstraintAst)lastAttribute, child);
}
}
}
if (expr == null)
{
expr = new TypeExpressionAst(lastAttribute.Extent, lastAttribute.TypeName);
}
}
for (int i = attributes.Count - 2; i >= 0; --i)
{
var typeConstraint = attributes[i] as TypeConstraintAst;
expr = typeConstraint != null
? new ConvertExpressionAst(ExtentOf(typeConstraint, expr), typeConstraint, expr)
: new AttributedExpressionAst(ExtentOf(attributes[i], expr), attributes[i], expr);
}
}
else
{
expr = PrimaryExpressionRule(withMemberAccess: true);
}
if (expr != null)
{
token = PeekToken();
TokenKind operation = (token.Kind == TokenKind.PlusPlus)
? TokenKind.PostfixPlusPlus
: (token.Kind == TokenKind.MinusMinus)
? TokenKind.PostfixMinusMinus
: TokenKind.Unknown;
if (operation != TokenKind.Unknown)
{
SkipToken();
expr = new UnaryExpressionAst(ExtentOf(expr, token), operation, expr);
}
}
return expr;
}
private ExpressionAst PrimaryExpressionRule(bool withMemberAccess)
{
//G primary-expression:
//G value
//G member-access
//G element-access
//G invocation-expression
//G post-increment-expression
//G post-decrement-expression
//G value:
//G parenthesized-expression
//G sub-expression
//G array-expression
//G script-block-expression
//G hash-literal-expression
//G literal
//G type-literal
//G variable
ExpressionAst expr;
Token token = NextToken();
switch (token.Kind)
{
case TokenKind.SplattedVariable:
case TokenKind.Variable:
expr = CheckUsingVariable((VariableToken)token, withMemberAccess: withMemberAccess);
break;
case TokenKind.Number:
expr = new ConstantExpressionAst((NumberToken)token);
break;
case TokenKind.HereStringExpandable:
case TokenKind.StringExpandable:
expr = ExpandableStringRule((StringExpandableToken)token);
break;
case TokenKind.HereStringLiteral:
case TokenKind.StringLiteral:
expr = new StringConstantExpressionAst((StringToken)token);
break;
case TokenKind.LParen:
expr = ParenthesizedExpressionRule(token);
break;
case TokenKind.AtParen:
case TokenKind.DollarParen:
expr = SubExpressionRule(token);
break;
case TokenKind.AtCurly:
expr = HashExpressionRule(token, false /* parsingSchemaElement */ );
break;
case TokenKind.LCurly:
expr = ScriptBlockExpressionRule(token);
break;
default:
UngetToken(token);
return null;
}
if (!withMemberAccess)
{
return expr;
}
return CheckPostPrimaryExpressionOperators(NextMemberAccessToken(true), expr);
}
private ExpressionAst CheckUsingVariable(VariableToken variableToken, bool withMemberAccess)
{
var variablePath = variableToken.VariablePath;
if (variablePath.IsDriveQualified && variablePath.DriveName.Equals("using", StringComparison.OrdinalIgnoreCase) && variablePath.UnqualifiedPath.Length > 0)
{
var realVariablePath = new VariablePath(variablePath.UnqualifiedPath);
ExpressionAst childExpr = new VariableExpressionAst(variableToken.Extent, realVariablePath, (variableToken.Kind == TokenKind.SplattedVariable));
if (withMemberAccess)
{
childExpr = CheckPostPrimaryExpressionOperators(NextMemberAccessToken(true), childExpr);
}
return new UsingExpressionAst(childExpr.Extent, childExpr);
}
return new VariableExpressionAst(variableToken);
}
private ExpressionAst CheckPostPrimaryExpressionOperators(Token token, ExpressionAst expr)
{
while (token != null)
{
// To support fluent style programming, allow newlines after the member access operator.
V3SkipNewlines();
if (token.Kind == TokenKind.Dot || token.Kind == TokenKind.ColonColon)
{
expr = MemberAccessRule(expr, token);
}
else if (token.Kind == TokenKind.LBracket)
{
expr = ElementAccessRule(expr, token);
}
token = NextMemberAccessToken(true);
}
return expr;
}
private ExpressionAst HashExpressionRule(Token atCurlyToken, bool parsingSchemaElement)
{
//G hash-literal-expression:
//G '@{' new-lines:opt hash-literal-body:opt new-lines:opt '}'
//G hash-literal-body:
//G hash-entry
//G hash-literal-body statement-terminators hash-entry
//G statement-terminators:
//G statement-terminator
//G statement-terminators statement-terminator
SkipNewlines();
List<KeyValuePair> keyValuePairs = new List<KeyValuePair>();
while (true)
{
KeyValuePair pair = GetKeyValuePair(parsingSchemaElement);
if (pair == null)
{
break;
}
keyValuePairs.Add(pair);
Token token = PeekToken();
if (token.Kind != TokenKind.NewLine && token.Kind != TokenKind.Semi)
{
break;
}
SkipNewlinesAndSemicolons();
}
IScriptExtent endExtent;
Token rCurly = NextToken();
if (rCurly.Kind != TokenKind.RCurly)
{
UngetToken(rCurly);
string errorId;
string errorMsg;
if (parsingSchemaElement)
{
errorId = nameof(ParserStrings.IncompletePropertyAssignmentBlock);
errorMsg = ParserStrings.IncompletePropertyAssignmentBlock;
}
else
{
errorId = nameof(ParserStrings.MissingEndCurlyBrace);
errorMsg = ParserStrings.MissingEndCurlyBrace;
}
ReportIncompleteInput(After(atCurlyToken), rCurly.Extent, errorId, errorMsg);
endExtent = Before(rCurly);
}
else
{
endExtent = rCurly.Extent;
}
var hashAst = new HashtableAst(ExtentOf(atCurlyToken, endExtent), keyValuePairs);
hashAst.IsSchemaElement = parsingSchemaElement;
return hashAst;
}
private KeyValuePair GetKeyValuePair(bool parsingSchemaElement)
{
//G hash-entry:
//G key-expression '=' new-lines:opt statement
Token equals;
ExpressionAst key;
var oldTokenizerMode = _tokenizer.Mode;
try
{
SetTokenizerMode(TokenizerMode.Expression);
key = LabelOrKeyRule();
if (key == null)
{
return null;
}
equals = NextToken();
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
if (equals.Kind != TokenKind.Equals)
{
// ErrorRecovery: Pretend we saw the '=' and a statement.
UngetToken(equals);
IScriptExtent errorExtent = After(key);
string errorId;
string errorMsg;
if (parsingSchemaElement)
{
errorId = nameof(ParserStrings.MissingEqualsInPropertyAssignmentBlock);
errorMsg = ParserStrings.MissingEqualsInPropertyAssignmentBlock;
}
else
{
errorId = nameof(ParserStrings.MissingEqualsInHashLiteral);
errorMsg = ParserStrings.MissingEqualsInHashLiteral;
}
ReportError(errorExtent, errorId, errorMsg);
SyncOnError(false, TokenKind.RCurly, TokenKind.Semi, TokenKind.NewLine);
return new KeyValuePair(key, new ErrorStatementAst(errorExtent));
}
StatementAst statement;
try
{
SetTokenizerMode(TokenizerMode.Command);
SkipNewlines();
statement = StatementRule();
if (statement == null)
{
// ErrorRecovery: pretend we saw a statement and keep parsing.
IScriptExtent errorExtent = After(equals);
string errorId;
string errorMsg;
if (parsingSchemaElement)
{
errorId = nameof(ParserStrings.MissingEqualsInPropertyAssignmentBlock);
errorMsg = ParserStrings.MissingEqualsInPropertyAssignmentBlock;
}
else
{
errorId = nameof(ParserStrings.MissingStatementInHashLiteral);
errorMsg = ParserStrings.MissingStatementInHashLiteral;
}
ReportIncompleteInput(errorExtent, errorId, errorMsg);
statement = new ErrorStatementAst(errorExtent);
}
}
finally
{
SetTokenizerMode(oldTokenizerMode);
}
return new KeyValuePair(key, statement);
}
private ExpressionAst ScriptBlockExpressionRule(Token lCurly)
{
//G script-block-expression:
//G '{' new-lines:opt script-block new-lines:opt '}'
ScriptBlockAst scriptBlockAst;
bool oldDisableCommaOperator = _disableCommaOperator;
var oldTokenizerMode = _tokenizer.Mode;
try
{
_disableCommaOperator = false;
SetTokenizerMode(TokenizerMode.Command);
SkipNewlines();
scriptBlockAst = ScriptBlockRule(lCurly, isFilter: false);
}
finally
{
_disableCommaOperator = oldDisableCommaOperator;
SetTokenizerMode(oldTokenizerMode);
}
return new ScriptBlockExpressionAst(scriptBlockAst.Extent, scriptBlockAst);
}
private ExpressionAst SubExpressionRule(Token firstToken)
{
//G array-expression:
//G '@(' new-lines:opt statement-list:opt new-lines:opt ')'
//G sub-expression:
//G '$(' new-lines:opt statement-list:opt new-lines:opt ')'
IScriptExtent statementListExtent;
List<TrapStatementAst> traps = new List<TrapStatementAst>();
List<StatementAst> statements = new List<StatementAst>();
Token rParen;
bool oldDisableCommaOperator = _disableCommaOperator;
var oldTokenizerMode = _tokenizer.Mode;
try
{
_disableCommaOperator = false;
SetTokenizerMode(TokenizerMode.Command);
SkipNewlines();
statementListExtent = StatementListRule(statements, traps);
SkipNewlines();
rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: Assume only the closing paren is missing, continue as though it was present.
UngetToken(rParen);
ReportIncompleteInput(rParen.Extent,
nameof(ParserStrings.MissingEndParenthesisInSubexpression),
ParserStrings.MissingEndParenthesisInSubexpression);
}
}
finally
{
_disableCommaOperator = oldDisableCommaOperator;
SetTokenizerMode(oldTokenizerMode);
}
// End extent is rparen, end of the statement list (if no rparen), or the first token (if no statements).
IScriptExtent extent = ExtentOf(firstToken,
rParen.Kind == TokenKind.RParen ? rParen.Extent : statementListExtent ?? firstToken.Extent);
if (firstToken.Kind == TokenKind.DollarParen)
{
return new SubExpressionAst(extent,
new StatementBlockAst(statementListExtent ?? PositionUtilities.EmptyExtent,
statements, traps));
}
Diagnostics.Assert(firstToken.Kind == TokenKind.AtParen, "only support $() and @() here.");
return new ArrayExpressionAst(extent,
new StatementBlockAst(statementListExtent ?? PositionUtilities.EmptyExtent, statements, traps));
}
private ExpressionAst ParenthesizedExpressionRule(Token lParen)
{
//G parenthesized-expression:
//G '(' new-lines:opt pipeline new-lines:opt ')'
Token rParen;
PipelineBaseAst pipelineAst;
var oldTokenizerMode = _tokenizer.Mode;
var oldDisableCommaOperator = _disableCommaOperator;
try
{
SetTokenizerMode(TokenizerMode.Command);
_disableCommaOperator = false;
SkipNewlines();
pipelineAst = PipelineRule();
if (pipelineAst == null)
{
IScriptExtent errorPosition = After(lParen);
ReportIncompleteInput(errorPosition,
nameof(ParserStrings.ExpectedExpression),
ParserStrings.ExpectedExpression);
pipelineAst = new ErrorStatementAst(errorPosition);
}
SkipNewlines();
rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: Assume only the closing paren is missing, continue as though it was present.
UngetToken(rParen);
ReportIncompleteInput(After(pipelineAst),
nameof(ParserStrings.MissingEndParenthesisInExpression),
ParserStrings.MissingEndParenthesisInExpression);
rParen = null;
}
}
finally
{
_disableCommaOperator = oldDisableCommaOperator;
SetTokenizerMode(oldTokenizerMode);
}
return new ParenExpressionAst(ExtentOf(lParen, ExtentFromFirstOf(rParen, pipelineAst)), pipelineAst);
}
private List<ExpressionAst> ParseNestedExpressions(StringExpandableToken expandableStringToken)
{
List<ExpressionAst> nestedExpressions = new List<ExpressionAst>();
List<Token> newNestedTokens = _savingTokens ? new List<Token>() : null;
foreach (var token in expandableStringToken.NestedTokens)
{
Diagnostics.Assert(!token.HasError || ErrorList.Any(), "No nested tokens should have unreported errors.");
ExpressionAst exprAst;
var varToken = token as VariableToken;
if (varToken != null)
{
exprAst = CheckUsingVariable(varToken, false);
if (_savingTokens) { newNestedTokens.Add(varToken); }
}
// Enable if we decide we still need to support
// "${}" or "$var:"
//else if (token.Kind == TokenKind.Unknown)
//{
// //Diagnostics.Assert(token.Text.Equals("${}", StringComparison.OrdinalIgnoreCase),
// // "The unknown token is only used in an expandable string when it's an empty variable name.");
// // TODO: Need strict-mode check at runtime.
// // TODO: in V2, "${}" expanded to '$', but "$var:" expanded to the empty string
// exprAst = new StringConstantExpressionAst(token.Extent, "$", StringConstantType.BareWord);
// if (_savingTokens) { newNestedTokens.Add(token); }
//}
else
{
TokenizerState ts = null;
try
{
ts = _tokenizer.StartNestedScan((UnscannedSubExprToken)token);
exprAst = PrimaryExpressionRule(withMemberAccess: true);
if (_savingTokens) { newNestedTokens.AddRange(_tokenizer.TokenList); }
}
finally
{
// _ungotToken is probably <EOF>, but if there were errors, it could be something
// else. Either way, we don't want it.
_ungotToken = null;
_tokenizer.FinishNestedScan(ts);
}
}
nestedExpressions.Add(exprAst);
}
if (_savingTokens) { expandableStringToken.NestedTokens = new ReadOnlyCollection<Token>(newNestedTokens); }
return nestedExpressions;
}
private ExpressionAst ExpandableStringRule(StringExpandableToken strToken)
{
//G value:
//G literal
ExpressionAst expr;
// We need to scan the nested tokens even if there was some error. This is used by the tab completion: "pshome is $psh<tab>
if (strToken.NestedTokens != null)
{
List<ExpressionAst> nestedExpressions = ParseNestedExpressions(strToken);
expr = new ExpandableStringExpressionAst(strToken, strToken.Value, strToken.FormatString, nestedExpressions);
}
else
{
expr = new StringConstantExpressionAst(strToken);
}
return expr;
}
private ExpressionAst MemberNameRule()
{
//G member-name:
//G simple-name
//G string-literal
//G string-literal-with-subexpression
//G expression-with-unary-operator
//G value
ExpressionAst simpleName = SimpleNameRule();
if (simpleName != null)
{
return simpleName;
}
Token token = PeekToken();
if (token.Kind.HasTrait(TokenFlags.UnaryOperator) || token.Kind == TokenKind.LBracket)
{
return UnaryExpressionRule();
}
return PrimaryExpressionRule(withMemberAccess: false);
}
private ExpressionAst MemberAccessRule(ExpressionAst targetExpr, Token operatorToken)
{
//G member-access: No whitespace is allowed between terms in these productions.
//G primary-expression '.' member-name
//G primary-expression '::' member-name
// On entry, we've verified that operatorToken is not preceded by whitespace.
CommandElementAst member = MemberNameRule();
if (member == null)
{
// ErrorRecovery: pretend we saw a property name, don't bother looking for an invocation,
// and keep parsing.
ReportIncompleteInput(After(operatorToken),
nameof(ParserStrings.MissingPropertyName),
ParserStrings.MissingPropertyName);
member = GetSingleCommandArgument(CommandArgumentContext.CommandArgument) ??
new ErrorExpressionAst(ExtentOf(targetExpr, operatorToken));
}
else
{
Token lParen = NextInvokeMemberToken();
if (lParen != null)
{
Diagnostics.Assert(lParen.Kind == TokenKind.LParen || lParen.Kind == TokenKind.LCurly, "token kind incorrect");
Diagnostics.Assert(member.Extent.EndColumnNumber == lParen.Extent.StartColumnNumber,
"member and paren must be adjacent");
return MemberInvokeRule(targetExpr, lParen, operatorToken, member);
}
}
return new MemberExpressionAst(ExtentOf(targetExpr, member),
targetExpr, member, operatorToken.Kind == TokenKind.ColonColon);
}
private ExpressionAst MemberInvokeRule(ExpressionAst targetExpr, Token lBracket, Token operatorToken, CommandElementAst member)
{
//G invocation-expression: target-expression passed as a parameter. lBracket can be '(' or '{'.
//G target-expression member-name invoke-param-list
//G invoke-param-list:
//G '(' invoke-param-paren-list
//G script-block
IScriptExtent lastExtent = null;
List<ExpressionAst> arguments;
if (lBracket.Kind == TokenKind.LParen)
{
arguments = this.InvokeParamParenListRule(lBracket, out lastExtent);
}
else
{
arguments = new List<ExpressionAst>();
// handle the construct $x.methodName{2+2} as through it had been written $x.methodName({2+2})
SkipNewlines();
ExpressionAst argument = ScriptBlockExpressionRule(lBracket);
arguments.Add(argument);
lastExtent = argument.Extent;
}
return new InvokeMemberExpressionAst(ExtentOf(targetExpr, lastExtent), targetExpr, member, arguments, operatorToken.Kind == TokenKind.ColonColon);
}
private List<ExpressionAst> InvokeParamParenListRule(Token lParen, out IScriptExtent lastExtent)
{
//G argument-list: '(' is passed in lParen
//G argument-expression-list:opt new-lines:opt ')'
//G argument-expression-list:
//G argument-expression
//G argument-expression new-lines:opt ',' argument-expression-list
//G argument-expression:
//G See grammar for expression - the only difference is that an
//G array-literal-expression is not allowed - the comma is used
//G to separate argument-expressions.
List<ExpressionAst> arguments = new List<ExpressionAst>();
Token comma = null;
Token rParen = null;
bool oldDisableCommaOperator = _disableCommaOperator;
bool reportedError = false;
try
{
_disableCommaOperator = true;
while (true)
{
SkipNewlines();
ExpressionAst argument = ExpressionRule();
if (argument == null)
{
if (comma != null)
{
// ErrorRecovery: sync at closing paren or newline.
ReportIncompleteInput(After(comma),
nameof(ParserStrings.MissingExpressionAfterToken),
ParserStrings.MissingExpressionAfterToken,
TokenKind.Comma.Text());
reportedError = true;
}
break;
}
arguments.Add(argument);
SkipNewlines();
comma = NextToken();
if (comma.Kind != TokenKind.Comma)
{
UngetToken(comma);
comma = null;
break;
}
}
SkipNewlines();
rParen = NextToken();
if (rParen.Kind != TokenKind.RParen)
{
// ErrorRecovery: pretend we saw a closing paren or curly and keep parsing.
UngetToken(rParen);
if (!reportedError)
{
ReportIncompleteInput(arguments.Any() ? After(arguments.Last()) : After(lParen),
nameof(ParserStrings.MissingEndParenthesisInMethodCall),
ParserStrings.MissingEndParenthesisInMethodCall);
}
rParen = null;
}
}
finally
{
_disableCommaOperator = oldDisableCommaOperator;
}
lastExtent = ExtentFromFirstOf(rParen, comma, arguments.LastOrDefault(), lParen);
return arguments;
}
private ExpressionAst ElementAccessRule(ExpressionAst primaryExpression, Token lBracket)
{
//G element-access:
//G primary-expression '[' new-lines:opt expression new-lines:opt ']'
SkipNewlines();
ExpressionAst indexExpr = ExpressionRule();
if (indexExpr == null)
{
// ErrorRecovery: hope we see a closing bracket. If we don't, we'll pretend we saw
// the closing bracket, but build an expression that can't compile.
var errorExtent = After(lBracket);
ReportIncompleteInput(errorExtent,
nameof(ParserStrings.MissingArrayIndexExpression),
ParserStrings.MissingArrayIndexExpression);
indexExpr = new ErrorExpressionAst(lBracket.Extent);
}
SkipNewlines();
Token rBracket = NextToken();
if (rBracket.Kind != TokenKind.RBracket)
{
// ErrorRecovery: just pretend we had a closing bracket and continue parsing.
UngetToken(rBracket);
// Skip reporting the error if we've already reported a missing index.
if (!(indexExpr is ErrorExpressionAst))
{
ReportIncompleteInput(After(indexExpr),
nameof(ParserStrings.MissingEndSquareBracket),
ParserStrings.MissingEndSquareBracket);
}
rBracket = null;
}
return new IndexExpressionAst(ExtentOf(primaryExpression, ExtentFromFirstOf(rBracket, indexExpr)), primaryExpression, indexExpr);
}
#endregion Expressions
#region Error Reporting
private void SaveError(ParseError error)
{
if (ErrorList.Any())
{
// Avoiding adding duplicate errors - can happen when the tokenizer resyncs.
if (ErrorList.Any(err => err.ErrorId.Equals(error.ErrorId, StringComparison.Ordinal)
&& err.Extent.EndColumnNumber == error.Extent.EndColumnNumber
&& err.Extent.EndLineNumber == error.Extent.EndLineNumber
&& err.Extent.StartColumnNumber == error.Extent.StartColumnNumber
&& err.Extent.StartLineNumber == error.Extent.StartLineNumber))
{
return;
}
}
ErrorList.Add(error);
}
private void SaveError(IScriptExtent extent, string errorId, string errorMsg, bool incompleteInput, params object[] args)
{
AssertErrorIdCorrespondsToMsgString(errorId, errorMsg);
if (args != null && args.Any())
{
errorMsg = string.Format(CultureInfo.CurrentCulture, errorMsg, args);
}
ParseError errorToSave = new ParseError(extent, errorId, errorMsg, incompleteInput);
SaveError(errorToSave);
}
/// <summary>
/// Debug assertion to ensure that all errors saved by the parser come
/// from resource (.resx) files.
/// </summary>
/// <param name="errorId">The error ID string (.resx key)</param>
/// <param name="errorMsg">The error message, which may be a template string (.resx value)</param>
[System.Diagnostics.Conditional("DEBUG")]
[System.Diagnostics.Conditional("ASSERTIONS_TRACE")]
private static void AssertErrorIdCorrespondsToMsgString(string errorId, string errorMsg)
{
// These types are the ones known to contain
// strings used by the parser as errors
Type[] resxTypes = new []
{
typeof(ParserStrings),
typeof(DiscoveryExceptions),
typeof(ExtendedTypeSystem),
typeof(MshSnapInCmdletResources),
typeof(ParameterBinderStrings)
};
// Go through each resource type and see if the errorId key is in it, and whether the value corresponds to the errorMsg
bool msgCorrespondsToString = false;
foreach (Type resxType in resxTypes)
{
string resxErrorBody = resxType.GetProperty(errorId, BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null) as string;
if (String.Equals(errorMsg, resxErrorBody, StringComparison.Ordinal))
{
msgCorrespondsToString = true;
break;
}
}
Diagnostics.Assert(msgCorrespondsToString, String.Format("Parser error ID \"{0}\" must correspond to the error message \"{1}\"", errorId, errorMsg));
}
private static object[] arrayOfOneArg
{
get { return t_arrayOfOneArg ?? (t_arrayOfOneArg = new object[1]); }
}
[ThreadStatic]
private static object[] t_arrayOfOneArg;
private static object[] arrayOfTwoArgs
{
get { return t_arrayOfTwoArgs ?? (t_arrayOfTwoArgs = new object[2]); }
}
[ThreadStatic]
private static object[] t_arrayOfTwoArgs;
internal bool ReportIncompleteInput(IScriptExtent extent, string errorId, string errorMsg)
{
// If the error position isn't at the end of the input, then we don't want to mark the error
// as incomplete input.
bool incompleteInput = _tokenizer.IsAtEndOfScript(extent, checkCommentsAndWhitespace: true);
SaveError(extent, errorId, errorMsg, incompleteInput, null);
return incompleteInput;
}
internal bool ReportIncompleteInput(IScriptExtent extent, string errorId, string errorMsg, object arg)
{
// If the error position isn't at the end of the input, then we don't want to mark the error
// as incomplete input.
bool incompleteInput = _tokenizer.IsAtEndOfScript(extent, checkCommentsAndWhitespace: true);
arrayOfOneArg[0] = arg;
SaveError(extent, errorId, errorMsg, incompleteInput, arrayOfOneArg);
return incompleteInput;
}
internal bool ReportIncompleteInput(IScriptExtent errorPosition,
IScriptExtent errorDetectedPosition,
string errorId,
string errorMsg,
params object[] args)
{
// If the error position isn't at the end of the input, then we don't want to mark the error
// as incomplete input.
bool incompleteInput = _tokenizer.IsAtEndOfScript(errorDetectedPosition, checkCommentsAndWhitespace: true);
SaveError(errorPosition, errorId, errorMsg, incompleteInput, args);
return incompleteInput;
}
internal void ReportError(IScriptExtent extent, string errorId, string errorMsg)
{
SaveError(extent, errorId, errorMsg, false, null);
}
internal void ReportError(IScriptExtent extent, string errorId, string errorMsg, object arg)
{
arrayOfOneArg[0] = arg;
SaveError(extent, errorId, errorMsg, false, arrayOfOneArg);
}
internal void ReportError(IScriptExtent extent, string errorId, string errorMsg, object arg1, object arg2)
{
arrayOfTwoArgs[0] = arg1;
arrayOfTwoArgs[1] = arg2;
SaveError(extent, errorId, errorMsg, false, arrayOfTwoArgs);
}
internal void ReportError(IScriptExtent extent, string errorId, string errorMsg, params object[] args)
{
SaveError(extent, errorId, errorMsg, false, args);
}
internal void ReportError(ParseError error)
{
SaveError(error);
}
private void ReportErrorsAsWarnings(Collection<Exception> errors)
{
var executionContext = Runspaces.Runspace.DefaultRunspace.ExecutionContext;
if (executionContext != null && executionContext.InternalHost != null && executionContext.InternalHost.UI != null)
{
foreach (var error in errors)
{
if (error != null)
{
executionContext.InternalHost.UI.WriteWarningLine(error.ToString());
}
}
}
}
#endregion Error Reporting
}
#region Error related classes
/// <summary>
///
/// </summary>
public class ParseError
{
/// <summary>
/// Creates a new parse error.
/// </summary>
/// <param name="extent">The IScriptExtent that represents the location of the error.</param>
/// <param name="errorId">The error ID to associate with the error.</param>
/// <param name="message">The message of the error.</param>
public ParseError(IScriptExtent extent, string errorId, string message)
: this(extent, errorId, message, false)
{
}
internal ParseError(IScriptExtent extent, string errorId, string message, bool incompleteInput)
{
Diagnostics.Assert(extent != null, "can't have a null position for a parse error");
Diagnostics.Assert(!string.IsNullOrEmpty(message), "can't have a null error message");
Diagnostics.Assert(!string.IsNullOrEmpty(errorId), "can't have a null error id");
Extent = extent;
ErrorId = errorId;
Message = message;
IncompleteInput = incompleteInput;
}
/// <summary>
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return PositionUtilities.VerboseMessage(Extent) + "\n" + Message;
}
/// <summary>
///
/// </summary>
public IScriptExtent Extent { get; }
/// <summary>
///
/// </summary>
public string ErrorId { get; }
/// <summary>
///
/// </summary>
public string Message { get; }
/// <summary>
///
/// </summary>
public bool IncompleteInput { get; }
}
#endregion Error related classes
// Guid is {eba789d9-533b-58d4-cd1f-2e6520e3a9c2}
[EventSource(Name = "Microsoft-PowerShell-Parser")]
internal class ParserEventSource : EventSource
{
internal static ParserEventSource Log = new ParserEventSource();
internal const int MaxScriptLengthToLog = 50;
public void ParseStart(string FileName, int Length) { WriteEvent(1, FileName, Length); }
public void ParseStop() { WriteEvent(2); }
public void ResolveSymbolsStart() { WriteEvent(3); }
public void ResolveSymbolsStop() { WriteEvent(4); }
public void SemanticChecksStart() { WriteEvent(5); }
public void SemanticChecksStop() { WriteEvent(6); }
public void CheckSecurityStart(string FileName) { WriteEvent(7, FileName); }
public void CheckSecurityStop(string FileName) { WriteEvent(8, FileName); }
public void CompileStart(string FileName, int Length, bool Optimized) { WriteEvent(9, FileName, Length, Optimized); }
public void CompileStop() { WriteEvent(10); }
internal static string GetFileOrScript(string fileName, string input)
{
return fileName ?? input.Substring(0, Math.Min(256, input.Length)).Trim();
}
}
}