diff --git a/Build/Sprache.proj b/Build/Sprache.proj
index 70d5e29..33a3017 100755
--- a/Build/Sprache.proj
+++ b/Build/Sprache.proj
@@ -50,7 +50,7 @@
-
+
diff --git a/README.md b/README.md
index 0737922..fae73f6 100755
--- a/README.md
+++ b/README.md
@@ -5,40 +5,62 @@ It doesn't compete with "industrial strength" language workbenches - it fits som
Usage
-----
+[![Join the chat at https://gitter.im/sprache/Sprache](https://badges.gitter.im/sprache/Sprache.svg)](https://gitter.im/sprache/Sprache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+
Unlike most parser-building frameworks, you use Sprache directly from your program code, and don't need to set up any build-time code generation tasks. Sprache itself is a single tiny assembly.
A simple parser might parse a sequence of characters:
- // Parse any number of capital 'A's in a row
- var parseA = Parse.Char('A').AtLeastOnce();
+```csharp
+// Parse any number of capital 'A's in a row
+var parseA = Parse.Char('A').AtLeastOnce();
+```
Sprache provides a number of built-in functions that can make bigger parsers from smaller ones, often callable via Linq query comprehensions:
- Parser identifier =
- from leading in Parse.Whitespace.Many()
- from first in Parse.Letter.Once()
- from rest in Parse.LetterOrDigit.Many()
- from trailing in Parse.Whitespace.Many()
- select new string(first.Concat(rest).ToArray());
+```csharp
+Parser identifier =
+ from leading in Parse.WhiteSpace.Many()
+ from first in Parse.Letter.Once()
+ from rest in Parse.LetterOrDigit.Many()
+ from trailing in Parse.WhiteSpace.Many()
+ select new string(first.Concat(rest).ToArray());
+
+var id = identifier.Parse(" abc123 ");
- var id = identifier.Parse(" abc123 ");
+Assert.AreEqual("abc123", id);
+```
- Assert.AreEqual("abc123", id);
-
-Background and Tutorials
-------------------------
+Examples and Tutorials
+----------------------
-The best place to start is [this introductory article.](http://nblumhardt.com/2010/01/building-an-external-dsl-in-c/)
+The best place to start is [this introductory article](http://nblumhardt.com/2010/01/building-an-external-dsl-in-c/).
-Examples included with the source demonstrate:
+**Examples** included with the source demonstrate:
* [Parsing XML directly to a Document object](https://github.com/sprache/Sprache/blob/master/src/XmlExample/Program.cs)
-* [Parsing numeric expressions to System.Linq.Expression objects](https://github.com/sprache/Sprache/blob/master/src/LinqyCalculator/ExpressionParser.cs)
-* [Parsing comma-separated (CSV) 'files' into lists of strings](https://github.com/sprache/Sprache/blob/master/src/Sprache.Tests/Scenarios/CsvTests.cs)
+* [Parsing numeric expressions to `System.Linq.Expression` objects](https://github.com/sprache/Sprache/blob/master/src/LinqyCalculator/ExpressionParser.cs)
+* [Parsing comma-separated values (CSV) into lists of strings](https://github.com/sprache/Sprache/blob/master/src/Sprache.Tests/Scenarios/CsvTests.cs)
+
+**Tutorials** explaining Sprache:
+
+ * A great [CodeProject article by Alexey Yakovlev ](http://www.codeproject.com/Articles/795056/Sprache-Calc-building-yet-another-expression-evalu) (and [in Russian](http://habrahabr.ru/post/228037/))
+ * Mike Hadlow's example of [parsing connection strings](http://mikehadlow.blogspot.com.au/2012/09/parsing-connection-string-with-sprache.html)
+
+**Real-world** parsers built with Sprache:
+
+ * The [template parser](https://github.com/OctopusDeploy/Octostache/blob/master/source/Octostache/Templates/TemplateParser.cs) in [Octostache](https://github.com/OctopusDeploy/Octostache), the variable substitution language of [Octopus Deploy](https://octopus.com)
+ * The [XAML binding expression parser](https://github.com/SuperJMN/OmniXAML/blob/master/Source/OmniXaml/Parsers/MarkupExtensions/MarkupExtensionParser.cs) in [OmniXaml](https://github.com/SuperJMN/OmniXAML), the cross-platform XAML implementation
+ * The query parser in [Seq](https://getseq.net), a structured log server for .NET
+ * The [connection string parser](https://github.com/EasyNetQ/EasyNetQ/blob/master/Source/EasyNetQ/ConnectionString/ConnectionStringGrammar.cs) in [EasyNetQ](http://easynetq.com/), a .NET API for RabbitMQ
+ * Sprache appears in the [credits for JetBrains ReSharper](https://confluence.jetbrains.com/display/ReSharper/Third-Party+Software+Shipped+With+ReSharper#Third-PartySoftwareShippedWithReSharper-Sprache)
+
+Background
+----------
-Parser combinators are covered extensively on the web. The original paper describing the monadic implementation by [Graham Hutton and Eric Meijer](http://www.cs.nott.ac.uk/~gmh/monparsing.pdf) is very readable. Sprache grew out of some exercises in [Hutton's Haskell book](http://www.amazon.com/Programming-Haskell-Graham-Hutton/dp/0521692695).
+Parser combinators are covered extensively on the web. The original paper describing the monadic implementation by [Graham Hutton and Eric Meijer](http://www.cs.nott.ac.uk/~gmh/monparsing.pdf) is very readable. Sprache was originally written by [Nicholas Blumhardt](http://nblumhardt.com) and grew out of some exercises in [Hutton's Haskell book](http://www.amazon.com/Programming-Haskell-Graham-Hutton/dp/0521692695).
-Sprache itself draws on some great C# tutorials:
+The implementation of Sprache draws on ideas from:
* [Luke Hoban's Blog](http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx)
* [Brian McNamara's Blog](http://lorgonblog.wordpress.com/2007/12/02/c-3-0-lambda-and-the-first-post-of-a-series-about-monadic-parser-combinators/)
diff --git a/Tools/NuGet/NuGet.exe b/Tools/NuGet/NuGet.exe
index e313ff9..c41a0d0 100755
Binary files a/Tools/NuGet/NuGet.exe and b/Tools/NuGet/NuGet.exe differ
diff --git a/src/LinqyCalculator/LinqyCalculator.csproj b/src/LinqyCalculator/LinqyCalculator.csproj
index 9b4bf46..9baf8ff 100644
--- a/src/LinqyCalculator/LinqyCalculator.csproj
+++ b/src/LinqyCalculator/LinqyCalculator.csproj
@@ -80,7 +80,7 @@
- {DF5FE6F0-5ABE-4363-9184-EB6EF64F0F61}
+ {82cc8cf8-b770-4756-850a-95dd14dd312f}
Sprache
diff --git a/src/Sprache.Tests/AssertInput.cs b/src/Sprache.Tests/AssertInput.cs
index 19037f4..d5debd5 100644
--- a/src/Sprache.Tests/AssertInput.cs
+++ b/src/Sprache.Tests/AssertInput.cs
@@ -6,7 +6,7 @@ namespace Sprache.Tests
{
static class AssertInput
{
- public static Input AdvanceMany(this Input input, int count)
+ public static IInput AdvanceMany(this IInput input, int count)
{
for (int i = 0; i < count; i++)
{
@@ -16,14 +16,14 @@ public static Input AdvanceMany(this Input input, int count)
return input;
}
- public static Input AdvanceAssert(this Input input, Action assertion)
+ public static IInput AdvanceAssert(this IInput input, Action assertion)
{
var result = input.Advance();
assertion(input, result);
return result;
}
- public static Input AdvanceManyAssert(this Input input, int count, Action assertion)
+ public static IInput AdvanceManyAssert(this Input input, int count, Action assertion)
{
var result = input.AdvanceMany(count);
assertion(input, result);
diff --git a/src/Sprache.Tests/AssertParser.cs b/src/Sprache.Tests/AssertParser.cs
index 57d2112..1a814af 100644
--- a/src/Sprache.Tests/AssertParser.cs
+++ b/src/Sprache.Tests/AssertParser.cs
@@ -31,7 +31,7 @@ public static void SucceedsWith(Parser parser, string input, Action res
parser.TryParse(input)
.IfFailure(f =>
{
- Assert.Fail("Parsing of \"{0}\" failed unexpectedly at position {1}: {2}", input, f.Remainder.Position, f.Message);
+ Assert.Fail("Parsing of \"{0}\" failed unexpectedly. {1}", input, f);
return f;
})
.IfSuccess(s =>
diff --git a/src/Sprache.Tests/DecimalTests.cs b/src/Sprache.Tests/DecimalTests.cs
index 944ed68..a616ae9 100644
--- a/src/Sprache.Tests/DecimalTests.cs
+++ b/src/Sprache.Tests/DecimalTests.cs
@@ -8,6 +8,7 @@ namespace Sprache.Tests
public class DecimalTests
{
private static readonly Parser DecimalParser = Parse.Decimal.End();
+ private static readonly Parser DecimalInvariantParser = Parse.DecimalInvariant.End();
private CultureInfo _previousCulture;
@@ -49,5 +50,12 @@ public void Letters()
{
DecimalParser.Parse("1A.5");
}
+
+ [Test]
+ public void LeadingDigitsInvariant()
+ {
+ Assert.AreEqual("12.23", DecimalInvariantParser.Parse("12.23"));
+ }
+
}
}
\ No newline at end of file
diff --git a/src/Sprache.Tests/InputTests.cs b/src/Sprache.Tests/InputTests.cs
index 132242d..e553b68 100644
--- a/src/Sprache.Tests/InputTests.cs
+++ b/src/Sprache.Tests/InputTests.cs
@@ -96,7 +96,7 @@ public void AdvancingInputAtEOL_ResetsColumnNumber()
[Test]
public void LineCountingSmokeTest()
{
- var i = new Input("abc\ndef");
+ IInput i = new Input("abc\ndef");
Assert.AreEqual(0, i.Position);
Assert.AreEqual(1, i.Line);
Assert.AreEqual(1, i.Column);
diff --git a/src/Sprache.Tests/ParseTests.cs b/src/Sprache.Tests/ParseTests.cs
index 62ce10c..3baa22e 100644
--- a/src/Sprache.Tests/ParseTests.cs
+++ b/src/Sprache.Tests/ParseTests.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -152,12 +153,6 @@ from rest in Parse.Char('a').Once()
select first.Concat(rest))
.Or(Parse.Char('a').Once());
- [Test, Ignore("Not Implemented")]
- public void CanParseLeftRecursiveGrammar()
- {
- AssertParser.SucceedsWith(ASeq.End(), "a,a,a", r => Assert.AreEqual(new string(r.ToArray()), "aaa"));
- }
-
[Test]
public void DetectsLeftRecursion()
{
@@ -176,12 +171,6 @@ from rest in Parse.Char('b').Once()
select first.Concat(rest))
.Or(Parse.Char('b').Once());
- [Test, Ignore("Not Implemented")]
- public void CanParseMutuallyLeftRecursiveGrammar()
- {
- AssertParser.SucceedsWithAll(ABSeq.End(), "baba");
- }
-
[Test]
public void DetectsMutualLeftRecursion()
{
@@ -270,6 +259,26 @@ public void RegexParserDoesNotConsumeInputOnFailedMatch()
Assert.AreEqual(0, r.Remainder.Position);
}
+ [Test]
+ public void RegexMatchParserConsumesInputOnSuccessfulMatch()
+ {
+ var digits = Parse.RegexMatch(@"\d(\d*)");
+ var r = digits.TryParse("123d");
+ Assert.IsTrue(r.WasSuccessful);
+ Assert.AreEqual("123", r.Value.Value);
+ Assert.AreEqual("23", r.Value.Groups[1].Value);
+ Assert.AreEqual(3, r.Remainder.Position);
+ }
+
+ [Test]
+ public void RegexMatchParserDoesNotConsumeInputOnFailedMatch()
+ {
+ var digits = Parse.RegexMatch(@"\d+");
+ var r = digits.TryParse("d123");
+ Assert.IsFalse(r.WasSuccessful);
+ Assert.AreEqual(0, r.Remainder.Position);
+ }
+
[Test]
public void PositionedParser()
{
@@ -348,6 +357,57 @@ public void RepeatParserCanParseWithCountOfZero()
Assert.AreEqual(0, r.Remainder.Position);
}
+ [Test]
+ public void RepeatParserCanParseAMinimumNumberOfValues()
+ {
+ var repeated = Parse.Char('a').Repeat(4, 5);
+
+ // Test failure.
+ var r = repeated.TryParse("aaa");
+ Assert.IsFalse(r.WasSuccessful);
+ Assert.AreEqual(0, r.Remainder.Position);
+
+ // Test success.
+ r = repeated.TryParse("aaaa");
+ Assert.IsTrue(r.WasSuccessful);
+ Assert.AreEqual(4, r.Remainder.Position);
+ }
+
+ [Test]
+ public void RepeatParserCanParseAMaximumNumberOfValues()
+ {
+ var repeated = Parse.Char('a').Repeat(4, 5);
+
+ var r = repeated.TryParse("aaaa");
+ Assert.IsTrue(r.WasSuccessful);
+ Assert.AreEqual(4, r.Remainder.Position);
+
+ r = repeated.TryParse("aaaaa");
+ Assert.IsTrue(r.WasSuccessful);
+ Assert.AreEqual(5, r.Remainder.Position);
+
+ r = repeated.TryParse("aaaaaa");
+ Assert.IsTrue(r.WasSuccessful);
+ Assert.AreEqual(5, r.Remainder.Position);
+ }
+
+ [Test]
+ public void RepeatParserErrorMessagesAreReadable()
+ {
+ var repeated = Parse.Char('a').Repeat(4, 5);
+
+ var expectedMessage = "Parsing failure: Unexpected 'end of input'; expected 'a' between 4 and 5 times, but found 3";
+
+ try
+ {
+ var r = repeated.Parse("aaa");
+ }
+ catch(ParseException ex)
+ {
+ Assert.That(ex.Message, Is.StringStarting(expectedMessage));
+ }
+ }
+
[Test]
public void CanParseSequence()
{
@@ -357,6 +417,17 @@ public void CanParseSequence()
Assert.IsTrue(r.Remainder.AtEnd);
}
+ [Test]
+ public void FailGracefullyOnSequence()
+ {
+ var sequence = Parse.Char('a').XDelimitedBy(Parse.Char(','));
+ AssertParser.FailsWith(sequence, "a,a,b", result =>
+ {
+ StringAssert.Contains("unexpected 'b'", result.Message);
+ CollectionAssert.Contains(result.Expectations, "a");
+ });
+ }
+
[Test]
public void CanParseContained()
{
diff --git a/src/Sprache.Tests/RegexTests.cs b/src/Sprache.Tests/RegexTests.cs
new file mode 100644
index 0000000..fc53f42
--- /dev/null
+++ b/src/Sprache.Tests/RegexTests.cs
@@ -0,0 +1,90 @@
+using System;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using NUnit.Framework;
+
+namespace Sprache.Tests
+{
+ ///
+ /// These tests exist in order to verify that the modification that is applied to
+ /// the regex passed to every call to the
+ /// or methods does not change the results
+ /// in any way.
+ ///
+ public class RegexTests
+ {
+ private const string _startsWithCarrot = "^([a-z]{3})([0-9]{3})$";
+ private const string _alternation = "(this)|(that)|(the other)";
+
+ private static readonly MethodInfo _optimizeRegexMethod = typeof(Parse).GetMethod("OptimizeRegex", BindingFlags.NonPublic | BindingFlags.Static);
+
+ [Test]
+ public void OptimizedRegexIsNotSuccessfulWhenTheMatchIsNotAtTheBeginningOfTheInput()
+ {
+ var regexOriginal = new Regex("[a-z]+");
+ var regexOptimized = OptimizeRegex(regexOriginal);
+
+ const string input = "123abc";
+
+ Assert.That(regexOriginal.IsMatch(input), Is.True);
+ Assert.That(regexOptimized.IsMatch(input), Is.False);
+ }
+
+ [Test]
+ public void OptimizedRegexIsSuccessfulWhenTheMatchIsAtTheBeginningOfTheInput()
+ {
+ var regexOriginal = new Regex("[a-z]+");
+ var regexOptimized = OptimizeRegex(regexOriginal);
+
+ const string input = "abc123";
+
+ Assert.That(regexOriginal.IsMatch(input), Is.True);
+ Assert.That(regexOptimized.IsMatch(input), Is.True);
+ }
+
+ [TestCase(_startsWithCarrot, RegexOptions.None, "abc123", TestName = "Starts with ^, no options, success")]
+ [TestCase(_startsWithCarrot, RegexOptions.ExplicitCapture, "abc123", TestName = "Starts with ^, explicit capture, success")]
+ [TestCase(_startsWithCarrot, RegexOptions.None, "123abc", TestName = "Starts with ^, no options, failure")]
+ [TestCase(_startsWithCarrot, RegexOptions.ExplicitCapture, "123abc", TestName = "Starts with ^, explicit capture, failure")]
+ [TestCase(_alternation, RegexOptions.None, "abc123", TestName = "Alternation, no options, success")]
+ [TestCase(_alternation, RegexOptions.ExplicitCapture, "that", TestName = "Alternation, explicit capture, success")]
+ [TestCase(_alternation, RegexOptions.None, "that", TestName = "Alternation, no options, failure")]
+ [TestCase(_alternation, RegexOptions.ExplicitCapture, "that", TestName = "Alternation, explicit capture, failure")]
+ public void RegexOptimizationDoesNotChangeRegexBehavior(string pattern, RegexOptions options, string input)
+ {
+ var regexOriginal = new Regex(pattern, options);
+ var regexOptimized = OptimizeRegex(regexOriginal);
+
+ var matchOriginal = regexOriginal.Match(input);
+ var matchModified = regexOptimized.Match(input);
+
+ Assert.That(matchModified.Success, Is.EqualTo(matchOriginal.Success));
+ Assert.That(matchModified.Value, Is.EqualTo(matchOriginal.Value));
+ Assert.That(matchModified.Groups.Count, Is.EqualTo(matchOriginal.Groups.Count));
+
+ for (int i = 0; i < matchModified.Groups.Count; i++)
+ {
+ Assert.That(matchModified.Groups[i].Success, Is.EqualTo(matchOriginal.Groups[i].Success));
+ Assert.That(matchModified.Groups[i].Value, Is.EqualTo(matchOriginal.Groups[i].Value));
+ }
+ }
+
+ ///
+ /// Calls the method via reflection.
+ ///
+ private static Regex OptimizeRegex(Regex regex)
+ {
+ // Reflection isn't the best way of verifying behavior,
+ // but cluttering the public api sucks worse.
+
+ if (_optimizeRegexMethod == null)
+ {
+ throw new Exception("Unable to locate a private static method named " +
+ "\"OptimizeRegex\" in the Parse class. Has it been renamed?");
+ }
+
+ var optimizedRegex = (Regex)_optimizeRegexMethod.Invoke(null, new object[] { regex });
+ return optimizedRegex;
+ }
+ }
+}
diff --git a/src/Sprache.Tests/Scenarios/AssemblerTests.cs b/src/Sprache.Tests/Scenarios/AssemblerTests.cs
new file mode 100644
index 0000000..c8216a3
--- /dev/null
+++ b/src/Sprache.Tests/Scenarios/AssemblerTests.cs
@@ -0,0 +1,175 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+
+namespace Sprache.Tests.Scenarios
+{
+ public class AssemblerParser
+ {
+ public static Parser Identifier = Parse.Identifier(Parse.Letter, Parse.LetterOrDigit);
+
+ public static Parser AsmToken(Parser parser)
+ {
+ var whitespace = Parse.WhiteSpace.Except(Parse.LineEnd);
+ return from leading in whitespace.Many()
+ from item in parser
+ from trailing in whitespace.Many()
+ select item;
+ }
+
+ public static Parser> Instruction =
+ from instructionName in AsmToken(Parse.LetterOrDigit.Many().Text())
+ from operands in AsmToken(Identifier).XDelimitedBy(Parse.Char(','))
+ select Tuple.Create(instructionName, operands.ToArray());
+
+ public static CommentParser Comment = new CommentParser { Single = ";", NewLine = Environment.NewLine };
+
+ public static Parser LabelId =
+ Parse.Identifier(Parse.Letter.Or(Parse.Chars("._?")), Parse.LetterOrDigit.Or(Parse.Chars("_@#$~.?")));
+
+ public static Parser Label =
+ from labelName in AsmToken(LabelId)
+ from colon in AsmToken(Parse.Char(':'))
+ select labelName;
+
+ public static readonly Parser> Assembler = (
+ from label in Label.Optional()
+ from instruction in Instruction.Optional()
+ from comment in AsmToken(Comment.SingleLineComment).Optional()
+ from lineTerminator in Parse.LineTerminator
+ select new AssemblerLine(
+ label.GetOrDefault(),
+ instruction.IsEmpty ? null : instruction.Get().Item1,
+ instruction.IsEmpty ? null : instruction.Get().Item2,
+ comment.GetOrDefault()
+ )
+ ).XMany().End();
+ }
+
+ [TestFixture]
+ public class AssemblerTests
+ {
+ [Test]
+ public void CanParseEmpty()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, "", CollectionAssert.IsEmpty);
+ }
+
+ [Test]
+ public void CanParseComment()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, ";comment",
+ lines => Assert.AreEqual(new AssemblerLine(null, null, null, "comment"), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseCommentWithSpaces()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, " ; comment ",
+ lines => Assert.AreEqual(new AssemblerLine(null, null, null, " comment "), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseLabel()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, "label:",
+ lines => Assert.AreEqual(new AssemblerLine("label", null, null, null), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseLabelWithSpaces()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, " label : ",
+ lines => Assert.AreEqual(new AssemblerLine("label", null, null, null), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseIntruction()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, "mov a,b",
+ lines => Assert.AreEqual(new AssemblerLine(null, "mov", new[] { "a", "b" }, null), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseIntructionWithSpaces()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, " mov a , b ",
+ lines => Assert.AreEqual(new AssemblerLine(null, "mov", new[] { "a", "b" }, null), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseAllTogether()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler, " label: mov a , b ; comment ",
+ lines =>
+ Assert.AreEqual(new AssemblerLine("label", "mov", new[] { "a", "b" }, " comment "), lines.Single()));
+ }
+
+ [Test]
+ public void CanParseSeceralLines()
+ {
+ AssertParser.SucceedsWith(AssemblerParser.Assembler,
+ @" ;multiline sample
+ label:
+ mov a , b;",
+ lines => CollectionAssert.AreEqual(
+ new[]
+ {
+ new AssemblerLine(null, null, null, "multiline sample"),
+ new AssemblerLine("label", null, null, null),
+ new AssemblerLine(null, "mov", new[] {"a", "b"}, "")
+ },
+ lines));
+ }
+ }
+
+ public class AssemblerLine
+ {
+ public readonly string Label;
+ public readonly string InstructionName;
+ public readonly string[] Operands;
+ public readonly string Comment;
+
+ public AssemblerLine(string label, string instructionName, string[] operands, string comment)
+ {
+ Label = label;
+ InstructionName = instructionName;
+ Operands = operands;
+ Comment = comment;
+ }
+
+ protected bool Equals(AssemblerLine other)
+ {
+ return ToString() == other.ToString();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj)) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != this.GetType()) return false;
+ return Equals((AssemblerLine)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hashCode = (Label != null ? Label.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (InstructionName != null ? InstructionName.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Operands != null ? Operands.GetHashCode() : 0);
+ hashCode = (hashCode * 397) ^ (Comment != null ? Comment.GetHashCode() : 0);
+ return hashCode;
+ }
+ }
+
+ public override string ToString()
+ {
+ return string.Join(" ",
+ Label == null ? "" : (Label + ":"),
+ InstructionName == null ? "" : InstructionName + string.Join(", ", Operands),
+ Comment == null ? "" : ";" + Comment);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Sprache.Tests/Scenarios/StarDateTest.cs b/src/Sprache.Tests/Scenarios/StarDateTest.cs
new file mode 100644
index 0000000..6061b7f
--- /dev/null
+++ b/src/Sprache.Tests/Scenarios/StarDateTest.cs
@@ -0,0 +1,30 @@
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Sprache.Tests.Scenarios
+{
+ [TestFixture]
+ public class StarDateTest
+ {
+ static readonly Parser StarTrek2009_StarDate =
+ from year in Parse.Digit.Many().Text()
+ from delimiter in Parse.Char('.')
+ from dayOfYear in Parse.Digit.Repeat(1,3).Text().End()
+ select new DateTime(int.Parse(year), 1, 1).AddDays(int.Parse(dayOfYear) - 1);
+
+ [Test]
+ public void ItIsPossibleToParseAStarDate()
+ {
+ Assert.That(StarTrek2009_StarDate.Parse("2259.55"), Is.EqualTo(new DateTime(2259, 2, 24)));
+ }
+
+ [Test, ExpectedException(typeof(ParseException))]
+ public void InvalidStarDatesAreNotParsed()
+ {
+ var date = StarTrek2009_StarDate.Parse("2259.4000");
+ }
+ }
+}
diff --git a/src/Sprache.Tests/Sprache.Tests.csproj b/src/Sprache.Tests/Sprache.Tests.csproj
index 61305a4..e4104f3 100644
--- a/src/Sprache.Tests/Sprache.Tests.csproj
+++ b/src/Sprache.Tests/Sprache.Tests.csproj
@@ -106,16 +106,13 @@
+
+
-
-
-
- {DF5FE6F0-5ABE-4363-9184-EB6EF64F0F61}
- Sprache
-
+
@@ -137,6 +134,15 @@
+
+
+ {82cc8cf8-b770-4756-850a-95dd14dd312f}
+ Sprache
+
+
+
+
+
-
- Properties\Version.cs
-
+
+
+
-
+
+
+
+
+
+
-
+
+
+ Properties\Version.cs
+
-
-
- False
- .NET Framework 3.5 SP1 Client Profile
- false
-
-
- False
- .NET Framework 3.5 SP1
- true
-
-
- False
- Windows Installer 3.1
- true
-
-
-
-
-
-
-
-
-
-
-
-
+
", "\r\n");
+
static readonly Parser Identifier =
from first in Parse.Letter.Once()
from rest in Parse.LetterOrDigit.XOr(Parse.Char('-')).XOr(Parse.Char('_')).Many()
@@ -81,10 +84,14 @@ from end in EndTag(tag)
static readonly Parser ShortNode = Tag(from id in Identifier
from slash in Parse.Char('/')
select new Node { Name = id });
-
+
static readonly Parser Node = ShortNode.Or(FullNode);
- static readonly Parser- Item = Node.Select(n => (Item)n).XOr(Content);
+ static readonly Parser
- Item =
+ from leading in Comment.MultiLineComment.Many()
+ from item in Node.Select(n => (Item)n).XOr(Content)
+ from trailing in Comment.MultiLineComment.Many()
+ select item;
public static readonly Parser Document =
from leading in Parse.WhiteSpace.Many()
@@ -96,8 +103,9 @@ class Program
{
static void Main()
{
- const string doc = "
hello,
world!
";
- var parsed = XmlParser.Document.Parse(doc);
+ StreamReader reader = new StreamReader("TestFile.xml");
+ var parsed = XmlParser.Document.Parse(reader.ReadToEnd());
+ reader.Close();
Console.WriteLine(parsed);
Console.ReadKey(true);
}
diff --git a/src/XmlExample/TestFile.xml b/src/XmlExample/TestFile.xml
new file mode 100644
index 0000000..f6aae14
--- /dev/null
+++ b/src/XmlExample/TestFile.xml
@@ -0,0 +1,7 @@
+
+
+ hello,
world!
+
+
\ No newline at end of file
diff --git a/src/XmlExample/XmlExample.csproj b/src/XmlExample/XmlExample.csproj
index 9d56df8..b05958e 100644
--- a/src/XmlExample/XmlExample.csproj
+++ b/src/XmlExample/XmlExample.csproj
@@ -103,12 +103,6 @@
-
-
- {DF5FE6F0-5ABE-4363-9184-EB6EF64F0F61}
- Sprache
-
-
False
@@ -129,6 +123,17 @@
+
+
+ {82cc8cf8-b770-4756-850a-95dd14dd312f}
+ Sprache
+
+
+
+
+ Always
+
+