otac0n / Pegasus

A PEG parser generator for .NET that integrates with MSBuild and Visual Studio.

Home Page:http://otac0n.com/Pegasus/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

A version of the runtime library targeting .NET 3.5

david-pfx opened this issue · comments

There are runtime environments that do not support .NET 4.+ and can only be targeted with a Pegasus parser if the runtime library itself targets 3.5 or earlier. One of those is Unity, which is my particular interest.

I have rebuilt Pegasus.Common targeting .NET 3.5, and the changes are few.

  1. A substitute for Tuple. I have one, but they are readily available. (source code, not Nuget)
  2. One use of dynamic, which seems to work OK as object.
  3. One use of String.Join using IEnumerable, which needs a ToArray().
  4. And avoid using String.Concat()!

Happy to provide code if it will help, but integrating it into the build is too hard for me.

I also tried to converte the runtime lib for Unity. (for .NET 2.0)
But I didn't find any issue about String.Concat(). Is there any special reason for not using it?
(String.Concat is much faster than StringBuilder for static strings)

The diff file is easier for sharing

diff --git a/Assets/Pegasus.Common/Cursor.cs b/Assets/Pegasus.Common/Cursor.cs
index 874531e..f3fe1d1 100644
--- a/Assets/Pegasus.Common/Cursor.cs
+++ b/Assets/Pegasus.Common/Cursor.cs
@@ -41,7 +41,7 @@ namespace Pegasus.Common
         {
             if (subject == null)
             {
-                throw new ArgumentNullException(nameof(subject));
+                throw new ArgumentNullException("subject"); // TODO: removed the nameof()
             }
 
             if (location < 0 || location > subject.Length)
@@ -82,40 +82,40 @@ namespace Pegasus.Common
         /// <summary>
         /// Gets the column number represented by the location.
         /// </summary>
-        public int Column { get; }
+        public int Column { get; set; }
 
         /// <summary>
         /// Gets the filename of the parsing subject.
         /// </summary>
-        public string FileName { get; }
+        public string FileName { get; set; }
 
         /// <summary>
         /// Gets the line number of the cursor.
         /// </summary>
-        public int Line { get; }
+        public int Line { get; set; }
 
         /// <summary>
         /// Gets the location within the parsing subject.
         /// </summary>
-        public int Location { get; }
+        public int Location { get; set; }
 
         /// <summary>
         /// Gets a hash code that varies with this cursor's state object.
         /// </summary>
         /// <remarks>This value, along with this cursor's location uniquely identify the parsing state.</remarks>
-        public int StateKey => this.stateKey;
+        public int StateKey { get { return this.stateKey; } }
 
         /// <summary>
         /// Gets the parsing subject.
         /// </summary>
-        public string Subject { get; }
+        public string Subject { get; set; }
 
         /// <summary>
         /// Gets the state value with the specified key.
         /// </summary>
         /// <param name="key">The key of the state value.</param>
         /// <returns>The state vale.</returns>
-        public dynamic this[string key]
+        public object this[string key] // TODO: removed the dynamic keyword
         {
             get
             {
@@ -142,7 +142,7 @@ namespace Pegasus.Common
         /// <param name="left">The first <see cref="Cursor"/> to compare, or null.</param>
         /// <param name="right">The second <see cref="Cursor"/> to compare, or null.</param>
         /// <returns>true if the value of <paramref name="left"/> is different from the value of <paramref name="right"/>; otherwise, false.</returns>
-        public static bool operator !=(Cursor left, Cursor right) => !object.Equals(left, right);
+        public static bool operator !=(Cursor left, Cursor right) { return !object.Equals(left, right); }
 
         /// <summary>
         /// Determines whether two specified cursors represent the same location.
@@ -150,7 +150,7 @@ namespace Pegasus.Common
         /// <param name="left">The first <see cref="Cursor"/> to compare, or null.</param>
         /// <param name="right">The second <see cref="Cursor"/> to compare, or null.</param>
         /// <returns>true if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, false.</returns>
-        public static bool operator ==(Cursor left, Cursor right) => object.Equals(left, right);
+        public static bool operator ==(Cursor left, Cursor right) { return object.Equals(left, right); }
 
         /// <summary>
         /// Returns a new <see cref="Cursor"/> representing the location after consuming the given <see cref="ParseResult{T}"/>.
@@ -177,19 +177,21 @@ namespace Pegasus.Common
         /// </summary>
         /// <param name="obj">An object to compare with this <see cref="Cursor"/>.</param>
         /// <returns>true if the objects are considered equal; otherwise, false.</returns>
-        public override bool Equals(object obj) => this.Equals(obj as Cursor);
+        public override bool Equals(object obj) { return this.Equals(obj as Cursor); }
 
         /// <summary>
         /// Determines whether the specified <see cref="Cursor"/> is equal to the current <see cref="Cursor"/>.
         /// </summary>
         /// <param name="other">A <see cref="Cursor"/> to compare with this <see cref="Cursor"/>.</param>
         /// <returns>true if the cursors represent the same location at the same state; otherwise, false.</returns>
-        public bool Equals(Cursor other) =>
-            !object.ReferenceEquals(other, null) &&
+        public bool Equals(Cursor other)
+        {
+            return !object.ReferenceEquals(other, null) &&
             this.Location == other.Location &&
             this.Subject == other.Subject &&
             this.FileName == other.FileName &&
-            this.stateKey == other.stateKey;
+            this.stateKey == other.stateKey;
+        }
 
         /// <summary>
         /// Serves as a hash function for this <see cref="Cursor"/>.
@@ -239,7 +241,7 @@ namespace Pegasus.Common
                 : new Cursor(this.Subject, this.Location, this.FileName, this.Line, this.Column, this.inTransition, new Dictionary<string, object>(this.state), this.stateKey, mutable);
         }
 
-        private static int GetNextStateKey() => Interlocked.Increment(ref previousStateKey);
+        private static int GetNextStateKey() { return Interlocked.Increment(ref previousStateKey); }
 
         private static void TrackLines(string subject, int start, int count, ref int line, ref int column, ref bool inTransition)
         {
diff --git a/Assets/Pegasus.Common/Highlighting/HighlightRule{T}.cs b/Assets/Pegasus.Common/Highlighting/HighlightRule{T}.cs
index 3bf0f77..f4930b9 100644
--- a/Assets/Pegasus.Common/Highlighting/HighlightRule{T}.cs
+++ b/Assets/Pegasus.Common/Highlighting/HighlightRule{T}.cs
@@ -19,9 +19,9 @@ namespace Pegasus.Common.Highlighting
         {
             this.Pattern = new Regex(
                 pattern,
-#if !PORTABLE
-                RegexOptions.Compiled |
-#endif
+//#if !PORTABLE
+//                RegexOptions.Compiled |
+//#endif
                 RegexOptions.IgnorePatternWhitespace);
             this.Value = value;
         }
@@ -29,11 +29,11 @@ namespace Pegasus.Common.Highlighting
         /// <summary>
         /// Gets the pattern to use for matching.
         /// </summary>
-        public Regex Pattern { get; }
+        public Regex Pattern { get; set; }
 
         /// <summary>
         /// Gets the value of the match.
         /// </summary>
-        public T Value { get; }
+        public T Value { get; set; }
     }
 }
diff --git a/Assets/Pegasus.Common/Highlighting/HighlightedSegment{T}.cs b/Assets/Pegasus.Common/Highlighting/HighlightedSegment{T}.cs
index a23641d..32357a7 100644
--- a/Assets/Pegasus.Common/Highlighting/HighlightedSegment{T}.cs
+++ b/Assets/Pegasus.Common/Highlighting/HighlightedSegment{T}.cs
@@ -27,16 +27,16 @@ namespace Pegasus.Common.Highlighting
         /// <summary>
         /// Gets the ending index of the segment.
         /// </summary>
-        public int End { get; }
+        public int End { get; set; }
 
         /// <summary>
         /// Gets the starting index of the segment.
         /// </summary>
-        public int Start { get; }
+        public int Start { get; set; }
 
         /// <summary>
         /// Gets the value of the segment.
         /// </summary>
-        public T Value { get; }
+        public T Value { get; set; }
     }
 }
