diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Expressions.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Expressions.cs
new file mode 100644
index 0000000000..cad84a614e
--- /dev/null
+++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.Expressions.cs
@@ -0,0 +1,41 @@
+using Microsoft.AspNet.Razor.Tokenizer.Symbols;
+
+namespace Microsoft.AspNet.Razor.Parser
+{
+ public partial class CSharpCodeParser
+ {
+ private void SetUpExpressions()
+ {
+ MapKeywords(AwaitExpression, CSharpKeyword.Await);
+ }
+
+ private void AwaitExpression(bool topLevel)
+ {
+ // Ensure that we're on the await statement (only runs in debug)
+ Assert(CSharpKeyword.Await);
+
+ // Accept the "await" and move on
+ AcceptAndMoveNext();
+
+ // Accept 1 or more spaces between the await and the following code.
+ AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
+
+ // Accept a single code piece to await. This will accept up until a method "call" signature.
+ // Ex: "@await |Foo|()" Inbetween the pipes is what is accepted. The Statement/ImplicitExpression
+ // handling capture method calls and the parameters passed in.
+ AcceptWhile(CSharpSymbolType.Identifier);
+
+ // Top level basically indicates if we're within an expression or statement.
+ // Ex: topLevel true = @await Foo() | topLevel false = @{ await Foo(); }
+ // Note that in this case @{ @await Foo() } top level is true for await.
+ // Therefore, if we're top level then we want to act like an implicit expression,
+ // otherwise just act as whatever we're contained in.
+ if (topLevel)
+ {
+ // Setup the Span to be an async implicit expression (an implicit expresison that allows spaces).
+ // Spaces are allowed because of "@await Foo()".
+ AsyncImplicitExpression();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs
index 07a8c721c4..1a6493fd92 100644
--- a/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs
+++ b/src/Microsoft.AspNet.Razor/Parser/CSharpCodeParser.cs
@@ -18,6 +18,7 @@ namespace Microsoft.AspNet.Razor.Parser
internal static ISet DefaultKeywords = new HashSet()
{
+ "await",
"if",
"do",
"try",
@@ -45,6 +46,7 @@ namespace Microsoft.AspNet.Razor.Parser
Keywords = new HashSet();
SetUpKeywords();
SetupDirectives();
+ SetUpExpressions();
}
protected internal ISet Keywords { get; private set; }
@@ -307,6 +309,18 @@ namespace Microsoft.AspNet.Razor.Parser
}
private void ImplicitExpression()
+ {
+ ImplicitExpression(AcceptedCharacters.NonWhiteSpace);
+ }
+
+ // Async implicit expressions include the "await" keyword and therefore need to allow spaces to
+ // separate the "await" and the following code.
+ private void AsyncImplicitExpression()
+ {
+ ImplicitExpression(AcceptedCharacters.AnyExceptNewline);
+ }
+
+ private void ImplicitExpression(AcceptedCharacters acceptedCharacters)
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
@@ -314,7 +328,7 @@ namespace Microsoft.AspNet.Razor.Parser
using (PushSpanConfig(span =>
{
span.EditHandler = new ImplicitExpressionEditHandler(Language.TokenizeString, Keywords, acceptTrailingDot: IsNested);
- span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace;
+ span.EditHandler.AcceptedCharacters = acceptedCharacters;
span.CodeGenerator = new ExpressionCodeGenerator();
}))
{
@@ -325,14 +339,14 @@ namespace Microsoft.AspNet.Razor.Parser
AcceptAndMoveNext();
}
}
- while (MethodCallOrArrayIndex());
+ while (MethodCallOrArrayIndex(acceptedCharacters));
PutCurrentBack();
Output(SpanKind.Code);
}
}
- private bool MethodCallOrArrayIndex()
+ private bool MethodCallOrArrayIndex(AcceptedCharacters acceptedCharacters)
{
if (!EndOfFile)
{
@@ -361,9 +375,11 @@ namespace Microsoft.AspNet.Razor.Parser
if (At(right))
{
AcceptAndMoveNext();
- Span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace;
+
+ // At the ending brace, restore the initial accepted characters.
+ Span.EditHandler.AcceptedCharacters = acceptedCharacters;
}
- return MethodCallOrArrayIndex();
+ return MethodCallOrArrayIndex(acceptedCharacters);
}
if (CurrentSymbol.Type == CSharpSymbolType.Dot)
{
diff --git a/src/Microsoft.AspNet.Razor/Tokenizer/CSharpKeywordDetector.cs b/src/Microsoft.AspNet.Razor/Tokenizer/CSharpKeywordDetector.cs
index 24dc4ca607..fcab8c06fa 100644
--- a/src/Microsoft.AspNet.Razor/Tokenizer/CSharpKeywordDetector.cs
+++ b/src/Microsoft.AspNet.Razor/Tokenizer/CSharpKeywordDetector.cs
@@ -10,6 +10,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer
{
private static readonly Dictionary _keywords = new Dictionary(StringComparer.Ordinal)
{
+ { "await", CSharpKeyword.Await },
{ "abstract", CSharpKeyword.Abstract },
{ "byte", CSharpKeyword.Byte },
{ "class", CSharpKeyword.Class },
diff --git a/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/CSharpKeyword.cs b/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/CSharpKeyword.cs
index a16b7c6c90..607df73f66 100644
--- a/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/CSharpKeyword.cs
+++ b/src/Microsoft.AspNet.Razor/Tokenizer/Symbols/CSharpKeyword.cs
@@ -4,6 +4,7 @@ namespace Microsoft.AspNet.Razor.Tokenizer.Symbols
{
public enum CSharpKeyword
{
+ Await,
Abstract,
Byte,
Class,