Initial commit.
This commit is contained in:
commit
ff854e3e15
|
|
@ -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
|
||||
|
|
@ -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/
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 "{0}" 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 == '_';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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
Loading…
Reference in New Issue