diff --git a/src/Sprache.Tests/Scenarios/AssemblerTests.cs b/src/Sprache.Tests/Scenarios/AssemblerTests.cs index 8731a1a..d0302cb 100644 --- a/src/Sprache.Tests/Scenarios/AssemblerTests.cs +++ b/src/Sprache.Tests/Scenarios/AssemblerTests.cs @@ -23,8 +23,7 @@ from instructionName in AsmToken(Parse.LetterOrDigit.Many().Text()) from operands in AsmToken(Identifier).XDelimitedBy(Parse.Char(',')) select Tuple.Create(instructionName, operands.ToArray()); - public static Parser Comment = - AsmToken(Parse.EndOfLineComment(";")); + public static CommentParser Comment = new CommentParser() { Single = ";" }; public static Parser LabelId = Parse.Identifier(Parse.Letter.Or(Parse.Chars("._?")), Parse.LetterOrDigit.Or(Parse.Chars("_@#$~.?"))); @@ -37,7 +36,7 @@ from colon in AsmToken(Parse.Char(':')) public static readonly Parser> Assembler = ( from label in Label.Optional() from instruction in Instruction.Optional() - from comment in Comment.Optional() + from comment in AsmToken(Comment.SingleLineComment).Optional() from lineTerminator in Parse.LineTerminator select new AssemblerLine( label.GetOrDefault(), @@ -150,7 +149,7 @@ 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); + return Equals((AssemblerLine)obj); } public override int GetHashCode() @@ -158,9 +157,9 @@ 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); + 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; } } diff --git a/src/Sprache/CommentParser.cs b/src/Sprache/CommentParser.cs new file mode 100644 index 0000000..5a53a4c --- /dev/null +++ b/src/Sprache/CommentParser.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sprache +{ + /// + /// Constructs customizable comment parsers. + /// + public class CommentParser : IComment + { + /// + ///Single-line comment header. + /// + public string Single { get; set; } + + /// + ///Newline character preference. + /// + public string NewLine { get; set; } + + /// + ///Multi-line comment opener. + /// + public string MultiOpen { get; set; } + + /// + ///Multi-line comment closer. + /// + public string MultiClose { get; set; } + + /// + /// Initializes a Comment with C-style headers and Windows newlines. + /// + public CommentParser() + { + Single = "//"; + MultiOpen = "/*"; + MultiClose = "*/"; + NewLine = "\n"; + } + + /// + /// Initializes a Comment with custom multi-line headers and newline characters. + /// Single-line headers are made null, it is assumed they would not be used. + /// + /// + /// + /// + public CommentParser(string multiOpen, string multiClose, string newLine = "\n") + { + Single = null; + MultiOpen = multiOpen; + MultiClose = multiClose; + NewLine = newLine; + } + + /// + /// Initializes a Comment with custom headers and newline characters. + /// + /// + /// + /// + /// + public CommentParser(string single, string multiOpen, string multiClose, string newLine = "\n") + { + Single = single; + MultiOpen = multiOpen; + MultiClose = multiClose; + NewLine = newLine; + } + + /// + ///Parse a single-line comment. + /// + public Parser SingleLineComment + { + get + { + if (Single == null) + throw new ParseException("Field 'Single' is null; single-line comments not allowed."); + + return from first in Parse.String(Single) + from rest in Parse.CharExcept(NewLine).Many().Text() + select rest; + } + private set { } + } + + /// + ///Parse a multi-line comment. + /// + public Parser MultiLineComment + { + get + { + if (MultiOpen == null) + throw new ParseException("Field 'MultiOpen' is null; multi-line comments not allowed."); + else if (MultiClose == null) + throw new ParseException("Field 'MultiClose' is null; multi-line comments not allowed."); + + return from first in Parse.String(MultiOpen) + from rest in Parse.AnyChar + .Until(Parse.String(MultiClose)).Text() + select rest; + } + private set { } + } + + /// + ///Parse a comment. + /// + public Parser AnyComment + { + get + { + if (Single != null && MultiOpen != null && MultiClose != null) + return SingleLineComment.Or(MultiLineComment); + else if (Single != null && (MultiOpen == null || MultiClose == null)) + return SingleLineComment; + else if (Single == null && (MultiOpen != null && MultiClose != null)) + return MultiLineComment; + else throw new ParseException("Unable to parse comment; check values of fields 'MultiOpen' and 'MultiClose'."); + } + private set { } + } + } +} diff --git a/src/Sprache/IComment.cs b/src/Sprache/IComment.cs new file mode 100644 index 0000000..0929892 --- /dev/null +++ b/src/Sprache/IComment.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Sprache +{ + interface IComment + { + /// + ///Single-line comment header. + /// + string Single { get; set; } + + /// + ///Newline character preference. + /// + string NewLine { get; set; } + + /// + ///Multi-line comment opener. + /// + string MultiOpen { get; set; } + + /// + ///Multi-line comment closer. + /// + string MultiClose { get; set; } + + /// + ///Parse a single-line comment. + /// + Parser SingleLineComment { get; } + + /// + ///Parse a multi-line comment. + /// + Parser MultiLineComment { get; } + + /// + ///Parse a comment. + /// + Parser AnyComment { get; } + } +} diff --git a/src/Sprache/Parse.Primitives.cs b/src/Sprache/Parse.Primitives.cs index b48ce70..0b7b631 100644 --- a/src/Sprache/Parse.Primitives.cs +++ b/src/Sprache/Parse.Primitives.cs @@ -20,19 +20,6 @@ from n in Char('\n') .Or(LineEnd) .Named("LineTerminator"); - /// - /// Parser for single line comment. Doesn't contain tail line ending - /// - /// Symbols to start comment. I.e. "//" for C#, "#" for perl, ";" for assembler - /// - public static Parser EndOfLineComment(string commentStart) - { - return - from start in String(commentStart) - from comment in CharExcept("\r\n").Many().Text() - select comment; - } - /// /// Parser for identifier starting with and continuing with /// diff --git a/src/Sprache/Sprache.csproj b/src/Sprache/Sprache.csproj index 58730ef..79a29d3 100644 --- a/src/Sprache/Sprache.csproj +++ b/src/Sprache/Sprache.csproj @@ -36,6 +36,8 @@ + + diff --git a/src/XmlExample/Program.cs b/src/XmlExample/Program.cs index 6048bda..2a27e14 100644 --- a/src/XmlExample/Program.cs +++ b/src/XmlExample/Program.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using Sprache; +using System.IO; namespace XmlExample { @@ -45,6 +46,8 @@ public override string ToString() public static class XmlParser { + static CommentParser Comment = new CommentParser("", "\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 0d75967..b05958e 100644 --- a/src/XmlExample/XmlExample.csproj +++ b/src/XmlExample/XmlExample.csproj @@ -129,6 +129,11 @@ Sprache
+ + + Always + +