diff --git a/Assets/Pegasus.Common/Highlighting/SyntaxHighlighter{T}.cs b/Assets/Pegasus.Common/Highlighting/SyntaxHighlighter{T}.cs
index 506c043..2a6a30e 100644
--- a/Assets/Pegasus.Common/Highlighting/SyntaxHighlighter{T}.cs
+++ b/Assets/Pegasus.Common/Highlighting/SyntaxHighlighter{T}.cs
@@ -7,6 +7,29 @@ namespace Pegasus.Common.Highlighting
     using System.Linq;
     using Pegasus.Common;
 
+    class Tuple<T1, T2>
+    {
+        T1 _v1;
+        T2 _v2;
+
+        public T1 Item1 { get { return _v1; } }
+        public T2 Item2 { get { return _v2; } }
+
+        public Tuple(T1 v1, T2 v2)
+        {
+            this._v1 = v1;
+            this._v2 = v2;
+        }
+    }
+
+    class Tuple
+    {
+        public static Tuple<T1, T2> Create<T1, T2>(T1 v1, T2 v2)
+        {
+            return new Tuple<T1, T2>(v1, v2);
+        }
+    }
+
     /// <summary>
     /// Provides syntax highlighting services for Pegasus grammars.
     /// </summary>
@@ -38,7 +61,7 @@ namespace Pegasus.Common.Highlighting
         {
             if (tokens == null)
             {
-                throw new ArgumentNullException(nameof(tokens));
+                throw new ArgumentNullException("tokens"); // TODO: removed the nameof()
             }
 
             var result = new List<HighlightedSegment<T>>();
@@ -88,12 +111,12 @@ namespace Pegasus.Common.Highlighting
         {
             if (tokens == null)
             {
-                throw new ArgumentNullException(nameof(tokens));
+                throw new ArgumentNullException("tokens"); // TODO: removed the nameof()
             }
 
             if (subject == null)
             {
-                throw new ArgumentNullException(nameof(subject));
+                throw new ArgumentNullException("subject"); // TODO: removed the nameof()
             }
 
             var result = new List<HighlightedSegment<T>>();
@@ -181,7 +204,7 @@ namespace Pegasus.Common.Highlighting
         {
             if (maxRule.HasValue && (maxRule.Value > this.list.Count || maxRule.Value < 0))
             {
-                throw new ArgumentOutOfRangeException(nameof(maxRule));
+                throw new ArgumentOutOfRangeException("maxRule"); // TODO: removed the nameof()
             }
 
             maxRule = maxRule ?? this.list.Count;
@@ -236,7 +259,7 @@ namespace Pegasus.Common.Highlighting
                 }
 
                 lexicalStack.Push(Tuple.Create(maxRule ?? this.list.Count, e));
-                var key = string.Join(" ", lexicalStack.Select(d => d.Item2.Name));
+                var key = string.Join(" ", lexicalStack.Select(d => d.Item2.Name).ToArray());
                 var result = this.Highlight(key, maxRule);
 
                 if (result != null)
diff --git a/Assets/Pegasus.Common/ListNode{T}.cs b/Assets/Pegasus.Common/ListNode{T}.cs
index 59878ab..71626c4 100644
--- a/Assets/Pegasus.Common/ListNode{T}.cs
+++ b/Assets/Pegasus.Common/ListNode{T}.cs
@@ -22,11 +22,11 @@ namespace Pegasus.Common
         /// <summary>
         /// Gets the head of the list.
         /// </summary>
-        public T Head { get; }
+        public T Head { get; set; }
 
         /// <summary>
         /// Gets the tail of the list.
         /// </summary>
-        public ListNode<T> Tail { get; }
+        public ListNode<T> Tail { get; set; }
     }
 }
