Initial commit.

This commit is contained in:
David Fowler 2014-01-11 20:12:30 -08:00
commit ff854e3e15
151 changed files with 19304 additions and 0 deletions

50
.gitattributes vendored Normal file
View File

@ -0,0 +1,50 @@
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
*.jpg binary
*.png binary
*.gif binary
*.cs text=auto diff=csharp
*.vb text=auto
*.resx text=auto
*.c text=auto
*.cpp text=auto
*.cxx text=auto
*.h text=auto
*.hxx text=auto
*.py text=auto
*.rb text=auto
*.java text=auto
*.html text=auto
*.htm text=auto
*.css text=auto
*.scss text=auto
*.sass text=auto
*.less text=auto
*.js text=auto
*.lisp text=auto
*.clj text=auto
*.sql text=auto
*.php text=auto
*.lua text=auto
*.m text=auto
*.asm text=auto
*.erl text=auto
*.fs text=auto
*.fsx text=auto
*.hs text=auto
*.csproj text=auto
*.vbproj text=auto
*.fsproj text=auto
*.dbproj text=auto
*.sln text=auto eol=crlf

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
[Oo]bj/
[Bb]in/
*.xap
*.user
/TestResults
*.vspscc
*.vssscc
deploy
deploy/*
*.suo
*.cache
*.docstates
_ReSharper.*
*.csproj.user
*[Rr]e[Ss]harper.user
_ReSharper.*/
packages/*
artifacts/*
msbuild.log
PublishProfiles/
*.psess
*.vsp
*.pidb
*.userprefs
*DS_Store
*.ncrunchsolution
*.log
*.vspx
*.log.txt
/tests/Microsoft.AspNet.SignalR.FunctionalTests/artifacts/
/samples/Microsoft.AspNet.SignalR.Client.WindowsStoreJavaScript.Samples/bld/
jquery.signalR.js
jquery.signalR.min.js
xamarin/SignalRPackage/component/lib/mobile/

22
Razor.sln Normal file
View File

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
VisualStudioVersion = 12.0.21005.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNet.Razor", "src\Microsoft.AspNet.Razor\Microsoft.AspNet.Razor.csproj", "{E75D8296-3BA6-4E67-AFEB-90FF77460B15}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E75D8296-3BA6-4E67-AFEB-90FF77460B15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E75D8296-3BA6-4E67-AFEB-90FF77460B15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E75D8296-3BA6-4E67-AFEB-90FF77460B15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E75D8296-3BA6-4E67-AFEB-90FF77460B15}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,49 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.CSharp;
using System;
namespace Microsoft.AspNet.Razor
{
/// <summary>
/// Defines the C# Code Language for Razor
/// </summary>
public class CSharpRazorCodeLanguage : RazorCodeLanguage
{
private const string CSharpLanguageName = "csharp";
/// <summary>
/// Returns the name of the language: "csharp"
/// </summary>
public override string LanguageName
{
get { return CSharpLanguageName; }
}
/// <summary>
/// Returns the type of the CodeDOM provider for this language
/// </summary>
public override Type CodeDomProviderType
{
get { return typeof(CSharpCodeProvider); }
}
/// <summary>
/// Constructs a new instance of the code parser for this language
/// </summary>
public override ParserBase CreateCodeParser()
{
return new CSharpCodeParser();
}
/// <summary>
/// Constructs a new instance of the code generator for this language with the specified settings
/// </summary>
public override RazorCodeGenerator CreateCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host)
{
return new CSharpRazorCodeGenerator(className, rootNamespaceName, sourceFileName, host);
}
}
}

View File

@ -0,0 +1,135 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.34003
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.Internal.Web.Utils {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class CommonResources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal CommonResources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNet.Razor.Common.CommonResources", typeof(CommonResources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Value cannot be null or an empty string..
/// </summary>
internal static string Argument_Cannot_Be_Null_Or_Empty {
get {
return ResourceManager.GetString("Argument_Cannot_Be_Null_Or_Empty", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value must be between {0} and {1}..
/// </summary>
internal static string Argument_Must_Be_Between {
get {
return ResourceManager.GetString("Argument_Must_Be_Between", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value must be a value from the &quot;{0}&quot; enumeration..
/// </summary>
internal static string Argument_Must_Be_Enum_Member {
get {
return ResourceManager.GetString("Argument_Must_Be_Enum_Member", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value must be greater than {0}..
/// </summary>
internal static string Argument_Must_Be_GreaterThan {
get {
return ResourceManager.GetString("Argument_Must_Be_GreaterThan", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value must be greater than or equal to {0}..
/// </summary>
internal static string Argument_Must_Be_GreaterThanOrEqualTo {
get {
return ResourceManager.GetString("Argument_Must_Be_GreaterThanOrEqualTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value must be less than {0}..
/// </summary>
internal static string Argument_Must_Be_LessThan {
get {
return ResourceManager.GetString("Argument_Must_Be_LessThan", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value must be less than or equal to {0}..
/// </summary>
internal static string Argument_Must_Be_LessThanOrEqualTo {
get {
return ResourceManager.GetString("Argument_Must_Be_LessThanOrEqualTo", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Value cannot be an empty string. It must either be null or a non-empty string..
/// </summary>
internal static string Argument_Must_Be_Null_Or_Non_Empty {
get {
return ResourceManager.GetString("Argument_Must_Be_Null_Or_Non_Empty", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Argument_Cannot_Be_Null_Or_Empty" xml:space="preserve">
<value>Value cannot be null or an empty string.</value>
</data>
<data name="Argument_Must_Be_Between" xml:space="preserve">
<value>Value must be between {0} and {1}.</value>
</data>
<data name="Argument_Must_Be_Enum_Member" xml:space="preserve">
<value>Value must be a value from the "{0}" enumeration.</value>
</data>
<data name="Argument_Must_Be_GreaterThan" xml:space="preserve">
<value>Value must be greater than {0}.</value>
</data>
<data name="Argument_Must_Be_GreaterThanOrEqualTo" xml:space="preserve">
<value>Value must be greater than or equal to {0}.</value>
</data>
<data name="Argument_Must_Be_LessThan" xml:space="preserve">
<value>Value must be less than {0}.</value>
</data>
<data name="Argument_Must_Be_LessThanOrEqualTo" xml:space="preserve">
<value>Value must be less than or equal to {0}.</value>
</data>
<data name="Argument_Must_Be_Null_Or_Non_Empty" xml:space="preserve">
<value>Value cannot be an empty string. It must either be null or a non-empty string.</value>
</data>
</root>

View File

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections;
namespace Microsoft.Internal.Web.Utils
{
internal class HashCodeCombiner
{
private long _combinedHash64 = 0x1505L;
public int CombinedHash
{
get { return _combinedHash64.GetHashCode(); }
}
public HashCodeCombiner Add(IEnumerable e)
{
if (e == null)
{
Add(0);
}
else
{
int count = 0;
foreach (object o in e)
{
Add(o);
count++;
}
Add(count);
}
return this;
}
public HashCodeCombiner Add(int i)
{
_combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i;
return this;
}
public HashCodeCombiner Add(object o)
{
int hashCode = (o != null) ? o.GetHashCode() : 0;
Add(hashCode);
return this;
}
public static HashCodeCombiner Start()
{
return new HashCodeCombiner();
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor
{
/// <summary>
/// Arguments for the DocumentParseComplete event in RazorEditorParser
/// </summary>
public class DocumentParseCompleteEventArgs : EventArgs
{
/// <summary>
/// Indicates if the tree structure has actually changed since the previous reparse.
/// </summary>
public bool TreeStructureChanged { get; set; }
/// <summary>
/// The results of the code generation and parsing
/// </summary>
public GeneratorResults GeneratorResults { get; set; }
/// <summary>
/// The TextChange which triggered the reparse
/// </summary>
public TextChange SourceChange { get; set; }
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public class AutoCompleteEditHandler : SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: base(tokenizer)
{
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public AutoCompleteEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
: base(tokenizer, accepted)
{
}
public bool AutoCompleteAtEndOfSpan { get; set; }
public string AutoCompleteString { get; set; }
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
if (((AutoCompleteAtEndOfSpan && IsAtEndOfSpan(target, normalizedChange)) || IsAtEndOfFirstLine(target, normalizedChange)) &&
normalizedChange.IsInsert &&
ParserHelpers.IsNewLine(normalizedChange.NewText) &&
AutoCompleteString != null)
{
return PartialParseResult.Rejected | PartialParseResult.AutoCompleteBlock;
}
return PartialParseResult.Rejected;
}
public override string ToString()
{
return base.ToString() + ",AutoComplete:[" + (AutoCompleteString ?? "<null>") + "]" + (AutoCompleteAtEndOfSpan ? ";AtEnd" : ";AtEOL");
}
public override bool Equals(object obj)
{
AutoCompleteEditHandler other = obj as AutoCompleteEditHandler;
return base.Equals(obj) &&
other != null &&
String.Equals(other.AutoCompleteString, AutoCompleteString, StringComparison.Ordinal) &&
AutoCompleteAtEndOfSpan == other.AutoCompleteAtEndOfSpan;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(base.GetHashCode())
.Add(AutoCompleteString)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,475 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Utils;
namespace Microsoft.AspNet.Razor.Editor
{
internal class BackgroundParser : IDisposable
{
private MainThreadState _main;
private BackgroundThread _bg;
public BackgroundParser(RazorEngineHost host, string fileName)
{
_main = new MainThreadState(fileName);
_bg = new BackgroundThread(_main, host, fileName);
_main.ResultsReady += (sender, args) => OnResultsReady(args);
}
/// <summary>
/// Fired on the main thread.
/// </summary>
public event EventHandler<DocumentParseCompleteEventArgs> ResultsReady;
public bool IsIdle
{
get { return _main.IsIdle; }
}
public void Start()
{
_bg.Start();
}
public void Cancel()
{
_main.Cancel();
}
public void QueueChange(TextChange change)
{
_main.QueueChange(change);
}
[SuppressMessage("Microsoft.Usage", "CA2213:DisposableFieldsShouldBeDisposed", MessageId = "_main", Justification = "MainThreadState is disposed when the background thread shuts down")]
public void Dispose()
{
_main.Cancel();
}
public IDisposable SynchronizeMainThreadState()
{
return _main.Lock();
}
protected virtual void OnResultsReady(DocumentParseCompleteEventArgs args)
{
var handler = ResultsReady;
if (handler != null)
{
handler(this, args);
}
}
internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<TextChange> changes)
{
return TreesAreDifferent(leftTree, rightTree, changes, CancellationToken.None);
}
internal static bool TreesAreDifferent(Block leftTree, Block rightTree, IEnumerable<TextChange> changes, CancellationToken cancelToken)
{
// Apply all the pending changes to the original tree
// PERF: If this becomes a bottleneck, we can probably do it the other way around,
// i.e. visit the tree and find applicable changes for each node.
foreach (TextChange change in changes)
{
cancelToken.ThrowIfCancellationRequested();
Span changeOwner = leftTree.LocateOwner(change);
// Apply the change to the tree
if (changeOwner == null)
{
return true;
}
EditResult result = changeOwner.EditHandler.ApplyChange(changeOwner, change, force: true);
changeOwner.ReplaceWith(result.EditedSpan);
}
// Now compare the trees
bool treesDifferent = !leftTree.EquivalentTo(rightTree);
return treesDifferent;
}
private abstract class ThreadStateBase
{
#if DEBUG
private int _id = -1;
#endif
protected ThreadStateBase()
{
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is only empty in Release builds. In Debug builds it contains references to instance variables")]
[Conditional("DEBUG")]
protected void SetThreadId(int id)
{
#if DEBUG
_id = id;
#endif
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is only empty in Release builds. In Debug builds it contains references to instance variables")]
[Conditional("DEBUG")]
protected void EnsureOnThread()
{
#if DEBUG
Debug.Assert(_id != -1, "SetThreadId was never called!");
Debug.Assert(Thread.CurrentThread.ManagedThreadId == _id, "Called from an unexpected thread!");
#endif
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This method is only empty in Release builds. In Debug builds it contains references to instance variables")]
[Conditional("DEBUG")]
protected void EnsureNotOnThread()
{
#if DEBUG
Debug.Assert(_id != -1, "SetThreadId was never called!");
Debug.Assert(Thread.CurrentThread.ManagedThreadId != _id, "Called from an unexpected thread!");
#endif
}
}
private class MainThreadState : ThreadStateBase, IDisposable
{
private CancellationTokenSource _cancelSource = new CancellationTokenSource();
private ManualResetEventSlim _hasParcel = new ManualResetEventSlim(false);
private CancellationTokenSource _currentParcelCancelSource;
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "Field is used in debug code and may be used later")]
private string _fileName;
private object _stateLock = new object();
private IList<TextChange> _changes = new List<TextChange>();
public MainThreadState(string fileName)
{
_fileName = fileName;
SetThreadId(Thread.CurrentThread.ManagedThreadId);
}
public event EventHandler<DocumentParseCompleteEventArgs> ResultsReady;
public CancellationToken CancelToken
{
get { return _cancelSource.Token; }
}
public bool IsIdle
{
get
{
lock (_stateLock)
{
return _currentParcelCancelSource == null;
}
}
}
public void Cancel()
{
EnsureOnThread();
_cancelSource.Cancel();
}
public IDisposable Lock()
{
Monitor.Enter(_stateLock);
return new DisposableAction(() => Monitor.Exit(_stateLock));
}
public void QueueChange(TextChange change)
{
RazorEditorTrace.TraceLine(RazorResources.Trace_QueuingParse, Path.GetFileName(_fileName), change);
EnsureOnThread();
lock (_stateLock)
{
// CurrentParcel token source is not null ==> There's a parse underway
if (_currentParcelCancelSource != null)
{
_currentParcelCancelSource.Cancel();
}
_changes.Add(change);
_hasParcel.Set();
}
}
public WorkParcel GetParcel()
{
EnsureNotOnThread(); // Only the background thread can get a parcel
_hasParcel.Wait(_cancelSource.Token);
_hasParcel.Reset();
lock (_stateLock)
{
// Create a cancellation source for this parcel
_currentParcelCancelSource = new CancellationTokenSource();
var changes = _changes;
_changes = new List<TextChange>();
return new WorkParcel(changes, _currentParcelCancelSource.Token);
}
}
public void ReturnParcel(DocumentParseCompleteEventArgs args)
{
lock (_stateLock)
{
// Clear the current parcel cancellation source
if (_currentParcelCancelSource != null)
{
_currentParcelCancelSource.Dispose();
_currentParcelCancelSource = null;
}
// If there are things waiting to be parsed, just don't fire the event because we're already out of date
if (_changes.Any())
{
return;
}
}
var handler = ResultsReady;
if (handler != null)
{
handler(this, args);
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (_currentParcelCancelSource != null)
{
_currentParcelCancelSource.Dispose();
_currentParcelCancelSource = null;
}
_cancelSource.Dispose();
_hasParcel.Dispose();
}
}
}
private class BackgroundThread : ThreadStateBase
{
private MainThreadState _main;
private Thread _backgroundThread;
private CancellationToken _shutdownToken;
private RazorEngineHost _host;
private string _fileName;
private Block _currentParseTree;
private IList<TextChange> _previouslyDiscarded = new List<TextChange>();
public BackgroundThread(MainThreadState main, RazorEngineHost host, string fileName)
{
// Run on MAIN thread!
_main = main;
_backgroundThread = new Thread(WorkerLoop);
_shutdownToken = _main.CancelToken;
_host = host;
_fileName = fileName;
SetThreadId(_backgroundThread.ManagedThreadId);
}
// **** ANY THREAD ****
public void Start()
{
_backgroundThread.Start();
}
// **** BACKGROUND THREAD ****
private void WorkerLoop()
{
long? elapsedMs = null;
string fileNameOnly = Path.GetFileName(_fileName);
#if EDITOR_TRACING
Stopwatch sw = new Stopwatch();
#endif
try
{
RazorEditorTrace.TraceLine(RazorResources.Trace_BackgroundThreadStart, fileNameOnly);
EnsureOnThread();
while (!_shutdownToken.IsCancellationRequested)
{
// Grab the parcel of work to do
WorkParcel parcel = _main.GetParcel();
if (parcel.Changes.Any())
{
RazorEditorTrace.TraceLine(RazorResources.Trace_ChangesArrived, fileNameOnly, parcel.Changes.Count);
try
{
DocumentParseCompleteEventArgs args = null;
using (var linkedCancel = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken, parcel.CancelToken))
{
if (parcel != null && !linkedCancel.IsCancellationRequested)
{
// Collect ALL changes
#if EDITOR_TRACING
if (_previouslyDiscarded != null && _previouslyDiscarded.Any())
{
RazorEditorTrace.TraceLine(RazorResources.Trace_CollectedDiscardedChanges, fileNameOnly, _previouslyDiscarded.Count);
}
#endif
var allChanges = Enumerable.Concat(
_previouslyDiscarded ?? Enumerable.Empty<TextChange>(), parcel.Changes).ToList();
var finalChange = allChanges.LastOrDefault();
if (finalChange != null)
{
#if EDITOR_TRACING
sw.Start();
#endif
GeneratorResults results = ParseChange(finalChange.NewBuffer, linkedCancel.Token);
#if EDITOR_TRACING
sw.Stop();
elapsedMs = sw.ElapsedMilliseconds;
sw.Reset();
#endif
RazorEditorTrace.TraceLine(
RazorResources.Trace_ParseComplete,
fileNameOnly,
elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?");
if (results != null && !linkedCancel.IsCancellationRequested)
{
// Clear discarded changes list
_previouslyDiscarded = null;
// Take the current tree and check for differences
#if EDITOR_TRACING
sw.Start();
#endif
bool treeStructureChanged = _currentParseTree == null || TreesAreDifferent(_currentParseTree, results.Document, allChanges, parcel.CancelToken);
#if EDITOR_TRACING
sw.Stop();
elapsedMs = sw.ElapsedMilliseconds;
sw.Reset();
#endif
_currentParseTree = results.Document;
RazorEditorTrace.TraceLine(RazorResources.Trace_TreesCompared,
fileNameOnly,
elapsedMs.HasValue ? elapsedMs.Value.ToString() : "?",
treeStructureChanged);
// Build Arguments
args = new DocumentParseCompleteEventArgs()
{
GeneratorResults = results,
SourceChange = finalChange,
TreeStructureChanged = treeStructureChanged
};
}
else
{
// Parse completed but we were cancelled in the mean time. Add these to the discarded changes set
RazorEditorTrace.TraceLine(RazorResources.Trace_ChangesDiscarded, fileNameOnly, allChanges.Count);
_previouslyDiscarded = allChanges;
}
#if CHECK_TREE
if (args != null)
{
// Rewind the buffer and sanity check the line mappings
finalChange.NewBuffer.Position = 0;
int lineCount = finalChange.NewBuffer.ReadToEnd().Split(new string[] { Environment.NewLine, "\r", "\n" }, StringSplitOptions.None).Count();
Debug.Assert(
!args.GeneratorResults.DesignTimeLineMappings.Any(pair => pair.Value.StartLine > lineCount),
"Found a design-time line mapping referring to a line outside the source file!");
Debug.Assert(
!args.GeneratorResults.Document.Flatten().Any(span => span.Start.LineIndex > lineCount),
"Found a span with a line number outside the source file");
Debug.Assert(
!args.GeneratorResults.Document.Flatten().Any(span => span.Start.AbsoluteIndex > parcel.NewBuffer.Length),
"Found a span with an absolute offset outside the source file");
}
#endif
}
}
}
if (args != null)
{
_main.ReturnParcel(args);
}
}
catch (OperationCanceledException)
{
}
}
else
{
RazorEditorTrace.TraceLine(RazorResources.Trace_NoChangesArrived, fileNameOnly, parcel.Changes.Count);
Thread.Yield();
}
}
}
catch (OperationCanceledException)
{
// Do nothing. Just shut down.
}
finally
{
RazorEditorTrace.TraceLine(RazorResources.Trace_BackgroundThreadShutdown, fileNameOnly);
// Clean up main thread resources
_main.Dispose();
}
}
private GeneratorResults ParseChange(ITextBuffer buffer, CancellationToken token)
{
EnsureOnThread();
// Create a template engine
RazorTemplateEngine engine = new RazorTemplateEngine(_host);
// Seek the buffer to the beginning
buffer.Position = 0;
try
{
return engine.GenerateCode(
input: buffer,
className: null,
rootNamespace: null,
sourceFileName: _fileName,
cancelToken: token);
}
catch (OperationCanceledException)
{
return null;
}
}
}
private class WorkParcel
{
public WorkParcel(IList<TextChange> changes, CancellationToken cancelToken)
{
Changes = changes;
CancelToken = cancelToken;
}
public CancellationToken CancelToken { get; private set; }
public IList<TextChange> Changes { get; private set; }
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Editor
{
public class EditResult
{
public EditResult(PartialParseResult result, SpanBuilder editedSpan)
{
Result = result;
EditedSpan = editedSpan;
}
public PartialParseResult Result { get; set; }
public SpanBuilder EditedSpan { get; set; }
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Razor.Editor
{
/// <summary>
/// Used within <see cref="F:SpanEditHandler.EditorHints"/>.
/// </summary>
[Flags]
public enum EditorHints
{
/// <summary>
/// The default (Markup or Code) editor behavior for Statement completion should be used.
/// Editors can always use the default behavior, even if the span is labeled with a different CompletionType.
/// </summary>
None = 0, // 0000 0000
/// <summary>
/// Indicates that Virtual Path completion should be used for this span if the editor supports it.
/// Editors need not support this mode of completion, and will use the default (<see cref="F:EditorHints.None"/>) behavior
/// if they do not support it.
/// </summary>
VirtualPath = 1, // 0000 0001
/// <summary>
/// Indicates that this span's content contains the path to the layout page for this document.
/// </summary>
LayoutPage = 2, // 0000 0010
}
}

View File

@ -0,0 +1,237 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Editor
{
public class ImplicitExpressionEditHandler : SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public ImplicitExpressionEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, ISet<string> keywords, bool acceptTrailingDot)
: base(tokenizer)
{
Initialize(keywords, acceptTrailingDot);
}
public bool AcceptTrailingDot { get; private set; }
public ISet<string> Keywords { get; private set; }
public override string ToString()
{
return String.Format(CultureInfo.InvariantCulture, "{0};ImplicitExpression[{1}];K{2}", base.ToString(), AcceptTrailingDot ? "ATD" : "RTD", Keywords.Count);
}
public override bool Equals(object obj)
{
ImplicitExpressionEditHandler other = obj as ImplicitExpressionEditHandler;
return other != null &&
base.Equals(other) &&
Keywords.SetEquals(other.Keywords) &&
AcceptTrailingDot == other.AcceptTrailingDot;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(base.GetHashCode())
.Add(AcceptTrailingDot)
.Add(Keywords)
.CombinedHash;
}
protected override PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
if (AcceptedCharacters == AcceptedCharacters.Any)
{
return PartialParseResult.Rejected;
}
if (IsAcceptableReplace(target, normalizedChange))
{
return HandleReplacement(target, normalizedChange);
}
int changeRelativePosition = normalizedChange.OldPosition - target.Start.AbsoluteIndex;
// Get the edit context
char? lastChar = null;
if (changeRelativePosition > 0 && target.Content.Length > 0)
{
lastChar = target.Content[changeRelativePosition - 1];
}
// Don't support 0->1 length edits
if (lastChar == null)
{
return PartialParseResult.Rejected;
}
// Only support insertions at the end of the span
if (IsAcceptableInsertion(target, normalizedChange))
{
// Handle the insertion
return HandleInsertion(target, lastChar.Value, normalizedChange);
}
if (IsAcceptableDeletion(target, normalizedChange))
{
return HandleDeletion(target, lastChar.Value, normalizedChange);
}
return PartialParseResult.Rejected;
}
private void Initialize(ISet<string> keywords, bool acceptTrailingDot)
{
Keywords = keywords ?? new HashSet<string>();
AcceptTrailingDot = acceptTrailingDot;
}
private static bool IsAcceptableReplace(Span target, TextChange change)
{
return IsEndReplace(target, change) ||
(change.IsReplace && RemainingIsWhitespace(target, change));
}
private static bool IsAcceptableDeletion(Span target, TextChange change)
{
return IsEndDeletion(target, change) ||
(change.IsDelete && RemainingIsWhitespace(target, change));
}
private static bool IsAcceptableInsertion(Span target, TextChange change)
{
return IsEndInsertion(target, change) ||
(change.IsInsert && RemainingIsWhitespace(target, change));
}
private static bool RemainingIsWhitespace(Span target, TextChange change)
{
int offset = (change.OldPosition - target.Start.AbsoluteIndex) + change.OldLength;
return String.IsNullOrWhiteSpace(target.Content.Substring(offset));
}
private PartialParseResult HandleReplacement(Span target, TextChange change)
{
// Special Case for IntelliSense commits.
// When IntelliSense commits, we get two changes (for example user typed "Date", then committed "DateTime" by pressing ".")
// 1. Insert "." at the end of this span
// 2. Replace the "Date." at the end of the span with "DateTime."
// We need partial parsing to accept case #2.
string oldText = GetOldText(target, change);
PartialParseResult result = PartialParseResult.Rejected;
if (EndsWithDot(oldText) && EndsWithDot(change.NewText))
{
result = PartialParseResult.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
}
}
return result;
}
private PartialParseResult HandleDeletion(Span target, char previousChar, TextChange change)
{
// What's left after deleting?
if (previousChar == '.')
{
return TryAcceptChange(target, change, PartialParseResult.Accepted | PartialParseResult.Provisional);
}
else if (ParserHelpers.IsIdentifierPart(previousChar))
{
return TryAcceptChange(target, change);
}
else
{
return PartialParseResult.Rejected;
}
}
private PartialParseResult HandleInsertion(Span target, char previousChar, TextChange change)
{
// What are we inserting after?
if (previousChar == '.')
{
return HandleInsertionAfterDot(target, change);
}
else if (ParserHelpers.IsIdentifierPart(previousChar) || previousChar == ')' || previousChar == ']')
{
return HandleInsertionAfterIdPart(target, change);
}
else
{
return PartialParseResult.Rejected;
}
}
private PartialParseResult HandleInsertionAfterIdPart(Span target, TextChange change)
{
// If the insertion is a full identifier part, accept it
if (ParserHelpers.IsIdentifier(change.NewText, requireIdentifierStart: false))
{
return TryAcceptChange(target, change);
}
else if (EndsWithDot(change.NewText))
{
// Accept it, possibly provisionally
PartialParseResult result = PartialParseResult.Accepted;
if (!AcceptTrailingDot)
{
result |= PartialParseResult.Provisional;
}
return TryAcceptChange(target, change, result);
}
else
{
return PartialParseResult.Rejected;
}
}
private static bool EndsWithDot(string content)
{
return (content.Length == 1 && content[0] == '.') ||
(content[content.Length - 1] == '.' &&
content.Take(content.Length - 1).All(ParserHelpers.IsIdentifierPart));
}
private PartialParseResult HandleInsertionAfterDot(Span target, TextChange change)
{
// If the insertion is a full identifier, accept it
if (ParserHelpers.IsIdentifier(change.NewText))
{
return TryAcceptChange(target, change);
}
return PartialParseResult.Rejected;
}
private PartialParseResult TryAcceptChange(Span target, TextChange change, PartialParseResult acceptResult = PartialParseResult.Accepted)
{
string content = change.ApplyChange(target);
if (StartsWithKeyword(content))
{
return PartialParseResult.Rejected | PartialParseResult.SpanContextChanged;
}
return acceptResult;
}
private bool StartsWithKeyword(string newContent)
{
using (StringReader reader = new StringReader(newContent))
{
return Keywords.Contains(reader.ReadWhile(ParserHelpers.IsIdentifierPart));
}
}
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Editor
{
internal static class RazorEditorTrace
{
private static bool? _enabled;
private static bool IsEnabled()
{
if (_enabled == null)
{
bool enabled;
if (Boolean.TryParse(Environment.GetEnvironmentVariable("RAZOR_EDITOR_TRACE"), out enabled))
{
Trace.WriteLine(String.Format(
CultureInfo.CurrentCulture,
RazorResources.Trace_Startup,
enabled ? RazorResources.Trace_Enabled : RazorResources.Trace_Disabled));
_enabled = enabled;
}
else
{
_enabled = false;
}
}
return _enabled.Value;
}
[Conditional("EDITOR_TRACING")]
public static void TraceLine(string format, params object[] args)
{
if (IsEnabled())
{
Trace.WriteLine(String.Format(
CultureInfo.CurrentCulture,
RazorResources.Trace_Format,
String.Format(CultureInfo.CurrentCulture, format, args)));
}
}
}
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Editor
{
public class SingleLineMarkupEditHandler : SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SingleLineMarkupEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: base(tokenizer)
{
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SingleLineMarkupEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
: base(tokenizer, accepted)
{
}
}
}

View File

@ -0,0 +1,184 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.Internal.Web.Utils;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Editor
{
// Manages edits to a span
public class SpanEditHandler
{
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SpanEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer)
: this(tokenizer, AcceptedCharacters.Any)
{
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public SpanEditHandler(Func<string, IEnumerable<ISymbol>> tokenizer, AcceptedCharacters accepted)
{
AcceptedCharacters = accepted;
Tokenizer = tokenizer;
}
public AcceptedCharacters AcceptedCharacters { get; set; }
/// <summary>
/// Provides a set of hints to editors which may be manipulating the document in which this span is located.
/// </summary>
public EditorHints EditorHints { get; set; }
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public Func<string, IEnumerable<ISymbol>> Tokenizer { get; set; }
public static SpanEditHandler CreateDefault()
{
return CreateDefault(s => Enumerable.Empty<ISymbol>());
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Func<T> is the recommended delegate type and requires this level of nesting.")]
public static SpanEditHandler CreateDefault(Func<string, IEnumerable<ISymbol>> tokenizer)
{
return new SpanEditHandler(tokenizer);
}
public virtual EditResult ApplyChange(Span target, TextChange change)
{
return ApplyChange(target, change, force: false);
}
public virtual EditResult ApplyChange(Span target, TextChange change, bool force)
{
PartialParseResult result = PartialParseResult.Accepted;
TextChange normalized = change.Normalize();
if (!force)
{
result = CanAcceptChange(target, normalized);
}
// If the change is accepted then apply the change
if (result.HasFlag(PartialParseResult.Accepted))
{
return new EditResult(result, UpdateSpan(target, normalized));
}
return new EditResult(result, new SpanBuilder(target));
}
public virtual bool OwnsChange(Span target, TextChange change)
{
int end = target.Start.AbsoluteIndex + target.Length;
int changeOldEnd = change.OldPosition + change.OldLength;
return change.OldPosition >= target.Start.AbsoluteIndex &&
(changeOldEnd < end || (changeOldEnd == end && AcceptedCharacters != AcceptedCharacters.None));
}
protected virtual PartialParseResult CanAcceptChange(Span target, TextChange normalizedChange)
{
return PartialParseResult.Rejected;
}
protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange)
{
string newContent = normalizedChange.ApplyChange(target);
SpanBuilder newSpan = new SpanBuilder(target);
newSpan.ClearSymbols();
foreach (ISymbol sym in Tokenizer(newContent))
{
sym.OffsetStart(target.Start);
newSpan.Accept(sym);
}
if (target.Next != null)
{
SourceLocation newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent);
target.Next.ChangeStart(newEnd);
}
return newSpan;
}
protected internal static bool IsAtEndOfFirstLine(Span target, TextChange change)
{
int endOfFirstLine = target.Content.IndexOfAny(new char[] { (char)0x000d, (char)0x000a, (char)0x2028, (char)0x2029 });
return (endOfFirstLine == -1 || (change.OldPosition - target.Start.AbsoluteIndex) <= endOfFirstLine);
}
/// <summary>
/// Returns true if the specified change is an insertion of text at the end of this span.
/// </summary>
protected internal static bool IsEndInsertion(Span target, TextChange change)
{
return change.IsInsert && IsAtEndOfSpan(target, change);
}
/// <summary>
/// Returns true if the specified change is an insertion of text at the end of this span.
/// </summary>
protected internal static bool IsEndDeletion(Span target, TextChange change)
{
return change.IsDelete && IsAtEndOfSpan(target, change);
}
/// <summary>
/// Returns true if the specified change is a replacement of text at the end of this span.
/// </summary>
protected internal static bool IsEndReplace(Span target, TextChange change)
{
return change.IsReplace && IsAtEndOfSpan(target, change);
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This method should only be used on Spans")]
protected internal static bool IsAtEndOfSpan(Span target, TextChange change)
{
return (change.OldPosition + change.OldLength) == (target.Start.AbsoluteIndex + target.Length);
}
/// <summary>
/// Returns the old text referenced by the change.
/// </summary>
/// <remarks>
/// If the content has already been updated by applying the change, this data will be _invalid_
/// </remarks>
protected internal static string GetOldText(Span target, TextChange change)
{
return target.Content.Substring(change.OldPosition - target.Start.AbsoluteIndex, change.OldLength);
}
// Is the specified span to the right of this span and immediately adjacent?
internal static bool IsAdjacentOnRight(Span target, Span other)
{
return target.Start.AbsoluteIndex < other.Start.AbsoluteIndex && target.Start.AbsoluteIndex + target.Length == other.Start.AbsoluteIndex;
}
// Is the specified span to the left of this span and immediately adjacent?
internal static bool IsAdjacentOnLeft(Span target, Span other)
{
return other.Start.AbsoluteIndex < target.Start.AbsoluteIndex && other.Start.AbsoluteIndex + other.Length == target.Start.AbsoluteIndex;
}
public override string ToString()
{
return GetType().Name + ";Accepts:" + AcceptedCharacters + ((EditorHints == EditorHints.None) ? String.Empty : (";Hints: " + EditorHints.ToString()));
}
public override bool Equals(object obj)
{
SpanEditHandler other = obj as SpanEditHandler;
return other != null &&
AcceptedCharacters == other.AcceptedCharacters &&
EditorHints == other.EditorHints;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(AcceptedCharacters)
.Add(EditorHints)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,69 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.CodeDom;
using System.Linq;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class AddImportCodeGenerator : SpanCodeGenerator
{
public AddImportCodeGenerator(string ns, int namespaceKeywordLength)
{
Namespace = ns;
NamespaceKeywordLength = namespaceKeywordLength;
}
public string Namespace { get; private set; }
public int NamespaceKeywordLength { get; set; }
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
// Try to find the namespace in the existing imports
string ns = Namespace;
if (!String.IsNullOrEmpty(ns) && Char.IsWhiteSpace(ns[0]))
{
ns = ns.Substring(1);
}
CodeNamespaceImport import = context.Namespace
.Imports
.OfType<CodeNamespaceImport>()
.Where(i => String.Equals(i.Namespace, ns.Trim(), StringComparison.Ordinal))
.FirstOrDefault();
if (import == null)
{
// It doesn't exist, create it
import = new CodeNamespaceImport(ns);
context.Namespace.Imports.Add(import);
}
// Attach our info to the existing/new import.
import.LinePragma = context.GenerateLinePragma(target);
}
public override string ToString()
{
return "Import:" + Namespace + ";KwdLen:" + NamespaceKeywordLength;
}
public override bool Equals(object obj)
{
AddImportCodeGenerator other = obj as AddImportCodeGenerator;
return other != null &&
String.Equals(Namespace, other.Namespace, StringComparison.Ordinal) &&
NamespaceKeywordLength == other.NamespaceKeywordLength;
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(Namespace)
.Add(NamespaceKeywordLength)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Globalization;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class AttributeBlockCodeGenerator : BlockCodeGenerator
{
public AttributeBlockCodeGenerator(string name, LocationTagged<string> prefix, LocationTagged<string> suffix)
{
Name = name;
Prefix = prefix;
Suffix = suffix;
}
public string Name { get; private set; }
public LocationTagged<string> Prefix { get; private set; }
public LocationTagged<string> Suffix { get; private set; }
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
if (context.Host.DesignTimeMode)
{
return; // Don't generate anything!
}
context.FlushBufferedStatement();
context.AddStatement(context.BuildCodeString(cw =>
{
if (!String.IsNullOrEmpty(context.TargetWriterName))
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteAttributeToMethodName);
cw.WriteSnippet(context.TargetWriterName);
cw.WriteParameterSeparator();
}
else
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteAttributeMethodName);
}
cw.WriteStringLiteral(Name);
cw.WriteParameterSeparator();
cw.WriteLocationTaggedString(Prefix);
cw.WriteParameterSeparator();
cw.WriteLocationTaggedString(Suffix);
// In VB, we need a line continuation
cw.WriteLineContinuation();
}));
}
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
if (context.Host.DesignTimeMode)
{
return; // Don't generate anything!
}
context.FlushBufferedStatement();
context.AddStatement(context.BuildCodeString(cw =>
{
cw.WriteEndMethodInvoke();
cw.WriteEndStatement();
}));
}
public override string ToString()
{
return String.Format(CultureInfo.CurrentCulture, "Attr:{0},{1:F},{2:F}", Name, Prefix, Suffix);
}
public override bool Equals(object obj)
{
AttributeBlockCodeGenerator other = obj as AttributeBlockCodeGenerator;
return other != null &&
String.Equals(other.Name, Name, StringComparison.Ordinal) &&
Equals(other.Prefix, Prefix) &&
Equals(other.Suffix, Suffix);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(Name)
.Add(Prefix)
.Add(Suffix)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Razor.Generator
{
internal abstract class BaseCodeWriter : CodeWriter
{
public override void WriteSnippet(string snippet)
{
InnerWriter.Write(snippet);
}
protected internal override void EmitStartMethodInvoke(string methodName)
{
EmitStartMethodInvoke(methodName, new string[0]);
}
protected internal override void EmitStartMethodInvoke(string methodName, params string[] genericArguments)
{
InnerWriter.Write(methodName);
if (genericArguments != null && genericArguments.Length > 0)
{
WriteStartGenerics();
for (int i = 0; i < genericArguments.Length; i++)
{
if (i > 0)
{
WriteParameterSeparator();
}
WriteSnippet(genericArguments[i]);
}
WriteEndGenerics();
}
InnerWriter.Write("(");
}
protected internal override void EmitEndMethodInvoke()
{
InnerWriter.Write(")");
}
protected internal override void EmitEndConstructor()
{
InnerWriter.Write(")");
}
protected internal override void EmitEndLambdaExpression()
{
}
public override void WriteParameterSeparator()
{
InnerWriter.Write(", ");
}
protected internal void WriteCommaSeparatedList<T>(T[] items, Action<T> writeItemAction)
{
for (int i = 0; i < items.Length; i++)
{
if (i > 0)
{
InnerWriter.Write(", ");
}
writeItemAction(items[i]);
}
}
protected internal virtual void WriteStartGenerics()
{
}
protected internal virtual void WriteEndGenerics()
{
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public abstract class BlockCodeGenerator : IBlockCodeGenerator
{
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "This class has no instance state")]
public static readonly IBlockCodeGenerator Null = new NullBlockCodeGenerator();
public virtual void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
}
public virtual void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
}
public override bool Equals(object obj)
{
return (obj as IBlockCodeGenerator) != null;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
private class NullBlockCodeGenerator : IBlockCodeGenerator
{
public void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
}
public void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
}
public override string ToString()
{
return "None";
}
}
}
}

View File

@ -0,0 +1,250 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
namespace Microsoft.AspNet.Razor.Generator
{
internal class CSharpCodeWriter : BaseCodeWriter
{
protected internal override void WriteStartGenerics()
{
InnerWriter.Write("<");
}
protected internal override void WriteEndGenerics()
{
InnerWriter.Write(">");
}
public override int WriteVariableDeclaration(string type, string name, string value)
{
InnerWriter.Write(type);
InnerWriter.Write(" ");
InnerWriter.Write(name);
if (!String.IsNullOrEmpty(value))
{
InnerWriter.Write(" = ");
InnerWriter.Write(value);
}
else
{
InnerWriter.Write(" = null");
}
return 0;
}
public override void WriteDisableUnusedFieldWarningPragma()
{
InnerWriter.Write("#pragma warning disable 219");
}
public override void WriteRestoreUnusedFieldWarningPragma()
{
InnerWriter.Write("#pragma warning restore 219");
}
public override void WriteStringLiteral(string literal)
{
if (literal == null)
{
throw new ArgumentNullException("literal");
}
// From CSharpCodeProvider in CodeDOM
// If the string is short, use C style quoting (e.g "\r\n")
// Also do it if it is too long to fit in one line
// If the string contains '\0', verbatim style won't work.
if (literal.Length >= 256 && literal.Length <= 1500 && literal.IndexOf('\0') == -1)
{
WriteVerbatimStringLiteral(literal);
}
else
{
WriteCStyleStringLiteral(literal);
}
}
private void WriteVerbatimStringLiteral(string literal)
{
// From CSharpCodeGenerator.QuoteSnippetStringVerbatim in CodeDOM
InnerWriter.Write("@\"");
for (int i = 0; i < literal.Length; i++)
{
if (literal[i] == '\"')
{
InnerWriter.Write("\"\"");
}
else
{
InnerWriter.Write(literal[i]);
}
}
InnerWriter.Write("\"");
}
private void WriteCStyleStringLiteral(string literal)
{
// From CSharpCodeGenerator.QuoteSnippetStringCStyle in CodeDOM
InnerWriter.Write("\"");
for (int i = 0; i < literal.Length; i++)
{
switch (literal[i])
{
case '\r':
InnerWriter.Write("\\r");
break;
case '\t':
InnerWriter.Write("\\t");
break;
case '\"':
InnerWriter.Write("\\\"");
break;
case '\'':
InnerWriter.Write("\\\'");
break;
case '\\':
InnerWriter.Write("\\\\");
break;
case '\0':
InnerWriter.Write("\\\0");
break;
case '\n':
InnerWriter.Write("\\n");
break;
case '\u2028':
case '\u2029':
// Inlined CSharpCodeGenerator.AppendEscapedChar
InnerWriter.Write("\\u");
InnerWriter.Write(((int)literal[i]).ToString("X4", CultureInfo.InvariantCulture));
break;
default:
InnerWriter.Write(literal[i]);
break;
}
if (i > 0 && i % 80 == 0)
{
// If current character is a high surrogate and the following
// character is a low surrogate, don't break them.
// Otherwise when we write the string to a file, we might lose
// the characters.
if (Char.IsHighSurrogate(literal[i])
&& (i < literal.Length - 1)
&& Char.IsLowSurrogate(literal[i + 1]))
{
InnerWriter.Write(literal[++i]);
}
InnerWriter.Write("\" +");
InnerWriter.Write(Environment.NewLine);
InnerWriter.Write('\"');
}
}
InnerWriter.Write("\"");
}
public override void WriteEndStatement()
{
InnerWriter.WriteLine(";");
}
public override void WriteIdentifier(string identifier)
{
InnerWriter.Write("@" + identifier);
}
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Lowercase is intended here. C# boolean literals are all lowercase")]
public override void WriteBooleanLiteral(bool value)
{
WriteSnippet(value.ToString().ToLowerInvariant());
}
protected internal override void EmitStartLambdaExpression(string[] parameterNames)
{
if (parameterNames == null)
{
throw new ArgumentNullException("parameterNames");
}
if (parameterNames.Length == 0 || parameterNames.Length > 1)
{
InnerWriter.Write("(");
}
WriteCommaSeparatedList(parameterNames, InnerWriter.Write);
if (parameterNames.Length == 0 || parameterNames.Length > 1)
{
InnerWriter.Write(")");
}
InnerWriter.Write(" => ");
}
protected internal override void EmitStartLambdaDelegate(string[] parameterNames)
{
if (parameterNames == null)
{
throw new ArgumentNullException("parameterNames");
}
EmitStartLambdaExpression(parameterNames);
InnerWriter.WriteLine("{");
}
protected internal override void EmitEndLambdaDelegate()
{
InnerWriter.Write("}");
}
protected internal override void EmitStartConstructor(string typeName)
{
if (typeName == null)
{
throw new ArgumentNullException("typeName");
}
InnerWriter.Write("new ");
InnerWriter.Write(typeName);
InnerWriter.Write("(");
}
public override void WriteReturn()
{
InnerWriter.Write("return ");
}
public override void WriteLinePragma(int? lineNumber, string fileName)
{
InnerWriter.WriteLine();
if (lineNumber != null)
{
InnerWriter.Write("#line ");
InnerWriter.Write(lineNumber);
InnerWriter.Write(" \"");
InnerWriter.Write(fileName);
InnerWriter.Write("\"");
InnerWriter.WriteLine();
}
else
{
InnerWriter.WriteLine("#line default");
InnerWriter.WriteLine("#line hidden");
}
}
public override void WriteHiddenLinePragma()
{
InnerWriter.WriteLine("#line hidden");
}
public override void WriteHelperHeaderPrefix(string templateTypeName, bool isStatic)
{
InnerWriter.Write("public ");
if (isStatic)
{
InnerWriter.Write("static ");
}
InnerWriter.Write(templateTypeName);
InnerWriter.Write(" ");
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.Razor.Generator
{
public class CSharpRazorCodeGenerator : RazorCodeGenerator
{
private const string HiddenLinePragma = "#line hidden";
public CSharpRazorCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host)
: base(className, rootNamespaceName, sourceFileName, host)
{
}
internal override Func<CodeWriter> CodeWriterFactory
{
get { return () => new CSharpCodeWriter(); }
}
[SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.CodeDom.CodeSnippetTypeMember.#ctor(System.String)", Justification = "Value is never to be localized")]
protected override void Initialize(CodeGeneratorContext context)
{
base.Initialize(context);
context.GeneratedClass.Members.Insert(0, new CodeSnippetTypeMember(HiddenLinePragma));
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Generator
{
public class CodeGenerationCompleteEventArgs : EventArgs
{
public CodeGenerationCompleteEventArgs(string virtualPath, string physicalPath, CodeCompileUnit generatedCode)
{
if (String.IsNullOrEmpty(virtualPath))
{
throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "virtualPath");
}
if (generatedCode == null)
{
throw new ArgumentNullException("generatedCode");
}
VirtualPath = virtualPath;
PhysicalPath = physicalPath;
GeneratedCode = generatedCode;
}
public CodeCompileUnit GeneratedCode { get; private set; }
public string VirtualPath { get; private set; }
public string PhysicalPath { get; private set; }
}
}

View File

@ -0,0 +1,334 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Utils;
namespace Microsoft.AspNet.Razor.Generator
{
public class CodeGeneratorContext
{
private const string DesignTimeHelperMethodName = "__RazorDesignTimeHelpers__";
private int _nextDesignTimePragmaId = 1;
private bool _expressionHelperVariableWriten;
private CodeMemberMethod _designTimeHelperMethod;
private StatementBuffer _currentBuffer = new StatementBuffer();
private CodeGeneratorContext()
{
ExpressionRenderingMode = ExpressionRenderingMode.WriteToOutput;
}
// Internal/Private state. Technically consumers might want to use some of these but they can implement them independently if necessary.
// It's way safer to make them internal for now, especially with the code generator stuff in a bit of flux.
internal ExpressionRenderingMode ExpressionRenderingMode { get; set; }
private Action<string, CodeLinePragma> StatementCollector { get; set; }
private Func<CodeWriter> CodeWriterFactory { get; set; }
public string SourceFile { get; internal set; }
public CodeCompileUnit CompileUnit { get; internal set; }
public CodeNamespace Namespace { get; internal set; }
public CodeTypeDeclaration GeneratedClass { get; internal set; }
public RazorEngineHost Host { get; private set; }
public IDictionary<int, GeneratedCodeMapping> CodeMappings { get; private set; }
public string TargetWriterName { get; set; }
public CodeMemberMethod TargetMethod { get; set; }
public string CurrentBufferedStatement
{
get { return _currentBuffer == null ? String.Empty : _currentBuffer.Builder.ToString(); }
}
public static CodeGeneratorContext Create(RazorEngineHost host, string className, string rootNamespace, string sourceFile, bool shouldGenerateLinePragmas)
{
return Create(host, null, className, rootNamespace, sourceFile, shouldGenerateLinePragmas);
}
internal static CodeGeneratorContext Create(RazorEngineHost host, Func<CodeWriter> writerFactory, string className, string rootNamespace, string sourceFile, bool shouldGenerateLinePragmas)
{
CodeGeneratorContext context = new CodeGeneratorContext()
{
Host = host,
CodeWriterFactory = writerFactory,
SourceFile = shouldGenerateLinePragmas ? sourceFile : null,
CompileUnit = new CodeCompileUnit(),
Namespace = new CodeNamespace(rootNamespace),
GeneratedClass = new CodeTypeDeclaration(className)
{
IsClass = true
},
TargetMethod = new CodeMemberMethod()
{
Name = host.GeneratedClassContext.ExecuteMethodName,
Attributes = MemberAttributes.Override | MemberAttributes.Public
},
CodeMappings = new Dictionary<int, GeneratedCodeMapping>()
};
context.CompileUnit.Namespaces.Add(context.Namespace);
context.Namespace.Types.Add(context.GeneratedClass);
context.GeneratedClass.Members.Add(context.TargetMethod);
context.Namespace.Imports.AddRange(host.NamespaceImports
.Select(s => new CodeNamespaceImport(s))
.ToArray());
return context;
}
public void AddDesignTimeHelperStatement(CodeSnippetStatement statement)
{
if (_designTimeHelperMethod == null)
{
_designTimeHelperMethod = new CodeMemberMethod()
{
Name = DesignTimeHelperMethodName,
Attributes = MemberAttributes.Private
};
_designTimeHelperMethod.Statements.Add(
new CodeSnippetStatement(BuildCodeString(cw => cw.WriteDisableUnusedFieldWarningPragma())));
_designTimeHelperMethod.Statements.Add(
new CodeSnippetStatement(BuildCodeString(cw => cw.WriteRestoreUnusedFieldWarningPragma())));
GeneratedClass.Members.Insert(0, _designTimeHelperMethod);
}
_designTimeHelperMethod.Statements.Insert(_designTimeHelperMethod.Statements.Count - 1, statement);
}
[SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "generatedCodeStart+1", Justification = "There is no risk of overflow in this case")]
public int AddCodeMapping(SourceLocation sourceLocation, int generatedCodeStart, int generatedCodeLength)
{
if (generatedCodeStart == Int32.MaxValue)
{
throw new ArgumentOutOfRangeException("generatedCodeStart");
}
GeneratedCodeMapping mapping = new GeneratedCodeMapping(
startOffset: sourceLocation.AbsoluteIndex,
startLine: sourceLocation.LineIndex + 1,
startColumn: sourceLocation.CharacterIndex + 1,
startGeneratedColumn: generatedCodeStart + 1,
codeLength: generatedCodeLength);
int id = _nextDesignTimePragmaId++;
CodeMappings[id] = mapping;
return id;
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This method requires that a Span be provided")]
public CodeLinePragma GenerateLinePragma(Span target)
{
return GenerateLinePragma(target, 0);
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This method requires that a Span be provided")]
public CodeLinePragma GenerateLinePragma(Span target, int generatedCodeStart)
{
return GenerateLinePragma(target, generatedCodeStart, target.Content.Length);
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "This method requires that a Span be provided")]
public CodeLinePragma GenerateLinePragma(Span target, int generatedCodeStart, int codeLength)
{
return GenerateLinePragma(target.Start, generatedCodeStart, codeLength);
}
public CodeLinePragma GenerateLinePragma(SourceLocation start, int generatedCodeStart, int codeLength)
{
if (!String.IsNullOrEmpty(SourceFile))
{
if (Host.DesignTimeMode)
{
int mappingId = AddCodeMapping(start, generatedCodeStart, codeLength);
return new CodeLinePragma(SourceFile, mappingId);
}
return new CodeLinePragma(SourceFile, start.LineIndex + 1);
}
return null;
}
public void BufferStatementFragment(Span sourceSpan)
{
BufferStatementFragment(sourceSpan.Content, sourceSpan);
}
public void BufferStatementFragment(string fragment)
{
BufferStatementFragment(fragment, null);
}
public void BufferStatementFragment(string fragment, Span sourceSpan)
{
if (sourceSpan != null && _currentBuffer.LinePragmaSpan == null)
{
_currentBuffer.LinePragmaSpan = sourceSpan;
// Pad the output as necessary
int start = _currentBuffer.Builder.Length;
if (_currentBuffer.GeneratedCodeStart != null)
{
start = _currentBuffer.GeneratedCodeStart.Value;
}
int paddingLength; // unused, in this case there is enough context in the original code to calculate the right padding length
// (padded.Length - _currentBuffer.Builder.Length)
string padded = CodeGeneratorPaddingHelper.Pad(Host, _currentBuffer.Builder.ToString(), sourceSpan, start, out paddingLength);
_currentBuffer.GeneratedCodeStart = start + (padded.Length - _currentBuffer.Builder.Length);
_currentBuffer.Builder.Clear();
_currentBuffer.Builder.Append(padded);
}
_currentBuffer.Builder.Append(fragment);
}
public void MarkStartOfGeneratedCode()
{
_currentBuffer.MarkStart();
}
public void MarkEndOfGeneratedCode()
{
_currentBuffer.MarkEnd();
}
public void FlushBufferedStatement()
{
if (_currentBuffer.Builder.Length > 0)
{
CodeLinePragma pragma = null;
if (_currentBuffer.LinePragmaSpan != null)
{
int start = _currentBuffer.Builder.Length;
if (_currentBuffer.GeneratedCodeStart != null)
{
start = _currentBuffer.GeneratedCodeStart.Value;
}
int len = _currentBuffer.Builder.Length - start;
if (_currentBuffer.CodeLength != null)
{
len = _currentBuffer.CodeLength.Value;
}
pragma = GenerateLinePragma(_currentBuffer.LinePragmaSpan, start, len);
}
AddStatement(_currentBuffer.Builder.ToString(), pragma);
_currentBuffer.Reset();
}
}
public void AddStatement(string generatedCode)
{
AddStatement(generatedCode, null);
}
public void AddStatement(string body, CodeLinePragma pragma)
{
if (StatementCollector == null)
{
TargetMethod.Statements.Add(new CodeSnippetStatement(body) { LinePragma = pragma });
}
else
{
StatementCollector(body, pragma);
}
}
public void EnsureExpressionHelperVariable()
{
if (!_expressionHelperVariableWriten)
{
GeneratedClass.Members.Insert(0,
new CodeMemberField(typeof(object), "__o")
{
Attributes = MemberAttributes.Private | MemberAttributes.Static
});
_expressionHelperVariableWriten = true;
}
}
public IDisposable ChangeStatementCollector(Action<string, CodeLinePragma> collector)
{
Action<string, CodeLinePragma> oldCollector = StatementCollector;
StatementCollector = collector;
return new DisposableAction(() =>
{
StatementCollector = oldCollector;
});
}
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "We explicitly want the lower-case string here")]
public void AddContextCall(Span contentSpan, string methodName, bool isLiteral)
{
AddStatement(BuildCodeString(cw =>
{
cw.WriteStartMethodInvoke(methodName);
if (!String.IsNullOrEmpty(TargetWriterName))
{
cw.WriteSnippet(TargetWriterName);
cw.WriteParameterSeparator();
}
cw.WriteStringLiteral(Host.InstrumentedSourceFilePath);
cw.WriteParameterSeparator();
cw.WriteSnippet(contentSpan.Start.AbsoluteIndex.ToString(CultureInfo.InvariantCulture));
cw.WriteParameterSeparator();
cw.WriteSnippet(contentSpan.Content.Length.ToString(CultureInfo.InvariantCulture));
cw.WriteParameterSeparator();
cw.WriteSnippet(isLiteral.ToString().ToLowerInvariant());
cw.WriteEndMethodInvoke();
cw.WriteEndStatement();
}));
}
internal CodeWriter CreateCodeWriter()
{
Debug.Assert(CodeWriterFactory != null);
if (CodeWriterFactory == null)
{
throw new InvalidOperationException(RazorResources.CreateCodeWriter_NoCodeWriter);
}
return CodeWriterFactory();
}
internal string BuildCodeString(Action<CodeWriter> action)
{
using (CodeWriter cw = CodeWriterFactory())
{
action(cw);
return cw.Content;
}
}
private class StatementBuffer
{
public StringBuilder Builder = new StringBuilder();
public int? GeneratedCodeStart;
public int? CodeLength;
public Span LinePragmaSpan;
public void Reset()
{
Builder.Clear();
GeneratedCodeStart = null;
CodeLength = null;
LinePragmaSpan = null;
}
public void MarkStart()
{
GeneratedCodeStart = Builder.Length;
}
public void MarkEnd()
{
CodeLength = Builder.Length - GeneratedCodeStart;
}
}
}
}

View File

@ -0,0 +1,204 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
internal static class CodeGeneratorPaddingHelper
{
private static readonly char[] _newLineChars = { '\r', '\n' };
// there is some duplicity of code here, but its very simple and since this is a host path, I'd rather not create another class to encapsulate the data.
public static int PaddingCharCount(RazorEngineHost host, Span target, int generatedStart)
{
int padding = CalculatePadding(host, target, generatedStart);
if (host.DesignTimeMode && host.IsIndentingWithTabs)
{
int spaces;
int tabs = Math.DivRem(padding, host.TabSize, out spaces);
return tabs + spaces;
}
else
{
return padding;
}
}
// Special case for statement padding to account for brace positioning in the editor.
public static string PadStatement(RazorEngineHost host, string code, Span target, ref int startGeneratedCode, out int paddingCharCount)
{
if (host == null)
{
throw new ArgumentNullException("host");
}
if (target == null)
{
throw new ArgumentNullException("target");
}
// We are passing 0 rather than startgeneratedcode intentionally (keeping v2 behavior).
int padding = CalculatePadding(host, target, 0);
// We treat statement padding specially so for brace positioning, so that in the following example:
// @if (foo > 0)
// {
// }
//
// the braces shows up under the @ rather than under the if.
if (host.DesignTimeMode &&
padding > 0 &&
target.Previous.Kind == SpanKind.Transition && // target.Previous is guaranteed to be none null if you got any padding.
String.Equals(target.Previous.Content, SyntaxConstants.TransitionString))
{
padding--;
startGeneratedCode--;
}
string generatedCode = PadInternal(host, code, padding, out paddingCharCount);
return generatedCode;
}
public static string Pad(RazorEngineHost host, string code, Span target, out int paddingCharCount)
{
int padding = CalculatePadding(host, target, 0);
return PadInternal(host, code, padding, out paddingCharCount);
}
public static string Pad(RazorEngineHost host, string code, Span target, int generatedStart, out int paddingCharCount)
{
int padding = CalculatePadding(host, target, generatedStart);
return PadInternal(host, code, padding, out paddingCharCount);
}
// internal for unit testing only, not intended to be used directly in code
internal static int CalculatePadding(RazorEngineHost host, Span target, int generatedStart)
{
if (host == null)
{
throw new ArgumentNullException("host");
}
if (target == null)
{
throw new ArgumentNullException("target");
}
int padding;
padding = CollectSpacesAndTabs(target, host.TabSize) - generatedStart;
// if we add generated text that is longer than the padding we wanted to insert we have no recourse and we have to skip padding
// example:
// Razor code at column zero: @somecode()
// Generated code will be:
// In design time: __o = somecode();
// In Run time: Write(somecode());
//
// In both cases the padding would have been 1 space to remote the space the @ symbol takes, which will be smaller than the 6 chars the hidden generated code takes.
if (padding < 0)
{
padding = 0;
}
return padding;
}
private static string PadInternal(RazorEngineHost host, string code, int padding, out int paddingCharCount)
{
if (host.DesignTimeMode && host.IsIndentingWithTabs)
{
int spaces;
int tabs = Math.DivRem(padding, host.TabSize, out spaces);
paddingCharCount = tabs + spaces;
return new string('\t', tabs) + new string(' ', spaces) + code;
}
else
{
paddingCharCount = padding;
return code.PadLeft(padding + code.Length, ' ');
}
}
private static int CollectSpacesAndTabs(Span target, int tabSize)
{
Span firstSpanInLine = target;
string currentContent = null;
while (firstSpanInLine.Previous != null)
{
// When scanning previous spans we need to be break down the spans with spaces.
// Because the parser doesn't so for example a span looking like \n\n\t needs to be broken down, and we should just grab the \t.
String previousContent = firstSpanInLine.Previous.Content ?? String.Empty;
int lastNewLineIndex = previousContent.LastIndexOfAny(_newLineChars);
if (lastNewLineIndex < 0)
{
firstSpanInLine = firstSpanInLine.Previous;
}
else
{
if (lastNewLineIndex != previousContent.Length - 1)
{
firstSpanInLine = firstSpanInLine.Previous;
currentContent = previousContent.Substring(lastNewLineIndex + 1);
}
break;
}
}
// We need to walk from the beginning of the line, because space + tab(tabSize) = tabSize columns, but tab(tabSize) + space = tabSize+1 columns.
Span currentSpanInLine = firstSpanInLine;
if (currentContent == null)
{
currentContent = currentSpanInLine.Content;
}
int padding = 0;
while (currentSpanInLine != target)
{
if (currentContent != null)
{
for (int i = 0; i < currentContent.Length; i++)
{
if (currentContent[i] == '\t')
{
// Example:
// <space><space><tab><tab>:
// iter 1) 1
// iter 2) 2
// iter 3) 4 = 2 + (4 - 2)
// iter 4) 8 = 4 + (4 - 0)
padding = padding + (tabSize - (padding % tabSize));
}
else
{
padding++;
}
}
}
currentSpanInLine = currentSpanInLine.Next;
currentContent = currentSpanInLine.Content;
}
return padding;
}
}
}

View File

@ -0,0 +1,210 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using System.Globalization;
using System.IO;
namespace Microsoft.AspNet.Razor.Generator
{
// Utility class which helps write code snippets
internal abstract class CodeWriter : IDisposable
{
private StringWriter _writer;
protected CodeWriter()
{
}
private enum WriterMode
{
Constructor,
MethodCall,
LambdaDelegate,
LambdaExpression
}
public string Content
{
get { return InnerWriter.ToString(); }
}
public StringWriter InnerWriter
{
get
{
if (_writer == null)
{
_writer = new StringWriter(CultureInfo.InvariantCulture);
}
return _writer;
}
}
public virtual bool SupportsMidStatementLinePragmas
{
get { return true; }
}
public abstract void WriteParameterSeparator();
public abstract void WriteReturn();
public abstract void WriteLinePragma(int? lineNumber, string fileName);
public abstract void WriteHelperHeaderPrefix(string templateTypeName, bool isStatic);
public abstract void WriteSnippet(string snippet);
public abstract void WriteStringLiteral(string literal);
public abstract int WriteVariableDeclaration(string type, string name, string value);
public virtual void WriteLinePragma()
{
WriteLinePragma(null);
}
public virtual void WriteLinePragma(CodeLinePragma pragma)
{
if (pragma == null)
{
WriteLinePragma(null, null);
}
else
{
WriteLinePragma(pragma.LineNumber, pragma.FileName);
}
}
public virtual void WriteHiddenLinePragma()
{
}
public virtual void WriteDisableUnusedFieldWarningPragma()
{
}
public virtual void WriteRestoreUnusedFieldWarningPragma()
{
}
public virtual void WriteIdentifier(string identifier)
{
InnerWriter.Write(identifier);
}
public virtual void WriteHelperHeaderSuffix(string templateTypeName)
{
}
public virtual void WriteHelperTrailer()
{
}
public void WriteStartMethodInvoke(string methodName)
{
EmitStartMethodInvoke(methodName);
}
public void WriteStartMethodInvoke(string methodName, params string[] genericArguments)
{
EmitStartMethodInvoke(methodName, genericArguments);
}
public void WriteEndMethodInvoke()
{
EmitEndMethodInvoke();
}
public virtual void WriteEndStatement()
{
}
public virtual void WriteStartAssignment(string variableName)
{
InnerWriter.Write(variableName);
InnerWriter.Write(" = ");
}
public void WriteStartLambdaExpression(params string[] parameterNames)
{
EmitStartLambdaExpression(parameterNames);
}
public void WriteStartConstructor(string typeName)
{
EmitStartConstructor(typeName);
}
public void WriteStartLambdaDelegate(params string[] parameterNames)
{
EmitStartLambdaDelegate(parameterNames);
}
public void WriteEndLambdaExpression()
{
EmitEndLambdaExpression();
}
public void WriteEndConstructor()
{
EmitEndConstructor();
}
public void WriteEndLambdaDelegate()
{
EmitEndLambdaDelegate();
}
public virtual void WriteLineContinuation()
{
}
public virtual void WriteBooleanLiteral(bool value)
{
WriteSnippet(value.ToString(CultureInfo.InvariantCulture));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Clear()
{
if (InnerWriter != null)
{
InnerWriter.GetStringBuilder().Clear();
}
}
public CodeSnippetStatement ToStatement()
{
return new CodeSnippetStatement(Content);
}
public CodeSnippetTypeMember ToTypeMember()
{
return new CodeSnippetTypeMember(Content);
}
protected internal abstract void EmitStartLambdaDelegate(string[] parameterNames);
protected internal abstract void EmitStartLambdaExpression(string[] parameterNames);
protected internal abstract void EmitStartConstructor(string typeName);
protected internal abstract void EmitStartMethodInvoke(string methodName);
protected internal virtual void EmitStartMethodInvoke(string methodName, params string[] genericArguments)
{
EmitStartMethodInvoke(methodName);
}
protected internal abstract void EmitEndLambdaDelegate();
protected internal abstract void EmitEndLambdaExpression();
protected internal abstract void EmitEndConstructor();
protected internal abstract void EmitEndMethodInvoke();
protected virtual void Dispose(bool disposing)
{
if (disposing && _writer != null)
{
_writer.Dispose();
}
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Globalization;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Generator
{
internal static class CodeWriterExtensions
{
public static void WriteLocationTaggedString(this CodeWriter writer, LocationTagged<string> value)
{
writer.WriteStartMethodInvoke("Tuple.Create");
writer.WriteStringLiteral(value.Value);
writer.WriteParameterSeparator();
writer.WriteSnippet(value.Location.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
writer.WriteEndMethodInvoke();
}
}
}

View File

@ -0,0 +1,142 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class DynamicAttributeBlockCodeGenerator : BlockCodeGenerator
{
private const string ValueWriterName = "__razor_attribute_value_writer";
private string _oldTargetWriter;
private bool _isExpression;
private ExpressionRenderingMode _oldRenderingMode;
public DynamicAttributeBlockCodeGenerator(LocationTagged<string> prefix, int offset, int line, int col)
: this(prefix, new SourceLocation(offset, line, col))
{
}
public DynamicAttributeBlockCodeGenerator(LocationTagged<string> prefix, SourceLocation valueStart)
{
Prefix = prefix;
ValueStart = valueStart;
}
public LocationTagged<string> Prefix { get; private set; }
public SourceLocation ValueStart { get; private set; }
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
if (context.Host.DesignTimeMode)
{
return; // Don't generate anything!
}
// What kind of block is nested within
string generatedCode;
Block child = target.Children.Where(n => n.IsBlock).Cast<Block>().FirstOrDefault();
if (child != null && child.Type == BlockType.Expression)
{
_isExpression = true;
generatedCode = context.BuildCodeString(cw =>
{
cw.WriteParameterSeparator();
cw.WriteStartMethodInvoke("Tuple.Create");
cw.WriteLocationTaggedString(Prefix);
cw.WriteParameterSeparator();
cw.WriteStartMethodInvoke("Tuple.Create", "System.Object", "System.Int32");
});
_oldRenderingMode = context.ExpressionRenderingMode;
context.ExpressionRenderingMode = ExpressionRenderingMode.InjectCode;
}
else
{
generatedCode = context.BuildCodeString(cw =>
{
cw.WriteParameterSeparator();
cw.WriteStartMethodInvoke("Tuple.Create");
cw.WriteLocationTaggedString(Prefix);
cw.WriteParameterSeparator();
cw.WriteStartMethodInvoke("Tuple.Create", "System.Object", "System.Int32");
cw.WriteStartConstructor(context.Host.GeneratedClassContext.TemplateTypeName);
cw.WriteStartLambdaDelegate(ValueWriterName);
});
}
context.MarkEndOfGeneratedCode();
context.BufferStatementFragment(generatedCode);
_oldTargetWriter = context.TargetWriterName;
context.TargetWriterName = ValueWriterName;
}
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
if (context.Host.DesignTimeMode)
{
return; // Don't generate anything!
}
string generatedCode;
if (_isExpression)
{
generatedCode = context.BuildCodeString(cw =>
{
cw.WriteParameterSeparator();
cw.WriteSnippet(ValueStart.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
cw.WriteEndMethodInvoke();
cw.WriteParameterSeparator();
// literal: false - This attribute value is not a literal value, it is dynamically generated
cw.WriteBooleanLiteral(false);
cw.WriteEndMethodInvoke();
cw.WriteLineContinuation();
});
context.ExpressionRenderingMode = _oldRenderingMode;
}
else
{
generatedCode = context.BuildCodeString(cw =>
{
cw.WriteEndLambdaDelegate();
cw.WriteEndConstructor();
cw.WriteParameterSeparator();
cw.WriteSnippet(ValueStart.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
cw.WriteEndMethodInvoke();
cw.WriteParameterSeparator();
// literal: false - This attribute value is not a literal value, it is dynamically generated
cw.WriteBooleanLiteral(false);
cw.WriteEndMethodInvoke();
cw.WriteLineContinuation();
});
}
context.AddStatement(generatedCode);
context.TargetWriterName = _oldTargetWriter;
}
public override string ToString()
{
return String.Format(CultureInfo.CurrentCulture, "DynAttr:{0:F}", Prefix);
}
public override bool Equals(object obj)
{
DynamicAttributeBlockCodeGenerator other = obj as DynamicAttributeBlockCodeGenerator;
return other != null &&
Equals(other.Prefix, Prefix);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(Prefix)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class ExpressionCodeGenerator : HybridCodeGenerator
{
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
if (context.Host.EnableInstrumentation && context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
Span contentSpan = target.Children
.OfType<Span>()
.Where(s => s.Kind == SpanKind.Code || s.Kind == SpanKind.Markup)
.FirstOrDefault();
if (contentSpan != null)
{
context.AddContextCall(contentSpan, context.Host.GeneratedClassContext.BeginContextMethodName, false);
}
}
string writeInvocation = context.BuildCodeString(cw =>
{
if (context.Host.DesignTimeMode)
{
context.EnsureExpressionHelperVariable();
cw.WriteStartAssignment("__o");
}
else if (context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
if (!String.IsNullOrEmpty(context.TargetWriterName))
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteToMethodName);
cw.WriteSnippet(context.TargetWriterName);
cw.WriteParameterSeparator();
}
else
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteMethodName);
}
}
});
context.BufferStatementFragment(writeInvocation);
context.MarkStartOfGeneratedCode();
}
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
string endBlock = context.BuildCodeString(cw =>
{
if (context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
if (!context.Host.DesignTimeMode)
{
cw.WriteEndMethodInvoke();
}
cw.WriteEndStatement();
}
else
{
cw.WriteLineContinuation();
}
});
context.MarkEndOfGeneratedCode();
context.BufferStatementFragment(endBlock);
context.FlushBufferedStatement();
if (context.Host.EnableInstrumentation && context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
Span contentSpan = target.Children
.OfType<Span>()
.Where(s => s.Kind == SpanKind.Code || s.Kind == SpanKind.Markup)
.FirstOrDefault();
if (contentSpan != null)
{
context.AddContextCall(contentSpan, context.Host.GeneratedClassContext.EndContextMethodName, false);
}
}
}
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
Span sourceSpan = null;
if (context.CreateCodeWriter().SupportsMidStatementLinePragmas || context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
sourceSpan = target;
}
context.BufferStatementFragment(target.Content, sourceSpan);
}
public override string ToString()
{
return "Expr";
}
public override bool Equals(object obj)
{
return obj is ExpressionCodeGenerator;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Razor.Generator
{
public enum ExpressionRenderingMode
{
/// <summary>
/// Indicates that expressions should be written to the output stream
/// </summary>
/// <example>
/// If @foo is rendered with WriteToOutput, the code generator would output the following code:
///
/// Write(foo);
/// </example>
WriteToOutput,
/// <summary>
/// Indicates that expressions should simply be placed as-is in the code, and the context in which
/// the code exists will be used to render it
/// </summary>
/// <example>
/// If @foo is rendered with InjectCode, the code generator would output the following code:
///
/// foo
/// </example>
InjectCode
}
}

View File

@ -0,0 +1,177 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Generator
{
public struct GeneratedClassContext
{
public static readonly string DefaultWriteMethodName = "Write";
public static readonly string DefaultWriteLiteralMethodName = "WriteLiteral";
public static readonly string DefaultExecuteMethodName = "Execute";
public static readonly string DefaultLayoutPropertyName = "Layout";
public static readonly string DefaultWriteAttributeMethodName = "WriteAttribute";
public static readonly string DefaultWriteAttributeToMethodName = "WriteAttributeTo";
public static readonly GeneratedClassContext Default = new GeneratedClassContext(DefaultExecuteMethodName,
DefaultWriteMethodName,
DefaultWriteLiteralMethodName);
public GeneratedClassContext(string executeMethodName, string writeMethodName, string writeLiteralMethodName)
: this()
{
if (String.IsNullOrEmpty(executeMethodName))
{
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
"executeMethodName"),
"executeMethodName");
}
if (String.IsNullOrEmpty(writeMethodName))
{
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
"writeMethodName"),
"writeMethodName");
}
if (String.IsNullOrEmpty(writeLiteralMethodName))
{
throw new ArgumentException(String.Format(CultureInfo.CurrentCulture,
CommonResources.Argument_Cannot_Be_Null_Or_Empty,
"writeLiteralMethodName"),
"writeLiteralMethodName");
}
WriteMethodName = writeMethodName;
WriteLiteralMethodName = writeLiteralMethodName;
ExecuteMethodName = executeMethodName;
WriteToMethodName = null;
WriteLiteralToMethodName = null;
TemplateTypeName = null;
DefineSectionMethodName = null;
LayoutPropertyName = DefaultLayoutPropertyName;
WriteAttributeMethodName = DefaultWriteAttributeMethodName;
WriteAttributeToMethodName = DefaultWriteAttributeToMethodName;
}
public GeneratedClassContext(string executeMethodName,
string writeMethodName,
string writeLiteralMethodName,
string writeToMethodName,
string writeLiteralToMethodName,
string templateTypeName)
: this(executeMethodName, writeMethodName, writeLiteralMethodName)
{
WriteToMethodName = writeToMethodName;
WriteLiteralToMethodName = writeLiteralToMethodName;
TemplateTypeName = templateTypeName;
}
public GeneratedClassContext(string executeMethodName,
string writeMethodName,
string writeLiteralMethodName,
string writeToMethodName,
string writeLiteralToMethodName,
string templateTypeName,
string defineSectionMethodName)
: this(executeMethodName, writeMethodName, writeLiteralMethodName, writeToMethodName, writeLiteralToMethodName, templateTypeName)
{
DefineSectionMethodName = defineSectionMethodName;
}
public GeneratedClassContext(string executeMethodName,
string writeMethodName,
string writeLiteralMethodName,
string writeToMethodName,
string writeLiteralToMethodName,
string templateTypeName,
string defineSectionMethodName,
string beginContextMethodName,
string endContextMethodName)
: this(executeMethodName, writeMethodName, writeLiteralMethodName, writeToMethodName, writeLiteralToMethodName, templateTypeName, defineSectionMethodName)
{
BeginContextMethodName = beginContextMethodName;
EndContextMethodName = endContextMethodName;
}
public string WriteMethodName { get; private set; }
public string WriteLiteralMethodName { get; private set; }
public string WriteToMethodName { get; private set; }
public string WriteLiteralToMethodName { get; private set; }
public string ExecuteMethodName { get; private set; }
// Optional Items
public string BeginContextMethodName { get; set; }
public string EndContextMethodName { get; set; }
public string LayoutPropertyName { get; set; }
public string DefineSectionMethodName { get; set; }
public string TemplateTypeName { get; set; }
public string WriteAttributeMethodName { get; set; }
public string WriteAttributeToMethodName { get; set; }
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Property is not a URL property")]
public string ResolveUrlMethodName { get; set; }
public bool AllowSections
{
get { return !String.IsNullOrEmpty(DefineSectionMethodName); }
}
public bool AllowTemplates
{
get { return !String.IsNullOrEmpty(TemplateTypeName); }
}
public bool SupportsInstrumentation
{
get { return !String.IsNullOrEmpty(BeginContextMethodName) && !String.IsNullOrEmpty(EndContextMethodName); }
}
public override bool Equals(object obj)
{
if (!(obj is GeneratedClassContext))
{
return false;
}
GeneratedClassContext other = (GeneratedClassContext)obj;
return String.Equals(DefineSectionMethodName, other.DefineSectionMethodName, StringComparison.Ordinal) &&
String.Equals(WriteMethodName, other.WriteMethodName, StringComparison.Ordinal) &&
String.Equals(WriteLiteralMethodName, other.WriteLiteralMethodName, StringComparison.Ordinal) &&
String.Equals(WriteToMethodName, other.WriteToMethodName, StringComparison.Ordinal) &&
String.Equals(WriteLiteralToMethodName, other.WriteLiteralToMethodName, StringComparison.Ordinal) &&
String.Equals(ExecuteMethodName, other.ExecuteMethodName, StringComparison.Ordinal) &&
String.Equals(TemplateTypeName, other.TemplateTypeName, StringComparison.Ordinal) &&
String.Equals(BeginContextMethodName, other.BeginContextMethodName, StringComparison.Ordinal) &&
String.Equals(EndContextMethodName, other.EndContextMethodName, StringComparison.Ordinal);
}
public override int GetHashCode()
{
// TODO: Use HashCodeCombiner
return DefineSectionMethodName.GetHashCode() ^
WriteMethodName.GetHashCode() ^
WriteLiteralMethodName.GetHashCode() ^
WriteToMethodName.GetHashCode() ^
WriteLiteralToMethodName.GetHashCode() ^
ExecuteMethodName.GetHashCode() ^
TemplateTypeName.GetHashCode() ^
BeginContextMethodName.GetHashCode() ^
EndContextMethodName.GetHashCode();
}
public static bool operator ==(GeneratedClassContext left, GeneratedClassContext right)
{
return left.Equals(right);
}
public static bool operator !=(GeneratedClassContext left, GeneratedClassContext right)
{
return !left.Equals(right);
}
}
}

View File

@ -0,0 +1,102 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Generator
{
public struct GeneratedCodeMapping
{
public GeneratedCodeMapping(int startLine, int startColumn, int startGeneratedColumn, int codeLength)
: this(null, startLine, startColumn, startGeneratedColumn, codeLength)
{
}
public GeneratedCodeMapping(int startOffset, int startLine, int startColumn, int startGeneratedColumn, int codeLength)
: this((int?)startOffset, startLine, startColumn, startGeneratedColumn, codeLength)
{
}
private GeneratedCodeMapping(int? startOffset, int startLine, int startColumn, int startGeneratedColumn, int codeLength)
: this()
{
if (startLine < 0)
{
throw new ArgumentOutOfRangeException("startLine", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "startLine", "0"));
}
if (startColumn < 0)
{
throw new ArgumentOutOfRangeException("startColumn", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "startColumn", "0"));
}
if (startGeneratedColumn < 0)
{
throw new ArgumentOutOfRangeException("startGeneratedColumn", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "startGeneratedColumn", "0"));
}
if (codeLength < 0)
{
throw new ArgumentOutOfRangeException("codeLength", String.Format(CultureInfo.CurrentCulture, CommonResources.Argument_Must_Be_GreaterThanOrEqualTo, "codeLength", "0"));
}
StartOffset = startOffset;
StartLine = startLine;
StartColumn = startColumn;
StartGeneratedColumn = startGeneratedColumn;
CodeLength = codeLength;
}
public int? StartOffset { get; set; }
public int CodeLength { get; set; }
public int StartColumn { get; set; }
public int StartGeneratedColumn { get; set; }
public int StartLine { get; set; }
public override bool Equals(object obj)
{
if (!(obj is GeneratedCodeMapping))
{
return false;
}
GeneratedCodeMapping other = (GeneratedCodeMapping)obj;
return CodeLength == other.CodeLength &&
StartColumn == other.StartColumn &&
StartGeneratedColumn == other.StartGeneratedColumn &&
StartLine == other.StartLine &&
// Null means it matches the other no matter what.
(StartOffset == null || other.StartOffset == null || StartOffset.Equals(other.StartOffset));
}
public override string ToString()
{
return String.Format(
CultureInfo.CurrentCulture,
"({0}, {1}, {2}) -> (?, {3}) [{4}]",
StartOffset == null ? "?" : StartOffset.Value.ToString(CultureInfo.CurrentCulture),
StartLine,
StartColumn,
StartGeneratedColumn,
CodeLength);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(CodeLength)
.Add(StartColumn)
.Add(StartGeneratedColumn)
.Add(StartLine)
.Add(StartOffset)
.CombinedHash;
}
public static bool operator ==(GeneratedCodeMapping left, GeneratedCodeMapping right)
{
return left.Equals(right);
}
public static bool operator !=(GeneratedCodeMapping left, GeneratedCodeMapping right)
{
return !left.Equals(right);
}
}
}

View File

@ -0,0 +1,116 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.CodeDom;
using System.Globalization;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class HelperCodeGenerator : BlockCodeGenerator
{
private const string HelperWriterName = "__razor_helper_writer";
private CodeWriter _writer;
private string _oldWriter;
private IDisposable _statementCollectorToken;
public HelperCodeGenerator(LocationTagged<string> signature, bool headerComplete)
{
Signature = signature;
HeaderComplete = headerComplete;
}
public LocationTagged<string> Signature { get; private set; }
public LocationTagged<string> Footer { get; set; }
public bool HeaderComplete { get; private set; }
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
_writer = context.CreateCodeWriter();
string prefix = context.BuildCodeString(
cw => cw.WriteHelperHeaderPrefix(context.Host.GeneratedClassContext.TemplateTypeName, context.Host.StaticHelpers));
_writer.WriteLinePragma(
context.GenerateLinePragma(Signature.Location, prefix.Length, Signature.Value.Length));
_writer.WriteSnippet(prefix);
_writer.WriteSnippet(Signature);
if (HeaderComplete)
{
_writer.WriteHelperHeaderSuffix(context.Host.GeneratedClassContext.TemplateTypeName);
}
_writer.WriteLinePragma(null);
if (HeaderComplete)
{
_writer.WriteReturn();
_writer.WriteStartConstructor(context.Host.GeneratedClassContext.TemplateTypeName);
_writer.WriteStartLambdaDelegate(HelperWriterName);
}
_statementCollectorToken = context.ChangeStatementCollector(AddStatementToHelper);
_oldWriter = context.TargetWriterName;
context.TargetWriterName = HelperWriterName;
}
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
_statementCollectorToken.Dispose();
if (HeaderComplete)
{
_writer.WriteEndLambdaDelegate();
_writer.WriteEndConstructor();
_writer.WriteEndStatement();
}
if (Footer != null && !String.IsNullOrEmpty(Footer.Value))
{
_writer.WriteLinePragma(
context.GenerateLinePragma(Footer.Location, 0, Footer.Value.Length));
_writer.WriteSnippet(Footer);
_writer.WriteLinePragma();
}
_writer.WriteHelperTrailer();
context.GeneratedClass.Members.Add(new CodeSnippetTypeMember(_writer.Content));
context.TargetWriterName = _oldWriter;
}
public override bool Equals(object obj)
{
HelperCodeGenerator other = obj as HelperCodeGenerator;
return other != null &&
base.Equals(other) &&
HeaderComplete == other.HeaderComplete &&
Equals(Signature, other.Signature);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(base.GetHashCode())
.Add(Signature)
.CombinedHash;
}
public override string ToString()
{
return "Helper:" + Signature.ToString("F", CultureInfo.CurrentCulture) + ";" + (HeaderComplete ? "C" : "I");
}
private void AddStatementToHelper(string statement, CodeLinePragma pragma)
{
if (pragma != null)
{
_writer.WriteLinePragma(pragma);
}
_writer.WriteSnippet(statement);
_writer.InnerWriter.WriteLine(); // CodeDOM normally inserts an extra line so we need to do so here.
if (pragma != null)
{
_writer.WriteLinePragma();
}
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public abstract class HybridCodeGenerator : ISpanCodeGenerator, IBlockCodeGenerator
{
public virtual void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
}
public virtual void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
}
public virtual void GenerateCode(Span target, CodeGeneratorContext context)
{
}
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public interface IBlockCodeGenerator
{
void GenerateStartBlockCode(Block target, CodeGeneratorContext context);
void GenerateEndBlockCode(Block target, CodeGeneratorContext context);
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public interface ISpanCodeGenerator
{
void GenerateCode(Span target, CodeGeneratorContext context);
}
}

View File

@ -0,0 +1,114 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Globalization;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class LiteralAttributeCodeGenerator : SpanCodeGenerator
{
public LiteralAttributeCodeGenerator(LocationTagged<string> prefix, LocationTagged<SpanCodeGenerator> valueGenerator)
{
Prefix = prefix;
ValueGenerator = valueGenerator;
}
public LiteralAttributeCodeGenerator(LocationTagged<string> prefix, LocationTagged<string> value)
{
Prefix = prefix;
Value = value;
}
public LocationTagged<string> Prefix { get; private set; }
public LocationTagged<string> Value { get; private set; }
public LocationTagged<SpanCodeGenerator> ValueGenerator { get; private set; }
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
if (context.Host.DesignTimeMode)
{
return;
}
ExpressionRenderingMode oldMode = context.ExpressionRenderingMode;
context.BufferStatementFragment(context.BuildCodeString(cw =>
{
cw.WriteParameterSeparator();
cw.WriteStartMethodInvoke("Tuple.Create");
cw.WriteLocationTaggedString(Prefix);
cw.WriteParameterSeparator();
if (ValueGenerator != null)
{
cw.WriteStartMethodInvoke("Tuple.Create", "System.Object", "System.Int32");
context.ExpressionRenderingMode = ExpressionRenderingMode.InjectCode;
}
else
{
cw.WriteLocationTaggedString(Value);
cw.WriteParameterSeparator();
// literal: true - This attribute value is a literal value
cw.WriteBooleanLiteral(true);
cw.WriteEndMethodInvoke();
// In VB, we need a line continuation
cw.WriteLineContinuation();
}
}));
if (ValueGenerator != null)
{
ValueGenerator.Value.GenerateCode(target, context);
context.FlushBufferedStatement();
context.ExpressionRenderingMode = oldMode;
context.AddStatement(context.BuildCodeString(cw =>
{
cw.WriteParameterSeparator();
cw.WriteSnippet(ValueGenerator.Location.AbsoluteIndex.ToString(CultureInfo.CurrentCulture));
cw.WriteEndMethodInvoke();
cw.WriteParameterSeparator();
// literal: false - This attribute value is not a literal value, it is dynamically generated
cw.WriteBooleanLiteral(false);
cw.WriteEndMethodInvoke();
// In VB, we need a line continuation
cw.WriteLineContinuation();
}));
}
else
{
context.FlushBufferedStatement();
}
}
public override string ToString()
{
if (ValueGenerator == null)
{
return String.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},{1:F}", Prefix, Value);
}
else
{
return String.Format(CultureInfo.CurrentCulture, "LitAttr:{0:F},<Sub:{1:F}>", Prefix, ValueGenerator);
}
}
public override bool Equals(object obj)
{
LiteralAttributeCodeGenerator other = obj as LiteralAttributeCodeGenerator;
return other != null &&
Equals(other.Prefix, Prefix) &&
Equals(other.Value, Value) &&
Equals(other.ValueGenerator, ValueGenerator);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(Prefix)
.Add(Value)
.Add(ValueGenerator)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class MarkupCodeGenerator : SpanCodeGenerator
{
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
if (!context.Host.DesignTimeMode && String.IsNullOrEmpty(target.Content))
{
return;
}
if (context.Host.EnableInstrumentation)
{
context.AddContextCall(target, context.Host.GeneratedClassContext.BeginContextMethodName, isLiteral: true);
}
if (!String.IsNullOrEmpty(target.Content) && !context.Host.DesignTimeMode)
{
string code = context.BuildCodeString(cw =>
{
if (!String.IsNullOrEmpty(context.TargetWriterName))
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteLiteralToMethodName);
cw.WriteSnippet(context.TargetWriterName);
cw.WriteParameterSeparator();
}
else
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteLiteralMethodName);
}
cw.WriteStringLiteral(target.Content);
cw.WriteEndMethodInvoke();
cw.WriteEndStatement();
});
context.AddStatement(code);
}
if (context.Host.EnableInstrumentation)
{
context.AddContextCall(target, context.Host.GeneratedClassContext.EndContextMethodName, isLiteral: true);
}
}
public override string ToString()
{
return "Markup";
}
public override bool Equals(object obj)
{
return obj is MarkupCodeGenerator;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

View File

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using System.Linq;
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Generator
{
public abstract class RazorCodeGenerator : ParserVisitor
{
private CodeGeneratorContext _context;
protected RazorCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host)
{
if (String.IsNullOrEmpty(className))
{
throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "className");
}
if (rootNamespaceName == null)
{
throw new ArgumentNullException("rootNamespaceName");
}
if (host == null)
{
throw new ArgumentNullException("host");
}
ClassName = className;
RootNamespaceName = rootNamespaceName;
SourceFileName = sourceFileName;
GenerateLinePragmas = String.IsNullOrEmpty(SourceFileName) ? false : true;
Host = host;
}
// Data pulled from constructor
public string ClassName { get; private set; }
public string RootNamespaceName { get; private set; }
public string SourceFileName { get; private set; }
public RazorEngineHost Host { get; private set; }
// Generation settings
public bool GenerateLinePragmas { get; set; }
public bool DesignTimeMode { get; set; }
public CodeGeneratorContext Context
{
get
{
EnsureContextInitialized();
return _context;
}
}
internal virtual Func<CodeWriter> CodeWriterFactory
{
get { return null; }
}
public override void VisitStartBlock(Block block)
{
block.CodeGenerator.GenerateStartBlockCode(block, Context);
}
public override void VisitEndBlock(Block block)
{
block.CodeGenerator.GenerateEndBlockCode(block, Context);
}
public override void VisitSpan(Span span)
{
span.CodeGenerator.GenerateCode(span, Context);
}
public override void OnComplete()
{
Context.FlushBufferedStatement();
}
private void EnsureContextInitialized()
{
if (_context == null)
{
_context = CodeGeneratorContext.Create(Host, CodeWriterFactory, ClassName, RootNamespaceName, SourceFileName, GenerateLinePragmas);
Initialize(_context);
}
}
protected virtual void Initialize(CodeGeneratorContext context)
{
context.Namespace.Imports.AddRange(Host.NamespaceImports.Select(s => new CodeNamespaceImport(s)).ToArray());
if (!String.IsNullOrEmpty(Host.DefaultBaseClass))
{
context.GeneratedClass.BaseTypes.Add(new CodeTypeReference(Host.DefaultBaseClass));
}
// Dev10 Bug 937438: Generate explicit Parameter-less constructor on Razor generated class
context.GeneratedClass.Members.Add(new CodeConstructor() { Attributes = MemberAttributes.Public });
}
}
}

View File

@ -0,0 +1,21 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class RazorCommentCodeGenerator : BlockCodeGenerator
{
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
// Flush the buffered statement since we're interrupting it with a comment.
if (!String.IsNullOrEmpty(context.CurrentBufferedStatement))
{
context.MarkEndOfGeneratedCode();
context.BufferStatementFragment(context.BuildCodeString(cw => cw.WriteLineContinuation()));
}
context.FlushBufferedStatement();
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.Internal.Web.Utils;
namespace Microsoft.AspNet.Razor.Generator
{
public class RazorDirectiveAttributeCodeGenerator : SpanCodeGenerator
{
public RazorDirectiveAttributeCodeGenerator(string name, string value)
{
if (String.IsNullOrEmpty(name))
{
throw new ArgumentException(CommonResources.Argument_Cannot_Be_Null_Or_Empty, "name");
}
Name = name;
Value = value ?? String.Empty; // Coerce to empty string if it was null.
}
public string Name { get; private set; }
public string Value { get; private set; }
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
var attributeType = new CodeTypeReference(typeof(RazorDirectiveAttribute));
var attributeDeclaration = new CodeAttributeDeclaration(
attributeType,
new CodeAttributeArgument(new CodePrimitiveExpression(Name)),
new CodeAttributeArgument(new CodePrimitiveExpression(Value)));
context.GeneratedClass.CustomAttributes.Add(attributeDeclaration);
}
public override string ToString()
{
return "Directive: " + Name + ", Value: " + Value;
}
public override bool Equals(object obj)
{
RazorDirectiveAttributeCodeGenerator other = obj as RazorDirectiveAttributeCodeGenerator;
return other != null &&
Name.Equals(other.Name, StringComparison.OrdinalIgnoreCase) &&
Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase);
}
public override int GetHashCode()
{
return Tuple.Create(Name.ToUpperInvariant(), Value.ToUpperInvariant())
.GetHashCode();
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class ResolveUrlCodeGenerator : SpanCodeGenerator
{
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
// Check if the host supports it
if (String.IsNullOrEmpty(context.Host.GeneratedClassContext.ResolveUrlMethodName))
{
// Nope, just use the default MarkupCodeGenerator behavior
new MarkupCodeGenerator().GenerateCode(target, context);
return;
}
if (!context.Host.DesignTimeMode && String.IsNullOrEmpty(target.Content))
{
return;
}
if (context.Host.EnableInstrumentation && context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
// Add a non-literal context call (non-literal because the expanded URL will not match the source character-by-character)
context.AddContextCall(target, context.Host.GeneratedClassContext.BeginContextMethodName, isLiteral: false);
}
if (!String.IsNullOrEmpty(target.Content) && !context.Host.DesignTimeMode)
{
string code = context.BuildCodeString(cw =>
{
if (context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
if (!String.IsNullOrEmpty(context.TargetWriterName))
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteLiteralToMethodName);
cw.WriteSnippet(context.TargetWriterName);
cw.WriteParameterSeparator();
}
else
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.WriteLiteralMethodName);
}
}
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.ResolveUrlMethodName);
cw.WriteStringLiteral(target.Content);
cw.WriteEndMethodInvoke();
if (context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
cw.WriteEndMethodInvoke();
cw.WriteEndStatement();
}
else
{
cw.WriteLineContinuation();
}
});
if (context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
context.AddStatement(code);
}
else
{
context.BufferStatementFragment(code);
}
}
if (context.Host.EnableInstrumentation && context.ExpressionRenderingMode == ExpressionRenderingMode.WriteToOutput)
{
context.AddContextCall(target, context.Host.GeneratedClassContext.EndContextMethodName, isLiteral: false);
}
}
public override string ToString()
{
return "VirtualPath";
}
public override bool Equals(object obj)
{
return obj is ResolveUrlCodeGenerator;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class SectionCodeGenerator : BlockCodeGenerator
{
public SectionCodeGenerator(string sectionName)
{
SectionName = sectionName;
}
public string SectionName { get; private set; }
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
string startBlock = context.BuildCodeString(cw =>
{
cw.WriteStartMethodInvoke(context.Host.GeneratedClassContext.DefineSectionMethodName);
cw.WriteStringLiteral(SectionName);
cw.WriteParameterSeparator();
cw.WriteStartLambdaDelegate();
});
context.AddStatement(startBlock);
}
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
string startBlock = context.BuildCodeString(cw =>
{
cw.WriteEndLambdaDelegate();
cw.WriteEndMethodInvoke();
cw.WriteEndStatement();
});
context.AddStatement(startBlock);
}
public override bool Equals(object obj)
{
SectionCodeGenerator other = obj as SectionCodeGenerator;
return other != null &&
base.Equals(other) &&
String.Equals(SectionName, other.SectionName, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add(base.GetHashCode())
.Add(SectionName)
.CombinedHash;
}
public override string ToString()
{
return "Section:" + SectionName;
}
}
}

View File

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class SetBaseTypeCodeGenerator : SpanCodeGenerator
{
public SetBaseTypeCodeGenerator(string baseType)
{
BaseType = baseType;
}
public string BaseType { get; private set; }
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
context.GeneratedClass.BaseTypes.Clear();
context.GeneratedClass.BaseTypes.Add(new CodeTypeReference(ResolveType(context, BaseType.Trim())));
if (context.Host.DesignTimeMode)
{
int generatedCodeStart = 0;
string code = context.BuildCodeString(cw =>
{
generatedCodeStart = cw.WriteVariableDeclaration(target.Content, "__inheritsHelper", null);
cw.WriteEndStatement();
});
int paddingCharCount;
CodeSnippetStatement stmt = new CodeSnippetStatement(
CodeGeneratorPaddingHelper.Pad(context.Host, code, target, generatedCodeStart, out paddingCharCount))
{
LinePragma = context.GenerateLinePragma(target, generatedCodeStart + paddingCharCount)
};
context.AddDesignTimeHelperStatement(stmt);
}
}
protected virtual string ResolveType(CodeGeneratorContext context, string baseType)
{
return baseType;
}
public override string ToString()
{
return "Base:" + BaseType;
}
public override bool Equals(object obj)
{
SetBaseTypeCodeGenerator other = obj as SetBaseTypeCodeGenerator;
return other != null &&
String.Equals(BaseType, other.BaseType, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return BaseType.GetHashCode();
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.CodeDom;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class SetLayoutCodeGenerator : SpanCodeGenerator
{
public SetLayoutCodeGenerator(string layoutPath)
{
LayoutPath = layoutPath;
}
public string LayoutPath { get; set; }
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
if (!context.Host.DesignTimeMode && !String.IsNullOrEmpty(context.Host.GeneratedClassContext.LayoutPropertyName))
{
context.TargetMethod.Statements.Add(
new CodeAssignStatement(
new CodePropertyReferenceExpression(null, context.Host.GeneratedClassContext.LayoutPropertyName),
new CodePrimitiveExpression(LayoutPath)));
}
}
public override string ToString()
{
return "Layout: " + LayoutPath;
}
public override bool Equals(object obj)
{
SetLayoutCodeGenerator other = obj as SetLayoutCodeGenerator;
return other != null && String.Equals(other.LayoutPath, LayoutPath, StringComparison.Ordinal);
}
public override int GetHashCode()
{
return LayoutPath.GetHashCode();
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class SetVBOptionCodeGenerator : SpanCodeGenerator
{
public static readonly string StrictCodeDomOptionName = "AllowLateBound";
public static readonly string ExplicitCodeDomOptionName = "RequireVariableDeclaration";
public SetVBOptionCodeGenerator(string optionName, bool value)
{
OptionName = optionName;
Value = value;
}
// CodeDOM Option Name, which is NOT the same as the VB Option Name
public string OptionName { get; private set; }
public bool Value { get; private set; }
public static SetVBOptionCodeGenerator Strict(bool onOffValue)
{
// Strict On = AllowLateBound Off
return new SetVBOptionCodeGenerator(StrictCodeDomOptionName, !onOffValue);
}
public static SetVBOptionCodeGenerator Explicit(bool onOffValue)
{
return new SetVBOptionCodeGenerator(ExplicitCodeDomOptionName, onOffValue);
}
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
context.CompileUnit.UserData[OptionName] = Value;
}
public override string ToString()
{
return "Option:" + OptionName + "=" + Value;
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public abstract class SpanCodeGenerator : ISpanCodeGenerator
{
[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "This class has no instance state")]
public static readonly ISpanCodeGenerator Null = new NullSpanCodeGenerator();
public virtual void GenerateCode(Span target, CodeGeneratorContext context)
{
}
public override bool Equals(object obj)
{
return (obj as ISpanCodeGenerator) != null;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
private class NullSpanCodeGenerator : ISpanCodeGenerator
{
public void GenerateCode(Span target, CodeGeneratorContext context)
{
}
public override string ToString()
{
return "None";
}
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class StatementCodeGenerator : SpanCodeGenerator
{
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
context.FlushBufferedStatement();
string generatedCode = context.BuildCodeString(cw =>
{
cw.WriteSnippet(target.Content);
});
int startGeneratedCode = target.Start.CharacterIndex;
int paddingCharCount;
generatedCode = CodeGeneratorPaddingHelper.PadStatement(context.Host, generatedCode, target, ref startGeneratedCode, out paddingCharCount);
context.AddStatement(
generatedCode,
context.GenerateLinePragma(target, paddingCharCount));
}
public override string ToString()
{
return "Stmt";
}
public override bool Equals(object obj)
{
return obj is StatementCodeGenerator;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class TemplateBlockCodeGenerator : BlockCodeGenerator
{
private const string TemplateWriterName = "__razor_template_writer";
private const string ItemParameterName = "item";
private string _oldTargetWriter;
public override void GenerateStartBlockCode(Block target, CodeGeneratorContext context)
{
string generatedCode = context.BuildCodeString(cw =>
{
cw.WriteStartLambdaExpression(ItemParameterName);
cw.WriteStartConstructor(context.Host.GeneratedClassContext.TemplateTypeName);
cw.WriteStartLambdaDelegate(TemplateWriterName);
});
context.MarkEndOfGeneratedCode();
context.BufferStatementFragment(generatedCode);
context.FlushBufferedStatement();
_oldTargetWriter = context.TargetWriterName;
context.TargetWriterName = TemplateWriterName;
}
public override void GenerateEndBlockCode(Block target, CodeGeneratorContext context)
{
string generatedCode = context.BuildCodeString(cw =>
{
cw.WriteEndLambdaDelegate();
cw.WriteEndConstructor();
cw.WriteEndLambdaExpression();
});
context.BufferStatementFragment(generatedCode);
context.TargetWriterName = _oldTargetWriter;
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.CodeDom;
using System.Diagnostics.Contracts;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Generator
{
public class TypeMemberCodeGenerator : SpanCodeGenerator
{
public override void GenerateCode(Span target, CodeGeneratorContext context)
{
string generatedCode = context.BuildCodeString(cw =>
{
cw.WriteSnippet(target.Content);
});
int paddingCharCount;
string paddedCode = CodeGeneratorPaddingHelper.Pad(context.Host, generatedCode, target, out paddingCharCount);
Contract.Assert(paddingCharCount > 0);
context.GeneratedClass.Members.Add(
new CodeSnippetTypeMember(paddedCode)
{
LinePragma = context.GenerateLinePragma(target, paddingCharCount)
});
}
public override string ToString()
{
return "TypeMember";
}
public override bool Equals(object obj)
{
return obj is TypeMemberCodeGenerator;
}
// C# complains at us if we don't provide an implementation, even one like this
public override int GetHashCode()
{
return base.GetHashCode();
}
}
}

View File

@ -0,0 +1,201 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Razor.Generator
{
internal class VBCodeWriter : BaseCodeWriter
{
public override bool SupportsMidStatementLinePragmas
{
get { return false; }
}
protected internal override void WriteStartGenerics()
{
InnerWriter.Write("(Of ");
}
protected internal override void WriteEndGenerics()
{
InnerWriter.Write(")");
}
public override void WriteLineContinuation()
{
InnerWriter.Write(" _");
}
public override int WriteVariableDeclaration(string type, string name, string value)
{
InnerWriter.Write("Dim ");
InnerWriter.Write(name);
InnerWriter.Write(" As ");
int typePos = InnerWriter.GetStringBuilder().Length;
InnerWriter.Write(type);
if (!String.IsNullOrEmpty(value))
{
InnerWriter.Write(" = ");
InnerWriter.Write(value);
}
else
{
InnerWriter.Write(" = Nothing");
}
return typePos;
}
public override void WriteStringLiteral(string literal)
{
bool inQuotes = true;
InnerWriter.Write("\"");
for (int i = 0; i < literal.Length; i++)
{
switch (literal[i])
{
case '\t':
case '\n':
case '\r':
case '\0':
case '\u2028':
case '\u2029':
// Exit quotes
EnsureOutOfQuotes(ref inQuotes);
// Write concat character
InnerWriter.Write("&");
// Write character literal
WriteCharLiteral(literal[i]);
break;
case '"':
case '“':
case '”':
case (char)0xff02:
EnsureInQuotes(ref inQuotes);
InnerWriter.Write(literal[i]);
InnerWriter.Write(literal[i]);
break;
default:
EnsureInQuotes(ref inQuotes);
InnerWriter.Write(literal[i]);
break;
}
if (i > 0 && (i % 80) == 0)
{
if ((Char.IsHighSurrogate(literal[i]) && (i < (literal.Length - 1))) && Char.IsLowSurrogate(literal[i + 1]))
{
InnerWriter.Write(literal[++i]);
}
if (inQuotes)
{
InnerWriter.Write("\"");
}
inQuotes = true;
InnerWriter.Write("& _ ");
InnerWriter.Write(Environment.NewLine);
InnerWriter.Write('"');
}
}
EnsureOutOfQuotes(ref inQuotes);
}
protected internal override void EmitStartLambdaExpression(string[] parameterNames)
{
InnerWriter.Write("Function (");
WriteCommaSeparatedList(parameterNames, InnerWriter.Write);
InnerWriter.Write(") ");
}
protected internal override void EmitStartConstructor(string typeName)
{
InnerWriter.Write("New ");
InnerWriter.Write(typeName);
InnerWriter.Write("(");
}
protected internal override void EmitStartLambdaDelegate(string[] parameterNames)
{
InnerWriter.Write("Sub (");
WriteCommaSeparatedList(parameterNames, InnerWriter.Write);
InnerWriter.WriteLine(")");
}
protected internal override void EmitEndLambdaDelegate()
{
InnerWriter.Write("End Sub");
}
private void WriteCharLiteral(char literal)
{
InnerWriter.Write("Global.Microsoft.VisualBasic.ChrW(");
InnerWriter.Write((int)literal);
InnerWriter.Write(")");
}
private void EnsureInQuotes(ref bool inQuotes)
{
if (!inQuotes)
{
InnerWriter.Write("&\"");
inQuotes = true;
}
}
private void EnsureOutOfQuotes(ref bool inQuotes)
{
if (inQuotes)
{
InnerWriter.Write("\"");
inQuotes = false;
}
}
public override void WriteReturn()
{
InnerWriter.Write("Return ");
}
public override void WriteLinePragma(int? lineNumber, string fileName)
{
InnerWriter.WriteLine();
if (lineNumber != null)
{
InnerWriter.Write("#ExternalSource(\"");
InnerWriter.Write(fileName);
InnerWriter.Write("\", ");
InnerWriter.Write(lineNumber);
InnerWriter.WriteLine(")");
}
else
{
InnerWriter.WriteLine("#End ExternalSource");
}
}
public override void WriteHelperHeaderPrefix(string templateTypeName, bool isStatic)
{
InnerWriter.Write("Public ");
if (isStatic)
{
InnerWriter.Write("Shared ");
}
InnerWriter.Write("Function ");
}
public override void WriteHelperHeaderSuffix(string templateTypeName)
{
InnerWriter.Write(" As ");
InnerWriter.WriteLine(templateTypeName);
}
public override void WriteHelperTrailer()
{
InnerWriter.WriteLine("End Function");
}
public override void WriteEndStatement()
{
InnerWriter.WriteLine();
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Razor.Generator
{
public class VBRazorCodeGenerator : RazorCodeGenerator
{
public VBRazorCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host)
: base(className, rootNamespaceName, sourceFileName, host)
{
}
internal override Func<CodeWriter> CodeWriterFactory
{
get { return () => new VBCodeWriter(); }
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.CodeDom;
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor
{
/// <summary>
/// Represents results from code generation (and parsing, since that is a pre-requisite of code generation)
/// </summary>
/// <remarks>
/// Since this inherits from ParserResults, it has all the data from ParserResults, and simply adds code generation data
/// </remarks>
public class GeneratorResults : ParserResults
{
public GeneratorResults(ParserResults parserResults,
CodeCompileUnit generatedCode,
IDictionary<int, GeneratedCodeMapping> designTimeLineMappings)
: this(parserResults.Document, parserResults.ParserErrors, generatedCode, designTimeLineMappings)
{
}
public GeneratorResults(Block document,
IList<RazorError> parserErrors,
CodeCompileUnit generatedCode,
IDictionary<int, GeneratedCodeMapping> designTimeLineMappings)
: this(parserErrors.Count == 0, document, parserErrors, generatedCode, designTimeLineMappings)
{
}
protected GeneratorResults(bool success,
Block document,
IList<RazorError> parserErrors,
CodeCompileUnit generatedCode,
IDictionary<int, GeneratedCodeMapping> designTimeLineMappings)
: base(success, document, parserErrors)
{
GeneratedCode = generatedCode;
DesignTimeLineMappings = designTimeLineMappings;
}
/// <summary>
/// The generated code
/// </summary>
public CodeCompileUnit GeneratedCode { get; private set; }
/// <summary>
/// If design-time mode was used in the Code Generator, this will contain the dictionary
/// of design-time generated code mappings
/// </summary>
public IDictionary<int, GeneratedCodeMapping> DesignTimeLineMappings { get; private set; }
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
//
// To add a suppression to this file, right-click the message in the
// Error List, point to "Suppress Message(s)", and click
// "In Project Suppression File".
// You do not need to add suppressions to this file manually.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "br", Scope = "resource", Target = "System.Web.Razor.Resources.RazorResources.resources", Justification = "Resource is referencing html tag")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Razor.Tokenizer.Symbols", Justification = "These namespaces are design to group classes by function. They will be reviewed to ensure they remain relevant.")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Razor.Tokenizer", Justification = "These namespaces are design to group classes by function. They will be reviewed to ensure they remain relevant.")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Razor.Text", Justification = "These namespaces are design to group classes by function. They will be reviewed to ensure they remain relevant.")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Razor.Parser", Justification = "These namespaces are design to group classes by function. They will be reviewed to ensure they remain relevant.")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Razor.Editor", Justification = "These namespaces are design to group classes by function. They will be reviewed to ensure they remain relevant.")]
[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Web.Razor", Justification = "These namespaces are design to group classes by function. They will be reviewed to ensure they remain relevant.")]
[assembly: SuppressMessage("Microsoft.Web.FxCop", "MW1000:UnusedResourceUsageRule", Justification = "There are numerous unused resources due to VB being disabled. This rule will be re-run after VB is restored")]

View File

@ -0,0 +1,216 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E75D8296-3BA6-4E67-AFEB-90FF77460B15}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.AspNet.Razor</RootNamespace>
<AssemblyName>Microsoft.AspNet.Razor</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Common\CommonResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>CommonResources.resx</DependentUpon>
</Compile>
<Compile Include="Common\HashCodeCombiner.cs" />
<Compile Include="CSharpRazorCodeLanguage.cs" />
<Compile Include="DocumentParseCompleteEventArgs.cs" />
<Compile Include="Editor\AutoCompleteEditHandler.cs" />
<Compile Include="Editor\BackgroundParser.cs" />
<Compile Include="Editor\EditorHints.cs" />
<Compile Include="Editor\EditResult.cs" />
<Compile Include="Editor\ImplicitExpressionEditHandler.cs" />
<Compile Include="Editor\RazorEditorTrace.cs" />
<Compile Include="Editor\SingleLineMarkupEditHandler.cs" />
<Compile Include="Editor\SpanEditHandler.cs" />
<Compile Include="GeneratorResults.cs" />
<Compile Include="Generator\AddImportCodeGenerator.cs" />
<Compile Include="Generator\AttributeBlockCodeGenerator.cs" />
<Compile Include="Generator\BaseCodeWriter.cs" />
<Compile Include="Generator\BlockCodeGenerator.cs" />
<Compile Include="Generator\CodeGenerationCompleteEventArgs.cs" />
<Compile Include="Generator\CodeGeneratorContext.cs" />
<Compile Include="Generator\CodeGeneratorPaddingHelper.cs" />
<Compile Include="Generator\CodeWriter.cs" />
<Compile Include="Generator\CodeWriterExtensions.cs" />
<Compile Include="Generator\CSharpCodeWriter.cs" />
<Compile Include="Generator\CSharpRazorCodeGenerator.cs" />
<Compile Include="Generator\DynamicAttributeBlockCodeGenerator.cs" />
<Compile Include="Generator\ExpressionCodeGenerator.cs" />
<Compile Include="Generator\ExpressionRenderingMode.cs" />
<Compile Include="Generator\GeneratedClassContext.cs" />
<Compile Include="Generator\GeneratedCodeMapping.cs" />
<Compile Include="Generator\HelperCodeGenerator.cs" />
<Compile Include="Generator\HybridCodeGenerator.cs" />
<Compile Include="Generator\IBlockCodeGenerator.cs" />
<Compile Include="Generator\ISpanCodeGenerator.cs" />
<Compile Include="Generator\LiteralAttributeCodeGenerator.cs" />
<Compile Include="Generator\MarkupCodeGenerator.cs" />
<Compile Include="Generator\RazorCodeGenerator.cs" />
<Compile Include="Generator\RazorCommentCodeGenerator.cs" />
<Compile Include="Generator\RazorDirectiveAttributeCodeGenerator.cs" />
<Compile Include="Generator\ResolveUrlCodeGenerator.cs" />
<Compile Include="Generator\SectionCodeGenerator.cs" />
<Compile Include="Generator\SetBaseTypeCodeGenerator.cs" />
<Compile Include="Generator\SetLayoutCodeGenerator.cs" />
<Compile Include="Generator\SetVBOptionCodeGenerator.cs" />
<Compile Include="Generator\SpanCodeGenerator.cs" />
<Compile Include="Generator\StatementCodeGenerator.cs" />
<Compile Include="Generator\TemplateBlockCodeGenerator.cs" />
<Compile Include="Generator\TypeMemberCodeGenerator.cs" />
<Compile Include="Generator\VBCodeWriter.cs" />
<Compile Include="Generator\VBRazorCodeGenerator.cs" />
<Compile Include="GlobalSuppressions.cs" />
<Compile Include="ParserResults.cs" />
<Compile Include="Parser\BalancingModes.cs" />
<Compile Include="Parser\CallbackVisitor.cs" />
<Compile Include="Parser\ConditionalAttributeCollapser.cs" />
<Compile Include="Parser\CSharpCodeParser.cs" />
<Compile Include="Parser\CSharpCodeParser.Directives.cs" />
<Compile Include="Parser\CSharpCodeParser.Statements.cs" />
<Compile Include="Parser\CSharpLanguageCharacteristics.cs" />
<Compile Include="Parser\HtmlLanguageCharacteristics.cs" />
<Compile Include="Parser\HtmlMarkupParser.Block.cs" />
<Compile Include="Parser\HtmlMarkupParser.cs" />
<Compile Include="Parser\HtmlMarkupParser.Document.cs" />
<Compile Include="Parser\HtmlMarkupParser.Section.cs" />
<Compile Include="Parser\ISyntaxTreeRewriter.cs" />
<Compile Include="Parser\LanguageCharacteristics.cs" />
<Compile Include="Parser\MarkupCollapser.cs" />
<Compile Include="Parser\MarkupRewriter.cs" />
<Compile Include="Parser\ParserBase.cs" />
<Compile Include="Parser\ParserContext.cs" />
<Compile Include="Parser\ParserHelpers.cs" />
<Compile Include="Parser\ParserVisitor.cs" />
<Compile Include="Parser\ParserVisitorExtensions.cs" />
<Compile Include="Parser\RazorParser.cs" />
<Compile Include="Parser\SyntaxConstants.cs" />
<Compile Include="Parser\SyntaxTree\AcceptedCharacters.cs" />
<Compile Include="Parser\SyntaxTree\Block.cs" />
<Compile Include="Parser\SyntaxTree\BlockBuilder.cs" />
<Compile Include="Parser\SyntaxTree\BlockType.cs" />
<Compile Include="Parser\SyntaxTree\EquivalenceComparer.cs" />
<Compile Include="Parser\SyntaxTree\RazorError.cs" />
<Compile Include="Parser\SyntaxTree\Span.cs" />
<Compile Include="Parser\SyntaxTree\SpanBuilder.cs" />
<Compile Include="Parser\SyntaxTree\SpanKind.cs" />
<Compile Include="Parser\SyntaxTree\SyntaxTreeNode.cs" />
<Compile Include="Parser\TextReaderExtensions.cs" />
<Compile Include="Parser\TokenizerBackedParser.cs" />
<Compile Include="Parser\TokenizerBackedParser.Helpers.cs" />
<Compile Include="Parser\VBCodeParser.cs" />
<Compile Include="Parser\VBCodeParser.Directives.cs" />
<Compile Include="Parser\VBCodeParser.Statements.cs" />
<Compile Include="Parser\VBLanguageCharacteristics.cs" />
<Compile Include="Parser\WhitespaceRewriter.cs" />
<Compile Include="PartialParseResult.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RazorCodeLanguage.cs" />
<Compile Include="RazorDebugHelpers.cs" />
<Compile Include="RazorDirectiveAttribute.cs" />
<Compile Include="RazorEditorParser.cs" />
<Compile Include="RazorEngineHost.cs" />
<Compile Include="RazorTemplateEngine.cs" />
<Compile Include="Resources\RazorResources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>RazorResources.resx</DependentUpon>
</Compile>
<Compile Include="StateMachine.cs" />
<Compile Include="Text\BufferingTextReader.cs" />
<Compile Include="Text\ITextBuffer.cs" />
<Compile Include="Text\LineTrackingStringBuffer.cs" />
<Compile Include="Text\LocationTagged.cs" />
<Compile Include="Text\LookaheadTextReader.cs" />
<Compile Include="Text\LookaheadToken.cs" />
<Compile Include="Text\SeekableTextReader.cs" />
<Compile Include="Text\SourceLocation.cs" />
<Compile Include="Text\SourceLocationTracker.cs" />
<Compile Include="Text\TextBufferReader.cs" />
<Compile Include="Text\TextChange.cs" />
<Compile Include="Text\TextChangeType.cs" />
<Compile Include="Text\TextDocumentReader.cs" />
<Compile Include="Text\TextExtensions.cs" />
<Compile Include="Tokenizer\CSharpHelpers.cs" />
<Compile Include="Tokenizer\CSharpKeywordDetector.cs" />
<Compile Include="Tokenizer\CSharpTokenizer.cs" />
<Compile Include="Tokenizer\HtmlTokenizer.cs" />
<Compile Include="Tokenizer\ITokenizer.cs" />
<Compile Include="Tokenizer\Symbols\CSharpKeyword.cs" />
<Compile Include="Tokenizer\Symbols\CSharpSymbol.cs" />
<Compile Include="Tokenizer\Symbols\CSharpSymbolType.cs" />
<Compile Include="Tokenizer\Symbols\HtmlSymbol.cs" />
<Compile Include="Tokenizer\Symbols\HtmlSymbolType.cs" />
<Compile Include="Tokenizer\Symbols\ISymbol.cs" />
<Compile Include="Tokenizer\Symbols\KnownSymbolType.cs" />
<Compile Include="Tokenizer\Symbols\SymbolBase.cs" />
<Compile Include="Tokenizer\Symbols\SymbolExtensions.cs" />
<Compile Include="Tokenizer\Symbols\SymbolTypeSuppressions.cs" />
<Compile Include="Tokenizer\Symbols\VBKeyword.cs" />
<Compile Include="Tokenizer\Symbols\VBSymbol.cs" />
<Compile Include="Tokenizer\Symbols\VBSymbolType.cs" />
<Compile Include="Tokenizer\Tokenizer.cs" />
<Compile Include="Tokenizer\TokenizerView.cs" />
<Compile Include="Tokenizer\VBHelpers.cs" />
<Compile Include="Tokenizer\VBKeywordDetector.cs" />
<Compile Include="Tokenizer\VBTokenizer.cs" />
<Compile Include="Tokenizer\XmlHelpers.cs" />
<Compile Include="Utils\CharUtils.cs" />
<Compile Include="Utils\DisposableAction.cs" />
<Compile Include="Utils\EnumeratorExtensions.cs" />
<Compile Include="Utils\EnumUtil.cs" />
<Compile Include="VBRazorCodeLanguage.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Common\CommonResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>CommonResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\RazorResources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>RazorResources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,15 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Razor.Parser
{
[Flags]
public enum BalancingModes
{
None = 0,
BacktrackOnFailure = 1,
NoErrorOnFailure = 2,
AllowCommentsAndTemplates = 4,
AllowEmbeddedTransitions = 8
}
}

View File

@ -0,0 +1,505 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class CSharpCodeParser
{
private void SetupDirectives()
{
MapDirectives(InheritsDirective, SyntaxConstants.CSharp.InheritsKeyword);
MapDirectives(FunctionsDirective, SyntaxConstants.CSharp.FunctionsKeyword);
MapDirectives(SectionDirective, SyntaxConstants.CSharp.SectionKeyword);
MapDirectives(HelperDirective, SyntaxConstants.CSharp.HelperKeyword);
MapDirectives(LayoutDirective, SyntaxConstants.CSharp.LayoutKeyword);
MapDirectives(SessionStateDirective, SyntaxConstants.CSharp.SessionStateKeyword);
}
protected virtual void LayoutDirective()
{
AssertDirective(SyntaxConstants.CSharp.LayoutKeyword);
AcceptAndMoveNext();
Context.CurrentBlock.Type = BlockType.Directive;
// Accept spaces, but not newlines
bool foundSomeWhitespace = At(CSharpSymbolType.WhiteSpace);
AcceptWhile(CSharpSymbolType.WhiteSpace);
Output(SpanKind.MetaCode, foundSomeWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any);
// First non-whitespace character starts the Layout Page, then newline ends it
AcceptUntil(CSharpSymbolType.NewLine);
Span.CodeGenerator = new SetLayoutCodeGenerator(Span.GetContent());
Span.EditHandler.EditorHints = EditorHints.LayoutPage | EditorHints.VirtualPath;
bool foundNewline = Optional(CSharpSymbolType.NewLine);
AddMarkerSymbolIfNecessary();
Output(SpanKind.MetaCode, foundNewline ? AcceptedCharacters.None : AcceptedCharacters.Any);
}
protected virtual void SessionStateDirective()
{
AssertDirective(SyntaxConstants.CSharp.SessionStateKeyword);
AcceptAndMoveNext();
SessionStateDirectiveCore();
}
protected void SessionStateDirectiveCore()
{
SessionStateTypeDirective(RazorResources.ParserEror_SessionDirectiveMissingValue, (key, value) => new RazorDirectiveAttributeCodeGenerator(key, value));
}
protected void SessionStateTypeDirective(string noValueError, Func<string, string, SpanCodeGenerator> createCodeGenerator)
{
// Set the block type
Context.CurrentBlock.Type = BlockType.Directive;
// Accept whitespace
CSharpSymbol remainingWs = AcceptSingleWhiteSpaceCharacter();
if (Span.Symbols.Count > 1)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
Output(SpanKind.MetaCode);
if (remainingWs != null)
{
Accept(remainingWs);
}
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
// Parse a Type Name
if (!ValidSessionStateValue())
{
Context.OnError(CurrentLocation, noValueError);
}
// Pull out the type name
string sessionStateValue = String.Concat(
Span.Symbols
.Cast<CSharpSymbol>()
.Select(sym => sym.Content)).Trim();
// Set up code generation
Span.CodeGenerator = createCodeGenerator(SyntaxConstants.CSharp.SessionStateKeyword, sessionStateValue);
// Output the span and finish the block
CompleteBlock();
Output(SpanKind.Code);
}
protected virtual bool ValidSessionStateValue()
{
return Optional(CSharpSymbolType.Identifier);
}
[SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Coupling will be reviewed at a later date")]
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "C# Keywords are always lower-case")]
protected virtual void HelperDirective()
{
bool nested = Context.IsWithin(BlockType.Helper);
// Set the block and span type
Context.CurrentBlock.Type = BlockType.Helper;
// Verify we're on "helper" and accept
AssertDirective(SyntaxConstants.CSharp.HelperKeyword);
Block block = new Block(CurrentSymbol.Content.ToString().ToLowerInvariant(), CurrentLocation);
AcceptAndMoveNext();
if (nested)
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Helpers_Cannot_Be_Nested);
}
// Accept a single whitespace character if present, if not, we should stop now
if (!At(CSharpSymbolType.WhiteSpace))
{
string error;
if (At(CSharpSymbolType.NewLine))
{
error = RazorResources.ErrorComponent_Newline;
}
else if (EndOfFile)
{
error = RazorResources.ErrorComponent_EndOfFile;
}
else
{
error = String.Format(CultureInfo.CurrentCulture, RazorResources.ErrorComponent_Character, CurrentSymbol.Content);
}
Context.OnError(
CurrentLocation,
RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
error);
PutCurrentBack();
Output(SpanKind.MetaCode);
return;
}
CSharpSymbol remainingWs = AcceptSingleWhiteSpaceCharacter();
// Output metacode and continue
Output(SpanKind.MetaCode);
if (remainingWs != null)
{
Accept(remainingWs);
}
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true)); // Don't accept newlines.
// Expecting an identifier (helper name)
bool errorReported = !Required(CSharpSymbolType.Identifier, errorIfNotFound: true, errorBase: RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start);
if (!errorReported)
{
Assert(CSharpSymbolType.Identifier);
AcceptAndMoveNext();
}
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
// Expecting parameter list start: "("
SourceLocation bracketErrorPos = CurrentLocation;
if (!Optional(CSharpSymbolType.LeftParenthesis))
{
if (!errorReported)
{
errorReported = true;
Context.OnError(
CurrentLocation,
RazorResources.ParseError_MissingCharAfterHelperName,
"(");
}
}
else
{
SourceLocation bracketStart = CurrentLocation;
if (!Balance(BalancingModes.NoErrorOnFailure,
CSharpSymbolType.LeftParenthesis,
CSharpSymbolType.RightParenthesis,
bracketStart))
{
errorReported = true;
Context.OnError(
bracketErrorPos,
RazorResources.ParseError_UnterminatedHelperParameterList);
}
Optional(CSharpSymbolType.RightParenthesis);
}
int bookmark = CurrentLocation.AbsoluteIndex;
IEnumerable<CSharpSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
// Expecting a "{"
SourceLocation errorLocation = CurrentLocation;
bool headerComplete = At(CSharpSymbolType.LeftBrace);
if (headerComplete)
{
Accept(ws);
AcceptAndMoveNext();
}
else
{
Context.Source.Position = bookmark;
NextToken();
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
if (!errorReported)
{
Context.OnError(
errorLocation,
RazorResources.ParseError_MissingCharAfterHelperParameters,
Language.GetSample(CSharpSymbolType.LeftBrace));
}
}
// Grab the signature and build the code generator
AddMarkerSymbolIfNecessary();
LocationTagged<string> signature = Span.GetContent();
HelperCodeGenerator blockGen = new HelperCodeGenerator(signature, headerComplete);
Context.CurrentBlock.CodeGenerator = blockGen;
// The block will generate appropriate code,
Span.CodeGenerator = SpanCodeGenerator.Null;
if (!headerComplete)
{
CompleteBlock();
Output(SpanKind.Code);
return;
}
else
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Output(SpanKind.Code);
}
// We're valid, so parse the nested block
AutoCompleteEditHandler bodyEditHandler = new AutoCompleteEditHandler(Language.TokenizeString);
using (PushSpanConfig(DefaultSpanConfig))
{
using (Context.StartBlock(BlockType.Statement))
{
Span.EditHandler = bodyEditHandler;
CodeBlock(false, block);
CompleteBlock(insertMarkerIfNecessary: true);
Output(SpanKind.Code);
}
}
Initialize(Span);
EnsureCurrent();
Span.CodeGenerator = SpanCodeGenerator.Null; // The block will generate the footer code.
if (!Optional(CSharpSymbolType.RightBrace))
{
// The } is missing, so set the initial signature span to use it as an autocomplete string
bodyEditHandler.AutoCompleteString = "}";
// Need to be able to accept anything to properly handle the autocomplete
bodyEditHandler.AcceptedCharacters = AcceptedCharacters.Any;
}
else
{
blockGen.Footer = Span.GetContent();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
CompleteBlock();
Output(SpanKind.Code);
}
protected virtual void SectionDirective()
{
bool nested = Context.IsWithin(BlockType.Section);
bool errorReported = false;
// Set the block and span type
Context.CurrentBlock.Type = BlockType.Section;
// Verify we're on "section" and accept
AssertDirective(SyntaxConstants.CSharp.SectionKeyword);
AcceptAndMoveNext();
if (nested)
{
Context.OnError(CurrentLocation, String.Format(CultureInfo.CurrentCulture, RazorResources.ParseError_Sections_Cannot_Be_Nested, RazorResources.SectionExample_CS));
errorReported = true;
}
IEnumerable<CSharpSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
// Get the section name
string sectionName = String.Empty;
if (!Required(CSharpSymbolType.Identifier,
errorIfNotFound: true,
errorBase: RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start))
{
if (!errorReported)
{
errorReported = true;
}
PutCurrentBack();
PutBack(ws);
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: false));
}
else
{
Accept(ws);
sectionName = CurrentSymbol.Content;
AcceptAndMoveNext();
}
Context.CurrentBlock.CodeGenerator = new SectionCodeGenerator(sectionName);
SourceLocation errorLocation = CurrentLocation;
ws = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
// Get the starting brace
bool sawStartingBrace = At(CSharpSymbolType.LeftBrace);
if (!sawStartingBrace)
{
if (!errorReported)
{
errorReported = true;
Context.OnError(errorLocation, RazorResources.ParseError_MissingOpenBraceAfterSection);
}
PutCurrentBack();
PutBack(ws);
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: false));
Optional(CSharpSymbolType.NewLine);
Output(SpanKind.MetaCode);
CompleteBlock();
return;
}
else
{
Accept(ws);
}
// Set up edit handler
AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString) { AutoCompleteAtEndOfSpan = true };
Span.EditHandler = editHandler;
Span.Accept(CurrentSymbol);
// Output Metacode then switch to section parser
Output(SpanKind.MetaCode);
SectionBlock("{", "}", caseSensitive: true);
Span.CodeGenerator = SpanCodeGenerator.Null;
// Check for the terminating "}"
if (!Optional(CSharpSymbolType.RightBrace))
{
editHandler.AutoCompleteString = "}";
Context.OnError(CurrentLocation,
RazorResources.ParseError_Expected_X,
Language.GetSample(CSharpSymbolType.RightBrace));
}
else
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
CompleteBlock(insertMarkerIfNecessary: false, captureWhitespaceToEndOfLine: true);
Output(SpanKind.MetaCode);
return;
}
protected virtual void FunctionsDirective()
{
// Set the block type
Context.CurrentBlock.Type = BlockType.Functions;
// Verify we're on "functions" and accept
AssertDirective(SyntaxConstants.CSharp.FunctionsKeyword);
Block block = new Block(CurrentSymbol);
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: false));
if (!At(CSharpSymbolType.LeftBrace))
{
Context.OnError(CurrentLocation,
RazorResources.ParseError_Expected_X,
Language.GetSample(CSharpSymbolType.LeftBrace));
CompleteBlock();
Output(SpanKind.MetaCode);
return;
}
else
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
// Capture start point and continue
SourceLocation blockStart = CurrentLocation;
AcceptAndMoveNext();
// Output what we've seen and continue
Output(SpanKind.MetaCode);
AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
Span.EditHandler = editHandler;
Balance(BalancingModes.NoErrorOnFailure, CSharpSymbolType.LeftBrace, CSharpSymbolType.RightBrace, blockStart);
Span.CodeGenerator = new TypeMemberCodeGenerator();
if (!At(CSharpSymbolType.RightBrace))
{
editHandler.AutoCompleteString = "}";
Context.OnError(block.Start, RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, block.Name, "}", "{");
CompleteBlock();
Output(SpanKind.Code);
}
else
{
Output(SpanKind.Code);
Assert(CSharpSymbolType.RightBrace);
Span.CodeGenerator = SpanCodeGenerator.Null;
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
AcceptAndMoveNext();
CompleteBlock();
Output(SpanKind.MetaCode);
}
}
protected virtual void InheritsDirective()
{
// Verify we're on the right keyword and accept
AssertDirective(SyntaxConstants.CSharp.InheritsKeyword);
AcceptAndMoveNext();
InheritsDirectiveCore();
}
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "directive", Justification = "This only occurs in Release builds, where this method is empty by design")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This only occurs in Release builds, where this method is empty by design")]
[Conditional("DEBUG")]
protected void AssertDirective(string directive)
{
Assert(CSharpSymbolType.Identifier);
Debug.Assert(String.Equals(CurrentSymbol.Content, directive, StringComparison.Ordinal));
}
protected void InheritsDirectiveCore()
{
BaseTypeDirective(RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName, baseType => new SetBaseTypeCodeGenerator(baseType));
}
protected void BaseTypeDirective(string noTypeNameError, Func<string, SpanCodeGenerator> createCodeGenerator)
{
// Set the block type
Context.CurrentBlock.Type = BlockType.Directive;
// Accept whitespace
CSharpSymbol remainingWs = AcceptSingleWhiteSpaceCharacter();
if (Span.Symbols.Count > 1)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
Output(SpanKind.MetaCode);
if (remainingWs != null)
{
Accept(remainingWs);
}
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
if (EndOfFile || At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine))
{
Context.OnError(CurrentLocation, noTypeNameError);
}
// Parse to the end of the line
AcceptUntil(CSharpSymbolType.NewLine);
if (!Context.DesignTimeMode)
{
// We want the newline to be treated as code, but it causes issues at design-time.
Optional(CSharpSymbolType.NewLine);
}
// Pull out the type name
string baseType = Span.GetContent();
// Set up code generation
Span.CodeGenerator = createCodeGenerator(baseType.Trim());
// Output the span and finish the block
CompleteBlock();
Output(SpanKind.Code);
}
}
}

View File

@ -0,0 +1,683 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class CSharpCodeParser
{
private void SetUpKeywords()
{
MapKeywords(ConditionalBlock, CSharpKeyword.For, CSharpKeyword.Foreach, CSharpKeyword.While, CSharpKeyword.Switch, CSharpKeyword.Lock);
MapKeywords(CaseStatement, false, CSharpKeyword.Case, CSharpKeyword.Default);
MapKeywords(IfStatement, CSharpKeyword.If);
MapKeywords(TryStatement, CSharpKeyword.Try);
MapKeywords(UsingKeyword, CSharpKeyword.Using);
MapKeywords(DoStatement, CSharpKeyword.Do);
MapKeywords(ReservedDirective, CSharpKeyword.Namespace, CSharpKeyword.Class);
}
protected virtual void ReservedDirective(bool topLevel)
{
Context.OnError(CurrentLocation, String.Format(CultureInfo.CurrentCulture, RazorResources.ParseError_ReservedWord, CurrentSymbol.Content));
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Context.CurrentBlock.Type = BlockType.Directive;
CompleteBlock();
Output(SpanKind.MetaCode);
}
private void KeywordBlock(bool topLevel)
{
HandleKeyword(topLevel, () =>
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
ImplicitExpression();
});
}
private void CaseStatement(bool topLevel)
{
Assert(CSharpSymbolType.Keyword);
Debug.Assert(CurrentSymbol.Keyword != null &&
(CurrentSymbol.Keyword.Value == CSharpKeyword.Case ||
CurrentSymbol.Keyword.Value == CSharpKeyword.Default));
AcceptUntil(CSharpSymbolType.Colon);
Optional(CSharpSymbolType.Colon);
}
private void DoStatement(bool topLevel)
{
Assert(CSharpKeyword.Do);
UnconditionalBlock();
WhileClause();
if (topLevel)
{
CompleteBlock();
}
}
private void WhileClause()
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
IEnumerable<CSharpSymbol> ws = SkipToNextImportantToken();
if (At(CSharpKeyword.While))
{
Accept(ws);
Assert(CSharpKeyword.While);
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
if (AcceptCondition() && Optional(CSharpSymbolType.Semicolon))
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
}
else
{
PutCurrentBack();
PutBack(ws);
}
}
private void UsingKeyword(bool topLevel)
{
Assert(CSharpKeyword.Using);
Block block = new Block(CurrentSymbol);
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: false, includeComments: true));
if (At(CSharpSymbolType.LeftParenthesis))
{
// using ( ==> Using Statement
UsingStatement(block);
}
else if (At(CSharpSymbolType.Identifier))
{
// using Identifier ==> Using Declaration
if (!topLevel)
{
Context.OnError(block.Start, RazorResources.ParseError_NamespaceImportAndTypeAlias_Cannot_Exist_Within_CodeBlock);
StandardStatement();
}
else
{
UsingDeclaration();
}
}
if (topLevel)
{
CompleteBlock();
}
}
private void UsingDeclaration()
{
// Set block type to directive
Context.CurrentBlock.Type = BlockType.Directive;
// Parse a type name
Assert(CSharpSymbolType.Identifier);
NamespaceOrTypeName();
IEnumerable<CSharpSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
if (At(CSharpSymbolType.Assign))
{
// Alias
Accept(ws);
Assert(CSharpSymbolType.Assign);
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
// One more namespace or type name
NamespaceOrTypeName();
}
else
{
PutCurrentBack();
PutBack(ws);
}
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.AnyExceptNewline;
Span.CodeGenerator = new AddImportCodeGenerator(
Span.GetContent(syms => syms.Skip(1)), // Skip "using"
SyntaxConstants.CSharp.UsingKeywordLength);
// Optional ";"
if (EnsureCurrent())
{
Optional(CSharpSymbolType.Semicolon);
}
}
private bool NamespaceOrTypeName()
{
if (Optional(CSharpSymbolType.Identifier) || Optional(CSharpSymbolType.Keyword))
{
Optional(CSharpSymbolType.QuestionMark); // Nullable
if (Optional(CSharpSymbolType.DoubleColon))
{
if (!Optional(CSharpSymbolType.Identifier))
{
Optional(CSharpSymbolType.Keyword);
}
}
if (At(CSharpSymbolType.LessThan))
{
TypeArgumentList();
}
if (Optional(CSharpSymbolType.Dot))
{
NamespaceOrTypeName();
}
while (At(CSharpSymbolType.LeftBracket))
{
Balance(BalancingModes.None);
Optional(CSharpSymbolType.RightBracket);
}
return true;
}
else
{
return false;
}
}
private void TypeArgumentList()
{
Assert(CSharpSymbolType.LessThan);
Balance(BalancingModes.None);
Optional(CSharpSymbolType.GreaterThan);
}
private void UsingStatement(Block block)
{
Assert(CSharpSymbolType.LeftParenthesis);
// Parse condition
if (AcceptCondition())
{
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
// Parse code block
ExpectCodeBlock(block);
}
}
private void TryStatement(bool topLevel)
{
Assert(CSharpKeyword.Try);
UnconditionalBlock();
AfterTryClause();
if (topLevel)
{
CompleteBlock();
}
}
private void IfStatement(bool topLevel)
{
Assert(CSharpKeyword.If);
ConditionalBlock(topLevel: false);
AfterIfClause();
if (topLevel)
{
CompleteBlock();
}
}
private void AfterTryClause()
{
// Grab whitespace
IEnumerable<CSharpSymbol> ws = SkipToNextImportantToken();
// Check for a catch or finally part
if (At(CSharpKeyword.Catch))
{
Accept(ws);
Assert(CSharpKeyword.Catch);
ConditionalBlock(topLevel: false);
AfterTryClause();
}
else if (At(CSharpKeyword.Finally))
{
Accept(ws);
Assert(CSharpKeyword.Finally);
UnconditionalBlock();
}
else
{
// Return whitespace and end the block
PutCurrentBack();
PutBack(ws);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
}
}
private void AfterIfClause()
{
// Grab whitespace and razor comments
IEnumerable<CSharpSymbol> ws = SkipToNextImportantToken();
// Check for an else part
if (At(CSharpKeyword.Else))
{
Accept(ws);
Assert(CSharpKeyword.Else);
ElseClause();
}
else
{
// No else, return whitespace
PutCurrentBack();
PutBack(ws);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
}
}
private void ElseClause()
{
if (!At(CSharpKeyword.Else))
{
return;
}
Block block = new Block(CurrentSymbol);
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
if (At(CSharpKeyword.If))
{
// ElseIf
block.Name = SyntaxConstants.CSharp.ElseIfKeyword;
ConditionalBlock(block);
AfterIfClause();
}
else if (!EndOfFile)
{
// Else
ExpectCodeBlock(block);
}
}
private void ExpectCodeBlock(Block block)
{
if (!EndOfFile)
{
// Check for "{" to make sure we're at a block
if (!At(CSharpSymbolType.LeftBrace))
{
Context.OnError(CurrentLocation,
RazorResources.ParseError_SingleLine_ControlFlowStatements_Not_Allowed,
Language.GetSample(CSharpSymbolType.LeftBrace),
CurrentSymbol.Content);
}
// Parse the statement and then we're done
Statement(block);
}
}
private void UnconditionalBlock()
{
Assert(CSharpSymbolType.Keyword);
Block block = new Block(CurrentSymbol);
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
ExpectCodeBlock(block);
}
private void ConditionalBlock(bool topLevel)
{
Assert(CSharpSymbolType.Keyword);
Block block = new Block(CurrentSymbol);
ConditionalBlock(block);
if (topLevel)
{
CompleteBlock();
}
}
private void ConditionalBlock(Block block)
{
AcceptAndMoveNext();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
// Parse the condition, if present (if not present, we'll let the C# compiler complain)
if (AcceptCondition())
{
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
ExpectCodeBlock(block);
}
}
private bool AcceptCondition()
{
if (At(CSharpSymbolType.LeftParenthesis))
{
bool complete = Balance(BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
if (!complete)
{
AcceptUntil(CSharpSymbolType.NewLine);
}
else
{
Optional(CSharpSymbolType.RightParenthesis);
}
return complete;
}
return true;
}
private void Statement()
{
Statement(null);
}
private void Statement(Block block)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
// Accept whitespace but always keep the last whitespace node so we can put it back if necessary
CSharpSymbol lastWs = AcceptWhiteSpaceInLines();
Debug.Assert(lastWs == null || (lastWs.Start.AbsoluteIndex + lastWs.Content.Length == CurrentLocation.AbsoluteIndex));
if (EndOfFile)
{
if (lastWs != null)
{
Accept(lastWs);
}
return;
}
CSharpSymbolType type = CurrentSymbol.Type;
SourceLocation loc = CurrentLocation;
bool isSingleLineMarkup = type == CSharpSymbolType.Transition && NextIs(CSharpSymbolType.Colon);
bool isMarkup = isSingleLineMarkup ||
type == CSharpSymbolType.LessThan ||
(type == CSharpSymbolType.Transition && NextIs(CSharpSymbolType.LessThan));
if (Context.DesignTimeMode || !isMarkup)
{
// CODE owns whitespace, MARKUP owns it ONLY in DesignTimeMode.
if (lastWs != null)
{
Accept(lastWs);
}
}
else
{
// MARKUP owns whitespace EXCEPT in DesignTimeMode.
PutCurrentBack();
PutBack(lastWs);
}
if (isMarkup)
{
if (type == CSharpSymbolType.Transition && !isSingleLineMarkup)
{
Context.OnError(loc, RazorResources.ParseError_AtInCode_Must_Be_Followed_By_Colon_Paren_Or_Identifier_Start);
}
// Markup block
Output(SpanKind.Code);
if (Context.DesignTimeMode && CurrentSymbol != null && (CurrentSymbol.Type == CSharpSymbolType.LessThan || CurrentSymbol.Type == CSharpSymbolType.Transition))
{
PutCurrentBack();
}
OtherParserBlock();
}
else
{
// What kind of statement is this?
HandleStatement(block, type);
}
}
private void HandleStatement(Block block, CSharpSymbolType type)
{
switch (type)
{
case CSharpSymbolType.RazorCommentTransition:
Output(SpanKind.Code);
RazorComment();
Statement(block);
break;
case CSharpSymbolType.LeftBrace:
// Verbatim Block
block = block ?? new Block(RazorResources.BlockName_Code, CurrentLocation);
AcceptAndMoveNext();
CodeBlock(block);
break;
case CSharpSymbolType.Keyword:
// Keyword block
HandleKeyword(false, StandardStatement);
break;
case CSharpSymbolType.Transition:
// Embedded Expression block
EmbeddedExpression();
break;
case CSharpSymbolType.RightBrace:
// Possible end of Code Block, just run the continuation
break;
case CSharpSymbolType.Comment:
AcceptAndMoveNext();
break;
default:
// Other statement
StandardStatement();
break;
}
}
private void EmbeddedExpression()
{
// First, verify the type of the block
Assert(CSharpSymbolType.Transition);
CSharpSymbol transition = CurrentSymbol;
NextToken();
if (At(CSharpSymbolType.Transition))
{
// Escaped "@"
Output(SpanKind.Code);
// Output "@" as hidden span
Accept(transition);
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Code);
Assert(CSharpSymbolType.Transition);
AcceptAndMoveNext();
StandardStatement();
}
else
{
// Throw errors as necessary, but continue parsing
if (At(CSharpSymbolType.Keyword))
{
Context.OnError(CurrentLocation,
RazorResources.ParseError_Unexpected_Keyword_After_At,
CSharpLanguageCharacteristics.GetKeyword(CurrentSymbol.Keyword.Value));
}
else if (At(CSharpSymbolType.LeftBrace))
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_Nested_CodeBlock);
}
// @( or @foo - Nested expression, parse a child block
PutCurrentBack();
PutBack(transition);
// Before exiting, add a marker span if necessary
AddMarkerSymbolIfNecessary();
NestedBlock();
}
}
private void StandardStatement()
{
while (!EndOfFile)
{
int bookmark = CurrentLocation.AbsoluteIndex;
IEnumerable<CSharpSymbol> read = ReadWhile(sym => sym.Type != CSharpSymbolType.Semicolon &&
sym.Type != CSharpSymbolType.RazorCommentTransition &&
sym.Type != CSharpSymbolType.Transition &&
sym.Type != CSharpSymbolType.LeftBrace &&
sym.Type != CSharpSymbolType.LeftParenthesis &&
sym.Type != CSharpSymbolType.LeftBracket &&
sym.Type != CSharpSymbolType.RightBrace);
if (At(CSharpSymbolType.LeftBrace) || At(CSharpSymbolType.LeftParenthesis) || At(CSharpSymbolType.LeftBracket))
{
Accept(read);
if (Balance(BalancingModes.AllowCommentsAndTemplates | BalancingModes.BacktrackOnFailure))
{
Optional(CSharpSymbolType.RightBrace);
}
else
{
// Recovery
AcceptUntil(CSharpSymbolType.LessThan, CSharpSymbolType.RightBrace);
return;
}
}
else if (At(CSharpSymbolType.Transition) && (NextIs(CSharpSymbolType.LessThan, CSharpSymbolType.Colon)))
{
Accept(read);
Output(SpanKind.Code);
Template();
}
else if (At(CSharpSymbolType.RazorCommentTransition))
{
Accept(read);
RazorComment();
}
else if (At(CSharpSymbolType.Semicolon))
{
Accept(read);
AcceptAndMoveNext();
return;
}
else if (At(CSharpSymbolType.RightBrace))
{
Accept(read);
return;
}
else
{
Context.Source.Position = bookmark;
NextToken();
AcceptUntil(CSharpSymbolType.LessThan, CSharpSymbolType.RightBrace);
return;
}
}
}
private void CodeBlock(Block block)
{
CodeBlock(true, block);
}
private void CodeBlock(bool acceptTerminatingBrace, Block block)
{
EnsureCurrent();
while (!EndOfFile && !At(CSharpSymbolType.RightBrace))
{
// Parse a statement, then return here
Statement();
EnsureCurrent();
}
if (EndOfFile)
{
Context.OnError(block.Start, RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, block.Name, '}', '{');
}
else if (acceptTerminatingBrace)
{
Assert(CSharpSymbolType.RightBrace);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
AcceptAndMoveNext();
}
}
private void HandleKeyword(bool topLevel, Action fallback)
{
Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Keyword && CurrentSymbol.Keyword != null);
Action<bool> handler;
if (_keywordParsers.TryGetValue(CurrentSymbol.Keyword.Value, out handler))
{
handler(topLevel);
}
else
{
fallback();
}
}
private IEnumerable<CSharpSymbol> SkipToNextImportantToken()
{
while (!EndOfFile)
{
IEnumerable<CSharpSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
if (At(CSharpSymbolType.RazorCommentTransition))
{
Accept(ws);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
RazorComment();
}
else
{
return ws;
}
}
return Enumerable.Empty<CSharpSymbol>();
}
// Common code for Parsers, but FxCop REALLY doesn't like it in the base class.. moving it here for now.
protected override void OutputSpanBeforeRazorComment()
{
AddMarkerSymbolIfNecessary();
Output(SpanKind.Code);
}
protected class Block
{
public Block(string name, SourceLocation start)
{
Name = name;
Start = start;
}
public Block(CSharpSymbol symbol)
: this(GetName(symbol), symbol.Start)
{
}
public string Name { get; set; }
public SourceLocation Start { get; set; }
private static string GetName(CSharpSymbol sym)
{
if (sym.Type == CSharpSymbolType.Keyword)
{
return CSharpLanguageCharacteristics.GetKeyword(sym.Keyword.Value);
}
return sym.Content;
}
}
}
}

View File

@ -0,0 +1,578 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class CSharpCodeParser : TokenizerBackedParser<CSharpTokenizer, CSharpSymbol, CSharpSymbolType>
{
internal static readonly int UsingKeywordLength = 5; // using
internal static ISet<string> DefaultKeywords = new HashSet<string>()
{
"if",
"do",
"try",
"for",
"foreach",
"while",
"switch",
"lock",
"using",
"section",
"inherits",
"helper",
"functions",
"namespace",
"class",
"layout",
"sessionstate"
};
private Dictionary<string, Action> _directiveParsers = new Dictionary<string, Action>();
private Dictionary<CSharpKeyword, Action<bool>> _keywordParsers = new Dictionary<CSharpKeyword, Action<bool>>();
public CSharpCodeParser()
{
Keywords = new HashSet<string>();
SetUpKeywords();
SetupDirectives();
}
protected internal ISet<string> Keywords { get; private set; }
public bool IsNested { get; set; }
protected override ParserBase OtherParser
{
get { return Context.MarkupParser; }
}
protected override LanguageCharacteristics<CSharpTokenizer, CSharpSymbol, CSharpSymbolType> Language
{
get { return CSharpLanguageCharacteristics.Instance; }
}
protected void MapDirectives(Action handler, params string[] directives)
{
foreach (string directive in directives)
{
_directiveParsers.Add(directive, handler);
Keywords.Add(directive);
}
}
protected bool TryGetDirectiveHandler(string directive, out Action handler)
{
return _directiveParsers.TryGetValue(directive, out handler);
}
private void MapKeywords(Action<bool> handler, params CSharpKeyword[] keywords)
{
MapKeywords(handler, topLevel: true, keywords: keywords);
}
private void MapKeywords(Action<bool> handler, bool topLevel, params CSharpKeyword[] keywords)
{
foreach (CSharpKeyword keyword in keywords)
{
_keywordParsers.Add(keyword, handler);
if (topLevel)
{
Keywords.Add(CSharpLanguageCharacteristics.GetKeyword(keyword));
}
}
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This only occurs in Release builds, where this method is empty by design")]
[Conditional("DEBUG")]
internal void Assert(CSharpKeyword expectedKeyword)
{
Debug.Assert(CurrentSymbol.Type == CSharpSymbolType.Keyword && CurrentSymbol.Keyword.HasValue && CurrentSymbol.Keyword.Value == expectedKeyword);
}
protected internal bool At(CSharpKeyword keyword)
{
return At(CSharpSymbolType.Keyword) && CurrentSymbol.Keyword.HasValue && CurrentSymbol.Keyword.Value == keyword;
}
protected internal bool AcceptIf(CSharpKeyword keyword)
{
if (At(keyword))
{
AcceptAndMoveNext();
return true;
}
return false;
}
protected static Func<CSharpSymbol, bool> IsSpacingToken(bool includeNewLines, bool includeComments)
{
return sym => sym.Type == CSharpSymbolType.WhiteSpace ||
(includeNewLines && sym.Type == CSharpSymbolType.NewLine) ||
(includeComments && sym.Type == CSharpSymbolType.Comment);
}
public override void ParseBlock()
{
using (PushSpanConfig(DefaultSpanConfig))
{
if (Context == null)
{
throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
}
// Unless changed, the block is a statement block
using (Context.StartBlock(BlockType.Statement))
{
NextToken();
AcceptWhile(IsSpacingToken(includeNewLines: true, includeComments: true));
CSharpSymbol current = CurrentSymbol;
if (At(CSharpSymbolType.StringLiteral) && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == SyntaxConstants.TransitionCharacter)
{
Tuple<CSharpSymbol, CSharpSymbol> split = Language.SplitSymbol(CurrentSymbol, 1, CSharpSymbolType.Transition);
current = split.Item1;
Context.Source.Position = split.Item2.Start.AbsoluteIndex;
NextToken();
}
else if (At(CSharpSymbolType.Transition))
{
NextToken();
}
// Accept "@" if we see it, but if we don't, that's OK. We assume we were started for a good reason
if (current.Type == CSharpSymbolType.Transition)
{
if (Span.Symbols.Count > 0)
{
Output(SpanKind.Code);
}
AtTransition(current);
}
else
{
// No "@" => Jump straight to AfterTransition
AfterTransition();
}
Output(SpanKind.Code);
}
}
}
private void DefaultSpanConfig(SpanBuilder span)
{
span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
span.CodeGenerator = new StatementCodeGenerator();
}
private void AtTransition(CSharpSymbol current)
{
Debug.Assert(current.Type == CSharpSymbolType.Transition);
Accept(current);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
// Output the "@" span and continue here
Output(SpanKind.Transition);
AfterTransition();
}
private void AfterTransition()
{
using (PushSpanConfig(DefaultSpanConfig))
{
EnsureCurrent();
try
{
// What type of block is this?
if (!EndOfFile)
{
if (CurrentSymbol.Type == CSharpSymbolType.LeftParenthesis)
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
ExplicitExpression();
return;
}
else if (CurrentSymbol.Type == CSharpSymbolType.Identifier)
{
Action handler;
if (TryGetDirectiveHandler(CurrentSymbol.Content, out handler))
{
Span.CodeGenerator = SpanCodeGenerator.Null;
handler();
return;
}
else
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
ImplicitExpression();
return;
}
}
else if (CurrentSymbol.Type == CSharpSymbolType.Keyword)
{
KeywordBlock(topLevel: true);
return;
}
else if (CurrentSymbol.Type == CSharpSymbolType.LeftBrace)
{
VerbatimBlock();
return;
}
}
// Invalid character
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
AddMarkerSymbolIfNecessary();
Span.CodeGenerator = new ExpressionCodeGenerator();
Span.EditHandler = new ImplicitExpressionEditHandler(
Language.TokenizeString,
DefaultKeywords,
acceptTrailingDot: IsNested)
{
AcceptedCharacters = AcceptedCharacters.NonWhiteSpace
};
if (At(CSharpSymbolType.WhiteSpace) || At(CSharpSymbolType.NewLine))
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_CS);
}
else if (EndOfFile)
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock);
}
else
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_CS, CurrentSymbol.Content);
}
}
finally
{
// Always put current character back in the buffer for the next parser.
PutCurrentBack();
}
}
}
private void VerbatimBlock()
{
Assert(CSharpSymbolType.LeftBrace);
Block block = new Block(RazorResources.BlockName_Code, CurrentLocation);
AcceptAndMoveNext();
// Set up the "{" span and output
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.MetaCode);
// Set up auto-complete and parse the code block
AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
Span.EditHandler = editHandler;
CodeBlock(false, block);
Span.CodeGenerator = new StatementCodeGenerator();
AddMarkerSymbolIfNecessary();
if (!At(CSharpSymbolType.RightBrace))
{
editHandler.AutoCompleteString = "}";
}
Output(SpanKind.Code);
if (Optional(CSharpSymbolType.RightBrace))
{
// Set up the "}" span
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
}
if (!At(CSharpSymbolType.WhiteSpace) && !At(CSharpSymbolType.NewLine))
{
PutCurrentBack();
}
CompleteBlock(insertMarkerIfNecessary: false);
Output(SpanKind.MetaCode);
}
private void ImplicitExpression()
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
using (PushSpanConfig(span =>
{
span.EditHandler = new ImplicitExpressionEditHandler(Language.TokenizeString, Keywords, acceptTrailingDot: IsNested);
span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace;
span.CodeGenerator = new ExpressionCodeGenerator();
}))
{
do
{
if (AtIdentifier(allowKeywords: true))
{
AcceptAndMoveNext();
}
}
while (MethodCallOrArrayIndex());
PutCurrentBack();
Output(SpanKind.Code);
}
}
private bool MethodCallOrArrayIndex()
{
if (!EndOfFile)
{
if (CurrentSymbol.Type == CSharpSymbolType.LeftParenthesis || CurrentSymbol.Type == CSharpSymbolType.LeftBracket)
{
// If we end within "(", whitespace is fine
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
CSharpSymbolType right;
bool success;
using (PushSpanConfig((span, prev) =>
{
prev(span);
span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
}))
{
right = Language.FlipBracket(CurrentSymbol.Type);
success = Balance(BalancingModes.BacktrackOnFailure | BalancingModes.AllowCommentsAndTemplates);
}
if (!success)
{
AcceptUntil(CSharpSymbolType.LessThan);
}
if (At(right))
{
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace;
}
return MethodCallOrArrayIndex();
}
if (CurrentSymbol.Type == CSharpSymbolType.Dot)
{
CSharpSymbol dot = CurrentSymbol;
if (NextToken())
{
if (At(CSharpSymbolType.Identifier) || At(CSharpSymbolType.Keyword))
{
// Accept the dot and return to the start
Accept(dot);
return true; // continue
}
else
{
// Put the symbol back
PutCurrentBack();
}
}
if (!IsNested)
{
// Put the "." back
PutBack(dot);
}
else
{
Accept(dot);
}
}
else if (!At(CSharpSymbolType.WhiteSpace) && !At(CSharpSymbolType.NewLine))
{
PutCurrentBack();
}
}
// Implicit Expression is complete
return false;
}
private void CompleteBlock()
{
CompleteBlock(insertMarkerIfNecessary: true);
}
private void CompleteBlock(bool insertMarkerIfNecessary)
{
CompleteBlock(insertMarkerIfNecessary, captureWhitespaceToEndOfLine: insertMarkerIfNecessary);
}
private void CompleteBlock(bool insertMarkerIfNecessary, bool captureWhitespaceToEndOfLine)
{
if (insertMarkerIfNecessary && Context.LastAcceptedCharacters != AcceptedCharacters.Any)
{
AddMarkerSymbolIfNecessary();
}
EnsureCurrent();
// Read whitespace, but not newlines
// If we're not inserting a marker span, we don't need to capture whitespace
if (!Context.WhiteSpaceIsSignificantToAncestorBlock &&
Context.CurrentBlock.Type != BlockType.Expression &&
captureWhitespaceToEndOfLine &&
!Context.DesignTimeMode &&
!IsNested)
{
CaptureWhitespaceAtEndOfCodeOnlyLine();
}
else
{
PutCurrentBack();
}
}
private void CaptureWhitespaceAtEndOfCodeOnlyLine()
{
IEnumerable<CSharpSymbol> ws = ReadWhile(sym => sym.Type == CSharpSymbolType.WhiteSpace);
if (At(CSharpSymbolType.NewLine))
{
Accept(ws);
AcceptAndMoveNext();
PutCurrentBack();
}
else
{
PutCurrentBack();
PutBack(ws);
}
}
private void ConfigureExplicitExpressionSpan(SpanBuilder sb)
{
sb.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
sb.CodeGenerator = new ExpressionCodeGenerator();
}
private void ExplicitExpression()
{
Block block = new Block(RazorResources.BlockName_ExplicitExpression, CurrentLocation);
Assert(CSharpSymbolType.LeftParenthesis);
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.MetaCode);
using (PushSpanConfig(ConfigureExplicitExpressionSpan))
{
bool success = Balance(
BalancingModes.BacktrackOnFailure | BalancingModes.NoErrorOnFailure | BalancingModes.AllowCommentsAndTemplates,
CSharpSymbolType.LeftParenthesis,
CSharpSymbolType.RightParenthesis,
block.Start);
if (!success)
{
AcceptUntil(CSharpSymbolType.LessThan);
Context.OnError(block.Start, RazorResources.ParseError_Expected_EndOfBlock_Before_EOF, block.Name, ")", "(");
}
// If necessary, put an empty-content marker symbol here
if (Span.Symbols.Count == 0)
{
Accept(new CSharpSymbol(CurrentLocation, String.Empty, CSharpSymbolType.Unknown));
}
// Output the content span and then capture the ")"
Output(SpanKind.Code);
}
Optional(CSharpSymbolType.RightParenthesis);
if (!EndOfFile)
{
PutCurrentBack();
}
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
CompleteBlock(insertMarkerIfNecessary: false);
Output(SpanKind.MetaCode);
}
private void Template()
{
if (Context.IsWithin(BlockType.Template))
{
Context.OnError(CurrentLocation, RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested);
}
Output(SpanKind.Code);
using (Context.StartBlock(BlockType.Template))
{
Context.CurrentBlock.CodeGenerator = new TemplateBlockCodeGenerator();
PutCurrentBack();
OtherParserBlock();
}
}
private void OtherParserBlock()
{
ParseWithOtherParser(p => p.ParseBlock());
}
private void SectionBlock(string left, string right, bool caseSensitive)
{
ParseWithOtherParser(p => p.ParseSection(Tuple.Create(left, right), caseSensitive));
}
private void NestedBlock()
{
Output(SpanKind.Code);
bool wasNested = IsNested;
IsNested = true;
using (PushSpanConfig())
{
ParseBlock();
}
Initialize(Span);
IsNested = wasNested;
NextToken();
}
protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
{
// No embedded transitions in C#, so ignore that param
return allowTemplatesAndComments
&& ((Language.IsTransition(CurrentSymbol)
&& NextIs(CSharpSymbolType.LessThan, CSharpSymbolType.Colon))
|| Language.IsCommentStart(CurrentSymbol));
}
protected override void HandleEmbeddedTransition()
{
if (Language.IsTransition(CurrentSymbol))
{
PutCurrentBack();
Template();
}
else if (Language.IsCommentStart(CurrentSymbol))
{
RazorComment();
}
}
private void ParseWithOtherParser(Action<ParserBase> parseAction)
{
using (PushSpanConfig())
{
Context.SwitchActiveParser();
parseAction(Context.MarkupParser);
Context.SwitchActiveParser();
}
Initialize(Span);
NextToken();
}
}
}

View File

@ -0,0 +1,190 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public class CSharpLanguageCharacteristics : LanguageCharacteristics<CSharpTokenizer, CSharpSymbol, CSharpSymbolType>
{
private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics();
private static Dictionary<CSharpSymbolType, string> _symbolSamples = new Dictionary<CSharpSymbolType, string>()
{
{ CSharpSymbolType.Arrow, "->" },
{ CSharpSymbolType.Minus, "-" },
{ CSharpSymbolType.Decrement, "--" },
{ CSharpSymbolType.MinusAssign, "-=" },
{ CSharpSymbolType.NotEqual, "!=" },
{ CSharpSymbolType.Not, "!" },
{ CSharpSymbolType.Modulo, "%" },
{ CSharpSymbolType.ModuloAssign, "%=" },
{ CSharpSymbolType.AndAssign, "&=" },
{ CSharpSymbolType.And, "&" },
{ CSharpSymbolType.DoubleAnd, "&&" },
{ CSharpSymbolType.LeftParenthesis, "(" },
{ CSharpSymbolType.RightParenthesis, ")" },
{ CSharpSymbolType.Star, "*" },
{ CSharpSymbolType.MultiplyAssign, "*=" },
{ CSharpSymbolType.Comma, "," },
{ CSharpSymbolType.Dot, "." },
{ CSharpSymbolType.Slash, "/" },
{ CSharpSymbolType.DivideAssign, "/=" },
{ CSharpSymbolType.DoubleColon, "::" },
{ CSharpSymbolType.Colon, ":" },
{ CSharpSymbolType.Semicolon, ";" },
{ CSharpSymbolType.QuestionMark, "?" },
{ CSharpSymbolType.NullCoalesce, "??" },
{ CSharpSymbolType.RightBracket, "]" },
{ CSharpSymbolType.LeftBracket, "[" },
{ CSharpSymbolType.XorAssign, "^=" },
{ CSharpSymbolType.Xor, "^" },
{ CSharpSymbolType.LeftBrace, "{" },
{ CSharpSymbolType.OrAssign, "|=" },
{ CSharpSymbolType.DoubleOr, "||" },
{ CSharpSymbolType.Or, "|" },
{ CSharpSymbolType.RightBrace, "}" },
{ CSharpSymbolType.Tilde, "~" },
{ CSharpSymbolType.Plus, "+" },
{ CSharpSymbolType.PlusAssign, "+=" },
{ CSharpSymbolType.Increment, "++" },
{ CSharpSymbolType.LessThan, "<" },
{ CSharpSymbolType.LessThanEqual, "<=" },
{ CSharpSymbolType.LeftShift, "<<" },
{ CSharpSymbolType.LeftShiftAssign, "<<=" },
{ CSharpSymbolType.Assign, "=" },
{ CSharpSymbolType.Equals, "==" },
{ CSharpSymbolType.GreaterThan, ">" },
{ CSharpSymbolType.GreaterThanEqual, ">=" },
{ CSharpSymbolType.RightShift, ">>" },
{ CSharpSymbolType.RightShiftAssign, ">>>" },
{ CSharpSymbolType.Hash, "#" },
{ CSharpSymbolType.Transition, "@" },
};
private CSharpLanguageCharacteristics()
{
}
public static CSharpLanguageCharacteristics Instance
{
get { return _instance; }
}
public override CSharpTokenizer CreateTokenizer(ITextDocument source)
{
return new CSharpTokenizer(source);
}
protected override CSharpSymbol CreateSymbol(SourceLocation location, string content, CSharpSymbolType type, IEnumerable<RazorError> errors)
{
return new CSharpSymbol(location, content, type, errors);
}
public override string GetSample(CSharpSymbolType type)
{
return GetSymbolSample(type);
}
public override CSharpSymbol CreateMarkerSymbol(SourceLocation location)
{
return new CSharpSymbol(location, String.Empty, CSharpSymbolType.Unknown);
}
public override CSharpSymbolType GetKnownSymbolType(KnownSymbolType type)
{
switch (type)
{
case KnownSymbolType.Identifier:
return CSharpSymbolType.Identifier;
case KnownSymbolType.Keyword:
return CSharpSymbolType.Keyword;
case KnownSymbolType.NewLine:
return CSharpSymbolType.NewLine;
case KnownSymbolType.WhiteSpace:
return CSharpSymbolType.WhiteSpace;
case KnownSymbolType.Transition:
return CSharpSymbolType.Transition;
case KnownSymbolType.CommentStart:
return CSharpSymbolType.RazorCommentTransition;
case KnownSymbolType.CommentStar:
return CSharpSymbolType.RazorCommentStar;
case KnownSymbolType.CommentBody:
return CSharpSymbolType.RazorComment;
default:
return CSharpSymbolType.Unknown;
}
}
public override CSharpSymbolType FlipBracket(CSharpSymbolType bracket)
{
switch (bracket)
{
case CSharpSymbolType.LeftBrace:
return CSharpSymbolType.RightBrace;
case CSharpSymbolType.LeftBracket:
return CSharpSymbolType.RightBracket;
case CSharpSymbolType.LeftParenthesis:
return CSharpSymbolType.RightParenthesis;
case CSharpSymbolType.LessThan:
return CSharpSymbolType.GreaterThan;
case CSharpSymbolType.RightBrace:
return CSharpSymbolType.LeftBrace;
case CSharpSymbolType.RightBracket:
return CSharpSymbolType.LeftBracket;
case CSharpSymbolType.RightParenthesis:
return CSharpSymbolType.LeftParenthesis;
case CSharpSymbolType.GreaterThan:
return CSharpSymbolType.LessThan;
default:
Debug.Fail("FlipBracket must be called with a bracket character");
return CSharpSymbolType.Unknown;
}
}
[SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "C# Keywords are lower-case")]
public static string GetKeyword(CSharpKeyword keyword)
{
return keyword.ToString().ToLowerInvariant();
}
public static string GetSymbolSample(CSharpSymbolType type)
{
string sample;
if (!_symbolSamples.TryGetValue(type, out sample))
{
switch (type)
{
case CSharpSymbolType.Identifier:
return RazorResources.CSharpSymbol_Identifier;
case CSharpSymbolType.Keyword:
return RazorResources.CSharpSymbol_Keyword;
case CSharpSymbolType.IntegerLiteral:
return RazorResources.CSharpSymbol_IntegerLiteral;
case CSharpSymbolType.NewLine:
return RazorResources.CSharpSymbol_Newline;
case CSharpSymbolType.WhiteSpace:
return RazorResources.CSharpSymbol_Whitespace;
case CSharpSymbolType.Comment:
return RazorResources.CSharpSymbol_Comment;
case CSharpSymbolType.RealLiteral:
return RazorResources.CSharpSymbol_RealLiteral;
case CSharpSymbolType.CharacterLiteral:
return RazorResources.CSharpSymbol_CharacterLiteral;
case CSharpSymbolType.StringLiteral:
return RazorResources.CSharpSymbol_StringLiteral;
default:
return RazorResources.Symbol_Unknown;
}
}
return sample;
}
}
}

View File

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Parser
{
public class CallbackVisitor : ParserVisitor
{
private Action<Span> _spanCallback;
private Action<RazorError> _errorCallback;
private Action<BlockType> _endBlockCallback;
private Action<BlockType> _startBlockCallback;
private Action _completeCallback;
public CallbackVisitor(Action<Span> spanCallback)
: this(spanCallback, _ =>
{
})
{
}
public CallbackVisitor(Action<Span> spanCallback, Action<RazorError> errorCallback)
: this(spanCallback, errorCallback, _ =>
{
}, _ =>
{
})
{
}
public CallbackVisitor(Action<Span> spanCallback, Action<RazorError> errorCallback, Action<BlockType> startBlockCallback, Action<BlockType> endBlockCallback)
: this(spanCallback, errorCallback, startBlockCallback, endBlockCallback, () =>
{
})
{
}
public CallbackVisitor(Action<Span> spanCallback, Action<RazorError> errorCallback, Action<BlockType> startBlockCallback, Action<BlockType> endBlockCallback, Action completeCallback)
{
_spanCallback = spanCallback;
_errorCallback = errorCallback;
_startBlockCallback = startBlockCallback;
_endBlockCallback = endBlockCallback;
_completeCallback = completeCallback;
}
public SynchronizationContext SynchronizationContext { get; set; }
public override void VisitStartBlock(Block block)
{
base.VisitStartBlock(block);
RaiseCallback(SynchronizationContext, block.Type, _startBlockCallback);
}
public override void VisitSpan(Span span)
{
base.VisitSpan(span);
RaiseCallback(SynchronizationContext, span, _spanCallback);
}
public override void VisitEndBlock(Block block)
{
base.VisitEndBlock(block);
RaiseCallback(SynchronizationContext, block.Type, _endBlockCallback);
}
public override void VisitError(RazorError err)
{
base.VisitError(err);
RaiseCallback(SynchronizationContext, err, _errorCallback);
}
public override void OnComplete()
{
base.OnComplete();
RaiseCallback<object>(SynchronizationContext, null, _ => _completeCallback());
}
private static void RaiseCallback<T>(SynchronizationContext syncContext, T param, Action<T> callback)
{
if (callback != null)
{
if (syncContext != null)
{
syncContext.Post(state => callback((T)state), param);
}
else
{
callback(param);
}
}
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
namespace Microsoft.AspNet.Razor.Parser
{
internal class ConditionalAttributeCollapser : MarkupRewriter
{
public ConditionalAttributeCollapser(Action<SpanBuilder, SourceLocation, string> markupSpanFactory) : base(markupSpanFactory)
{
}
protected override bool CanRewrite(Block block)
{
AttributeBlockCodeGenerator gen = block.CodeGenerator as AttributeBlockCodeGenerator;
return gen != null && block.Children.Any() && block.Children.All(IsLiteralAttributeValue);
}
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
{
// Collect the content of this node
string content = String.Concat(block.Children.Cast<Span>().Select(s => s.Content));
// Create a new span containing this content
SpanBuilder span = new SpanBuilder();
span.EditHandler = new SpanEditHandler(HtmlTokenizer.Tokenize);
FillSpan(span, block.Children.Cast<Span>().First().Start, content);
return span.Build();
}
private bool IsLiteralAttributeValue(SyntaxTreeNode node)
{
if (node.IsBlock)
{
return false;
}
Span span = node as Span;
Debug.Assert(span != null);
LiteralAttributeCodeGenerator litGen = span.CodeGenerator as LiteralAttributeCodeGenerator;
return span != null &&
((litGen != null && litGen.ValueGenerator == null) ||
span.CodeGenerator == SpanCodeGenerator.Null ||
span.CodeGenerator is MarkupCodeGenerator);
}
}
}

View File

@ -0,0 +1,132 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public class HtmlLanguageCharacteristics : LanguageCharacteristics<HtmlTokenizer, HtmlSymbol, HtmlSymbolType>
{
private static readonly HtmlLanguageCharacteristics _instance = new HtmlLanguageCharacteristics();
private HtmlLanguageCharacteristics()
{
}
public static HtmlLanguageCharacteristics Instance
{
get { return _instance; }
}
public override string GetSample(HtmlSymbolType type)
{
switch (type)
{
case HtmlSymbolType.Text:
return RazorResources.HtmlSymbol_Text;
case HtmlSymbolType.WhiteSpace:
return RazorResources.HtmlSymbol_WhiteSpace;
case HtmlSymbolType.NewLine:
return RazorResources.HtmlSymbol_NewLine;
case HtmlSymbolType.OpenAngle:
return "<";
case HtmlSymbolType.Bang:
return "!";
case HtmlSymbolType.Solidus:
return "/";
case HtmlSymbolType.QuestionMark:
return "?";
case HtmlSymbolType.DoubleHyphen:
return "--";
case HtmlSymbolType.LeftBracket:
return "[";
case HtmlSymbolType.CloseAngle:
return ">";
case HtmlSymbolType.RightBracket:
return "]";
case HtmlSymbolType.Equals:
return "=";
case HtmlSymbolType.DoubleQuote:
return "\"";
case HtmlSymbolType.SingleQuote:
return "'";
case HtmlSymbolType.Transition:
return "@";
case HtmlSymbolType.Colon:
return ":";
case HtmlSymbolType.RazorComment:
return RazorResources.HtmlSymbol_RazorComment;
case HtmlSymbolType.RazorCommentStar:
return "*";
case HtmlSymbolType.RazorCommentTransition:
return "@";
default:
return RazorResources.Symbol_Unknown;
}
}
public override HtmlTokenizer CreateTokenizer(ITextDocument source)
{
return new HtmlTokenizer(source);
}
public override HtmlSymbolType FlipBracket(HtmlSymbolType bracket)
{
switch (bracket)
{
case HtmlSymbolType.LeftBracket:
return HtmlSymbolType.RightBracket;
case HtmlSymbolType.OpenAngle:
return HtmlSymbolType.CloseAngle;
case HtmlSymbolType.RightBracket:
return HtmlSymbolType.LeftBracket;
case HtmlSymbolType.CloseAngle:
return HtmlSymbolType.OpenAngle;
default:
Debug.Fail("FlipBracket must be called with a bracket character");
return HtmlSymbolType.Unknown;
}
}
public override HtmlSymbol CreateMarkerSymbol(SourceLocation location)
{
return new HtmlSymbol(location, String.Empty, HtmlSymbolType.Unknown);
}
public override HtmlSymbolType GetKnownSymbolType(KnownSymbolType type)
{
switch (type)
{
case KnownSymbolType.CommentStart:
return HtmlSymbolType.RazorCommentTransition;
case KnownSymbolType.CommentStar:
return HtmlSymbolType.RazorCommentStar;
case KnownSymbolType.CommentBody:
return HtmlSymbolType.RazorComment;
case KnownSymbolType.Identifier:
return HtmlSymbolType.Text;
case KnownSymbolType.Keyword:
return HtmlSymbolType.Text;
case KnownSymbolType.NewLine:
return HtmlSymbolType.NewLine;
case KnownSymbolType.Transition:
return HtmlSymbolType.Transition;
case KnownSymbolType.WhiteSpace:
return HtmlSymbolType.WhiteSpace;
default:
return HtmlSymbolType.Unknown;
}
}
protected override HtmlSymbol CreateSymbol(SourceLocation location, string content, HtmlSymbolType type, IEnumerable<RazorError> errors)
{
return new HtmlSymbol(location, content, type, errors);
}
}
}

View File

@ -0,0 +1,832 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class HtmlMarkupParser
{
private SourceLocation _lastTagStart = SourceLocation.Zero;
private HtmlSymbol _bufferedOpenAngle;
public override void ParseBlock()
{
if (Context == null)
{
throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
}
using (PushSpanConfig(DefaultMarkupSpan))
{
using (Context.StartBlock(BlockType.Markup))
{
if (!NextToken())
{
return;
}
AcceptWhile(IsSpacingToken(includeNewLines: true));
if (CurrentSymbol.Type == HtmlSymbolType.OpenAngle)
{
// "<" => Implicit Tag Block
TagBlock(new Stack<Tuple<HtmlSymbol, SourceLocation>>());
}
else if (CurrentSymbol.Type == HtmlSymbolType.Transition)
{
// "@" => Explicit Tag/Single Line Block OR Template
Output(SpanKind.Markup);
// Definitely have a transition span
Assert(HtmlSymbolType.Transition);
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Transition);
if (At(HtmlSymbolType.Transition))
{
Span.CodeGenerator = SpanCodeGenerator.Null;
AcceptAndMoveNext();
Output(SpanKind.MetaCode);
}
AfterTransition();
}
else
{
Context.OnError(CurrentSymbol.Start, RazorResources.ParseError_MarkupBlock_Must_Start_With_Tag);
}
Output(SpanKind.Markup);
}
}
}
private void DefaultMarkupSpan(SpanBuilder span)
{
span.CodeGenerator = new MarkupCodeGenerator();
span.EditHandler = new SpanEditHandler(Language.TokenizeString, AcceptedCharacters.Any);
}
private void AfterTransition()
{
// "@:" => Explicit Single Line Block
if (CurrentSymbol.Type == HtmlSymbolType.Text && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == ':')
{
// Split the token
Tuple<HtmlSymbol, HtmlSymbol> split = Language.SplitSymbol(CurrentSymbol, 1, HtmlSymbolType.Colon);
// The first part (left) is added to this span and we return a MetaCode span
Accept(split.Item1);
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.MetaCode);
if (split.Item2 != null)
{
Accept(split.Item2);
}
NextToken();
SingleLineMarkup();
}
else if (CurrentSymbol.Type == HtmlSymbolType.OpenAngle)
{
TagBlock(new Stack<Tuple<HtmlSymbol, SourceLocation>>());
}
}
private void SingleLineMarkup()
{
// Parse until a newline, it's that simple!
// First, signal to code parser that whitespace is significant to us.
bool old = Context.WhiteSpaceIsSignificantToAncestorBlock;
Context.WhiteSpaceIsSignificantToAncestorBlock = true;
Span.EditHandler = new SingleLineMarkupEditHandler(Language.TokenizeString);
SkipToAndParseCode(HtmlSymbolType.NewLine);
if (!EndOfFile && CurrentSymbol.Type == HtmlSymbolType.NewLine)
{
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
PutCurrentBack();
Context.WhiteSpaceIsSignificantToAncestorBlock = old;
Output(SpanKind.Markup);
}
private void TagBlock(Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
{
// Skip Whitespace and Text
bool complete = false;
do
{
SkipToAndParseCode(HtmlSymbolType.OpenAngle);
if (EndOfFile)
{
EndTagBlock(tags, complete: true);
}
else
{
_bufferedOpenAngle = null;
_lastTagStart = CurrentLocation;
Assert(HtmlSymbolType.OpenAngle);
_bufferedOpenAngle = CurrentSymbol;
SourceLocation tagStart = CurrentLocation;
if (!NextToken())
{
Accept(_bufferedOpenAngle);
EndTagBlock(tags, complete: false);
}
else
{
complete = AfterTagStart(tagStart, tags);
}
}
}
while (tags.Count > 0);
EndTagBlock(tags, complete);
}
private bool AfterTagStart(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
{
if (!EndOfFile)
{
switch (CurrentSymbol.Type)
{
case HtmlSymbolType.Solidus:
// End Tag
return EndTag(tagStart, tags);
case HtmlSymbolType.Bang:
// Comment
Accept(_bufferedOpenAngle);
return BangTag();
case HtmlSymbolType.QuestionMark:
// XML PI
Accept(_bufferedOpenAngle);
return XmlPI();
default:
// Start Tag
return StartTag(tags);
}
}
if (tags.Count == 0)
{
Context.OnError(CurrentLocation, RazorResources.ParseError_OuterTagMissingName);
}
return false;
}
private bool XmlPI()
{
// Accept "?"
Assert(HtmlSymbolType.QuestionMark);
AcceptAndMoveNext();
return AcceptUntilAll(HtmlSymbolType.QuestionMark, HtmlSymbolType.CloseAngle);
}
private bool BangTag()
{
// Accept "!"
Assert(HtmlSymbolType.Bang);
if (AcceptAndMoveNext())
{
if (CurrentSymbol.Type == HtmlSymbolType.DoubleHyphen)
{
AcceptAndMoveNext();
return AcceptUntilAll(HtmlSymbolType.DoubleHyphen, HtmlSymbolType.CloseAngle);
}
else if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket)
{
if (AcceptAndMoveNext())
{
return CData();
}
}
else
{
AcceptAndMoveNext();
return AcceptUntilAll(HtmlSymbolType.CloseAngle);
}
}
return false;
}
private bool CData()
{
if (CurrentSymbol.Type == HtmlSymbolType.Text && String.Equals(CurrentSymbol.Content, "cdata", StringComparison.OrdinalIgnoreCase))
{
if (AcceptAndMoveNext())
{
if (CurrentSymbol.Type == HtmlSymbolType.LeftBracket)
{
return AcceptUntilAll(HtmlSymbolType.RightBracket, HtmlSymbolType.RightBracket, HtmlSymbolType.CloseAngle);
}
}
}
return false;
}
private bool EndTag(SourceLocation tagStart, Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
{
// Accept "/" and move next
Assert(HtmlSymbolType.Solidus);
HtmlSymbol solidus = CurrentSymbol;
if (!NextToken())
{
Accept(_bufferedOpenAngle);
Accept(solidus);
return false;
}
else
{
string tagName = String.Empty;
if (At(HtmlSymbolType.Text))
{
tagName = CurrentSymbol.Content;
}
bool matched = RemoveTag(tags, tagName, tagStart);
if (tags.Count == 0 &&
String.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) &&
matched)
{
Output(SpanKind.Markup);
return EndTextTag(solidus);
}
Accept(_bufferedOpenAngle);
Accept(solidus);
AcceptUntil(HtmlSymbolType.CloseAngle);
// Accept the ">"
return Optional(HtmlSymbolType.CloseAngle);
}
}
private bool EndTextTag(HtmlSymbol solidus)
{
SourceLocation start = _bufferedOpenAngle.Start;
Accept(_bufferedOpenAngle);
Accept(solidus);
Assert(HtmlSymbolType.Text);
AcceptAndMoveNext();
bool seenCloseAngle = Optional(HtmlSymbolType.CloseAngle);
if (!seenCloseAngle)
{
Context.OnError(start, RazorResources.ParseError_TextTagCannotContainAttributes);
}
else
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Transition);
return seenCloseAngle;
}
private bool IsTagRecoveryStopPoint(HtmlSymbol sym)
{
return sym.Type == HtmlSymbolType.CloseAngle ||
sym.Type == HtmlSymbolType.Solidus ||
sym.Type == HtmlSymbolType.OpenAngle ||
sym.Type == HtmlSymbolType.SingleQuote ||
sym.Type == HtmlSymbolType.DoubleQuote;
}
private void TagContent()
{
if (!At(HtmlSymbolType.WhiteSpace))
{
// We should be right after the tag name, so if there's no whitespace, something is wrong
RecoverToEndOfTag();
}
else
{
// We are here ($): <tag$ foo="bar" biz="~/Baz" />
while (!EndOfFile && !IsEndOfTag())
{
BeforeAttribute();
}
}
}
private bool IsEndOfTag()
{
if (At(HtmlSymbolType.Solidus))
{
if (NextIs(HtmlSymbolType.CloseAngle))
{
return true;
}
else
{
AcceptAndMoveNext();
}
}
return At(HtmlSymbolType.CloseAngle) || At(HtmlSymbolType.OpenAngle);
}
private void BeforeAttribute()
{
// http://dev.w3.org/html5/spec/tokenization.html#before-attribute-name-state
// Capture whitespace
var whitespace = ReadWhile(sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine);
if (At(HtmlSymbolType.Transition))
{
// Transition outside of attribute value => Switch to recovery mode
Accept(whitespace);
RecoverToEndOfTag();
return;
}
// http://dev.w3.org/html5/spec/tokenization.html#attribute-name-state
// Read the 'name' (i.e. read until the '=' or whitespace/newline)
var name = Enumerable.Empty<HtmlSymbol>();
if (At(HtmlSymbolType.Text))
{
name = ReadWhile(sym =>
sym.Type != HtmlSymbolType.WhiteSpace &&
sym.Type != HtmlSymbolType.NewLine &&
sym.Type != HtmlSymbolType.Equals &&
sym.Type != HtmlSymbolType.CloseAngle &&
sym.Type != HtmlSymbolType.OpenAngle &&
(sym.Type != HtmlSymbolType.Solidus || !NextIs(HtmlSymbolType.CloseAngle)));
}
else
{
// Unexpected character in tag, enter recovery
Accept(whitespace);
RecoverToEndOfTag();
return;
}
if (!At(HtmlSymbolType.Equals))
{
// Saw a space or newline after the name, so just skip this attribute and continue around the loop
Accept(whitespace);
Accept(name);
return;
}
Output(SpanKind.Markup);
// Start a new markup block for the attribute
using (Context.StartBlock(BlockType.Markup))
{
AttributePrefix(whitespace, name);
}
}
private void AttributePrefix(IEnumerable<HtmlSymbol> whitespace, IEnumerable<HtmlSymbol> nameSymbols)
{
// First, determine if this is a 'data-' attribute (since those can't use conditional attributes)
LocationTagged<string> name = nameSymbols.GetContent(Span.Start);
bool attributeCanBeConditional = !name.Value.StartsWith("data-", StringComparison.OrdinalIgnoreCase);
// Accept the whitespace and name
Accept(whitespace);
Accept(nameSymbols);
Assert(HtmlSymbolType.Equals); // We should be at "="
AcceptAndMoveNext();
HtmlSymbolType quote = HtmlSymbolType.Unknown;
if (At(HtmlSymbolType.SingleQuote) || At(HtmlSymbolType.DoubleQuote))
{
quote = CurrentSymbol.Type;
AcceptAndMoveNext();
}
// We now have the prefix: (i.e. ' foo="')
LocationTagged<string> prefix = Span.GetContent();
if (attributeCanBeConditional)
{
Span.CodeGenerator = SpanCodeGenerator.Null; // The block code generator will render the prefix
Output(SpanKind.Markup);
// Read the values
while (!EndOfFile && !IsEndOfAttributeValue(quote, CurrentSymbol))
{
AttributeValue(quote);
}
// Capture the suffix
LocationTagged<string> suffix = new LocationTagged<string>(String.Empty, CurrentLocation);
if (quote != HtmlSymbolType.Unknown && At(quote))
{
suffix = CurrentSymbol.GetContent();
AcceptAndMoveNext();
}
if (Span.Symbols.Count > 0)
{
Span.CodeGenerator = SpanCodeGenerator.Null; // Again, block code generator will render the suffix
Output(SpanKind.Markup);
}
// Create the block code generator
Context.CurrentBlock.CodeGenerator = new AttributeBlockCodeGenerator(
name, prefix, suffix);
}
else
{
// Not a "conditional" attribute, so just read the value
SkipToAndParseCode(sym => IsEndOfAttributeValue(quote, sym));
if (quote != HtmlSymbolType.Unknown)
{
Optional(quote);
}
Output(SpanKind.Markup);
}
}
private void AttributeValue(HtmlSymbolType quote)
{
SourceLocation prefixStart = CurrentLocation;
var prefix = ReadWhile(sym => sym.Type == HtmlSymbolType.WhiteSpace || sym.Type == HtmlSymbolType.NewLine);
Accept(prefix);
if (At(HtmlSymbolType.Transition))
{
SourceLocation valueStart = CurrentLocation;
PutCurrentBack();
// Output the prefix but as a null-span. DynamicAttributeBlockCodeGenerator will render it
Span.CodeGenerator = SpanCodeGenerator.Null;
// Dynamic value, start a new block and set the code generator
using (Context.StartBlock(BlockType.Markup))
{
Context.CurrentBlock.CodeGenerator = new DynamicAttributeBlockCodeGenerator(prefix.GetContent(prefixStart), valueStart);
OtherParserBlock();
}
}
else if (At(HtmlSymbolType.Text) && CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == '~' && NextIs(HtmlSymbolType.Solidus))
{
// Virtual Path value
SourceLocation valueStart = CurrentLocation;
VirtualPath();
Span.CodeGenerator = new LiteralAttributeCodeGenerator(
prefix.GetContent(prefixStart),
new LocationTagged<SpanCodeGenerator>(new ResolveUrlCodeGenerator(), valueStart));
}
else
{
// Literal value
// 'quote' should be "Unknown" if not quoted and symbols coming from the tokenizer should never have "Unknown" type.
var value = ReadWhile(sym =>
// These three conditions find separators which break the attribute value into portions
sym.Type != HtmlSymbolType.WhiteSpace &&
sym.Type != HtmlSymbolType.NewLine &&
sym.Type != HtmlSymbolType.Transition &&
// This condition checks for the end of the attribute value (it repeats some of the checks above but for now that's ok)
!IsEndOfAttributeValue(quote, sym));
Accept(value);
Span.CodeGenerator = new LiteralAttributeCodeGenerator(prefix.GetContent(prefixStart), value.GetContent(prefixStart));
}
Output(SpanKind.Markup);
}
private bool IsEndOfAttributeValue(HtmlSymbolType quote, HtmlSymbol sym)
{
return EndOfFile || sym == null ||
(quote != HtmlSymbolType.Unknown
? sym.Type == quote // If quoted, just wait for the quote
: IsUnquotedEndOfAttributeValue(sym));
}
private bool IsUnquotedEndOfAttributeValue(HtmlSymbol sym)
{
// If unquoted, we have a larger set of terminating characters:
// http://dev.w3.org/html5/spec/tokenization.html#attribute-value-unquoted-state
// Also we need to detect "/" and ">"
return sym.Type == HtmlSymbolType.DoubleQuote ||
sym.Type == HtmlSymbolType.SingleQuote ||
sym.Type == HtmlSymbolType.OpenAngle ||
sym.Type == HtmlSymbolType.Equals ||
(sym.Type == HtmlSymbolType.Solidus && NextIs(HtmlSymbolType.CloseAngle)) ||
sym.Type == HtmlSymbolType.CloseAngle ||
sym.Type == HtmlSymbolType.WhiteSpace ||
sym.Type == HtmlSymbolType.NewLine;
}
private void VirtualPath()
{
Assert(HtmlSymbolType.Text);
Debug.Assert(CurrentSymbol.Content.Length > 0 && CurrentSymbol.Content[0] == '~');
// Parse until a transition symbol, whitespace, newline or quote. We support only a fairly minimal subset of Virtual Paths
AcceptUntil(HtmlSymbolType.Transition, HtmlSymbolType.WhiteSpace, HtmlSymbolType.NewLine, HtmlSymbolType.SingleQuote, HtmlSymbolType.DoubleQuote);
// Output a Virtual Path span
Span.EditHandler.EditorHints = EditorHints.VirtualPath;
}
private void RecoverToEndOfTag()
{
// Accept until ">", "/" or "<", but parse code
while (!EndOfFile)
{
SkipToAndParseCode(IsTagRecoveryStopPoint);
if (!EndOfFile)
{
EnsureCurrent();
switch (CurrentSymbol.Type)
{
case HtmlSymbolType.SingleQuote:
case HtmlSymbolType.DoubleQuote:
ParseQuoted();
break;
case HtmlSymbolType.OpenAngle:
// Another "<" means this tag is invalid.
case HtmlSymbolType.Solidus:
// Empty tag
case HtmlSymbolType.CloseAngle:
// End of tag
return;
default:
AcceptAndMoveNext();
break;
}
}
}
}
private void ParseQuoted()
{
HtmlSymbolType type = CurrentSymbol.Type;
AcceptAndMoveNext();
ParseQuoted(type);
}
private void ParseQuoted(HtmlSymbolType type)
{
SkipToAndParseCode(type);
if (!EndOfFile)
{
Assert(type);
AcceptAndMoveNext();
}
}
private bool StartTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
{
// If we're at text, it's the name, otherwise the name is ""
HtmlSymbol tagName;
if (At(HtmlSymbolType.Text))
{
tagName = CurrentSymbol;
}
else
{
tagName = new HtmlSymbol(CurrentLocation, String.Empty, HtmlSymbolType.Unknown);
}
Tuple<HtmlSymbol, SourceLocation> tag = Tuple.Create(tagName, _lastTagStart);
if (tags.Count == 0 && String.Equals(tag.Item1.Content, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase))
{
Output(SpanKind.Markup);
Span.CodeGenerator = SpanCodeGenerator.Null;
Accept(_bufferedOpenAngle);
Assert(HtmlSymbolType.Text);
AcceptAndMoveNext();
int bookmark = CurrentLocation.AbsoluteIndex;
IEnumerable<HtmlSymbol> tokens = ReadWhile(IsSpacingToken(includeNewLines: true));
bool empty = At(HtmlSymbolType.Solidus);
if (empty)
{
Accept(tokens);
Assert(HtmlSymbolType.Solidus);
AcceptAndMoveNext();
bookmark = CurrentLocation.AbsoluteIndex;
tokens = ReadWhile(IsSpacingToken(includeNewLines: true));
}
if (!Optional(HtmlSymbolType.CloseAngle))
{
Context.Source.Position = bookmark;
NextToken();
Context.OnError(tag.Item2, RazorResources.ParseError_TextTagCannotContainAttributes);
}
else
{
Accept(tokens);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
if (!empty)
{
tags.Push(tag);
}
Output(SpanKind.Transition);
return true;
}
Accept(_bufferedOpenAngle);
Optional(HtmlSymbolType.Text);
return RestOfTag(tag, tags);
}
private bool RestOfTag(Tuple<HtmlSymbol, SourceLocation> tag, Stack<Tuple<HtmlSymbol, SourceLocation>> tags)
{
TagContent();
// We are now at a possible end of the tag
// Found '<', so we just abort this tag.
if (At(HtmlSymbolType.OpenAngle))
{
return false;
}
bool isEmpty = At(HtmlSymbolType.Solidus);
// Found a solidus, so don't accept it but DON'T push the tag to the stack
if (isEmpty)
{
AcceptAndMoveNext();
}
// Check for the '>' to determine if the tag is finished
bool seenClose = Optional(HtmlSymbolType.CloseAngle);
if (!seenClose)
{
Context.OnError(tag.Item2, RazorResources.ParseError_UnfinishedTag, tag.Item1.Content);
}
else
{
if (!isEmpty)
{
// Is this a void element?
string tagName = tag.Item1.Content.Trim();
if (VoidElements.Contains(tagName))
{
// Technically, void elements like "meta" are not allowed to have end tags. Just in case they do,
// we need to look ahead at the next set of tokens. If we see "<", "/", tag name, accept it and the ">" following it
// Place a bookmark
int bookmark = CurrentLocation.AbsoluteIndex;
// Skip whitespace
IEnumerable<HtmlSymbol> ws = ReadWhile(IsSpacingToken(includeNewLines: true));
// Open Angle
if (At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus))
{
HtmlSymbol openAngle = CurrentSymbol;
NextToken();
Assert(HtmlSymbolType.Solidus);
HtmlSymbol solidus = CurrentSymbol;
NextToken();
if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase))
{
// Accept up to here
Accept(ws);
Accept(openAngle);
Accept(solidus);
AcceptAndMoveNext();
// Accept to '>', '<' or EOF
AcceptUntil(HtmlSymbolType.CloseAngle, HtmlSymbolType.OpenAngle);
// Accept the '>' if we saw it. And if we do see it, we're complete
return Optional(HtmlSymbolType.CloseAngle);
} // At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, tagName, StringComparison.OrdinalIgnoreCase)
} // At(HtmlSymbolType.OpenAngle) && NextIs(HtmlSymbolType.Solidus)
// Go back to the bookmark and just finish this tag at the close angle
Context.Source.Position = bookmark;
NextToken();
}
else if (String.Equals(tagName, "script", StringComparison.OrdinalIgnoreCase))
{
SkipToEndScriptAndParseCode();
}
else
{
// Push the tag on to the stack
tags.Push(tag);
}
}
}
return seenClose;
}
private void SkipToEndScriptAndParseCode()
{
// Special case for <script>: Skip to end of script tag and parse code
bool seenEndScript = false;
while (!seenEndScript && !EndOfFile)
{
SkipToAndParseCode(HtmlSymbolType.OpenAngle);
SourceLocation tagStart = CurrentLocation;
AcceptAndMoveNext();
AcceptWhile(HtmlSymbolType.WhiteSpace);
if (Optional(HtmlSymbolType.Solidus))
{
AcceptWhile(HtmlSymbolType.WhiteSpace);
if (At(HtmlSymbolType.Text) && String.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase))
{
// </script!
SkipToAndParseCode(HtmlSymbolType.CloseAngle);
if (!Optional(HtmlSymbolType.CloseAngle))
{
Context.OnError(tagStart, RazorResources.ParseError_UnfinishedTag, "script");
}
seenEndScript = true;
}
}
}
}
private bool AcceptUntilAll(params HtmlSymbolType[] endSequence)
{
while (!EndOfFile)
{
SkipToAndParseCode(endSequence[0]);
if (AcceptAll(endSequence))
{
return true;
}
}
Debug.Assert(EndOfFile);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
return false;
}
private bool RemoveTag(Stack<Tuple<HtmlSymbol, SourceLocation>> tags, string tagName, SourceLocation tagStart)
{
Tuple<HtmlSymbol, SourceLocation> currentTag = null;
while (tags.Count > 0)
{
currentTag = tags.Pop();
if (String.Equals(tagName, currentTag.Item1.Content, StringComparison.OrdinalIgnoreCase))
{
// Matched the tag
return true;
}
}
if (currentTag != null)
{
Context.OnError(currentTag.Item2, RazorResources.ParseError_MissingEndTag, currentTag.Item1.Content);
}
else
{
Context.OnError(tagStart, RazorResources.ParseError_UnexpectedEndTag, tagName);
}
return false;
}
private void EndTagBlock(Stack<Tuple<HtmlSymbol, SourceLocation>> tags, bool complete)
{
if (tags.Count > 0)
{
// Ended because of EOF, not matching close tag. Throw error for last tag
while (tags.Count > 1)
{
tags.Pop();
}
Tuple<HtmlSymbol, SourceLocation> tag = tags.Pop();
Context.OnError(tag.Item2, RazorResources.ParseError_MissingEndTag, tag.Item1.Content);
}
else if (complete)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
tags.Clear();
if (!Context.DesignTimeMode)
{
AcceptWhile(HtmlSymbolType.WhiteSpace);
if (!EndOfFile && CurrentSymbol.Type == HtmlSymbolType.NewLine)
{
AcceptAndMoveNext();
}
}
else if (Span.EditHandler.AcceptedCharacters == AcceptedCharacters.Any)
{
AcceptWhile(HtmlSymbolType.WhiteSpace);
Optional(HtmlSymbolType.NewLine);
}
PutCurrentBack();
if (!complete)
{
AddMarkerSymbolIfNecessary();
}
Output(SpanKind.Markup);
}
}
}

View File

@ -0,0 +1,72 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class HtmlMarkupParser
{
public override void ParseDocument()
{
if (Context == null)
{
throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
}
using (PushSpanConfig(DefaultMarkupSpan))
{
using (Context.StartBlock(BlockType.Markup))
{
NextToken();
while (!EndOfFile)
{
SkipToAndParseCode(HtmlSymbolType.OpenAngle);
ScanTagInDocumentContext();
}
AddMarkerSymbolIfNecessary();
Output(SpanKind.Markup);
}
}
}
/// <summary>
/// Reads the content of a tag (if present) in the MarkupDocument (or MarkupSection) context,
/// where we don't care about maintaining a stack of tags.
/// </summary>
/// <returns>A boolean indicating if we scanned at least one tag.</returns>
private bool ScanTagInDocumentContext()
{
if (Optional(HtmlSymbolType.OpenAngle))
{
if (At(HtmlSymbolType.Bang))
{
BangTag();
return true;
}
else if (At(HtmlSymbolType.QuestionMark))
{
XmlPI();
return true;
}
else if (!At(HtmlSymbolType.Solidus))
{
bool scriptTag = At(HtmlSymbolType.Text) &&
String.Equals(CurrentSymbol.Content, "script", StringComparison.OrdinalIgnoreCase);
Optional(HtmlSymbolType.Text);
TagContent(); // Parse the tag, don't care about the content
Optional(HtmlSymbolType.Solidus);
Optional(HtmlSymbolType.CloseAngle);
if (scriptTag)
{
SkipToEndScriptAndParseCode();
}
return true;
}
}
return false;
}
}
}

View File

@ -0,0 +1,197 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class HtmlMarkupParser
{
private bool CaseSensitive { get; set; }
private StringComparison Comparison
{
get { return CaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; }
}
public override void ParseSection(Tuple<string, string> nestingSequences, bool caseSensitive)
{
if (Context == null)
{
throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
}
using (PushSpanConfig(DefaultMarkupSpan))
{
using (Context.StartBlock(BlockType.Markup))
{
NextToken();
CaseSensitive = caseSensitive;
if (nestingSequences.Item1 == null)
{
NonNestingSection(nestingSequences.Item2.Split());
}
else
{
NestingSection(nestingSequences);
}
AddMarkerSymbolIfNecessary();
Output(SpanKind.Markup);
}
}
}
private void NonNestingSection(string[] nestingSequenceComponents)
{
do
{
SkipToAndParseCode(sym => sym.Type == HtmlSymbolType.OpenAngle || AtEnd(nestingSequenceComponents));
ScanTagInDocumentContext();
if (!EndOfFile && AtEnd(nestingSequenceComponents))
{
break;
}
}
while (!EndOfFile);
PutCurrentBack();
}
private void NestingSection(Tuple<string, string> nestingSequences)
{
int nesting = 1;
while (nesting > 0 && !EndOfFile)
{
SkipToAndParseCode(sym =>
sym.Type == HtmlSymbolType.Text ||
sym.Type == HtmlSymbolType.OpenAngle);
if (At(HtmlSymbolType.Text))
{
nesting += ProcessTextToken(nestingSequences, nesting);
if (CurrentSymbol != null)
{
AcceptAndMoveNext();
}
else if (nesting > 0)
{
NextToken();
}
}
else
{
ScanTagInDocumentContext();
}
}
}
private bool AtEnd(string[] nestingSequenceComponents)
{
EnsureCurrent();
if (String.Equals(CurrentSymbol.Content, nestingSequenceComponents[0], Comparison))
{
int bookmark = CurrentSymbol.Start.AbsoluteIndex;
try
{
foreach (string component in nestingSequenceComponents)
{
if (!EndOfFile && !String.Equals(CurrentSymbol.Content, component, Comparison))
{
return false;
}
NextToken();
while (!EndOfFile && IsSpacingToken(includeNewLines: true)(CurrentSymbol))
{
NextToken();
}
}
return true;
}
finally
{
Context.Source.Position = bookmark;
NextToken();
}
}
return false;
}
private int ProcessTextToken(Tuple<string, string> nestingSequences, int currentNesting)
{
for (int i = 0; i < CurrentSymbol.Content.Length; i++)
{
int nestingDelta = HandleNestingSequence(nestingSequences.Item1, i, currentNesting, 1);
if (nestingDelta == 0)
{
nestingDelta = HandleNestingSequence(nestingSequences.Item2, i, currentNesting, -1);
}
if (nestingDelta != 0)
{
return nestingDelta;
}
}
return 0;
}
private int HandleNestingSequence(string sequence, int position, int currentNesting, int retIfMatched)
{
if (sequence != null &&
CurrentSymbol.Content[position] == sequence[0] &&
position + sequence.Length <= CurrentSymbol.Content.Length)
{
string possibleStart = CurrentSymbol.Content.Substring(position, sequence.Length);
if (String.Equals(possibleStart, sequence, Comparison))
{
// Capture the current symbol and "put it back" (really we just want to clear CurrentSymbol)
int bookmark = Context.Source.Position;
HtmlSymbol sym = CurrentSymbol;
PutCurrentBack();
// Carve up the symbol
Tuple<HtmlSymbol, HtmlSymbol> pair = Language.SplitSymbol(sym, position, HtmlSymbolType.Text);
HtmlSymbol preSequence = pair.Item1;
Debug.Assert(pair.Item2 != null);
pair = Language.SplitSymbol(pair.Item2, sequence.Length, HtmlSymbolType.Text);
HtmlSymbol sequenceToken = pair.Item1;
HtmlSymbol postSequence = pair.Item2;
// Accept the first chunk (up to the nesting sequence we just saw)
if (!String.IsNullOrEmpty(preSequence.Content))
{
Accept(preSequence);
}
if (currentNesting + retIfMatched == 0)
{
// This is 'popping' the final entry on the stack of nesting sequences
// A caller higher in the parsing stack will accept the sequence token, so advance
// to it
Context.Source.Position = sequenceToken.Start.AbsoluteIndex;
}
else
{
// This isn't the end of the last nesting sequence, accept the token and keep going
Accept(sequenceToken);
// Position at the start of the postSequence symbol
if (postSequence != null)
{
Context.Source.Position = postSequence.Start.AbsoluteIndex;
}
else
{
Context.Source.Position = bookmark;
}
}
// Return the value we were asked to return if matched, since we found a nesting sequence
return retIfMatched;
}
}
return 0;
}
}
}

View File

@ -0,0 +1,187 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class HtmlMarkupParser : TokenizerBackedParser<HtmlTokenizer, HtmlSymbol, HtmlSymbolType>
{
//From http://dev.w3.org/html5/spec/Overview.html#elements-0
private ISet<string> _voidElements = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"area",
"base",
"br",
"col",
"command",
"embed",
"hr",
"img",
"input",
"keygen",
"link",
"meta",
"param",
"source",
"track",
"wbr"
};
public ISet<string> VoidElements
{
get { return _voidElements; }
}
protected override ParserBase OtherParser
{
get { return Context.CodeParser; }
}
protected override LanguageCharacteristics<HtmlTokenizer, HtmlSymbol, HtmlSymbolType> Language
{
get { return HtmlLanguageCharacteristics.Instance; }
}
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
{
span.Kind = SpanKind.Markup;
span.CodeGenerator = new MarkupCodeGenerator();
base.BuildSpan(span, start, content);
}
protected override void OutputSpanBeforeRazorComment()
{
Output(SpanKind.Markup);
}
protected void SkipToAndParseCode(HtmlSymbolType type)
{
SkipToAndParseCode(sym => sym.Type == type);
}
protected void SkipToAndParseCode(Func<HtmlSymbol, bool> condition)
{
HtmlSymbol last = null;
bool startOfLine = false;
while (!EndOfFile && !condition(CurrentSymbol))
{
if (At(HtmlSymbolType.NewLine))
{
if (last != null)
{
Accept(last);
}
// Mark the start of a new line
startOfLine = true;
last = null;
AcceptAndMoveNext();
}
else if (At(HtmlSymbolType.Transition))
{
HtmlSymbol transition = CurrentSymbol;
NextToken();
if (At(HtmlSymbolType.Transition))
{
if (last != null)
{
Accept(last);
last = null;
}
Output(SpanKind.Markup);
Accept(transition);
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Markup);
AcceptAndMoveNext();
continue; // while
}
else
{
if (!EndOfFile)
{
PutCurrentBack();
}
PutBack(transition);
}
// Handle whitespace rewriting
if (last != null)
{
if (!Context.DesignTimeMode && last.Type == HtmlSymbolType.WhiteSpace && startOfLine)
{
// Put the whitespace back too
startOfLine = false;
PutBack(last);
last = null;
}
else
{
// Accept last
Accept(last);
last = null;
}
}
OtherParserBlock();
}
else if (At(HtmlSymbolType.RazorCommentTransition))
{
if (last != null)
{
Accept(last);
last = null;
}
AddMarkerSymbolIfNecessary();
Output(SpanKind.Markup);
RazorComment();
}
else
{
// As long as we see whitespace, we're still at the "start" of the line
startOfLine &= At(HtmlSymbolType.WhiteSpace);
// If there's a last token, accept it
if (last != null)
{
Accept(last);
last = null;
}
// Advance
last = CurrentSymbol;
NextToken();
}
}
if (last != null)
{
Accept(last);
}
}
protected static Func<HtmlSymbol, bool> IsSpacingToken(bool includeNewLines)
{
return sym => sym.Type == HtmlSymbolType.WhiteSpace || (includeNewLines && sym.Type == HtmlSymbolType.NewLine);
}
private void OtherParserBlock()
{
AddMarkerSymbolIfNecessary();
Output(SpanKind.Markup);
using (PushSpanConfig())
{
Context.SwitchActiveParser();
Context.CodeParser.ParseBlock();
Context.SwitchActiveParser();
}
Initialize(Span);
NextToken();
}
}
}

View File

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Parser
{
internal interface ISyntaxTreeRewriter
{
Block Rewrite(Block input);
}
}

View File

@ -0,0 +1,113 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
[SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes", Justification = "All generic type parameters are required")]
public abstract class LanguageCharacteristics<TTokenizer, TSymbol, TSymbolType>
where TTokenizer : Tokenizer<TSymbol, TSymbolType>
where TSymbol : SymbolBase<TSymbolType>
{
public abstract string GetSample(TSymbolType type);
public abstract TTokenizer CreateTokenizer(ITextDocument source);
public abstract TSymbolType FlipBracket(TSymbolType bracket);
public abstract TSymbol CreateMarkerSymbol(SourceLocation location);
public virtual IEnumerable<TSymbol> TokenizeString(string content)
{
return TokenizeString(SourceLocation.Zero, content);
}
public virtual IEnumerable<TSymbol> TokenizeString(SourceLocation start, string input)
{
using (SeekableTextReader reader = new SeekableTextReader(input))
{
TTokenizer tok = CreateTokenizer(reader);
TSymbol sym;
while ((sym = tok.NextSymbol()) != null)
{
sym.OffsetStart(start);
yield return sym;
}
}
}
public virtual bool IsWhiteSpace(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.WhiteSpace);
}
public virtual bool IsNewLine(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.NewLine);
}
public virtual bool IsIdentifier(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Identifier);
}
public virtual bool IsKeyword(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Keyword);
}
public virtual bool IsTransition(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Transition);
}
public virtual bool IsCommentStart(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.CommentStart);
}
public virtual bool IsCommentStar(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.CommentStar);
}
public virtual bool IsCommentBody(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.CommentBody);
}
public virtual bool IsUnknown(TSymbol symbol)
{
return IsKnownSymbolType(symbol, KnownSymbolType.Unknown);
}
public virtual bool IsKnownSymbolType(TSymbol symbol, KnownSymbolType type)
{
return symbol != null && Equals(symbol.Type, GetKnownSymbolType(type));
}
public virtual Tuple<TSymbol, TSymbol> SplitSymbol(TSymbol symbol, int splitAt, TSymbolType leftType)
{
TSymbol left = CreateSymbol(symbol.Start, symbol.Content.Substring(0, splitAt), leftType, Enumerable.Empty<RazorError>());
TSymbol right = null;
if (splitAt < symbol.Content.Length)
{
right = CreateSymbol(SourceLocationTracker.CalculateNewLocation(symbol.Start, left.Content), symbol.Content.Substring(splitAt), symbol.Type, symbol.Errors);
}
return Tuple.Create(left, right);
}
public abstract TSymbolType GetKnownSymbolType(KnownSymbolType type);
public virtual bool KnowsSymbolType(KnownSymbolType type)
{
return type == KnownSymbolType.Unknown || !Equals(GetKnownSymbolType(type), GetKnownSymbolType(KnownSymbolType.Unknown));
}
protected abstract TSymbol CreateSymbol(SourceLocation location, string content, TSymbolType type, IEnumerable<RazorError> errors);
}
}

View File

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Linq;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser
{
internal class MarkupCollapser : MarkupRewriter
{
public MarkupCollapser(Action<SpanBuilder, SourceLocation, string> markupSpanFactory) : base(markupSpanFactory)
{
}
protected override bool CanRewrite(Span span)
{
return span.Kind == SpanKind.Markup && span.CodeGenerator is MarkupCodeGenerator;
}
protected override SyntaxTreeNode RewriteSpan(BlockBuilder parent, Span span)
{
// Only rewrite if we have a previous that is also markup (CanRewrite does this check for us!)
Span previous = parent.Children.LastOrDefault() as Span;
if (previous == null || !CanRewrite(previous))
{
return span;
}
// Merge spans
parent.Children.Remove(previous);
SpanBuilder merged = new SpanBuilder();
FillSpan(merged, previous.Start, previous.Content + span.Content);
return merged.Build();
}
}
}

View File

@ -0,0 +1,105 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser
{
internal abstract class MarkupRewriter : ParserVisitor, ISyntaxTreeRewriter
{
private Stack<BlockBuilder> _blocks = new Stack<BlockBuilder>();
private Action<SpanBuilder, SourceLocation, string> _markupSpanFactory;
protected MarkupRewriter(Action<SpanBuilder, SourceLocation, string> markupSpanFactory)
{
if (markupSpanFactory == null)
{
throw new ArgumentNullException("markupSpanFactory");
}
_markupSpanFactory = markupSpanFactory;
}
protected BlockBuilder Parent
{
get { return _blocks.Count > 0 ? _blocks.Peek() : null; }
}
public virtual Block Rewrite(Block input)
{
input.Accept(this);
Debug.Assert(_blocks.Count == 1);
return _blocks.Pop().Build();
}
public override void VisitBlock(Block block)
{
if (CanRewrite(block))
{
SyntaxTreeNode newNode = RewriteBlock(_blocks.Peek(), block);
if (newNode != null)
{
_blocks.Peek().Children.Add(newNode);
}
}
else
{
// Not rewritable.
BlockBuilder builder = new BlockBuilder(block);
builder.Children.Clear();
_blocks.Push(builder);
base.VisitBlock(block);
Debug.Assert(ReferenceEquals(builder, _blocks.Peek()));
if (_blocks.Count > 1)
{
_blocks.Pop();
_blocks.Peek().Children.Add(builder.Build());
}
}
}
public override void VisitSpan(Span span)
{
if (CanRewrite(span))
{
SyntaxTreeNode newNode = RewriteSpan(_blocks.Peek(), span);
if (newNode != null)
{
_blocks.Peek().Children.Add(newNode);
}
}
else
{
_blocks.Peek().Children.Add(span);
}
}
protected virtual bool CanRewrite(Block block)
{
return false;
}
protected virtual bool CanRewrite(Span span)
{
return false;
}
protected virtual SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
{
throw new NotImplementedException();
}
protected virtual SyntaxTreeNode RewriteSpan(BlockBuilder parent, Span span)
{
throw new NotImplementedException();
}
protected void FillSpan(SpanBuilder builder, SourceLocation start, string content)
{
_markupSpanFactory(builder, start, content);
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser
{
public abstract class ParserBase
{
private ParserContext _context;
public virtual ParserContext Context
{
get { return _context; }
set
{
Debug.Assert(_context == null, "Context has already been set for this parser!");
_context = value;
_context.AssertOnOwnerTask();
}
}
public virtual bool IsMarkupParser
{
get { return false; }
}
protected abstract ParserBase OtherParser { get; }
public abstract void BuildSpan(SpanBuilder span, SourceLocation start, string content);
public abstract void ParseBlock();
// Markup Parsers need the ParseDocument and ParseSection methods since the markup parser is the first parser to hit the document
// and the logic may be different than the ParseBlock method.
public virtual void ParseDocument()
{
Debug.Assert(IsMarkupParser);
throw new NotSupportedException(RazorResources.ParserIsNotAMarkupParser);
}
public virtual void ParseSection(Tuple<string, string> nestingSequences, bool caseSensitive)
{
Debug.Assert(IsMarkupParser);
throw new NotSupportedException(RazorResources.ParserIsNotAMarkupParser);
}
}
}

View File

@ -0,0 +1,305 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Utils;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class ParserContext
{
private int? _ownerTaskId;
private bool _terminated = false;
private Stack<BlockBuilder> _blockStack = new Stack<BlockBuilder>();
public ParserContext(ITextDocument source, ParserBase codeParser, ParserBase markupParser, ParserBase activeParser)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (codeParser == null)
{
throw new ArgumentNullException("codeParser");
}
if (markupParser == null)
{
throw new ArgumentNullException("markupParser");
}
if (activeParser == null)
{
throw new ArgumentNullException("activeParser");
}
if (activeParser != codeParser && activeParser != markupParser)
{
throw new ArgumentException(RazorResources.ActiveParser_Must_Be_Code_Or_Markup_Parser, "activeParser");
}
CaptureOwnerTask();
Source = new TextDocumentReader(source);
CodeParser = codeParser;
MarkupParser = markupParser;
ActiveParser = activeParser;
Errors = new List<RazorError>();
}
public IList<RazorError> Errors { get; private set; }
public TextDocumentReader Source { get; set; }
public ParserBase CodeParser { get; private set; }
public ParserBase MarkupParser { get; private set; }
public ParserBase ActiveParser { get; private set; }
public bool DesignTimeMode { get; set; }
public BlockBuilder CurrentBlock
{
get { return _blockStack.Peek(); }
}
public Span LastSpan { get; private set; }
public bool WhiteSpaceIsSignificantToAncestorBlock { get; set; }
public AcceptedCharacters LastAcceptedCharacters
{
get
{
if (LastSpan == null)
{
return AcceptedCharacters.None;
}
return LastSpan.EditHandler.AcceptedCharacters;
}
}
internal Stack<BlockBuilder> BlockStack
{
get { return _blockStack; }
}
public char CurrentCharacter
{
get
{
if (_terminated)
{
return '\0';
}
#if DEBUG
if (CheckInfiniteLoop())
{
return '\0';
}
#endif
int ch = Source.Peek();
if (ch == -1)
{
return '\0';
}
return (char)ch;
}
}
public bool EndOfFile
{
get { return _terminated || Source.Peek() == -1; }
}
public void AddSpan(Span span)
{
EnusreNotTerminated();
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(RazorResources.ParserContext_NoCurrentBlock);
}
_blockStack.Peek().Children.Add(span);
LastSpan = span;
}
/// <summary>
/// Starts a block of the specified type
/// </summary>
/// <param name="blockType">The type of the block to start</param>
public IDisposable StartBlock(BlockType blockType)
{
EnusreNotTerminated();
AssertOnOwnerTask();
_blockStack.Push(new BlockBuilder() { Type = blockType });
return new DisposableAction(EndBlock);
}
/// <summary>
/// Starts a block
/// </summary>
public IDisposable StartBlock()
{
EnusreNotTerminated();
AssertOnOwnerTask();
_blockStack.Push(new BlockBuilder());
return new DisposableAction(EndBlock);
}
/// <summary>
/// Ends the current block
/// </summary>
public void EndBlock()
{
EnusreNotTerminated();
AssertOnOwnerTask();
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(RazorResources.EndBlock_Called_Without_Matching_StartBlock);
}
if (_blockStack.Count > 1)
{
BlockBuilder block = _blockStack.Pop();
_blockStack.Peek().Children.Add(block.Build());
}
else
{
// If we're at 1, terminate the parser
_terminated = true;
}
}
/// <summary>
/// Gets a boolean indicating if any of the ancestors of the current block is of the specified type
/// </summary>
public bool IsWithin(BlockType type)
{
return _blockStack.Any(b => b.Type == type);
}
public void SwitchActiveParser()
{
EnusreNotTerminated();
AssertOnOwnerTask();
if (ReferenceEquals(ActiveParser, CodeParser))
{
ActiveParser = MarkupParser;
}
else
{
ActiveParser = CodeParser;
}
}
public void OnError(SourceLocation location, string message)
{
EnusreNotTerminated();
AssertOnOwnerTask();
Errors.Add(new RazorError(message, location));
}
public void OnError(SourceLocation location, string message, params object[] args)
{
EnusreNotTerminated();
AssertOnOwnerTask();
OnError(location, String.Format(CultureInfo.CurrentCulture, message, args));
}
public ParserResults CompleteParse()
{
if (_blockStack.Count == 0)
{
throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_NoRootBlock);
}
if (_blockStack.Count != 1)
{
throw new InvalidOperationException(RazorResources.ParserContext_CannotCompleteTree_OutstandingBlocks);
}
return new ParserResults(_blockStack.Pop().Build(), Errors);
}
[Conditional("DEBUG")]
internal void CaptureOwnerTask()
{
if (Task.CurrentId != null)
{
_ownerTaskId = Task.CurrentId;
}
}
[Conditional("DEBUG")]
internal void AssertOnOwnerTask()
{
if (_ownerTaskId != null)
{
Debug.Assert(_ownerTaskId == Task.CurrentId);
}
}
[Conditional("DEBUG")]
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "The method body is empty in Release builds")]
internal void AssertCurrent(char expected)
{
Debug.Assert(CurrentCharacter == expected);
}
private void EnusreNotTerminated()
{
if (_terminated)
{
throw new InvalidOperationException(RazorResources.ParserContext_ParseComplete);
}
}
}
// Debug Helpers
#if DEBUG
[DebuggerDisplay("{Unparsed}")]
public partial class ParserContext
{
private const int InfiniteLoopCountThreshold = 1000;
private int _infiniteLoopGuardCount = 0;
private SourceLocation? _infiniteLoopGuardLocation = null;
internal string Unparsed
{
get
{
string remaining = Source.ReadToEnd();
Source.Position -= remaining.Length;
return remaining;
}
}
private bool CheckInfiniteLoop()
{
// Infinite loop guard
// Basically, if this property is accessed 1000 times in a row without having advanced the source reader to the next position, we
// cause a parser error
if (_infiniteLoopGuardLocation != null)
{
if (Source.Location == _infiniteLoopGuardLocation.Value)
{
_infiniteLoopGuardCount++;
if (_infiniteLoopGuardCount > InfiniteLoopCountThreshold)
{
Debug.Fail("An internal parser error is causing an infinite loop at this location.");
_terminated = true;
return true;
}
}
else
{
_infiniteLoopGuardCount = 0;
}
}
_infiniteLoopGuardLocation = Source.Location;
return false;
}
}
#endif
}

View File

@ -0,0 +1,146 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
namespace Microsoft.AspNet.Razor.Parser
{
public static class ParserHelpers
{
public static bool IsNewLine(char value)
{
return value == '\r' // Carriage return
|| value == '\n' // Linefeed
|| value == '\u0085' // Next Line
|| value == '\u2028' // Line separator
|| value == '\u2029'; // Paragraph separator
}
public static bool IsNewLine(string value)
{
return (value.Length == 1 && (IsNewLine(value[0]))) ||
(String.Equals(value, "\r\n", StringComparison.Ordinal));
}
// Returns true if the character is Whitespace and NOT a newline
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", Justification = "This would be a breaking change in a shipping API")]
public static bool IsWhitespace(char value)
{
return value == ' ' ||
value == '\f' ||
value == '\t' ||
value == '\u000B' || // Vertical Tab
Char.GetUnicodeCategory(value) == UnicodeCategory.SpaceSeparator;
}
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Whitespace", Justification = "This would be a breaking change in a shipping API")]
public static bool IsWhitespaceOrNewLine(char value)
{
return IsWhitespace(value) || IsNewLine(value);
}
public static bool IsIdentifier(string value)
{
return IsIdentifier(value, requireIdentifierStart: true);
}
public static bool IsIdentifier(string value, bool requireIdentifierStart)
{
IEnumerable<char> identifierPart = value;
if (requireIdentifierStart)
{
identifierPart = identifierPart.Skip(1);
}
return (!requireIdentifierStart || IsIdentifierStart(value[0])) && identifierPart.All(IsIdentifierPart);
}
public static bool IsHexDigit(char value)
{
return (value >= '0' && value <= '9') || (value >= 'A' && value <= 'F') || (value >= 'a' && value <= 'f');
}
public static bool IsIdentifierStart(char value)
{
return value == '_' || IsLetter(value);
}
public static bool IsIdentifierPart(char value)
{
return IsLetter(value)
|| IsDecimalDigit(value)
|| IsConnecting(value)
|| IsCombining(value)
|| IsFormatting(value);
}
public static bool IsTerminatingCharToken(char value)
{
return IsNewLine(value) || value == '\'';
}
public static bool IsTerminatingQuotedStringToken(char value)
{
return IsNewLine(value) || value == '"';
}
public static bool IsDecimalDigit(char value)
{
return Char.GetUnicodeCategory(value) == UnicodeCategory.DecimalDigitNumber;
}
public static bool IsLetterOrDecimalDigit(char value)
{
return IsLetter(value) || IsDecimalDigit(value);
}
public static bool IsLetter(char value)
{
var cat = Char.GetUnicodeCategory(value);
return cat == UnicodeCategory.UppercaseLetter
|| cat == UnicodeCategory.LowercaseLetter
|| cat == UnicodeCategory.TitlecaseLetter
|| cat == UnicodeCategory.ModifierLetter
|| cat == UnicodeCategory.OtherLetter
|| cat == UnicodeCategory.LetterNumber;
}
public static bool IsFormatting(char value)
{
return Char.GetUnicodeCategory(value) == UnicodeCategory.Format;
}
public static bool IsCombining(char value)
{
var cat = Char.GetUnicodeCategory(value);
return cat == UnicodeCategory.SpacingCombiningMark || cat == UnicodeCategory.NonSpacingMark;
}
public static bool IsConnecting(char value)
{
return Char.GetUnicodeCategory(value) == UnicodeCategory.ConnectorPunctuation;
}
public static string SanitizeClassName(string inputName)
{
if (!IsIdentifierStart(inputName[0]) && IsIdentifierPart(inputName[0]))
{
inputName = "_" + inputName;
}
return new String((from value in inputName
select IsIdentifierPart(value) ? value : '_')
.ToArray());
}
public static bool IsEmailPart(char character)
{
// Source: http://tools.ietf.org/html/rfc5322#section-3.4.1
// We restrict the allowed characters to alpha-numerics and '_' in order to ensure we cover most of the cases where an
// email address is intended without restricting the usage of code within JavaScript, CSS, and other contexts.
return Char.IsLetter(character) || Char.IsDigit(character) || character == '_';
}
}
}

View File

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Threading;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Parser
{
public abstract class ParserVisitor
{
public CancellationToken? CancelToken { get; set; }
public virtual void VisitBlock(Block block)
{
VisitStartBlock(block);
foreach (SyntaxTreeNode node in block.Children)
{
node.Accept(this);
}
VisitEndBlock(block);
}
public virtual void VisitStartBlock(Block block)
{
ThrowIfCanceled();
}
public virtual void VisitSpan(Span span)
{
ThrowIfCanceled();
}
public virtual void VisitEndBlock(Block block)
{
ThrowIfCanceled();
}
public virtual void VisitError(RazorError err)
{
ThrowIfCanceled();
}
public virtual void OnComplete()
{
ThrowIfCanceled();
}
public virtual void ThrowIfCanceled()
{
if (CancelToken != null && CancelToken.Value.IsCancellationRequested)
{
throw new OperationCanceledException();
}
}
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor.Parser
{
public static class ParserVisitorExtensions
{
public static void Visit(this ParserVisitor self, ParserResults result)
{
if (self == null)
{
throw new ArgumentNullException("self");
}
if (result == null)
{
throw new ArgumentNullException("result");
}
result.Document.Accept(self);
foreach (RazorError error in result.ParserErrors)
{
self.VisitError(error);
}
self.OnComplete();
}
}
}

View File

@ -0,0 +1,158 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser
{
public class RazorParser
{
public RazorParser(ParserBase codeParser, ParserBase markupParser)
{
if (codeParser == null)
{
throw new ArgumentNullException("codeParser");
}
if (markupParser == null)
{
throw new ArgumentNullException("markupParser");
}
MarkupParser = markupParser;
CodeParser = codeParser;
Optimizers = new List<ISyntaxTreeRewriter>()
{
// Move whitespace from start of expression block to markup
new WhiteSpaceRewriter(MarkupParser.BuildSpan),
// Collapse conditional attributes where the entire value is literal
new ConditionalAttributeCollapser(MarkupParser.BuildSpan),
};
}
internal ParserBase CodeParser { get; private set; }
internal ParserBase MarkupParser { get; private set; }
internal IList<ISyntaxTreeRewriter> Optimizers { get; private set; }
public bool DesignTimeMode { get; set; }
public virtual void Parse(TextReader input, ParserVisitor visitor)
{
Parse(new SeekableTextReader(input), visitor);
}
public virtual ParserResults Parse(TextReader input)
{
return ParseCore(new SeekableTextReader(input));
}
public virtual ParserResults Parse(ITextDocument input)
{
return ParseCore(input);
}
#pragma warning disable 0618
[Obsolete("Lookahead-based readers have been deprecated, use overrides which accept a TextReader or ITextDocument instead")]
public virtual void Parse(LookaheadTextReader input, ParserVisitor visitor)
{
ParserResults results = ParseCore(new SeekableTextReader(input));
// Replay the results on the visitor
visitor.Visit(results);
}
[Obsolete("Lookahead-based readers have been deprecated, use overrides which accept a TextReader or ITextDocument instead")]
public virtual ParserResults Parse(LookaheadTextReader input)
{
return ParseCore(new SeekableTextReader(input));
}
#pragma warning restore 0618
public virtual Task CreateParseTask(TextReader input, Action<Span> spanCallback, Action<RazorError> errorCallback)
{
return CreateParseTask(input, new CallbackVisitor(spanCallback, errorCallback));
}
public virtual Task CreateParseTask(TextReader input, Action<Span> spanCallback, Action<RazorError> errorCallback, SynchronizationContext context)
{
return CreateParseTask(input, new CallbackVisitor(spanCallback, errorCallback) { SynchronizationContext = context });
}
public virtual Task CreateParseTask(TextReader input, Action<Span> spanCallback, Action<RazorError> errorCallback, CancellationToken cancelToken)
{
return CreateParseTask(input, new CallbackVisitor(spanCallback, errorCallback) { CancelToken = cancelToken });
}
public virtual Task CreateParseTask(TextReader input, Action<Span> spanCallback, Action<RazorError> errorCallback, SynchronizationContext context, CancellationToken cancelToken)
{
return CreateParseTask(input, new CallbackVisitor(spanCallback, errorCallback)
{
SynchronizationContext = context,
CancelToken = cancelToken
});
}
[SuppressMessage("Microsoft.Web.FxCop", "MW1200:DoNotConstructTaskInstances", Justification = "This rule is not applicable to this assembly.")]
public virtual Task CreateParseTask(TextReader input,
ParserVisitor consumer)
{
return new Task(() =>
{
try
{
Parse(input, consumer);
}
catch (OperationCanceledException)
{
return; // Just return if we're cancelled.
}
});
}
private ParserResults ParseCore(ITextDocument input)
{
// Setup the parser context
ParserContext context = new ParserContext(input, CodeParser, MarkupParser, MarkupParser)
{
DesignTimeMode = DesignTimeMode
};
MarkupParser.Context = context;
CodeParser.Context = context;
// Execute the parse
MarkupParser.ParseDocument();
// Get the result
ParserResults results = context.CompleteParse();
// Rewrite whitespace if supported
Block current = results.Document;
foreach (ISyntaxTreeRewriter rewriter in Optimizers)
{
current = rewriter.Rewrite(current);
}
// Link the leaf nodes into a chain
Span prev = null;
foreach (Span node in current.Flatten())
{
node.Previous = prev;
if (prev != null)
{
prev.Next = node;
}
prev = node;
}
// Return the new result
return new ParserResults(current, results.ParserErrors);
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.Razor.Parser
{
public static class SyntaxConstants
{
public static readonly string TextTagName = "text";
public static readonly char TransitionCharacter = '@';
public static readonly string TransitionString = "@";
public static readonly string StartCommentSequence = "@*";
public static readonly string EndCommentSequence = "*@";
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Class is nested to provide better organization")]
[SuppressMessage("Microsoft.Naming", "CA1724:TypeNamesShouldNotMatchNamespaces", Justification = "This type name should not cause a conflict")]
public static class CSharp
{
public static readonly int UsingKeywordLength = 5;
public static readonly string InheritsKeyword = "inherits";
public static readonly string FunctionsKeyword = "functions";
public static readonly string SectionKeyword = "section";
public static readonly string HelperKeyword = "helper";
public static readonly string ElseIfKeyword = "else if";
public static readonly string NamespaceKeyword = "namespace";
public static readonly string ClassKeyword = "class";
public static readonly string LayoutKeyword = "layout";
public static readonly string SessionStateKeyword = "sessionstate";
}
[SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Class is nested to provide better organization")]
public static class VB
{
public static readonly int ImportsKeywordLength = 7;
public static readonly string EndKeyword = "End";
public static readonly string CodeKeyword = "Code";
public static readonly string FunctionsKeyword = "Functions";
public static readonly string SectionKeyword = "Section";
public static readonly string StrictKeyword = "Strict";
public static readonly string ExplicitKeyword = "Explicit";
public static readonly string OffKeyword = "Off";
public static readonly string HelperKeyword = "Helper";
public static readonly string SelectCaseKeyword = "Select Case";
public static readonly string LayoutKeyword = "Layout";
public static readonly string EndCodeKeyword = "End Code";
public static readonly string EndHelperKeyword = "End Helper";
public static readonly string EndFunctionsKeyword = "End Functions";
public static readonly string EndSectionKeyword = "End Section";
public static readonly string SessionStateKeyword = "SessionState";
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
[Flags]
public enum AcceptedCharacters
{
None = 0,
NewLine = 1,
WhiteSpace = 2,
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "NonWhite", Justification = "This is not a compound word, it is two words")]
NonWhiteSpace = 4,
AllWhiteSpace = NewLine | WhiteSpace,
Any = AllWhiteSpace | NonWhiteSpace,
[SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Newline", Justification = "This would be a breaking change to a previous released API")]
AnyExceptNewline = NonWhiteSpace | WhiteSpace
}
}

View File

@ -0,0 +1,201 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public class Block : SyntaxTreeNode
{
public Block(BlockBuilder source)
{
if (source.Type == null)
{
throw new InvalidOperationException(RazorResources.Block_Type_Not_Specified);
}
Type = source.Type.Value;
Children = source.Children;
Name = source.Name;
CodeGenerator = source.CodeGenerator;
source.Reset();
foreach (SyntaxTreeNode node in Children)
{
node.Parent = this;
}
}
internal Block(BlockType type, IEnumerable<SyntaxTreeNode> contents, IBlockCodeGenerator generator)
{
Type = type;
CodeGenerator = generator;
Children = contents;
}
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Type is the most appropriate name for this property and there is little chance of confusion with GetType")]
public BlockType Type { get; private set; }
public IEnumerable<SyntaxTreeNode> Children { get; private set; }
public string Name { get; private set; }
public IBlockCodeGenerator CodeGenerator { get; private set; }
public override bool IsBlock
{
get { return true; }
}
public override SourceLocation Start
{
get
{
SyntaxTreeNode child = Children.FirstOrDefault();
if (child == null)
{
return SourceLocation.Zero;
}
else
{
return child.Start;
}
}
}
public override int Length
{
get { return Children.Sum(child => child.Length); }
}
public Span FindFirstDescendentSpan()
{
SyntaxTreeNode current = this;
while (current != null && current.IsBlock)
{
current = ((Block)current).Children.FirstOrDefault();
}
return current as Span;
}
public Span FindLastDescendentSpan()
{
SyntaxTreeNode current = this;
while (current != null && current.IsBlock)
{
current = ((Block)current).Children.LastOrDefault();
}
return current as Span;
}
public override void Accept(ParserVisitor visitor)
{
visitor.VisitBlock(this);
}
public override string ToString()
{
return String.Format(CultureInfo.CurrentCulture, "{0} Block at {1}::{2} (Gen:{3})", Type, Start, Length, CodeGenerator);
}
public override bool Equals(object obj)
{
Block other = obj as Block;
return other != null &&
Type == other.Type &&
Equals(CodeGenerator, other.CodeGenerator) &&
ChildrenEqual(Children, other.Children);
}
public override int GetHashCode()
{
return (int)Type;
}
public IEnumerable<Span> Flatten()
{
// Create an enumerable that flattens the tree for use by syntax highlighters, etc.
foreach (SyntaxTreeNode element in Children)
{
Span span = element as Span;
if (span != null)
{
yield return span;
}
else
{
Block block = element as Block;
foreach (Span childSpan in block.Flatten())
{
yield return childSpan;
}
}
}
}
public Span LocateOwner(TextChange change)
{
// Ask each child recursively
Span owner = null;
foreach (SyntaxTreeNode element in Children)
{
Span span = element as Span;
if (span == null)
{
owner = ((Block)element).LocateOwner(change);
}
else
{
if (change.OldPosition < span.Start.AbsoluteIndex)
{
// Early escape for cases where changes overlap multiple spans
// In those cases, the span will return false, and we don't want to search the whole tree
// So if the current span starts after the change, we know we've searched as far as we need to
break;
}
owner = span.EditHandler.OwnsChange(span, change) ? span : owner;
}
if (owner != null)
{
break;
}
}
return owner;
}
private static bool ChildrenEqual(IEnumerable<SyntaxTreeNode> left, IEnumerable<SyntaxTreeNode> right)
{
IEnumerator<SyntaxTreeNode> leftEnum = left.GetEnumerator();
IEnumerator<SyntaxTreeNode> rightEnum = right.GetEnumerator();
while (leftEnum.MoveNext())
{
if (!rightEnum.MoveNext() || // More items in left than in right
!Equals(leftEnum.Current, rightEnum.Current))
{
// Nodes are not equal
return false;
}
}
if (rightEnum.MoveNext())
{
// More items in right than left
return false;
}
return true;
}
public override bool EquivalentTo(SyntaxTreeNode node)
{
Block other = node as Block;
if (other == null || other.Type != Type)
{
return false;
}
return Enumerable.SequenceEqual(Children, other.Children, new EquivalenceComparer());
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Generator;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public class BlockBuilder
{
public BlockBuilder()
{
Reset();
}
public BlockBuilder(Block original)
{
Type = original.Type;
Children = new List<SyntaxTreeNode>(original.Children);
Name = original.Name;
CodeGenerator = original.CodeGenerator;
}
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Type is the most appropriate name for this property and there is little chance of confusion with GetType")]
public BlockType? Type { get; set; }
public IList<SyntaxTreeNode> Children { get; private set; }
public string Name { get; set; }
public IBlockCodeGenerator CodeGenerator { get; set; }
public Block Build()
{
return new Block(this);
}
public void Reset()
{
Type = null;
Name = null;
Children = new List<SyntaxTreeNode>();
CodeGenerator = BlockCodeGenerator.Null;
}
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public enum BlockType
{
// Code
Statement,
Directive,
Functions,
Expression,
Helper,
// Markup
Markup,
Section,
Template,
// Special
Comment
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
internal class EquivalenceComparer : IEqualityComparer<SyntaxTreeNode>
{
public bool Equals(SyntaxTreeNode x, SyntaxTreeNode y)
{
return x.EquivalentTo(y);
}
public int GetHashCode(SyntaxTreeNode obj)
{
return obj.GetHashCode();
}
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Globalization;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public class RazorError : IEquatable<RazorError>
{
public RazorError(string message, SourceLocation location)
: this(message, location, 1)
{
}
public RazorError(string message, int absoluteIndex, int lineIndex, int columnIndex)
: this(message, new SourceLocation(absoluteIndex, lineIndex, columnIndex))
{
}
public RazorError(string message, SourceLocation location, int length)
{
Message = message;
Location = location;
Length = length;
}
public RazorError(string message, int absoluteIndex, int lineIndex, int columnIndex, int length)
: this(message, new SourceLocation(absoluteIndex, lineIndex, columnIndex), length)
{
}
public string Message { get; private set; }
public SourceLocation Location { get; private set; }
public int Length { get; private set; }
public override string ToString()
{
return String.Format(CultureInfo.CurrentCulture, "Error @ {0}({2}) - [{1}]", Location, Message, Length);
}
public override bool Equals(object obj)
{
RazorError err = obj as RazorError;
return (err != null) && (Equals(err));
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public bool Equals(RazorError other)
{
return String.Equals(other.Message, Message, StringComparison.Ordinal) &&
Location.Equals(other.Location);
}
}
}

View File

@ -0,0 +1,153 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
using Microsoft.Internal.Web.Utils;
using System;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public class Span : SyntaxTreeNode
{
private SourceLocation _start;
public Span(SpanBuilder builder)
{
ReplaceWith(builder);
}
public SpanKind Kind { get; protected set; }
public IEnumerable<ISymbol> Symbols { get; protected set; }
// Allow test code to re-link spans
public Span Previous { get; protected internal set; }
public Span Next { get; protected internal set; }
public SpanEditHandler EditHandler { get; protected set; }
public ISpanCodeGenerator CodeGenerator { get; protected set; }
public override bool IsBlock
{
get { return false; }
}
public override int Length
{
get { return Content.Length; }
}
public override SourceLocation Start
{
get { return _start; }
}
public string Content { get; private set; }
public void Change(Action<SpanBuilder> changes)
{
SpanBuilder builder = new SpanBuilder(this);
changes(builder);
ReplaceWith(builder);
}
public void ReplaceWith(SpanBuilder builder)
{
Debug.Assert(!builder.Symbols.Any() || builder.Symbols.All(s => s != null));
Kind = builder.Kind;
Symbols = builder.Symbols;
EditHandler = builder.EditHandler;
CodeGenerator = builder.CodeGenerator ?? SpanCodeGenerator.Null;
_start = builder.Start;
// Since we took references to the values in SpanBuilder, clear its references out
builder.Reset();
// Calculate other properties
Content = Symbols.Aggregate(new StringBuilder(), (sb, sym) => sb.Append(sym.Content), sb => sb.ToString());
}
/// <summary>
/// Accepts the specified visitor
/// </summary>
/// <remarks>
/// Calls the VisitSpan method on the specified visitor, passing in this
/// </remarks>
public override void Accept(ParserVisitor visitor)
{
visitor.VisitSpan(this);
}
public override string ToString()
{
StringBuilder builder = new StringBuilder();
builder.Append(Kind);
builder.AppendFormat(" Span at {0}::{1} - [{2}]", Start, Length, Content);
builder.Append(" Edit: <");
builder.Append(EditHandler.ToString());
builder.Append(">");
builder.Append(" Gen: <");
builder.Append(CodeGenerator.ToString());
builder.Append("> {");
builder.Append(String.Join(";", Symbols.GroupBy(sym => sym.GetType()).Select(grp => String.Concat(grp.Key.Name, ":", grp.Count()))));
builder.Append("}");
return builder.ToString();
}
public void ChangeStart(SourceLocation newStart)
{
_start = newStart;
Span current = this;
SourceLocationTracker tracker = new SourceLocationTracker(newStart);
tracker.UpdateLocation(Content);
while ((current = current.Next) != null)
{
current._start = tracker.CurrentLocation;
tracker.UpdateLocation(current.Content);
}
}
internal void SetStart(SourceLocation newStart)
{
_start = newStart;
}
/// <summary>
/// Checks that the specified span is equivalent to the other in that it has the same start point and content.
/// </summary>
public override bool EquivalentTo(SyntaxTreeNode node)
{
Span other = node as Span;
return other != null &&
Kind.Equals(other.Kind) &&
Start.Equals(other.Start) &&
EditHandler.Equals(other.EditHandler) &&
String.Equals(other.Content, Content, StringComparison.Ordinal);
}
public override bool Equals(object obj)
{
Span other = obj as Span;
return other != null &&
Kind.Equals(other.Kind) &&
EditHandler.Equals(other.EditHandler) &&
CodeGenerator.Equals(other.CodeGenerator) &&
Symbols.SequenceEqual(other.Symbols);
}
public override int GetHashCode()
{
return HashCodeCombiner.Start()
.Add((int)Kind)
.Add(Start)
.Add(Content)
.CombinedHash;
}
}
}

View File

@ -0,0 +1,84 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public class SpanBuilder
{
private IList<ISymbol> _symbols = new List<ISymbol>();
private SourceLocationTracker _tracker = new SourceLocationTracker();
public SpanBuilder(Span original)
{
Kind = original.Kind;
_symbols = new List<ISymbol>(original.Symbols);
EditHandler = original.EditHandler;
CodeGenerator = original.CodeGenerator;
Start = original.Start;
}
public SpanBuilder()
{
Reset();
}
public SourceLocation Start { get; set; }
public SpanKind Kind { get; set; }
public ReadOnlyCollection<ISymbol> Symbols
{
get { return new ReadOnlyCollection<ISymbol>(_symbols); }
}
public SpanEditHandler EditHandler { get; set; }
public ISpanCodeGenerator CodeGenerator { get; set; }
public void Reset()
{
_symbols = new List<ISymbol>();
EditHandler = SpanEditHandler.CreateDefault(s => Enumerable.Empty<ISymbol>());
CodeGenerator = SpanCodeGenerator.Null;
Start = SourceLocation.Zero;
}
public Span Build()
{
return new Span(this);
}
public void ClearSymbols()
{
_symbols.Clear();
}
// Short-cut method for adding a symbol
public void Accept(ISymbol symbol)
{
if (symbol == null)
{
return;
}
if (_symbols.Count == 0)
{
Start = symbol.Start;
symbol.ChangeStart(SourceLocation.Zero);
_tracker.CurrentLocation = SourceLocation.Zero;
}
else
{
symbol.ChangeStart(_tracker.CurrentLocation);
}
_symbols.Add(symbol);
_tracker.UpdateLocation(symbol.Content);
}
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public enum SpanKind
{
Transition,
MetaCode,
Comment,
Code,
Markup
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser.SyntaxTree
{
public abstract class SyntaxTreeNode
{
public Block Parent { get; internal set; }
/// <summary>
/// Returns true if this element is a block (to avoid casting)
/// </summary>
public abstract bool IsBlock { get; }
/// <summary>
/// The length of all the content contained in this node
/// </summary>
public abstract int Length { get; }
/// <summary>
/// The start point of this node
/// </summary>
public abstract SourceLocation Start { get; }
/// <summary>
/// Accepts a parser visitor, calling the appropriate visit method and passing in this instance
/// </summary>
/// <param name="visitor">The visitor to accept</param>
public abstract void Accept(ParserVisitor visitor);
/// <summary>
/// Determines if the specified node is equivalent to this node
/// </summary>
/// <param name="node">The node to compare this node with</param>
/// <returns>
/// true if the provided node has all the same content and metadata, though the specific quantity and type of symbols may be different.
/// </returns>
public abstract bool EquivalentTo(SyntaxTreeNode node);
}
}

View File

@ -0,0 +1,109 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Linq;
using System.Text;
namespace Microsoft.AspNet.Razor.Parser
{
internal static class TextReaderExtensions
{
public static string ReadUntil(this TextReader reader, char terminator)
{
return ReadUntil(reader, terminator, inclusive: false);
}
public static string ReadUntil(this TextReader reader, char terminator, bool inclusive)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
// Rather not allocate an array to use ReadUntil(TextReader, params char[]) so we'll just call the predicate version directly
return reader.ReadUntil(c => c == terminator, inclusive);
}
public static string ReadUntil(this TextReader reader, params char[] terminators)
{
// NOTE: Using named parameters would be difficult here, hence the inline comment
return reader.ReadUntil(inclusive: false, terminators: terminators);
}
public static string ReadUntil(this TextReader reader, bool inclusive, params char[] terminators)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
if (terminators == null)
{
throw new ArgumentNullException("terminators");
}
return reader.ReadUntil(c => terminators.Any(tc => tc == c), inclusive: inclusive);
}
public static string ReadUntil(this TextReader reader, Predicate<char> condition)
{
return reader.ReadUntil(condition, inclusive: false);
}
public static string ReadUntil(this TextReader reader, Predicate<char> condition, bool inclusive)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
if (condition == null)
{
throw new ArgumentNullException("condition");
}
StringBuilder builder = new StringBuilder();
int ch = -1;
while ((ch = reader.Peek()) != -1 && !condition((char)ch))
{
reader.Read(); // Advance the reader
builder.Append((char)ch);
}
if (inclusive && reader.Peek() != -1)
{
builder.Append((char)reader.Read());
}
return builder.ToString();
}
public static string ReadWhile(this TextReader reader, Predicate<char> condition)
{
return reader.ReadWhile(condition, inclusive: false);
}
public static string ReadWhile(this TextReader reader, Predicate<char> condition, bool inclusive)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
if (condition == null)
{
throw new ArgumentNullException("condition");
}
return reader.ReadUntil(ch => !condition(ch), inclusive);
}
public static string ReadWhiteSpace(this TextReader reader)
{
return reader.ReadWhile(c => Char.IsWhiteSpace(c));
}
public static string ReadUntilWhiteSpace(this TextReader reader)
{
return reader.ReadUntil(c => Char.IsWhiteSpace(c));
}
}
}

View File

@ -0,0 +1,550 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
using Microsoft.AspNet.Razor.Utils;
namespace Microsoft.AspNet.Razor.Parser
{
public abstract partial class TokenizerBackedParser<TTokenizer, TSymbol, TSymbolType> : ParserBase
where TTokenizer : Tokenizer<TSymbol, TSymbolType>
where TSymbol : SymbolBase<TSymbolType>
{
// Helpers
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This only occurs in Release builds, where this method is empty by design")]
[Conditional("DEBUG")]
internal void Assert(TSymbolType expectedType)
{
Debug.Assert(!EndOfFile && Equals(CurrentSymbol.Type, expectedType));
}
protected internal void PutBack(TSymbol symbol)
{
if (symbol != null)
{
Tokenizer.PutBack(symbol);
}
}
/// <summary>
/// Put the specified symbols back in the input stream. The provided list MUST be in the ORDER THE SYMBOLS WERE READ. The
/// list WILL be reversed and the Putback(TSymbol) will be called on each item.
/// </summary>
/// <remarks>
/// If a document contains symbols: a, b, c, d, e, f
/// and AcceptWhile or AcceptUntil is used to collect until d
/// the list returned by AcceptWhile/Until will contain: a, b, c IN THAT ORDER
/// that is the correct format for providing to this method. The caller of this method would,
/// in that case, want to put c, b and a back into the stream, so "a, b, c" is the CORRECT order
/// </remarks>
protected internal void PutBack(IEnumerable<TSymbol> symbols)
{
foreach (TSymbol symbol in symbols.Reverse())
{
PutBack(symbol);
}
}
protected internal void PutCurrentBack()
{
if (!EndOfFile && CurrentSymbol != null)
{
PutBack(CurrentSymbol);
}
}
protected internal bool Balance(BalancingModes mode)
{
TSymbolType left = CurrentSymbol.Type;
TSymbolType right = Language.FlipBracket(left);
SourceLocation start = CurrentLocation;
AcceptAndMoveNext();
if (EndOfFile && !mode.HasFlag(BalancingModes.NoErrorOnFailure))
{
Context.OnError(start,
RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
Language.GetSample(left),
Language.GetSample(right));
}
return Balance(mode, left, right, start);
}
protected internal bool Balance(BalancingModes mode, TSymbolType left, TSymbolType right, SourceLocation start)
{
int startPosition = CurrentLocation.AbsoluteIndex;
int nesting = 1;
if (!EndOfFile)
{
IList<TSymbol> syms = new List<TSymbol>();
do
{
if (IsAtEmbeddedTransition(
mode.HasFlag(BalancingModes.AllowCommentsAndTemplates),
mode.HasFlag(BalancingModes.AllowEmbeddedTransitions)))
{
Accept(syms);
syms.Clear();
HandleEmbeddedTransition();
// Reset backtracking since we've already outputted some spans.
startPosition = CurrentLocation.AbsoluteIndex;
}
if (At(left))
{
nesting++;
}
else if (At(right))
{
nesting--;
}
if (nesting > 0)
{
syms.Add(CurrentSymbol);
}
}
while (nesting > 0 && NextToken());
if (nesting > 0)
{
if (!mode.HasFlag(BalancingModes.NoErrorOnFailure))
{
Context.OnError(start,
RazorResources.ParseError_Expected_CloseBracket_Before_EOF,
Language.GetSample(left),
Language.GetSample(right));
}
if (mode.HasFlag(BalancingModes.BacktrackOnFailure))
{
Context.Source.Position = startPosition;
NextToken();
}
else
{
Accept(syms);
}
}
else
{
// Accept all the symbols we saw
Accept(syms);
}
}
return nesting == 0;
}
protected internal bool NextIs(TSymbolType type)
{
return NextIs(sym => sym != null && Equals(type, sym.Type));
}
protected internal bool NextIs(params TSymbolType[] types)
{
return NextIs(sym => sym != null && types.Any(t => Equals(t, sym.Type)));
}
protected internal bool NextIs(Func<TSymbol, bool> condition)
{
TSymbol cur = CurrentSymbol;
NextToken();
bool result = condition(CurrentSymbol);
PutCurrentBack();
PutBack(cur);
EnsureCurrent();
return result;
}
protected internal bool Was(TSymbolType type)
{
return PreviousSymbol != null && Equals(PreviousSymbol.Type, type);
}
protected internal bool At(TSymbolType type)
{
return !EndOfFile && CurrentSymbol != null && Equals(CurrentSymbol.Type, type);
}
protected internal bool AcceptAndMoveNext()
{
Accept(CurrentSymbol);
return NextToken();
}
protected TSymbol AcceptSingleWhiteSpaceCharacter()
{
if (Language.IsWhiteSpace(CurrentSymbol))
{
Tuple<TSymbol, TSymbol> pair = Language.SplitSymbol(CurrentSymbol, 1, Language.GetKnownSymbolType(KnownSymbolType.WhiteSpace));
Accept(pair.Item1);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
NextToken();
return pair.Item2;
}
return null;
}
protected internal void Accept(IEnumerable<TSymbol> symbols)
{
foreach (TSymbol symbol in symbols)
{
Accept(symbol);
}
}
protected internal void Accept(TSymbol symbol)
{
if (symbol != null)
{
foreach (RazorError error in symbol.Errors)
{
Context.Errors.Add(error);
}
Span.Accept(symbol);
}
}
protected internal bool AcceptAll(params TSymbolType[] types)
{
foreach (TSymbolType type in types)
{
if (CurrentSymbol == null || !Equals(CurrentSymbol.Type, type))
{
return false;
}
AcceptAndMoveNext();
}
return true;
}
protected internal void AddMarkerSymbolIfNecessary()
{
AddMarkerSymbolIfNecessary(CurrentLocation);
}
protected internal void AddMarkerSymbolIfNecessary(SourceLocation location)
{
if (Span.Symbols.Count == 0 && Context.LastAcceptedCharacters != AcceptedCharacters.Any)
{
Accept(Language.CreateMarkerSymbol(location));
}
}
protected internal void Output(SpanKind kind)
{
Configure(kind, null);
Output();
}
protected internal void Output(SpanKind kind, AcceptedCharacters accepts)
{
Configure(kind, accepts);
Output();
}
protected internal void Output(AcceptedCharacters accepts)
{
Configure(null, accepts);
Output();
}
private void Output()
{
if (Span.Symbols.Count > 0)
{
Context.AddSpan(Span.Build());
Initialize(Span);
}
}
protected IDisposable PushSpanConfig()
{
return PushSpanConfig(newConfig: (Action<SpanBuilder, Action<SpanBuilder>>)null);
}
protected IDisposable PushSpanConfig(Action<SpanBuilder> newConfig)
{
return PushSpanConfig(newConfig == null ? (Action<SpanBuilder, Action<SpanBuilder>>)null : (span, _) => newConfig(span));
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The Action<T> parameters are preferred over custom delegates")]
protected IDisposable PushSpanConfig(Action<SpanBuilder, Action<SpanBuilder>> newConfig)
{
Action<SpanBuilder> old = SpanConfig;
ConfigureSpan(newConfig);
return new DisposableAction(() => SpanConfig = old);
}
protected void ConfigureSpan(Action<SpanBuilder> config)
{
SpanConfig = config;
Initialize(Span);
}
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "The Action<T> parameters are preferred over custom delegates")]
protected void ConfigureSpan(Action<SpanBuilder, Action<SpanBuilder>> config)
{
Action<SpanBuilder> prev = SpanConfig;
if (config == null)
{
SpanConfig = null;
}
else
{
SpanConfig = span => config(span, prev);
}
Initialize(Span);
}
protected internal void Expected(KnownSymbolType type)
{
Expected(Language.GetKnownSymbolType(type));
}
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "types", Justification = "It is used in debug builds")]
protected internal void Expected(params TSymbolType[] types)
{
Debug.Assert(!EndOfFile && CurrentSymbol != null && types.Contains(CurrentSymbol.Type));
AcceptAndMoveNext();
}
protected internal bool Optional(KnownSymbolType type)
{
return Optional(Language.GetKnownSymbolType(type));
}
protected internal bool Optional(TSymbolType type)
{
if (At(type))
{
AcceptAndMoveNext();
return true;
}
return false;
}
protected internal bool Required(TSymbolType expected, bool errorIfNotFound, string errorBase)
{
bool found = At(expected);
if (!found && errorIfNotFound)
{
string error;
if (Language.IsNewLine(CurrentSymbol))
{
error = RazorResources.ErrorComponent_Newline;
}
else if (Language.IsWhiteSpace(CurrentSymbol))
{
error = RazorResources.ErrorComponent_Whitespace;
}
else if (EndOfFile)
{
error = RazorResources.ErrorComponent_EndOfFile;
}
else
{
error = String.Format(CultureInfo.CurrentCulture, RazorResources.ErrorComponent_Character, CurrentSymbol.Content);
}
Context.OnError(
CurrentLocation,
errorBase,
error);
}
return found;
}
protected bool EnsureCurrent()
{
if (CurrentSymbol == null)
{
return NextToken();
}
return true;
}
protected internal void AcceptWhile(TSymbolType type)
{
AcceptWhile(sym => Equals(type, sym.Type));
}
// We want to avoid array allocations and enumeration where possible, so we use the same technique as String.Format
protected internal void AcceptWhile(TSymbolType type1, TSymbolType type2)
{
AcceptWhile(sym => Equals(type1, sym.Type) || Equals(type2, sym.Type));
}
protected internal void AcceptWhile(TSymbolType type1, TSymbolType type2, TSymbolType type3)
{
AcceptWhile(sym => Equals(type1, sym.Type) || Equals(type2, sym.Type) || Equals(type3, sym.Type));
}
protected internal void AcceptWhile(params TSymbolType[] types)
{
AcceptWhile(sym => types.Any(expected => Equals(expected, sym.Type)));
}
protected internal void AcceptUntil(TSymbolType type)
{
AcceptWhile(sym => !Equals(type, sym.Type));
}
// We want to avoid array allocations and enumeration where possible, so we use the same technique as String.Format
protected internal void AcceptUntil(TSymbolType type1, TSymbolType type2)
{
AcceptWhile(sym => !Equals(type1, sym.Type) && !Equals(type2, sym.Type));
}
protected internal void AcceptUntil(TSymbolType type1, TSymbolType type2, TSymbolType type3)
{
AcceptWhile(sym => !Equals(type1, sym.Type) && !Equals(type2, sym.Type) && !Equals(type3, sym.Type));
}
protected internal void AcceptUntil(params TSymbolType[] types)
{
AcceptWhile(sym => types.All(expected => !Equals(expected, sym.Type)));
}
protected internal void AcceptWhile(Func<TSymbol, bool> condition)
{
Accept(ReadWhileLazy(condition));
}
protected internal IEnumerable<TSymbol> ReadWhile(Func<TSymbol, bool> condition)
{
return ReadWhileLazy(condition).ToList();
}
protected TSymbol AcceptWhiteSpaceInLines()
{
TSymbol lastWs = null;
while (Language.IsWhiteSpace(CurrentSymbol) || Language.IsNewLine(CurrentSymbol))
{
// Capture the previous whitespace node
if (lastWs != null)
{
Accept(lastWs);
}
if (Language.IsWhiteSpace(CurrentSymbol))
{
lastWs = CurrentSymbol;
}
else if (Language.IsNewLine(CurrentSymbol))
{
// Accept newline and reset last whitespace tracker
Accept(CurrentSymbol);
lastWs = null;
}
Tokenizer.Next();
}
return lastWs;
}
protected bool AtIdentifier(bool allowKeywords)
{
return CurrentSymbol != null &&
(Language.IsIdentifier(CurrentSymbol) ||
(allowKeywords && Language.IsKeyword(CurrentSymbol)));
}
// Don't open this to sub classes because it's lazy but it looks eager.
// You have to advance the Enumerable to read the next characters.
internal IEnumerable<TSymbol> ReadWhileLazy(Func<TSymbol, bool> condition)
{
while (EnsureCurrent() && condition(CurrentSymbol))
{
yield return CurrentSymbol;
NextToken();
}
}
private void Configure(SpanKind? kind, AcceptedCharacters? accepts)
{
if (kind != null)
{
Span.Kind = kind.Value;
}
if (accepts != null)
{
Span.EditHandler.AcceptedCharacters = accepts.Value;
}
}
protected virtual void OutputSpanBeforeRazorComment()
{
throw new InvalidOperationException(RazorResources.Language_Does_Not_Support_RazorComment);
}
private void CommentSpanConfig(SpanBuilder span)
{
span.CodeGenerator = SpanCodeGenerator.Null;
span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
}
protected void RazorComment()
{
if (!Language.KnowsSymbolType(KnownSymbolType.CommentStart) ||
!Language.KnowsSymbolType(KnownSymbolType.CommentStar) ||
!Language.KnowsSymbolType(KnownSymbolType.CommentBody))
{
throw new InvalidOperationException(RazorResources.Language_Does_Not_Support_RazorComment);
}
OutputSpanBeforeRazorComment();
using (PushSpanConfig(CommentSpanConfig))
{
using (Context.StartBlock(BlockType.Comment))
{
Context.CurrentBlock.CodeGenerator = new RazorCommentCodeGenerator();
SourceLocation start = CurrentLocation;
Expected(KnownSymbolType.CommentStart);
Output(SpanKind.Transition, AcceptedCharacters.None);
Expected(KnownSymbolType.CommentStar);
Output(SpanKind.MetaCode, AcceptedCharacters.None);
Optional(KnownSymbolType.CommentBody);
AddMarkerSymbolIfNecessary();
Output(SpanKind.Comment);
bool errorReported = false;
if (!Optional(KnownSymbolType.CommentStar))
{
errorReported = true;
Context.OnError(start, RazorResources.ParseError_RazorComment_Not_Terminated);
}
else
{
Output(SpanKind.MetaCode, AcceptedCharacters.None);
}
if (!Optional(KnownSymbolType.CommentStart))
{
if (!errorReported)
{
errorReported = true;
Context.OnError(start, RazorResources.ParseError_RazorComment_Not_Terminated);
}
}
else
{
Output(SpanKind.Transition, AcceptedCharacters.None);
}
}
}
Initialize(Span);
}
}
}

View File

@ -0,0 +1,88 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
[SuppressMessage("Microsoft.Design", "CA1005:AvoidExcessiveParametersOnGenericTypes", Justification = "All generic type parameters are required")]
public abstract partial class TokenizerBackedParser<TTokenizer, TSymbol, TSymbolType> : ParserBase
where TTokenizer : Tokenizer<TSymbol, TSymbolType>
where TSymbol : SymbolBase<TSymbolType>
{
private TokenizerView<TTokenizer, TSymbol, TSymbolType> _tokenizer;
protected TokenizerBackedParser()
{
Span = new SpanBuilder();
}
protected SpanBuilder Span { get; set; }
protected TokenizerView<TTokenizer, TSymbol, TSymbolType> Tokenizer
{
get { return _tokenizer ?? InitTokenizer(); }
}
protected Action<SpanBuilder> SpanConfig { get; set; }
protected TSymbol CurrentSymbol
{
get { return Tokenizer.Current; }
}
protected TSymbol PreviousSymbol { get; private set; }
protected SourceLocation CurrentLocation
{
get { return (EndOfFile || CurrentSymbol == null) ? Context.Source.Location : CurrentSymbol.Start; }
}
protected bool EndOfFile
{
get { return Tokenizer.EndOfFile; }
}
protected abstract LanguageCharacteristics<TTokenizer, TSymbol, TSymbolType> Language { get; }
protected virtual void HandleEmbeddedTransition()
{
}
protected virtual bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
{
return false;
}
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
{
foreach (ISymbol sym in Language.TokenizeString(start, content))
{
span.Accept(sym);
}
}
protected void Initialize(SpanBuilder span)
{
if (SpanConfig != null)
{
SpanConfig(span);
}
}
protected internal bool NextToken()
{
PreviousSymbol = CurrentSymbol;
return Tokenizer.Next();
}
private TokenizerView<TTokenizer, TSymbol, TSymbolType> InitTokenizer()
{
return _tokenizer = new TokenizerView<TTokenizer, TSymbol, TSymbolType>(Language.CreateTokenizer(Context.Source));
}
}
}

View File

@ -0,0 +1,411 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class VBCodeParser : TokenizerBackedParser<VBTokenizer, VBSymbol, VBSymbolType>
{
private void SetUpDirectives()
{
MapDirective(SyntaxConstants.VB.CodeKeyword, EndTerminatedDirective(SyntaxConstants.VB.CodeKeyword,
BlockType.Statement,
new StatementCodeGenerator(),
allowMarkup: true));
MapDirective(SyntaxConstants.VB.FunctionsKeyword, EndTerminatedDirective(SyntaxConstants.VB.FunctionsKeyword,
BlockType.Functions,
new TypeMemberCodeGenerator(),
allowMarkup: false));
MapDirective(SyntaxConstants.VB.SectionKeyword, SectionDirective);
MapDirective(SyntaxConstants.VB.HelperKeyword, HelperDirective);
MapDirective(SyntaxConstants.VB.LayoutKeyword, LayoutDirective);
MapDirective(SyntaxConstants.VB.SessionStateKeyword, SessionStateDirective);
}
protected virtual bool LayoutDirective()
{
AssertDirective(SyntaxConstants.VB.LayoutKeyword);
AcceptAndMoveNext();
Context.CurrentBlock.Type = BlockType.Directive;
// Accept spaces, but not newlines
bool foundSomeWhitespace = At(VBSymbolType.WhiteSpace);
AcceptWhile(VBSymbolType.WhiteSpace);
Output(SpanKind.MetaCode, foundSomeWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any);
// First non-whitespace character starts the Layout Page, then newline ends it
AcceptUntil(VBSymbolType.NewLine);
Span.CodeGenerator = new SetLayoutCodeGenerator(Span.GetContent());
Span.EditHandler.EditorHints = EditorHints.LayoutPage | EditorHints.VirtualPath;
bool foundNewline = Optional(VBSymbolType.NewLine);
AddMarkerSymbolIfNecessary();
Output(SpanKind.MetaCode, foundNewline ? AcceptedCharacters.None : AcceptedCharacters.Any);
return true;
}
protected virtual bool SessionStateDirective()
{
AssertDirective(SyntaxConstants.VB.SessionStateKeyword);
AcceptAndMoveNext();
Context.CurrentBlock.Type = BlockType.Directive;
// Accept spaces, but not newlines
bool foundSomeWhitespace = At(VBSymbolType.WhiteSpace);
AcceptWhile(VBSymbolType.WhiteSpace);
Output(SpanKind.MetaCode, foundSomeWhitespace ? AcceptedCharacters.None : AcceptedCharacters.Any);
// First non-whitespace character starts the session state directive, then newline ends it
AcceptUntil(VBSymbolType.NewLine);
var value = String.Concat(Span.Symbols.Select(sym => sym.Content));
Span.CodeGenerator = new RazorDirectiveAttributeCodeGenerator(SyntaxConstants.VB.SessionStateKeyword, value);
bool foundNewline = Optional(VBSymbolType.NewLine);
AddMarkerSymbolIfNecessary();
Output(SpanKind.MetaCode, foundNewline ? AcceptedCharacters.None : AcceptedCharacters.Any);
return true;
}
protected virtual bool HelperDirective()
{
if (Context.IsWithin(BlockType.Helper))
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Helpers_Cannot_Be_Nested);
}
Context.CurrentBlock.Type = BlockType.Helper;
SourceLocation blockStart = CurrentLocation;
AssertDirective(SyntaxConstants.VB.HelperKeyword);
AcceptAndMoveNext();
VBSymbolType firstAfterKeyword = VBSymbolType.Unknown;
if (CurrentSymbol != null)
{
firstAfterKeyword = CurrentSymbol.Type;
}
VBSymbol remainingWs = null;
if (At(VBSymbolType.NewLine))
{
// Accept a _single_ new line, we'll be aborting later.
AcceptAndMoveNext();
}
else
{
remainingWs = AcceptSingleWhiteSpaceCharacter();
}
if (firstAfterKeyword == VBSymbolType.WhiteSpace || firstAfterKeyword == VBSymbolType.NewLine)
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
Output(SpanKind.MetaCode);
if (firstAfterKeyword != VBSymbolType.WhiteSpace)
{
string error;
if (At(VBSymbolType.NewLine))
{
error = RazorResources.ErrorComponent_Newline;
}
else if (EndOfFile)
{
error = RazorResources.ErrorComponent_EndOfFile;
}
else
{
error = String.Format(CultureInfo.CurrentCulture, RazorResources.ErrorComponent_Character, CurrentSymbol.Content);
}
Context.OnError(
CurrentLocation,
RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start,
error);
// Bail out.
PutCurrentBack();
Output(SpanKind.Code);
return false;
}
if (remainingWs != null)
{
Accept(remainingWs);
}
bool errorReported = !Required(VBSymbolType.Identifier, RazorResources.ParseError_Unexpected_Character_At_Helper_Name_Start);
AcceptWhile(VBSymbolType.WhiteSpace);
SourceLocation parensStart = CurrentLocation;
bool headerComplete = false;
if (!Optional(VBSymbolType.LeftParenthesis))
{
if (!errorReported)
{
errorReported = true;
Context.OnError(CurrentLocation,
RazorResources.ParseError_MissingCharAfterHelperName,
VBSymbol.GetSample(VBSymbolType.LeftParenthesis));
}
}
else if (!Balance(BalancingModes.NoErrorOnFailure, VBSymbolType.LeftParenthesis, VBSymbolType.RightParenthesis, parensStart))
{
Context.OnError(parensStart, RazorResources.ParseError_UnterminatedHelperParameterList);
}
else
{
Expected(VBSymbolType.RightParenthesis);
headerComplete = true;
}
AddMarkerSymbolIfNecessary();
Context.CurrentBlock.CodeGenerator = new HelperCodeGenerator(
Span.GetContent(),
headerComplete);
AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
Span.EditHandler = editHandler;
Output(SpanKind.Code);
if (headerComplete)
{
bool old = IsNested;
IsNested = true;
using (Context.StartBlock(BlockType.Statement))
{
using (PushSpanConfig(StatementBlockSpanConfiguration(new StatementCodeGenerator())))
{
try
{
if (!EndTerminatedDirectiveBody(SyntaxConstants.VB.HelperKeyword, blockStart, allowAllTransitions: true))
{
if (Context.LastAcceptedCharacters != AcceptedCharacters.Any)
{
AddMarkerSymbolIfNecessary();
}
editHandler.AutoCompleteString = SyntaxConstants.VB.EndHelperKeyword;
return false;
}
else
{
return true;
}
}
finally
{
Output(SpanKind.Code);
IsNested = old;
}
}
}
}
else
{
Output(SpanKind.Code);
}
PutCurrentBack();
return false;
}
protected virtual bool SectionDirective()
{
SourceLocation start = CurrentLocation;
AssertDirective(SyntaxConstants.VB.SectionKeyword);
AcceptAndMoveNext();
if (Context.IsWithin(BlockType.Section))
{
Context.OnError(CurrentLocation, RazorResources.ParseError_Sections_Cannot_Be_Nested, RazorResources.SectionExample_VB);
}
if (At(VBSymbolType.NewLine))
{
AcceptAndMoveNext();
}
else
{
AcceptVBSpaces();
}
string sectionName = null;
if (!At(VBSymbolType.Identifier))
{
Context.OnError(CurrentLocation,
RazorResources.ParseError_Unexpected_Character_At_Section_Name_Start,
GetCurrentSymbolDisplay());
}
else
{
sectionName = CurrentSymbol.Content;
AcceptAndMoveNext();
}
Context.CurrentBlock.Type = BlockType.Section;
Context.CurrentBlock.CodeGenerator = new SectionCodeGenerator(sectionName ?? String.Empty);
AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
Span.EditHandler = editHandler;
PutCurrentBack();
Output(SpanKind.MetaCode);
// Parse the section
OtherParserBlock(null, SyntaxConstants.VB.EndSectionKeyword);
Span.CodeGenerator = SpanCodeGenerator.Null;
bool complete = false;
if (!At(VBKeyword.End))
{
Context.OnError(start,
RazorResources.ParseError_BlockNotTerminated,
SyntaxConstants.VB.SectionKeyword,
SyntaxConstants.VB.EndSectionKeyword);
editHandler.AutoCompleteString = SyntaxConstants.VB.EndSectionKeyword;
}
else
{
AcceptAndMoveNext();
AcceptWhile(VBSymbolType.WhiteSpace);
if (!At(SyntaxConstants.VB.SectionKeyword))
{
Context.OnError(start,
RazorResources.ParseError_BlockNotTerminated,
SyntaxConstants.VB.SectionKeyword,
SyntaxConstants.VB.EndSectionKeyword);
}
else
{
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
complete = true;
}
}
PutCurrentBack();
Output(SpanKind.MetaCode);
return complete;
}
protected virtual Func<bool> EndTerminatedDirective(string directive, BlockType blockType, SpanCodeGenerator codeGenerator, bool allowMarkup)
{
return () =>
{
SourceLocation blockStart = CurrentLocation;
Context.CurrentBlock.Type = blockType;
AssertDirective(directive);
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.MetaCode);
using (PushSpanConfig(StatementBlockSpanConfiguration(codeGenerator)))
{
AutoCompleteEditHandler editHandler = new AutoCompleteEditHandler(Language.TokenizeString);
Span.EditHandler = editHandler;
if (!EndTerminatedDirectiveBody(directive, blockStart, allowMarkup))
{
editHandler.AutoCompleteString = String.Concat(SyntaxConstants.VB.EndKeyword, " ", directive);
return false;
}
return true;
}
};
}
protected virtual bool EndTerminatedDirectiveBody(string directive, SourceLocation blockStart, bool allowAllTransitions)
{
while (!EndOfFile)
{
VBSymbol lastWhitespace = AcceptWhiteSpaceInLines();
if (IsAtEmbeddedTransition(allowTemplatesAndComments: allowAllTransitions, allowTransitions: allowAllTransitions))
{
HandleEmbeddedTransition(lastWhitespace);
}
else
{
if (At(VBKeyword.End))
{
Accept(lastWhitespace);
VBSymbol end = CurrentSymbol;
NextToken();
IEnumerable<VBSymbol> ws = ReadVBSpaces();
if (At(directive))
{
if (Context.LastAcceptedCharacters != AcceptedCharacters.Any)
{
AddMarkerSymbolIfNecessary(end.Start);
}
Output(SpanKind.Code);
Accept(end);
Accept(ws);
AcceptAndMoveNext();
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.MetaCode);
return true;
}
else
{
Accept(end);
Accept(ws);
AcceptAndMoveNext();
}
}
else
{
Accept(lastWhitespace);
AcceptAndMoveNext();
}
}
}
// This is a language keyword, so it does not need to be localized
Context.OnError(blockStart, RazorResources.ParseError_BlockNotTerminated, directive, String.Concat(SyntaxConstants.VB.EndKeyword, " ", directive));
return false;
}
protected bool At(string directive)
{
return At(VBSymbolType.Identifier) && String.Equals(CurrentSymbol.Content, directive, StringComparison.OrdinalIgnoreCase);
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "'this' is used in DEBUG builds")]
[Conditional("DEBUG")]
protected void AssertDirective(string directive)
{
Assert(VBSymbolType.Identifier);
Debug.Assert(String.Equals(directive, CurrentSymbol.Content, StringComparison.OrdinalIgnoreCase));
}
private string GetCurrentSymbolDisplay()
{
if (EndOfFile)
{
return RazorResources.ErrorComponent_EndOfFile;
}
else if (At(VBSymbolType.NewLine))
{
return RazorResources.ErrorComponent_Newline;
}
else if (At(VBSymbolType.WhiteSpace))
{
return RazorResources.ErrorComponent_Whitespace;
}
else
{
return String.Format(CultureInfo.CurrentCulture, RazorResources.ErrorComponent_Character, CurrentSymbol.Content);
}
}
}
}

View File

@ -0,0 +1,315 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class VBCodeParser : TokenizerBackedParser<VBTokenizer, VBSymbol, VBSymbolType>
{
private void SetUpKeywords()
{
MapKeyword(VBKeyword.Using, EndTerminatedStatement(VBKeyword.Using, supportsExit: false, supportsContinue: false)); // http://msdn.microsoft.com/en-us/library/htd05whh.aspx
MapKeyword(VBKeyword.While, EndTerminatedStatement(VBKeyword.While, supportsExit: true, supportsContinue: true)); // http://msdn.microsoft.com/en-us/library/zh1f56zs.aspx
MapKeyword(VBKeyword.If, EndTerminatedStatement(VBKeyword.If, supportsExit: false, supportsContinue: false)); // http://msdn.microsoft.com/en-us/library/752y8abs.aspx
MapKeyword(VBKeyword.Select, EndTerminatedStatement(VBKeyword.Select, supportsExit: true, supportsContinue: false, blockName: SyntaxConstants.VB.SelectCaseKeyword)); // http://msdn.microsoft.com/en-us/library/cy37t14y.aspx
MapKeyword(VBKeyword.Try, EndTerminatedStatement(VBKeyword.Try, supportsExit: true, supportsContinue: false)); // http://msdn.microsoft.com/en-us/library/fk6t46tz.aspx
MapKeyword(VBKeyword.With, EndTerminatedStatement(VBKeyword.With, supportsExit: false, supportsContinue: false)); // http://msdn.microsoft.com/en-us/library/wc500chb.aspx
MapKeyword(VBKeyword.SyncLock, EndTerminatedStatement(VBKeyword.SyncLock, supportsExit: false, supportsContinue: false)); // http://msdn.microsoft.com/en-us/library/3a86s51t.aspx
// http://msdn.microsoft.com/en-us/library/5z06z1kb.aspx
// http://msdn.microsoft.com/en-us/library/5ebk1751.aspx
MapKeyword(VBKeyword.For, KeywordTerminatedStatement(VBKeyword.For, VBKeyword.Next, supportsExit: true, supportsContinue: true));
MapKeyword(VBKeyword.Do, KeywordTerminatedStatement(VBKeyword.Do, VBKeyword.Loop, supportsExit: true, supportsContinue: true)); // http://msdn.microsoft.com/en-us/library/eked04a7.aspx
MapKeyword(VBKeyword.Imports, ImportsStatement);
MapKeyword(VBKeyword.Option, OptionStatement);
MapKeyword(VBKeyword.Inherits, InheritsStatement);
MapKeyword(VBKeyword.Class, ReservedWord);
MapKeyword(VBKeyword.Namespace, ReservedWord);
}
protected virtual bool InheritsStatement()
{
Assert(VBKeyword.Inherits);
Span.CodeGenerator = SpanCodeGenerator.Null;
Context.CurrentBlock.Type = BlockType.Directive;
AcceptAndMoveNext();
SourceLocation endInherits = CurrentLocation;
if (At(VBSymbolType.WhiteSpace))
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
}
AcceptWhile(VBSymbolType.WhiteSpace);
Output(SpanKind.MetaCode);
if (EndOfFile || At(VBSymbolType.WhiteSpace) || At(VBSymbolType.NewLine))
{
Context.OnError(endInherits, RazorResources.ParseError_InheritsKeyword_Must_Be_Followed_By_TypeName);
}
// Just accept to a newline
AcceptUntil(VBSymbolType.NewLine);
if (!Context.DesignTimeMode)
{
// We want the newline to be treated as code, but it causes issues at design-time.
Optional(VBSymbolType.NewLine);
}
string baseType = Span.GetContent();
Span.CodeGenerator = new SetBaseTypeCodeGenerator(baseType.Trim());
Output(SpanKind.Code);
return false;
}
protected virtual bool OptionStatement()
{
try
{
Context.CurrentBlock.Type = BlockType.Directive;
Assert(VBKeyword.Option);
AcceptAndMoveNext();
AcceptWhile(VBSymbolType.WhiteSpace);
if (!At(VBSymbolType.Identifier))
{
if (CurrentSymbol != null)
{
Context.OnError(CurrentLocation, String.Format(CultureInfo.CurrentCulture,
RazorResources.ParseError_Unexpected,
CurrentSymbol.Content));
}
return false;
}
SourceLocation optionLoc = CurrentLocation;
string option = CurrentSymbol.Content;
AcceptAndMoveNext();
AcceptWhile(VBSymbolType.WhiteSpace);
bool boolVal;
if (At(VBKeyword.On))
{
AcceptAndMoveNext();
boolVal = true;
}
else if (At(VBSymbolType.Identifier))
{
if (String.Equals(CurrentSymbol.Content, SyntaxConstants.VB.OffKeyword, StringComparison.OrdinalIgnoreCase))
{
AcceptAndMoveNext();
boolVal = false;
}
else
{
Context.OnError(CurrentLocation, String.Format(CultureInfo.CurrentCulture,
RazorResources.ParseError_InvalidOptionValue,
option,
CurrentSymbol.Content));
AcceptAndMoveNext();
return false;
}
}
else
{
if (!EndOfFile)
{
Context.OnError(CurrentLocation, String.Format(CultureInfo.CurrentCulture,
RazorResources.ParseError_Unexpected,
CurrentSymbol.Content));
AcceptAndMoveNext();
}
return false;
}
if (String.Equals(option, SyntaxConstants.VB.StrictKeyword, StringComparison.OrdinalIgnoreCase))
{
Span.CodeGenerator = SetVBOptionCodeGenerator.Strict(boolVal);
}
else if (String.Equals(option, SyntaxConstants.VB.ExplicitKeyword, StringComparison.OrdinalIgnoreCase))
{
Span.CodeGenerator = SetVBOptionCodeGenerator.Explicit(boolVal);
}
else
{
Span.CodeGenerator = new SetVBOptionCodeGenerator(option, boolVal);
Context.OnError(optionLoc, RazorResources.ParseError_UnknownOption, option);
}
}
finally
{
if (Span.Symbols.Count > 0)
{
Output(SpanKind.MetaCode);
}
}
return true;
}
protected virtual bool ImportsStatement()
{
Context.CurrentBlock.Type = BlockType.Directive;
Assert(VBKeyword.Imports);
AcceptAndMoveNext();
AcceptVBSpaces();
if (At(VBSymbolType.WhiteSpace) || At(VBSymbolType.NewLine))
{
Context.OnError(CurrentLocation, RazorResources.ParseError_NamespaceOrTypeAliasExpected);
}
// Just accept to a newline
AcceptUntil(VBSymbolType.NewLine);
Optional(VBSymbolType.NewLine);
string ns = String.Concat(Span.Symbols.Skip(1).Select(s => s.Content));
Span.CodeGenerator = new AddImportCodeGenerator(ns, SyntaxConstants.VB.ImportsKeywordLength);
Output(SpanKind.MetaCode);
return false;
}
protected virtual Func<bool> EndTerminatedStatement(VBKeyword keyword, bool supportsExit, bool supportsContinue)
{
return EndTerminatedStatement(keyword, supportsExit, supportsContinue, blockName: keyword.ToString());
}
protected virtual Func<bool> EndTerminatedStatement(VBKeyword keyword, bool supportsExit, bool supportsContinue, string blockName)
{
return () =>
{
using (PushSpanConfig(StatementBlockSpanConfiguration(new StatementCodeGenerator())))
{
SourceLocation blockStart = CurrentLocation;
Assert(keyword);
AcceptAndMoveNext();
while (!EndOfFile)
{
VBSymbol lastWhitespace = AcceptWhiteSpaceInLines();
if (IsAtEmbeddedTransition(allowTemplatesAndComments: true, allowTransitions: true))
{
HandleEmbeddedTransition(lastWhitespace);
}
else
{
Accept(lastWhitespace);
if ((supportsExit && At(VBKeyword.Exit)) || (supportsContinue && At(VBKeyword.Continue)))
{
HandleExitOrContinue(keyword);
}
else if (At(VBKeyword.End))
{
AcceptAndMoveNext();
AcceptVBSpaces();
if (At(keyword))
{
AcceptAndMoveNext();
if (!Context.DesignTimeMode)
{
Optional(VBSymbolType.NewLine);
}
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
return false;
}
}
else if (At(keyword))
{
// Parse nested statement
EndTerminatedStatement(keyword, supportsExit, supportsContinue)();
}
else if (!EndOfFile)
{
AcceptAndMoveNext();
}
}
}
Context.OnError(blockStart,
RazorResources.ParseError_BlockNotTerminated,
blockName,
// This is a language keyword, so it does not need to be localized
String.Concat(VBKeyword.End, " ", keyword));
return false;
}
};
}
protected virtual Func<bool> KeywordTerminatedStatement(VBKeyword start, VBKeyword terminator, bool supportsExit, bool supportsContinue)
{
return () =>
{
using (PushSpanConfig(StatementBlockSpanConfiguration(new StatementCodeGenerator())))
{
SourceLocation blockStart = CurrentLocation;
Assert(start);
AcceptAndMoveNext();
while (!EndOfFile)
{
VBSymbol lastWhitespace = AcceptWhiteSpaceInLines();
if (IsAtEmbeddedTransition(allowTemplatesAndComments: true, allowTransitions: true))
{
HandleEmbeddedTransition(lastWhitespace);
}
else
{
Accept(lastWhitespace);
if ((supportsExit && At(VBKeyword.Exit)) || (supportsContinue && At(VBKeyword.Continue)))
{
HandleExitOrContinue(start);
}
else if (At(start))
{
// Parse nested statement
KeywordTerminatedStatement(start, terminator, supportsExit, supportsContinue)();
}
else if (At(terminator))
{
AcceptUntil(VBSymbolType.NewLine);
Optional(VBSymbolType.NewLine);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.AnyExceptNewline;
return false;
}
else if (!EndOfFile)
{
AcceptAndMoveNext();
}
}
}
Context.OnError(blockStart,
RazorResources.ParseError_BlockNotTerminated,
start, terminator);
return false;
}
};
}
protected void HandleExitOrContinue(VBKeyword keyword)
{
Assert(VBSymbolType.Keyword);
Debug.Assert(CurrentSymbol.Keyword == VBKeyword.Continue || CurrentSymbol.Keyword == VBKeyword.Exit);
// Accept, read whitespace and look for the next keyword
AcceptAndMoveNext();
AcceptWhile(VBSymbolType.WhiteSpace);
// If this is the start keyword, skip it and continue (to avoid starting a nested statement block)
Optional(keyword);
}
}
}

View File

@ -0,0 +1,601 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.AspNet.Razor.Editor;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Resources;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public partial class VBCodeParser : TokenizerBackedParser<VBTokenizer, VBSymbol, VBSymbolType>
{
internal static ISet<string> DefaultKeywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"functions",
"code",
"section",
"do",
"while",
"if",
"select",
"for",
"try",
"with",
"synclock",
"using",
"imports",
"inherits",
"option",
"helper",
"namespace",
"class",
"layout",
"sessionstate"
};
private Dictionary<VBKeyword, Func<bool>> _keywordHandlers = new Dictionary<VBKeyword, Func<bool>>();
private Dictionary<string, Func<bool>> _directiveHandlers = new Dictionary<string, Func<bool>>(StringComparer.OrdinalIgnoreCase);
[SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Necessary state is initialized before calling virtual methods")]
public VBCodeParser()
{
DirectParentIsCode = false;
Keywords = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
SetUpKeywords();
SetUpDirectives();
}
protected internal ISet<string> Keywords { get; private set; }
protected override LanguageCharacteristics<VBTokenizer, VBSymbol, VBSymbolType> Language
{
get { return VBLanguageCharacteristics.Instance; }
}
protected override ParserBase OtherParser
{
get { return Context.MarkupParser; }
}
private bool IsNested { get; set; }
private bool DirectParentIsCode { get; set; }
protected override bool IsAtEmbeddedTransition(bool allowTemplatesAndComments, bool allowTransitions)
{
return (allowTransitions && Language.IsTransition(CurrentSymbol) && !Was(VBSymbolType.Dot)) ||
(allowTemplatesAndComments && Language.IsCommentStart(CurrentSymbol)) ||
(Language.IsTransition(CurrentSymbol) && NextIs(VBSymbolType.Transition));
}
protected override void HandleEmbeddedTransition()
{
HandleEmbeddedTransition(null);
}
protected void HandleEmbeddedTransition(VBSymbol lastWhiteSpace)
{
if (At(VBSymbolType.RazorCommentTransition))
{
Accept(lastWhiteSpace);
RazorComment();
}
else if ((At(VBSymbolType.Transition) && !Was(VBSymbolType.Dot)))
{
HandleTransition(lastWhiteSpace);
}
}
public override void ParseBlock()
{
if (Context == null)
{
throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
}
using (PushSpanConfig())
{
if (Context == null)
{
throw new InvalidOperationException(RazorResources.Parser_Context_Not_Set);
}
Initialize(Span);
NextToken();
using (Context.StartBlock())
{
IEnumerable<VBSymbol> syms = ReadWhile(sym => sym.Type == VBSymbolType.WhiteSpace);
if (At(VBSymbolType.Transition))
{
Accept(syms);
Span.CodeGenerator = new StatementCodeGenerator();
Output(SpanKind.Code);
}
else
{
PutBack(syms);
EnsureCurrent();
}
// Allow a transition span, but don't require it
if (Optional(VBSymbolType.Transition))
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.Transition);
}
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
// Determine the type of the block
bool isComplete = false;
Action<SpanBuilder> config = null;
if (!EndOfFile)
{
switch (CurrentSymbol.Type)
{
case VBSymbolType.Identifier:
if (!TryDirectiveBlock(ref isComplete))
{
ImplicitExpression();
}
break;
case VBSymbolType.LeftParenthesis:
isComplete = ExplicitExpression();
break;
case VBSymbolType.Keyword:
Context.CurrentBlock.Type = BlockType.Statement;
Context.CurrentBlock.CodeGenerator = BlockCodeGenerator.Null;
isComplete = KeywordBlock();
break;
case VBSymbolType.WhiteSpace:
case VBSymbolType.NewLine:
config = ImplictExpressionSpanConfig;
Context.OnError(CurrentLocation,
RazorResources.ParseError_Unexpected_WhiteSpace_At_Start_Of_CodeBlock_VB);
break;
default:
config = ImplictExpressionSpanConfig;
Context.OnError(CurrentLocation,
RazorResources.ParseError_Unexpected_Character_At_Start_Of_CodeBlock_VB,
CurrentSymbol.Content);
break;
}
}
else
{
config = ImplictExpressionSpanConfig;
Context.OnError(CurrentLocation,
RazorResources.ParseError_Unexpected_EndOfFile_At_Start_Of_CodeBlock);
}
using (PushSpanConfig(config))
{
if (!isComplete && Span.Symbols.Count == 0 && Context.LastAcceptedCharacters != AcceptedCharacters.Any)
{
AddMarkerSymbolIfNecessary();
}
Output(SpanKind.Code);
PutCurrentBack();
}
}
}
}
private void ImplictExpressionSpanConfig(SpanBuilder span)
{
span.CodeGenerator = new ExpressionCodeGenerator();
span.EditHandler = new ImplicitExpressionEditHandler(
Language.TokenizeString,
Keywords,
acceptTrailingDot: DirectParentIsCode)
{
AcceptedCharacters = AcceptedCharacters.NonWhiteSpace
};
}
private Action<SpanBuilder> StatementBlockSpanConfiguration(SpanCodeGenerator codeGenerator)
{
return span =>
{
span.Kind = SpanKind.Code;
span.CodeGenerator = codeGenerator;
span.EditHandler = SpanEditHandler.CreateDefault(Language.TokenizeString);
};
}
// Pass "complete" flag by ref, not out because some paths may not change it.
private bool TryDirectiveBlock(ref bool complete)
{
Assert(VBSymbolType.Identifier);
Func<bool> handler;
if (_directiveHandlers.TryGetValue(CurrentSymbol.Content, out handler))
{
Context.CurrentBlock.CodeGenerator = BlockCodeGenerator.Null;
complete = handler();
return true;
}
return false;
}
private bool KeywordBlock()
{
Assert(VBSymbolType.Keyword);
Func<bool> handler;
if (_keywordHandlers.TryGetValue(CurrentSymbol.Keyword.Value, out handler))
{
Span.CodeGenerator = new StatementCodeGenerator();
Context.CurrentBlock.Type = BlockType.Statement;
return handler();
}
else
{
ImplicitExpression();
return false;
}
}
private bool ExplicitExpression()
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
SourceLocation start = CurrentLocation;
Expected(VBSymbolType.LeftParenthesis);
Span.CodeGenerator = SpanCodeGenerator.Null;
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Output(SpanKind.MetaCode);
Span.CodeGenerator = new ExpressionCodeGenerator();
using (PushSpanConfig(span => span.CodeGenerator = new ExpressionCodeGenerator()))
{
if (!Balance(BalancingModes.NoErrorOnFailure |
BalancingModes.BacktrackOnFailure |
BalancingModes.AllowCommentsAndTemplates,
VBSymbolType.LeftParenthesis,
VBSymbolType.RightParenthesis,
start))
{
Context.OnError(start,
RazorResources.ParseError_Expected_EndOfBlock_Before_EOF,
RazorResources.BlockName_ExplicitExpression,
VBSymbol.GetSample(VBSymbolType.RightParenthesis),
VBSymbol.GetSample(VBSymbolType.LeftParenthesis));
AcceptUntil(VBSymbolType.NewLine);
AddMarkerSymbolIfNecessary();
Output(SpanKind.Code);
PutCurrentBack();
return false;
}
else
{
AddMarkerSymbolIfNecessary();
Output(SpanKind.Code);
Expected(VBSymbolType.RightParenthesis);
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.None;
Span.CodeGenerator = SpanCodeGenerator.Null;
Output(SpanKind.MetaCode);
PutCurrentBack();
return true;
}
}
}
private void ImplicitExpression()
{
Context.CurrentBlock.Type = BlockType.Expression;
Context.CurrentBlock.CodeGenerator = new ExpressionCodeGenerator();
using (PushSpanConfig(ImplictExpressionSpanConfig))
{
Expected(VBSymbolType.Identifier, VBSymbolType.Keyword);
Span.CodeGenerator = new ExpressionCodeGenerator();
while (!EndOfFile)
{
switch (CurrentSymbol.Type)
{
case VBSymbolType.LeftParenthesis:
SourceLocation start = CurrentLocation;
AcceptAndMoveNext();
Action<SpanBuilder> oldConfig = SpanConfig;
using (PushSpanConfig())
{
ConfigureSpan(span =>
{
oldConfig(span);
span.EditHandler.AcceptedCharacters = AcceptedCharacters.Any;
});
Balance(BalancingModes.AllowCommentsAndTemplates,
VBSymbolType.LeftParenthesis,
VBSymbolType.RightParenthesis,
start);
}
if (Optional(VBSymbolType.RightParenthesis))
{
Span.EditHandler.AcceptedCharacters = AcceptedCharacters.NonWhiteSpace;
}
break;
case VBSymbolType.Dot:
VBSymbol dot = CurrentSymbol;
NextToken();
if (At(VBSymbolType.Identifier) || At(VBSymbolType.Keyword))
{
Accept(dot);
AcceptAndMoveNext();
}
else if (At(VBSymbolType.Transition))
{
VBSymbol at = CurrentSymbol;
NextToken();
if (At(VBSymbolType.Identifier) || At(VBSymbolType.Keyword))
{
Accept(dot);
Accept(at);
AcceptAndMoveNext();
}
else
{
PutBack(at);
PutBack(dot);
}
}
else
{
PutCurrentBack();
if (IsNested)
{
Accept(dot);
}
else
{
PutBack(dot);
}
return;
}
break;
default:
PutCurrentBack();
return;
}
}
}
}
protected void MapKeyword(VBKeyword keyword, Func<bool> action)
{
_keywordHandlers[keyword] = action;
Keywords.Add(keyword.ToString());
}
protected void MapDirective(string directive, Func<bool> action)
{
_directiveHandlers[directive] = action;
Keywords.Add(directive);
}
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This only occurs in Release builds, where this method is empty by design")]
[Conditional("DEBUG")]
protected void Assert(VBKeyword keyword)
{
Debug.Assert(CurrentSymbol.Type == VBSymbolType.Keyword && CurrentSymbol.Keyword == keyword);
}
protected bool At(VBKeyword keyword)
{
return At(VBSymbolType.Keyword) && CurrentSymbol.Keyword == keyword;
}
protected void OtherParserBlock()
{
OtherParserBlock(null, null);
}
protected void OtherParserBlock(string startSequence, string endSequence)
{
using (PushSpanConfig())
{
if (Span.Symbols.Count > 0)
{
Output(SpanKind.Code);
}
Context.SwitchActiveParser();
bool old = DirectParentIsCode;
DirectParentIsCode = false;
Debug.Assert(ReferenceEquals(Context.ActiveParser, Context.MarkupParser));
if (!String.IsNullOrEmpty(startSequence) || !String.IsNullOrEmpty(endSequence))
{
Context.MarkupParser.ParseSection(Tuple.Create(startSequence, endSequence), false);
}
else
{
Context.MarkupParser.ParseBlock();
}
DirectParentIsCode = old;
Context.SwitchActiveParser();
EnsureCurrent();
}
Initialize(Span);
}
protected void HandleTransition(VBSymbol lastWhiteSpace)
{
if (At(VBSymbolType.RazorCommentTransition))
{
Accept(lastWhiteSpace);
RazorComment();
return;
}
// Check the next character
VBSymbol transition = CurrentSymbol;
NextToken();
if (At(VBSymbolType.LessThan) || At(VBSymbolType.Colon))
{
// Put the transition back
PutCurrentBack();
PutBack(transition);
// If we're in design-time mode, accept the whitespace, otherwise put it back
if (Context.DesignTimeMode)
{
Accept(lastWhiteSpace);
}
else
{
PutBack(lastWhiteSpace);
}
// Switch to markup
OtherParserBlock();
}
else if (At(VBSymbolType.Transition))
{
if (Context.IsWithin(BlockType.Template))
{
Context.OnError(transition.Start, RazorResources.ParseError_InlineMarkup_Blocks_Cannot_Be_Nested);
}
Accept(lastWhiteSpace);
VBSymbol transition2 = CurrentSymbol;
NextToken();
if (At(VBSymbolType.LessThan) || At(VBSymbolType.Colon))
{
PutCurrentBack();
PutBack(transition2);
PutBack(transition);
Output(SpanKind.Code);
// Start a template block and switch to Markup
using (Context.StartBlock(BlockType.Template))
{
Context.CurrentBlock.CodeGenerator = new TemplateBlockCodeGenerator();
OtherParserBlock();
Initialize(Span);
}
}
else
{
Accept(transition);
Accept(transition2);
}
}
else
{
Accept(lastWhiteSpace);
PutCurrentBack();
PutBack(transition);
bool old = IsNested;
IsNested = true;
NestedBlock();
IsNested = old;
}
}
protected override void OutputSpanBeforeRazorComment()
{
Output(SpanKind.Code);
}
protected bool ReservedWord()
{
Context.CurrentBlock.Type = BlockType.Directive;
Context.OnError(CurrentLocation, RazorResources.ParseError_ReservedWord, CurrentSymbol.Content);
Span.CodeGenerator = SpanCodeGenerator.Null;
AcceptAndMoveNext();
Output(SpanKind.MetaCode, AcceptedCharacters.None);
return true;
}
protected void NestedBlock()
{
using (PushSpanConfig())
{
Output(SpanKind.Code);
bool old = DirectParentIsCode;
DirectParentIsCode = true;
ParseBlock();
DirectParentIsCode = old;
}
Initialize(Span);
}
protected bool Required(VBSymbolType expected, string errorBase)
{
if (!Optional(expected))
{
Context.OnError(CurrentLocation, errorBase, GetCurrentSymbolDisplay());
return false;
}
return true;
}
protected bool Optional(VBKeyword keyword)
{
if (At(keyword))
{
AcceptAndMoveNext();
return true;
}
return false;
}
protected void AcceptVBSpaces()
{
Accept(ReadVBSpacesLazy());
}
protected IEnumerable<VBSymbol> ReadVBSpaces()
{
return ReadVBSpacesLazy().ToList();
}
public bool IsDirectiveDefined(string directive)
{
return _directiveHandlers.ContainsKey(directive);
}
private IEnumerable<VBSymbol> ReadVBSpacesLazy()
{
foreach (var symbol in ReadWhileLazy(sym => sym.Type == VBSymbolType.WhiteSpace))
{
yield return symbol;
}
while (At(VBSymbolType.LineContinuation))
{
int bookmark = CurrentLocation.AbsoluteIndex;
VBSymbol under = CurrentSymbol;
NextToken();
if (At(VBSymbolType.NewLine))
{
yield return under;
yield return CurrentSymbol;
NextToken();
foreach (var symbol in ReadVBSpaces())
{
yield return symbol;
}
}
else
{
Context.Source.Position = bookmark;
NextToken();
yield break;
}
}
}
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
using Microsoft.AspNet.Razor.Tokenizer;
using Microsoft.AspNet.Razor.Tokenizer.Symbols;
namespace Microsoft.AspNet.Razor.Parser
{
public class VBLanguageCharacteristics : LanguageCharacteristics<VBTokenizer, VBSymbol, VBSymbolType>
{
private static readonly VBLanguageCharacteristics _instance = new VBLanguageCharacteristics();
private VBLanguageCharacteristics()
{
}
public static VBLanguageCharacteristics Instance
{
get { return _instance; }
}
public override VBTokenizer CreateTokenizer(ITextDocument source)
{
return new VBTokenizer(source);
}
public override string GetSample(VBSymbolType type)
{
return VBSymbol.GetSample(type);
}
public override VBSymbolType FlipBracket(VBSymbolType bracket)
{
switch (bracket)
{
case VBSymbolType.LeftBrace:
return VBSymbolType.RightBrace;
case VBSymbolType.LeftBracket:
return VBSymbolType.RightBracket;
case VBSymbolType.LeftParenthesis:
return VBSymbolType.RightParenthesis;
case VBSymbolType.RightBrace:
return VBSymbolType.LeftBrace;
case VBSymbolType.RightBracket:
return VBSymbolType.LeftBracket;
case VBSymbolType.RightParenthesis:
return VBSymbolType.LeftParenthesis;
default:
return VBSymbolType.Unknown;
}
}
public override VBSymbol CreateMarkerSymbol(SourceLocation location)
{
return new VBSymbol(location, String.Empty, VBSymbolType.Unknown);
}
public override VBSymbolType GetKnownSymbolType(KnownSymbolType type)
{
switch (type)
{
case KnownSymbolType.CommentStart:
return VBSymbolType.RazorCommentTransition;
case KnownSymbolType.CommentStar:
return VBSymbolType.RazorCommentStar;
case KnownSymbolType.CommentBody:
return VBSymbolType.RazorComment;
case KnownSymbolType.Identifier:
return VBSymbolType.Identifier;
case KnownSymbolType.Keyword:
return VBSymbolType.Keyword;
case KnownSymbolType.NewLine:
return VBSymbolType.NewLine;
case KnownSymbolType.Transition:
return VBSymbolType.Transition;
case KnownSymbolType.WhiteSpace:
return VBSymbolType.WhiteSpace;
default:
return VBSymbolType.Unknown;
}
}
protected override VBSymbol CreateSymbol(SourceLocation location, string content, VBSymbolType type, IEnumerable<RazorError> errors)
{
return new VBSymbol(location, content, type, errors);
}
}
}

View File

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
using Microsoft.AspNet.Razor.Text;
namespace Microsoft.AspNet.Razor.Parser
{
internal class WhiteSpaceRewriter : MarkupRewriter
{
public WhiteSpaceRewriter(Action<SpanBuilder, SourceLocation, string> markupSpanFactory) : base(markupSpanFactory)
{
}
protected override bool CanRewrite(Block block)
{
return block.Type == BlockType.Expression && Parent != null;
}
//public override void VisitBlock(Block block)
//{
// BlockBuilder parent = null;
// if (_blocks.Count > 0)
// {
// parent = _blocks.Peek();
// }
// BlockBuilder newBlock = new BlockBuilder(block);
// newBlock.Children.Clear();
// _blocks.Push(newBlock);
// if (block.Type == BlockType.Expression && parent != null)
// {
// VisitExpressionBlock(block, parent);
// }
// else
// {
// base.VisitBlock(block);
// }
// if (_blocks.Count > 1)
// {
// parent.Children.Add(_blocks.Pop().Build());
// }
//}
//public override void VisitSpan(Span span)
//{
// Debug.Assert(_blocks.Count > 0);
// _blocks.Peek().Children.Add(span);
//}
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block)
{
BlockBuilder newBlock = new BlockBuilder(block);
newBlock.Children.Clear();
Span ws = block.Children.FirstOrDefault() as Span;
IEnumerable<SyntaxTreeNode> newNodes = block.Children;
if (ws.Content.All(Char.IsWhiteSpace))
{
// Add this node to the parent
SpanBuilder builder = new SpanBuilder(ws);
builder.ClearSymbols();
FillSpan(builder, ws.Start, ws.Content);
parent.Children.Add(builder.Build());
// Remove the old whitespace node
newNodes = block.Children.Skip(1);
}
foreach (SyntaxTreeNode node in newNodes)
{
newBlock.Children.Add(node);
}
return newBlock.Build();
}
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Parser.SyntaxTree;
namespace Microsoft.AspNet.Razor
{
/// <summary>
/// Represents the results of parsing a Razor document
/// </summary>
public class ParserResults
{
public ParserResults(Block document, IList<RazorError> parserErrors)
: this(parserErrors == null || parserErrors.Count == 0, document, parserErrors)
{
}
protected ParserResults(bool success, Block document, IList<RazorError> errors)
{
Success = success;
Document = document;
ParserErrors = errors ?? new List<RazorError>();
}
/// <summary>
/// Indicates if parsing was successful (no errors)
/// </summary>
public bool Success { get; private set; }
/// <summary>
/// The root node in the document's syntax tree
/// </summary>
public Block Document { get; private set; }
/// <summary>
/// The list of errors which occurred during parsing.
/// </summary>
public IList<RazorError> ParserErrors { get; private set; }
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.Razor
{
// Flags:
// Provisional, ContextChanged, Accepted, Rejected
// 000001 1 - Rejected,
// 000010 2 - Accepted
// 000100 4 - Provisional
// 001000 8 - Context Changed
// 010000 16 - Auto Complete Block
/// <summary>
/// The result of attempting an incremental parse
/// </summary>
/// <remarks>
/// Either the Accepted or Rejected flag is ALWAYS set.
/// Additionally, Provisional may be set with Accepted and SpanContextChanged may be set with Rejected.
/// Provisional may NOT be set with Rejected and SpanContextChanged may NOT be set with Accepted.
/// </remarks>
[Flags]
[SuppressMessage("Microsoft.Naming", "CA1714:FlagsEnumsShouldHavePluralNames", Justification = "The singular name is more appropriate here")]
public enum PartialParseResult
{
/// <summary>
/// Indicates that the edit could not be accepted and that a reparse is underway.
/// </summary>
Rejected = 1,
/// <summary>
/// Indicates that the edit was accepted and has been added to the parse tree
/// </summary>
Accepted = 2,
/// <summary>
/// Indicates that the edit was accepted, but that a reparse should be forced when idle time is available
/// since the edit may be misclassified
/// </summary>
/// <remarks>
/// This generally occurs when a "." is typed in an Implicit Expression, since editors require that this
/// be assigned to Code in order to properly support features like IntelliSense. However, if no further edits
/// occur following the ".", it should be treated as Markup.
/// </remarks>
Provisional = 4,
/// <summary>
/// Indicates that the edit caused a change in the span's context and that if any statement completions were active prior to starting this
/// partial parse, they should be reinitialized.
/// </summary>
SpanContextChanged = 8,
/// <summary>
/// Indicates that the edit requires an auto completion to occur
/// </summary>
AutoCompleteBlock = 16
}
}

View File

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Razor.Roslyn")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Razor.Roslyn")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("833fc87c-ec39-45a0-9e51-31461d27ced6")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Razor.Generator;
using Microsoft.AspNet.Razor.Parser;
namespace Microsoft.AspNet.Razor
{
/// <summary>
/// Represents a code language in Razor.
/// </summary>
public abstract class RazorCodeLanguage
{
private static IDictionary<string, RazorCodeLanguage> _services = new Dictionary<string, RazorCodeLanguage>(StringComparer.OrdinalIgnoreCase)
{
{ "cshtml", new CSharpRazorCodeLanguage() },
{ "vbhtml", new VBRazorCodeLanguage() }
};
/// <summary>
/// Gets the list of registered languages mapped to file extensions (without a ".")
/// </summary>
public static IDictionary<string, RazorCodeLanguage> Languages
{
get { return _services; }
}
/// <summary>
/// The name of the language (for use in System.Web.Compilation.BuildProvider.GetDefaultCompilerTypeForLanguage)
/// </summary>
public abstract string LanguageName { get; }
/// <summary>
/// The type of the CodeDOM provider for this language
/// </summary>
public abstract Type CodeDomProviderType { get; }
/// <summary>
/// Gets the RazorCodeLanguage registered for the specified file extension
/// </summary>
/// <param name="fileExtension">The extension, with or without a "."</param>
/// <returns>The language registered for that extension</returns>
public static RazorCodeLanguage GetLanguageByExtension(string fileExtension)
{
RazorCodeLanguage service = null;
Languages.TryGetValue(fileExtension.TrimStart('.'), out service);
return service;
}
/// <summary>
/// Constructs the code parser. Must return a new instance on EVERY call to ensure thread-safety
/// </summary>
public abstract ParserBase CreateCodeParser();
/// <summary>
/// Constructs the code generator. Must return a new instance on EVERY call to ensure thread-safety
/// </summary>
public abstract RazorCodeGenerator CreateCodeGenerator(string className, string rootNamespaceName, string sourceFileName, RazorEngineHost host);
}
}

Some files were not shown because too many files have changed in this diff Show More