diff --git a/Assets/Pegasus.Common/ParseResult{T}.cs b/Assets/Pegasus.Common/ParseResult{T}.cs
index 55cef0b..0ecd80f 100644
--- a/Assets/Pegasus.Common/ParseResult{T}.cs
+++ b/Assets/Pegasus.Common/ParseResult{T}.cs
@@ -29,17 +29,17 @@ namespace Pegasus.Common
         /// <summary>
         /// Gets the ending cursor of the match.
         /// </summary>
-        public Cursor EndCursor { get; }
+        public Cursor EndCursor { get; set; }
 
         /// <summary>
         /// Gets the starting cursor of the match.
         /// </summary>
-        public Cursor StartCursor { get; }
+        public Cursor StartCursor { get; set; }
 
         /// <summary>
         /// Gets the resulting value of the parsing operation.
         /// </summary>
-        public T Value { get; }
+        public T Value { get; set; }
 
         /// <summary>
         /// Determines whether two specified parse results have different values.
@@ -47,7 +47,7 @@ namespace Pegasus.Common
         /// <param name="left">The first <see cref="ParseResult{T}"/> to compare, or null.</param>
         /// <param name="right">The second <see cref="ParseResult{T}"/> to compare, or null.</param>
         /// <returns>true if the value of <paramref name="left"/> is different from the value of <paramref name="right"/>; otherwise, false.</returns>
-        public static bool operator !=(ParseResult<T> left, ParseResult<T> right) => !object.Equals(left, right);
+        public static bool operator !=(ParseResult<T> left, ParseResult<T> right) { return !object.Equals(left, right); }
 
         /// <summary>
         /// Determines whether two specified parse results have the same value.
@@ -55,25 +55,27 @@ namespace Pegasus.Common
         /// <param name="left">The first <see cref="ParseResult{T}"/> to compare, or null.</param>
         /// <param name="right">The second <see cref="ParseResult{T}"/> to compare, or null.</param>
         /// <returns>true if the value of <paramref name="left"/> is the same as the value of <paramref name="right"/>; otherwise, false.</returns>
-        public static bool operator ==(ParseResult<T> left, ParseResult<T> right) => object.Equals(left, right);
+        public static bool operator ==(ParseResult<T> left, ParseResult<T> right) { return object.Equals(left, right); }
 
         /// <summary>
         /// Determines whether the specified object is equal to the current <see cref="ParseResult{T}"/>.
         /// </summary>
         /// <param name="obj">An object to compare with this <see cref="ParseResult{T}"/>.</param>
         /// <returns>true if the objects are considered equal; otherwise, false.</returns>
-        public override bool Equals(object obj) => this.Equals(obj as ParseResult<T>);
+        public override bool Equals(object obj) { return this.Equals(obj as ParseResult<T>); }
 
         /// <summary>
         /// Determines whether the specified <see cref="ParseResult{T}"/> is equal to the current <see cref="ParseResult{T}"/>.
         /// </summary>
         /// <param name="other">A <see cref="ParseResult{T}"/> to compare with this <see cref="ParseResult{T}"/>.</param>
         /// <returns>true if the parse results are considered equal; otherwise, false.</returns>
-        public bool Equals(ParseResult<T> other) =>
-            !object.ReferenceEquals(other, null) &&
+        public bool Equals(ParseResult<T> other)
+        {
+            return !object.ReferenceEquals(other, null) &&
             this.StartCursor == other.StartCursor &&
             this.EndCursor == other.EndCursor &&
-            object.Equals(this.Value, other.Value);
+            object.Equals(this.Value, other.Value);
+        }
 
         /// <summary>
         /// Serves as a hash function for this <see cref="ParseResult{T}"/>.
diff --git a/Assets/Pegasus.Common/Tracing/DiagnosticsTracer.cs b/Assets/Pegasus.Common/Tracing/DiagnosticsTracer.cs
index 89a2d91..ae29837 100644
--- a/Assets/Pegasus.Common/Tracing/DiagnosticsTracer.cs
+++ b/Assets/Pegasus.Common/Tracing/DiagnosticsTracer.cs
@@ -17,8 +17,9 @@ namespace Pegasus.Common.Tracing
         /// <summary>
         /// Gets the instance of <see cref="NullTracer"/>.
         /// </summary>
-        public static DiagnosticsTracer Instance { get; } = new DiagnosticsTracer();
-
+        public static DiagnosticsTracer Instance { get { return _inst; } }
+        static DiagnosticsTracer _inst = new DiagnosticsTracer();
+
         /// <inheritdoc/>
         public void TraceCacheHit<T>(string ruleName, Cursor cursor, CacheKey cacheKey, IParseResult<T> parseResult)
         {
@@ -42,7 +43,7 @@ namespace Pegasus.Common.Tracing
         [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Validation excluded for performance reasons.")]
         public void TraceRuleEnter(string ruleName, Cursor cursor)
         {
-            Trace.WriteLine($"Begin '{ruleName}' at ({cursor.Line},{cursor.Column}) with state key {cursor.StateKey}");
+            Trace.WriteLine(string.Format("Begin '{0}' at ({1},{2}) with state key {3}", ruleName, cursor.Line, cursor.Column, cursor.StateKey));
             Trace.Indent();
         }
 
@@ -53,7 +54,7 @@ namespace Pegasus.Common.Tracing
         {
             var success = parseResult != null;
             Trace.Unindent();
-            Trace.WriteLine($"End '{ruleName}' with {(success ? "success" : "failure")} at ({cursor.Line},{cursor.Column}) with state key {cursor.StateKey}");
+            Trace.WriteLine(string.Format("End '{0}' with {1} at ({2},{3}) with state key {4}", ruleName, (success ? "success" : "failure"), cursor.Line, cursor.Column, cursor.StateKey));
         }
     }
 }
diff --git a/Assets/Pegasus.Common/Tracing/NullTracer.cs b/Assets/Pegasus.Common/Tracing/NullTracer.cs
index d9b617e..7b9f05f 100644
--- a/Assets/Pegasus.Common/Tracing/NullTracer.cs
+++ b/Assets/Pegasus.Common/Tracing/NullTracer.cs
@@ -14,7 +14,8 @@ namespace Pegasus.Common.Tracing
         /// <summary>
         /// Gets the instance of <see cref="NullTracer"/>.
         /// </summary>
-        public static NullTracer Instance { get; } = new NullTracer();
+        public static NullTracer Instance { get { return _inst; } }
+        static NullTracer _inst = new NullTracer();
 
         /// <inheritdoc/>
         public void TraceCacheHit<T>(string ruleName, Cursor cursor, CacheKey cacheKey, IParseResult<T> parseResult)

I had no reason to target ,NET 2.0, and my changes were minor, just the ones I mentioned: one instance of String.Join(), one instance of dynamic and a substitute for Tuple. I had far more trouble massaging the project file to get it to build without all the dependencies.

The examples use an overload of String.Concat() that is not available at 3.5, so must be avoided. That's all.

Thanks for your answer!
I was worried whether I missed anything.

I've added a .NET 3.5 version of the library in the latest pre-release packages.

I think it's worth doing, even though the actual target market might be quite small. I look forward to throwing out my hack and using the next release in my Unity project.

Fixed in bd78015