diff --git a/DbcParserLib.Tests/CommentLineParserTests.cs b/DbcParserLib.Tests/CommentLineParserTests.cs index c56a878..b6cf4de 100644 --- a/DbcParserLib.Tests/CommentLineParserTests.cs +++ b/DbcParserLib.Tests/CommentLineParserTests.cs @@ -3,6 +3,7 @@ using Moq; using System.Collections.Generic; using DbcParserLib.Observers; +using System.IO; namespace DbcParserLib.Tests { @@ -92,20 +93,22 @@ public void FullLineIsParsed() [Test] public void FullMultilineIsParsed() { - var multiLineComment = new[] - { - "CM_ SG_ 75 channelName \"This is the first line", - "this is the second line", - "this is the third line\";" - }; - var expectedText = Helpers.ConcatenateTextComment(multiLineComment, 23); + var dbcString = @"CM_ SG_ 75 channelName ""This is the first line +this is the second line +this is the third line"";"; + + var expectedText = "This is the first line\r\nthis is the second line\r\nthis is the third line"; var dbcBuilderMock = m_repository.Create(); dbcBuilderMock.Setup(mock => mock.AddSignalComment(75, "channelName", expectedText)); var commentLineParser = CreateParser(); - var reader = new ArrayBasedLineProvider(multiLineComment); - Assert.That(commentLineParser.TryParse(multiLineComment[0], dbcBuilderMock.Object, reader), Is.True); + using (var reader = new StringReader(dbcString)) + { + var nextLineProvider = new NextLineProvider(reader, new SilentFailureObserver()); + nextLineProvider.TryGetLine(out var line); + Assert.That(commentLineParser.TryParse(line, dbcBuilderMock.Object, nextLineProvider), Is.True); + } } [Test] @@ -122,20 +125,23 @@ public void FullLineIsParsedAndRobustToWhiteSpace() [Test] public void FullMultilineIsParsedAndRobustToWhiteSpace() { - var multiLineComment = new[] - { - "CM_ SG_ 75 channelName \"This is the first line", - " this is the second line", - " this is the third line\";" - }; - var expectedText = Helpers.ConcatenateTextComment(multiLineComment, 23); + var dbcString = @"CM_ SG_ 75 channelName ""This is the first line + this is the second line + this is the third line"";"; + + // Spaces at linestart are always removed + var expectedText = "This is the first line\r\nthis is the second line\r\nthis is the third line"; var dbcBuilderMock = m_repository.Create(); dbcBuilderMock.Setup(mock => mock.AddSignalComment(75, "channelName", expectedText)); var commentLineParser = CreateParser(); - var reader = new ArrayBasedLineProvider(multiLineComment); - Assert.That(commentLineParser.TryParse(multiLineComment[0], dbcBuilderMock.Object, reader), Is.True); + using (var reader = new StringReader(dbcString)) + { + var nextLineProvider = new NextLineProvider(reader, new SilentFailureObserver()); + nextLineProvider.TryGetLine(out var line); + Assert.That(commentLineParser.TryParse(line, dbcBuilderMock.Object, nextLineProvider), Is.True); + } } [Test] @@ -152,20 +158,23 @@ public void FullLineIsParsedForMessageAndRobustToWhiteSpace() [Test] public void FullMultilineIsParsedForMessageAndRobustToWhiteSpace() { - var multiLineComment = new[] - { - "CM_ BO_ 75 \"This is the first line", - " this is the second line", - " this is the third line\";" - }; - var expectedText = Helpers.ConcatenateTextComment(multiLineComment, 11); + var dbcString = @"CM_ BO_ 75 ""This is the first line + this is the second line + this is the third line"";"; + + // Spaces at linestart are always removed + var expectedText = "This is the first line\r\nthis is the second line\r\nthis is the third line"; var dbcBuilderMock = m_repository.Create(); dbcBuilderMock.Setup(mock => mock.AddMessageComment(75, expectedText)); var commentLineParser = CreateParser(); - var reader = new ArrayBasedLineProvider(multiLineComment); - Assert.That(commentLineParser.TryParse(multiLineComment[0], dbcBuilderMock.Object, reader), Is.True); + using (var reader = new StringReader(dbcString)) + { + var nextLineProvider = new NextLineProvider(reader, new SilentFailureObserver()); + nextLineProvider.TryGetLine(out var line); + Assert.That(commentLineParser.TryParse(line, dbcBuilderMock.Object, nextLineProvider), Is.True); + } } [Test] @@ -202,20 +211,23 @@ public void FullLineIsParsedForNodeAndRobustToWhiteSpace() [Test] public void FullMultilineIsParsedForNodeAndRobustToWhiteSpace() { - var multiLineComment = new[] - { - "CM_ BU_ node_name \"This is the first line", - " this is the second line", - " this is the third line\";" - }; - var expectedText = Helpers.ConcatenateTextComment(multiLineComment, 18); + var dbcString = @"CM_ BU_ node_name ""This is the first line + this is the second line + this is the third line"";"; + + // Spaces at linestart are always removed + var expectedText = "This is the first line\r\nthis is the second line\r\nthis is the third line"; var dbcBuilderMock = m_repository.Create(); dbcBuilderMock.Setup(mock => mock.AddNodeComment("node_name", expectedText)); var commentLineParser = CreateParser(); - var reader = new ArrayBasedLineProvider(multiLineComment); - Assert.That(commentLineParser.TryParse(multiLineComment[0], dbcBuilderMock.Object, reader), Is.True); + using (var reader = new StringReader(dbcString)) + { + var nextLineProvider = new NextLineProvider(reader, new SilentFailureObserver()); + nextLineProvider.TryGetLine(out var line); + Assert.That(commentLineParser.TryParse(line, dbcBuilderMock.Object, nextLineProvider), Is.True); + } } [Test] @@ -255,6 +267,12 @@ public void AnotherMalformedLineIsAcceptedWithoutInteraction() [TestCase("CM_ BO_ 865 \"Test with incorrect \"syntax\"\";")] [TestCase("CM_ EV_ VarName \"Test with incorrect \"syntax\"\";")] [TestCase("CM_ \"Test with incorrect \"syntax\"\";")] + [TestCase("CM_ BO_ 1160 \"This is a very fine comment;\"")] + [TestCase("CM_ BO_ 1160 \"This is a very fine comment\"")] + [TestCase("CM_ BO_ 1160 \"This is a very fine comment; Right?\"")] + [TestCase("CM_ SG_ SignalName \"This is a very fine comment; Right?\"")] + [TestCase("CM_ BU_ NodeName \"This is a very fine comment; Right?\"")] + [TestCase("CM_ EV_ VarName \"This is a very fine comment; Right?\"")] public void CommentSyntaxErrorIsObserved(string commentLine) { var observerMock = m_repository.Create(); diff --git a/DbcParserLib.Tests/ParserTests.cs b/DbcParserLib.Tests/ParserTests.cs index 8a3b188..152964a 100644 --- a/DbcParserLib.Tests/ParserTests.cs +++ b/DbcParserLib.Tests/ParserTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using System.Linq; using DbcParserLib.Model; +using DbcParserLib.Observers; namespace DbcParserLib.Tests { @@ -550,5 +551,349 @@ public void CheckGlobalPropertiesTest() // Should check the extended multiplexing stuff once implemented } + + [Test] + public void MultilineValueTableTest() + { + var dbcString = @" +BO_ 134 TestMessage: 1 Test + SG_ TEST_BuckleSwitch : 0|8@0+ (1,0) [0|0] """" Receiver + +VAL_ 134 TEST_BuckleSwitch 0 ""Buckled "" 1 "" Unbuckle "" 2 ""Not Used"" 3 ""Not Used +Default value: 0x0"";"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Count, Is.EqualTo(4)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Key, Is.EqualTo(3)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Value, Is.EqualTo("Not Used Default value: 0x0")); + } + + [Test] + public void MultilineValueTableTestWithOnlyNewLine() + { + var dbcString = "BO_ 134 TestMessage: 1 Test \n SG_ TEST_BuckleSwitch : 0|8@0+ (1,0) [0|0] \"\" Receiver\n VAL_ 134 TEST_BuckleSwitch 0 \"Buckled \" 1 \" Unbuckle \" 2 \"Not Used\" 3 \"Not Used \nDefault value: 0x0\";"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Count, Is.EqualTo(4)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Key, Is.EqualTo(3)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Value, Is.EqualTo("Not Used Default value: 0x0")); + } + + [Test] + public void MultilineSignalTestEOF() + { + var dbcString = @"BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ + (1,0) [0|0] """" EPAS"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().Receiver.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().Receiver.First(), Is.EqualTo("EPAS")); + } + + [Test] + public void MultilineSignalTestNextLineEmpty() + { + var dbcString = @"BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ + (1,0) [0|0] """" EPAS +"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().Receiver.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().Receiver.First(), Is.EqualTo("EPAS")); + } + + [Test] + public void MultilineSignalTestNextLineOtherDefinition() + { + var dbcString = @"BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ + (1,0) [0|0] """" EPAS + SG_ DAS_steeringAngleRequest : 0|16@0+ (1,0) [0|0] """" EPAS +"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().Receiver.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.First().Receiver.First(), Is.EqualTo("EPAS")); + } + + [Test] + public void SingleLineMultiValueTableTest() + { + var dbcString = @"BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|0] """" EPAS + SG_ DAS_steeringAngleRequest : 0|16@0+ (1,0) [0|0] """" EPAS + +VAL_ 1160 DAS_steeringControlType 1 ""ANGLE_CONTROL"" 3 ""DISABLED"" 0 ""NONE"" 2 ""RESERVED"" ; VAL_ 1160 DAS_steeringAngleRequest 16384 ""ZERO_ANGLE"" ;"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Count, Is.EqualTo(4)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Key, Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Value, Is.EqualTo("RESERVED")); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Key, Is.EqualTo(16384)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Value, Is.EqualTo("ZERO_ANGLE")); + } + + [Test] + public void CommentWithEmptyLines() + { + var dbcString = @" +BO_ 1043 BLINKERS: 8 XXX + +CM_ BO_ 1043 ""Message comment first line + +third line"";"; + + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.EqualTo($"Message comment first line{Environment.NewLine}{Environment.NewLine}third line")); + } + + [Test] + public void CommentEndLineWithTermination() + { + var dbcString = @" +BO_ 1043 BLINKERS: 8 XXX + +CM_ BO_ 1043 ""Message comment first line; +second line"";"; + + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.EqualTo($"Message comment first line;{Environment.NewLine}second line")); + } + + [Test] + public void CommentContainingTermination() + { + var dbcString = @" +BO_ 1043 BLINKERS: 8 XXX + +CM_ BO_ 1043 ""Message; comment; first; line +second; line"";"; + + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.EqualTo($"Message; comment; first; line{Environment.NewLine}second; line")); + } + + [Test] + public void HandleMultiDefinitionLineAfterMultiLine() + { + var dbcString = @" +BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|0] """" EPAS + SG_ DAS_steeringAngleRequest : 0|16@0+ (1,0) [0|0] """" EPAS + +CM_ BO_ 1160 ""Message comment first line +second line""; VAL_ 1160 DAS_steeringControlType 1 ""ANGLE_CONTROL"" 3 ""DISABLED"" 0 ""NONE"" 2 ""RESERVED""; VAL_ 1160 DAS_steeringAngleRequest 16384 ""ZERO_ANGLE""; "; + + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.EqualTo($"Message comment first line{Environment.NewLine}second line")); + + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Count, Is.EqualTo(4)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Key, Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Value, Is.EqualTo("RESERVED")); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Key, Is.EqualTo(16384)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Value, Is.EqualTo("ZERO_ANGLE")); + } + + [Test] + public void CommentWithTerminationAndMultiDefinitionInLine() + { + var dbcString = @" +BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|0] """" EPAS + SG_ DAS_steeringAngleRequest : 0|16@0+ (1,0) [0|0] """" EPAS + +CM_ BO_ 1160 ""This is a very fine comment; Right?""; VAL_ 1160 DAS_steeringControlType 1 ""ANGLE_CONTROL"" 3 ""DISABLED"" 0 ""NONE"" 2 ""RESERVED""; +VAL_ 1160 DAS_steeringAngleRequest 16384 ""ZERO_ANGLE""; "; + + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.EqualTo("This is a very fine comment; Right?")); + + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Count, Is.EqualTo(4)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Key, Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Value, Is.EqualTo("RESERVED")); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Key, Is.EqualTo(16384)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Value, Is.EqualTo("ZERO_ANGLE")); + } + + [Test] + public void CommentFromIssue97() + { + var dbcString = @" +BO_ 256 New_Message_1: 8 Vector__XXX + SG_ New_Signal_2 : 8|8@1- (1,0) [0|0] """" Vector__XXX + SG_ New_Signal_1 : 0|8@1- (1,0) [0|0] """" Vector__XXX + +CM_ SG_ 256 New_Signal_1 ""Flag to indicate newly created object in CAN bus. +Object history flag +0x0: new object in the cycle; +0x1: object existed in previous cycle""; +BA_DEF_ ""DBName"" STRING ;"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.Null); + + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(2)); + + Assert.That(dbc.Messages.First().Signals.First().Name, Is.EqualTo("New_Signal_2")); + Assert.That(dbc.Messages.First().Signals.Last().Name, Is.EqualTo("New_Signal_1")); + Assert.That(dbc.Messages.First().Signals.Last().Comment, Is.EqualTo($"Flag to indicate newly created object in CAN bus.{Environment.NewLine}Object history flag{Environment.NewLine}0x0: new object in the cycle;{Environment.NewLine}0x1: object existed in previous cycle")); + } + + + [Test] + public void ParseFullFile() + { + var dbcString = @" +VERSION """" + +NS_ : + NS_DESC_ + CM_ + BA_DEF_ + BA_ + VAL_ + CAT_DEF_ + CAT_ + FILTER + BA_DEF_DEF_ + EV_DATA_ + ENVVAR_DATA_ + SGTYPE_ + SGTYPE_VAL_ + BA_DEF_SGTYPE_ + BA_SGTYPE_ + SIG_TYPE_REF_ + VAL_TABLE_ + SIG_GROUP_ + SIG_VALTYPE_ + SIGTYPE_VALTYPE_ + BO_TX_BU_ + BA_DEF_REL_ + BA_REL_ + BA_DEF_DEF_REL_ + BU_SG_REL_ + BU_EV_REL_ + BU_BO_REL_ + SG_MUL_VAL_ + +BS_: + +BU_: + +BO_ 1160 DAS_steeringControl: 4 NEO + SG_ DAS_steeringControlType : 23|2@0+ (1,0) [0|0] """" EPAS + SG_ DAS_steeringAngleRequest : 0|16@0+ (1,0) [0|0] """" EPAS + +CM_ BO_ 1160 ""This is a very fine comment; Right?""; +VAL_ 1160 DAS_steeringControlType 1 ""ANGLE_CONTROL"" 3 ""DISABLED"" 0 ""NONE"" 2 ""RESERVED""; +VAL_ 1160 DAS_steeringAngleRequest 16384 ""ZERO_ANGLE"";"; + + var failureObserver = new SimpleFailureObserver(); + Parser.SetParsingFailuresObserver(failureObserver); + var dbc = Parser.Parse(dbcString); + var errorList = failureObserver.GetErrorList(); + + Assert.That(errorList, Has.Count.EqualTo(0)); + Assert.That(dbc.Messages.Count(), Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Comment, Is.EqualTo("This is a very fine comment; Right?")); + + Assert.That(dbc.Messages.First().Signals.Count(), Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Count, Is.EqualTo(4)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Key, Is.EqualTo(2)); + Assert.That(dbc.Messages.First().Signals.First().ValueTableMap.Last().Value, Is.EqualTo("RESERVED")); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Count, Is.EqualTo(1)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Key, Is.EqualTo(16384)); + Assert.That(dbc.Messages.First().Signals.Last().ValueTableMap.Last().Value, Is.EqualTo("ZERO_ANGLE")); + } } } \ No newline at end of file diff --git a/DbcParserLib.Tests/PropertiesLineParserTests.cs b/DbcParserLib.Tests/PropertiesLineParserTests.cs index 8096011..a8fbe64 100644 --- a/DbcParserLib.Tests/PropertiesLineParserTests.cs +++ b/DbcParserLib.Tests/PropertiesLineParserTests.cs @@ -12,6 +12,7 @@ namespace DbcParserLib.Tests public class PropertiesLineParserTests { private MockRepository m_repository; + private static readonly SilentFailureObserver m_silentFailureObserver = new SilentFailureObserver(); [SetUp] public void Setup() @@ -27,7 +28,7 @@ public void Teardown() private static List CreateParser() { - var observer = new SilentFailureObserver(); + var observer = m_silentFailureObserver; return new List() { new PropertiesLineParser(observer), new PropertiesDefinitionLineParser(observer) @@ -47,10 +48,10 @@ private static bool ParseLine(string line, List lineParser, IDbcBui [Test] public void IntDefinitionCustomPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" INT 5 10;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -61,12 +62,12 @@ public void IntDefinitionCustomPropertyIsParsedTest() [Test] public void IntDefinitionCustomPropertyNoBoundariesIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var message = new Message { ID = 2394947585 }; builder.AddMessage(message); var msgCycleTimeLineParser = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BO_ ""AttributeName"" INT 0 0;", msgCycleTimeLineParser, builder, nextLineProvider), Is.True); @@ -85,10 +86,10 @@ public void IntDefinitionCustomPropertyNoBoundariesIsParsedTest() [Test] public void HexDefinitionCustomPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" HEX 5 10;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -99,12 +100,12 @@ public void HexDefinitionCustomPropertyIsParsedTest() [Test] public void HexDefinitionCustomPropertyNoBoundariesIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var message = new Message { ID = 2394947585 }; builder.AddMessage(message); var msgCycleTimeLineParser = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BO_ ""AttributeName"" HEX 0 0;", msgCycleTimeLineParser, builder, nextLineProvider), Is.True); @@ -122,10 +123,10 @@ public void HexDefinitionCustomPropertyNoBoundariesIsParsedTest() [Test] public void FloatDefinitionCustomPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" FLOAT 5 10.5;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -136,12 +137,12 @@ public void FloatDefinitionCustomPropertyIsParsedTest() [Test] public void FloatDefinitionCustomPropertyNoBoundariesIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var message = new Message { ID = 2394947585 }; builder.AddMessage(message); var msgCycleTimeLineParser = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BO_ ""AttributeName"" FLOAT 0 0;", msgCycleTimeLineParser, builder, nextLineProvider), Is.True); @@ -203,10 +204,10 @@ public void ScientificNotationFloatDefinitionCustomPropertyIsParsedTest() [Test] public void StringDefinitionCustomPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" STRING;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -217,10 +218,10 @@ public void StringDefinitionCustomPropertyIsParsedTest() [Test] public void StringDefinitionCustomPropertyOnEnvironmentVariableIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ EV_ ""AttributeName"" STRING;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -231,10 +232,10 @@ public void StringDefinitionCustomPropertyOnEnvironmentVariableIsParsedTest() [Test] public void StringDefinitionCustomPropertyAsGlobalIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ ""AttributeName"" STRING;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -379,12 +380,12 @@ public void EnumDefinitionCustomPropertyWithWhiteSpaceBetweenEntriesIsParsedTest [Test] public void MsgCycleTimePropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var message = new Message { ID = 2394947585 }; builder.AddMessage(message); var msgCycleTimeLineParser = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BO_ ""GenMsgCycleTime"" INT 0 0;", msgCycleTimeLineParser, builder, nextLineProvider), Is.True); @@ -403,14 +404,14 @@ public void MsgCycleTimePropertyIsParsedTest() [Test] public void SigInitialValueIntegerPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var message = new Message { ID = 2394947585 }; builder.AddMessage(message); var signal = new Signal { Name = "sig_name" }; builder.AddSignal(signal); var sigInitialValueLineParser = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ SG_ ""GenSigStartValue"" INT 0 200;", sigInitialValueLineParser, builder, nextLineProvider), Is.True); @@ -430,14 +431,14 @@ public void SigInitialValueIntegerPropertyIsParsedTest() [Test] public void SigInitialValueHexPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var message = new Message { ID = 2394947585 }; builder.AddMessage(message); var signal = new Signal { Name = "sig_name" }; builder.AddSignal(signal); var sigInitialValueLineParser = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ SG_ ""GenSigStartValue"" HEX 0 200;", sigInitialValueLineParser, builder, nextLineProvider), Is.True); @@ -457,12 +458,12 @@ public void SigInitialValueHexPropertyIsParsedTest() [Test] public void NodeCustomPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var node = new Node { Name = "Node1" }; builder.AddNode(node); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" HEX 0 200;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -481,13 +482,13 @@ public void NodeCustomPropertyIsParsedTest() [Test] public void NodeScientificNotationCustomPropertyIsParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var node = new Node { Name = "Node1" }; builder.AddNode(node); var dbc = builder.Build(); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" FLOAT 0 10;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -500,12 +501,12 @@ public void NodeScientificNotationCustomPropertyIsParsedTest() [Test] public void NodeMultipleCustomPropertyAreParsedTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var node = new Node { Name = "Node1" }; builder.AddNode(node); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName1"" INT 0 200;", customPropertyLineParsers, builder, nextLineProvider), Is.True); @@ -525,14 +526,14 @@ public void NodeMultipleCustomPropertyAreParsedTest() [Test] public void CustomPropertyIsAssignedToDifferentNodesTest() { - var builder = new DbcBuilder(new SilentFailureObserver()); + var builder = new DbcBuilder(m_silentFailureObserver); var node1 = new Node { Name = "Node1" }; var node2 = new Node { Name = "Node2" }; builder.AddNode(node1); builder.AddNode(node2); var customPropertyLineParsers = CreateParser(); - var nextLineProvider = new NextLineProvider(new StringReader(string.Empty)); + var nextLineProvider = new NextLineProvider(new StringReader(string.Empty), m_silentFailureObserver); Assert.Multiple(() => { Assert.That(ParseLine(@"BA_DEF_ BU_ ""AttributeName"" INT 0 200;", customPropertyLineParsers, builder, nextLineProvider), Is.True); diff --git a/DbcParserLib.Tests/SignalLineParserTests.cs b/DbcParserLib.Tests/SignalLineParserTests.cs index b3599b3..5472626 100644 --- a/DbcParserLib.Tests/SignalLineParserTests.cs +++ b/DbcParserLib.Tests/SignalLineParserTests.cs @@ -49,13 +49,13 @@ public void RandomStartIsIgnored() } [Test] - public void OnlyPrefixWithSpacesIsAcceptedWithNoInteractions() + public void OnlyPrefixIsIgnored() { var dbcBuilderMock = m_repository.Create(); var signalLineParser = CreateParser(); var nextLineProviderMock = m_repository.Create(); - Assert.That(signalLineParser.TryParse("SG_ ", dbcBuilderMock.Object, nextLineProviderMock.Object), Is.True); + Assert.That(signalLineParser.TryParse("SG_ ", dbcBuilderMock.Object, nextLineProviderMock.Object), Is.False); } [Test] @@ -219,7 +219,6 @@ public void ParseSignalWithDifferentColonSpaces() [TestCase("SG_ qGearboxOilMin : 0|16@1+ (0.1,0) [0|6553.5] l/min NATEC")] [TestCase("SG_ \"qGearboxOilMin\" : 0|16@1+ (0.1,0) [0|6553.5] \"l/min\" NATEC")] [TestCase("SG_ qGearboxOilMin 0|16@1+ (0.1,0) [0|6553.5] \"l/min\" NATEC")] - [TestCase("SG_ ")] public void SignalSyntaxErrorIsObserved(string line) { var observerMock = m_repository.Create(); diff --git a/DbcParserLib/ExtensionsAndHelpers.cs b/DbcParserLib/ExtensionsAndHelpers.cs index 591afa6..5da49c5 100644 --- a/DbcParserLib/ExtensionsAndHelpers.cs +++ b/DbcParserLib/ExtensionsAndHelpers.cs @@ -2,11 +2,17 @@ using System.Linq; using System.Collections.Generic; using DbcParserLib.Model; +using System.Text.RegularExpressions; namespace DbcParserLib { public static class ExtensionsAndHelpers { + public const string DoubleQuotes = "\""; + public const string Space = " "; + public static readonly string[] CommaSpaceSeparator = { " ", "," }; + private static readonly string[] SpaceArray = { " " }; + public static bool Motorola(this Signal signal) { return signal.Msb(); @@ -116,6 +122,21 @@ internal static bool InitialValue(this Signal signal, out double initialValue) return false; } + public static string[] SplitBySpace(this string value) + { + return value.Split(SpaceArray, System.StringSplitOptions.RemoveEmptyEntries); + } + + // Sequence of return codes was taken from the internals of "String.ReplaceLineEndings" method. + private const string NewLineChars = "\r\f\u0085\u2028\u2029\n"; + private static readonly string pattern = $"[{Regex.Escape(NewLineChars)}]+"; + + public static string ReplaceNewlinesWithSpace(this string input) + { + // Would like to use "String.ReplaceLineEndings" but its unavailable because of the target frameworks + // Feel free to optimate + return Regex.Replace(input, pattern, " "); + } } internal class StringToDictionaryParser @@ -142,7 +163,7 @@ public static bool ParseString(string text, out IReadOnlyDictionary private bool ParseKey(string text, int offset) { - var index = text.IndexOf(Helpers.DoubleQuotes, offset, StringComparison.InvariantCulture); + var index = text.IndexOf(ExtensionsAndHelpers.DoubleQuotes, offset, StringComparison.InvariantCulture); if(index == -1) return true; @@ -153,7 +174,7 @@ private bool ParseKey(string text, int offset) private bool ParseValue(string text, int offset, int key) { - var index = text.IndexOf(Helpers.DoubleQuotes, offset, StringComparison.InvariantCulture); + var index = text.IndexOf(ExtensionsAndHelpers.DoubleQuotes, offset, StringComparison.InvariantCulture); if (index == -1) return false; diff --git a/DbcParserLib/Helpers.cs b/DbcParserLib/Helpers.cs deleted file mode 100644 index 113c8a1..0000000 --- a/DbcParserLib/Helpers.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text; -using System.Text.RegularExpressions; - -namespace DbcParserLib -{ - public static class Helpers - { - public const string Space = " "; - public const string Comma = ","; - public const string DoubleQuotes = "\""; - - private static readonly string[] SpaceArray = new[] { Space }; - - public static string[] SplitBySpace(this string value) - { - return value.Split(SpaceArray, System.StringSplitOptions.RemoveEmptyEntries); - } - - public static string ConvertToMultiLine(string[] records, int offset) - { - var sb = new StringBuilder(); - for (var i = offset; i < records.Length - 1; i += 2) - { - sb.AppendFormat("{0} {1} {2}", records[i], records[i+1], '\n'); - } - - return sb.ToString(); - } - - public static string ConcatenateTextComment(string[] strings, int startingIndex) - { - var sb = new StringBuilder(); - foreach(var s in strings ) - { - sb.AppendLine(Regex.Replace(s, @"""|;", "")); - } - var commentText = sb.ToString().Substring(startingIndex); - return commentText.Substring(0, commentText.Length - 2); - } - } - -} \ No newline at end of file diff --git a/DbcParserLib/NextLineProvider.cs b/DbcParserLib/NextLineProvider.cs index 45a8d6f..e1ecbfb 100644 --- a/DbcParserLib/NextLineProvider.cs +++ b/DbcParserLib/NextLineProvider.cs @@ -1,25 +1,184 @@ -using System.IO; +using DbcParserLib.Observers; +using System; +using System.IO; +using System.Linq; +using System.Text; namespace DbcParserLib { public class NextLineProvider : INextLineProvider { - private TextReader m_reader; + private PeekableTextReader m_reader; - public NextLineProvider(TextReader reader) + private const string lineTermination = ";"; + + private readonly string[] keywords = { - m_reader = reader; + "VERSION", + "FILTER", + + "NS_DESC_", + "NS_", + + "CM_", + + "BA_DEF_DEF_REL_", + "BA_DEF_REL_", + "BA_REL_", + "BA_DEF_SGTYPE_", + "BA_SGTYPE_", + "BA_DEF_DEF_", + "BA_DEF_", + "BA_", + + "CAT_DEF_", + "CAT_", + + "SGTYPE_VAL_", + "SGTYPE_", + + "SIGTYPE_VALTYPE_", + + "VAL_TABLE_", + "VAL_", + + "SIG_GROUP_", + "SIG_VALTYPE_", + "SIG_TYPE_REF_", + + "EV_DATA_", + "ENVVAR_DATA_", + "EV_", + + "BO_TX_BU_", + "BO_", + + "BU_SG_REL_", + "BU_EV_REL_", + "BU_BO_REL_", + "BU_", + + "SG_MUL_VAL_", + "SG_", + + "BS_", + }; + + public NextLineProvider(TextReader reader, IParseFailureObserver observer) + { + m_reader = new PeekableTextReader(reader, observer); } public bool TryGetLine(out string line) { line = null; - if (m_reader.Peek() >= 0) + + var readLine = m_reader.ReadLine(); + + if (readLine is null) + { + return false; + } + + line = readLine.Trim(); + + if (string.IsNullOrEmpty(line)) { - line = m_reader.ReadLine(); return true; } - return false; + + line = HandleMultipleDefinitionsPerLine(line); + line = HandleMultiline(line); + + return true; + } + + private string HandleMultipleDefinitionsPerLine(string line) + { + var lineTerminationIndex = line.IndexOf(lineTermination, StringComparison.Ordinal); + var afterTerminationPosition = lineTerminationIndex + lineTermination.Length; + + // If line has no termination just return the line + if (lineTerminationIndex < 0) + { + return line; + } + + while (afterTerminationPosition < line.Length) + { + var partAfterTermination = line.Substring(afterTerminationPosition); + + // If part after the termination is another keyword then add the remaining line as virtual line + // and return line up to and including line termination; + if (CheckLineIsDefinition(partAfterTermination.TrimStart())) + { + m_reader.SetVirtualLine(partAfterTermination); + return line.Substring(0, afterTerminationPosition); + } + + var indexOfNextTermination = partAfterTermination.IndexOf(lineTermination, StringComparison.Ordinal); + + if (indexOfNextTermination < 0) + { + // You run in here if the line has atleast one termination but no followup definition + // Should only occure in comments but these should end in termination char so will most likely + // lead to an parsing error + return line; + } + + afterTerminationPosition = afterTerminationPosition + indexOfNextTermination + lineTermination.Length; + } + // If the terminationcharacter is found at the end of the line and no subdefinitions where found retrun the line + return line; + } + + private string HandleMultiline(string line) + { + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine(line); + + var numEmptyLines = 0; + while (true) + { + var checkLine = m_reader.PeekLine(); + + if (checkLine is null) + { + break; + } + + checkLine = checkLine.Trim(); + + if (string.IsNullOrEmpty(checkLine)) + { + numEmptyLines++; + continue; + } + + if (CheckLineIsDefinition(checkLine) == false) + { + for (int i = 0; i < numEmptyLines; i++) + { + stringBuilder.AppendLine(m_reader.ReadLine().Trim()); + } + numEmptyLines = 0; + + var lineToAdd = m_reader.ReadLine().Trim(); + lineToAdd = HandleMultipleDefinitionsPerLine(lineToAdd); + + stringBuilder.AppendLine(lineToAdd); + continue; + } + + break; + } + + return stringBuilder.ToString(); + } + + private bool CheckLineIsDefinition(string line) + { + return keywords.Any(keywordPrefix => line.StartsWith(keywordPrefix)); } } -} \ No newline at end of file +} diff --git a/DbcParserLib/Observers/IParseFailureObserver.cs b/DbcParserLib/Observers/IParseFailureObserver.cs index 875be68..d88c442 100644 --- a/DbcParserLib/Observers/IParseFailureObserver.cs +++ b/DbcParserLib/Observers/IParseFailureObserver.cs @@ -38,7 +38,7 @@ public interface IParseFailureObserver void PropertyValueOutOfIndex(string propertyName, string index); void ExtraMessageTransmittersSyntaxError(); void ExtraMessageTransmittersDuplicate(uint messageId, string duplicateTransmitter); - void UnknownLine(); + void UnknownLine(string line); void NoMessageFound(); void Clear(); } diff --git a/DbcParserLib/Observers/SilentFailureObserver.cs b/DbcParserLib/Observers/SilentFailureObserver.cs index eb96a9c..3db1b66 100644 --- a/DbcParserLib/Observers/SilentFailureObserver.cs +++ b/DbcParserLib/Observers/SilentFailureObserver.cs @@ -140,7 +140,7 @@ public void ExtraMessageTransmittersDuplicate(uint messageId, string duplicateTr { } - public void UnknownLine() + public void UnknownLine(string line) { } diff --git a/DbcParserLib/Observers/SimpleFailureObserver.cs b/DbcParserLib/Observers/SimpleFailureObserver.cs index 45e0a12..a4dd823 100644 --- a/DbcParserLib/Observers/SimpleFailureObserver.cs +++ b/DbcParserLib/Observers/SimpleFailureObserver.cs @@ -184,9 +184,9 @@ public void ExtraMessageTransmittersDuplicate(uint messageId, string duplicateTr AddError($"Duplicate additional transmitter '{duplicateTransmitter}' in message '{messageId}'"); } - public void UnknownLine() + public void UnknownLine(string line) { - AddError("Unknown syntax"); + AddError($"Unknown syntax in line: '{line}'"); } public void NoMessageFound() diff --git a/DbcParserLib/Parser.cs b/DbcParserLib/Parser.cs index 3b4b18a..c653cbb 100644 --- a/DbcParserLib/Parser.cs +++ b/DbcParserLib/Parser.cs @@ -66,24 +66,29 @@ private static Dbc ParseFromReader(TextReader reader) m_parseObserver.Clear(); var builder = new DbcBuilder(m_parseObserver); - var nextLineProvider = new NextLineProvider(reader); + var nextLineProvider = new NextLineProvider(reader, m_parseObserver); - while (reader.Peek() >= 0) - ParseLine(reader.ReadLine(), builder, nextLineProvider); + while (nextLineProvider.TryGetLine(out var line)) + { + ParseLine(line, builder, nextLineProvider); + } return builder.Build(); } private static void ParseLine(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - m_parseObserver.CurrentLine++; if (string.IsNullOrWhiteSpace(line)) + { return; + } - foreach(var parser in LineParsers) + foreach (var parser in LineParsers) { - if(parser.TryParse(line, builder, nextLineProvider)) + if (parser.TryParse(line, builder, nextLineProvider)) + { break; + } } } } diff --git a/DbcParserLib/Parsers/CommentLineParser.cs b/DbcParserLib/Parsers/CommentLineParser.cs index 2ec0bf9..2c8787e 100644 --- a/DbcParserLib/Parsers/CommentLineParser.cs +++ b/DbcParserLib/Parsers/CommentLineParser.cs @@ -13,11 +13,11 @@ internal class CommentLineParser : ILineParser private const string EnvVarNameGroup = "EnvVarName"; private const string CommentLineStarter = "CM_ "; - private readonly string m_genericCommentParsingRegex = $@"CM_\s+""*(?<{CharGroup}>[^""]*)""*\s*;"; - private readonly string m_nodeParsingRegex = $@"CM_ BU_\s+(?<{NodeNameGroup}>[a-zA-Z_][\w]*)\s+""*(?<{CharGroup}>[^""]*)""*\s*;"; - private readonly string m_messageParsingRegex = $@"CM_ BO_\s+(?<{MessageIdGroup}>\d+)\s+""*(?<{CharGroup}>[^""]*)""*\s*;"; - private readonly string m_signalParsingRegex = $@"CM_ SG_\s+(?<{MessageIdGroup}>\d+)\s+(?<{SignalNameGroup}>[a-zA-Z_][\w]*)\s+""*(?<{CharGroup}>[^""]*)""*\s*;"; - private readonly string m_environmentVariableParsingRegex = $@"CM_ EV_\s+(?<{EnvVarNameGroup}>[a-zA-Z_][\w]*)\s+""*(?<{CharGroup}>[^""]*)""*\s*;"; + private readonly string m_genericCommentParsingRegex = $@"CM_\s+""*(?<{CharGroup}>[^""]*)""*\s*;$"; + private readonly string m_nodeParsingRegex = $@"CM_ BU_\s+(?<{NodeNameGroup}>[a-zA-Z_][\w]*)\s+""*(?<{CharGroup}>[^""]*)""*\s*;$"; + private readonly string m_messageParsingRegex = $@"CM_ BO_\s+(?<{MessageIdGroup}>\d+)\s+""*(?<{CharGroup}>[^""]*)""*\s*;$"; + private readonly string m_signalParsingRegex = $@"CM_ SG_\s+(?<{MessageIdGroup}>\d+)\s+(?<{SignalNameGroup}>[a-zA-Z_][\w]*)\s+""*(?<{CharGroup}>[^""]*)""*\s*;$"; + private readonly string m_environmentVariableParsingRegex = $@"CM_ EV_\s+(?<{EnvVarNameGroup}>[a-zA-Z_][\w]*)\s+""*(?<{CharGroup}>[^""]*)""*\s*;$"; private readonly IParseFailureObserver m_observer; @@ -33,9 +33,6 @@ public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLin if (cleanLine.StartsWith(CommentLineStarter) == false) return false; - if (!cleanLine.EndsWith(";")) - cleanLine = GetNextLines(cleanLine, m_observer, nextLineProvider); - if (cleanLine.StartsWith("CM_ SG_")) { SetSignalComment(cleanLine, m_observer, builder, nextLineProvider); diff --git a/DbcParserLib/Parsers/EnvironmentDataVariableLineParser.cs b/DbcParserLib/Parsers/EnvironmentDataVariableLineParser.cs index 642cb90..4907f07 100644 --- a/DbcParserLib/Parsers/EnvironmentDataVariableLineParser.cs +++ b/DbcParserLib/Parsers/EnvironmentDataVariableLineParser.cs @@ -20,7 +20,7 @@ public EnvironmentDataVariableLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(EnvironmentDataVariableLineStarter) == false) return false; diff --git a/DbcParserLib/Parsers/EnvironmentVariableLineParser.cs b/DbcParserLib/Parsers/EnvironmentVariableLineParser.cs index 69848d4..1733fe2 100644 --- a/DbcParserLib/Parsers/EnvironmentVariableLineParser.cs +++ b/DbcParserLib/Parsers/EnvironmentVariableLineParser.cs @@ -31,7 +31,7 @@ public EnvironmentVariableLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(EnvironmentVariableLineStarter) == false) return false; diff --git a/DbcParserLib/Parsers/MessageLineParser.cs b/DbcParserLib/Parsers/MessageLineParser.cs index b4342a0..b14d910 100644 --- a/DbcParserLib/Parsers/MessageLineParser.cs +++ b/DbcParserLib/Parsers/MessageLineParser.cs @@ -24,10 +24,12 @@ public MessageLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - if (line.Trim().StartsWith(MessageLineStarter) == false) + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); + + if (cleanLine.StartsWith(MessageLineStarter) == false) return false; - var match = Regex.Match(line, m_messageRegex); + var match = Regex.Match(cleanLine, m_messageRegex); if (match.Success) { var msg = new Message() diff --git a/DbcParserLib/Parsers/NodeLineParser.cs b/DbcParserLib/Parsers/NodeLineParser.cs index a0ea2ec..f7c78e5 100644 --- a/DbcParserLib/Parsers/NodeLineParser.cs +++ b/DbcParserLib/Parsers/NodeLineParser.cs @@ -22,11 +22,13 @@ public NodeLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - if (line.TrimStart().StartsWith(NodeLineStarter) == false) + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); + + if (cleanLine.StartsWith(NodeLineStarter) == false) return false; // Empty node list - if (line.Trim().Equals(NodeLineStarter)) + if (cleanLine.Equals(NodeLineStarter)) return true; var match = m_regex.Match(line); diff --git a/DbcParserLib/Parsers/PropertiesDefinitionLineParser.cs b/DbcParserLib/Parsers/PropertiesDefinitionLineParser.cs index 2fcea88..7bbee66 100644 --- a/DbcParserLib/Parsers/PropertiesDefinitionLineParser.cs +++ b/DbcParserLib/Parsers/PropertiesDefinitionLineParser.cs @@ -40,7 +40,7 @@ public PropertiesDefinitionLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(PropertiesDefinitionLineStarter) == false && cleanLine.StartsWith(PropertiesDefinitionDefaultLineStarter) == false) @@ -50,7 +50,7 @@ public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLin { var match = Regex.Match(cleanLine, m_propertyDefinitionDefaultParsingRegex); if (match.Success) - builder.AddCustomPropertyDefaultValue(match.Groups[AttributeNameGroup].Value, match.Groups[AttributeValueGroup].Value.Replace(Helpers.DoubleQuotes, ""), !match.Groups[AttributeValueGroup].Value.StartsWith(Helpers.DoubleQuotes)); + builder.AddCustomPropertyDefaultValue(match.Groups[AttributeNameGroup].Value, match.Groups[AttributeValueGroup].Value.Replace(ExtensionsAndHelpers.DoubleQuotes, ""), !match.Groups[AttributeValueGroup].Value.StartsWith(ExtensionsAndHelpers.DoubleQuotes)); else m_observer.PropertyDefaultSyntaxError(); return true; @@ -128,8 +128,8 @@ public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLin { Values = match.Groups[EnumValueGroup] .Value - .Replace(Helpers.DoubleQuotes, string.Empty) - .Replace(Helpers.Space, string.Empty) + .Replace(ExtensionsAndHelpers.DoubleQuotes, string.Empty) + .Replace(ExtensionsAndHelpers.Space, string.Empty) .Split(',') }; } diff --git a/DbcParserLib/Parsers/PropertiesLineParser.cs b/DbcParserLib/Parsers/PropertiesLineParser.cs index 031f0b9..85d16f2 100644 --- a/DbcParserLib/Parsers/PropertiesLineParser.cs +++ b/DbcParserLib/Parsers/PropertiesLineParser.cs @@ -29,7 +29,7 @@ public PropertiesLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(PropertiesLineStarter) == false) return false; @@ -37,8 +37,8 @@ public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLin var match = Regex.Match(cleanLine, m_propertyParsingRegex); if (match.Success) { - var isNumeric = !match.Groups[AttributeValueGroup].Value.StartsWith(Helpers.DoubleQuotes); - var stringValue = match.Groups[AttributeValueGroup].Value.Replace(Helpers.DoubleQuotes, string.Empty); + var isNumeric = !match.Groups[AttributeValueGroup].Value.StartsWith(ExtensionsAndHelpers.DoubleQuotes); + var stringValue = match.Groups[AttributeValueGroup].Value.Replace(ExtensionsAndHelpers.DoubleQuotes, string.Empty); if (match.Groups[IsNodeEnvGroup].Value.Equals(string.Empty) == false) builder.AddNodeCustomProperty(match.Groups[AttributeNameGroup].Value, match.Groups[NodeEnvNameGroup].Value, stringValue, isNumeric); diff --git a/DbcParserLib/Parsers/SignalLineParser.cs b/DbcParserLib/Parsers/SignalLineParser.cs index c0be5e4..11bc915 100644 --- a/DbcParserLib/Parsers/SignalLineParser.cs +++ b/DbcParserLib/Parsers/SignalLineParser.cs @@ -22,7 +22,6 @@ internal class SignalLineParser : ILineParser private const string ReceiverGroup = "Receiver"; private const string SignalLineStarter = "SG_ "; private const string SignedSymbol = "-"; - private static readonly string[] CommaSpaceSeparator = { Helpers.Space, Helpers.Comma }; private readonly string m_signalRegex = $@"\s*SG_\s+(?<{NameGroup}>[\w]+)\s*(?<{MultiplexerGroup}>[Mm\d]*)\s*:\s*(?<{StartBirGroup}>\d+)\|(?<{SizeGroup}>\d+)@(?<{ByteOrderGroup}>[01])" + $@"(?<{ValueTypeGroup}>[+-])\s+\((?<{FactorGroup}>[\d\+\-eE.]+),(?<{OffsetGroup}>[\d\+\-eE.]+)\)\s+\[(?<{MinGroup}>[\d\+\-eE.]+)\|(?<{MaxGroup}>[\d\+\-eE.]+)\]" + @@ -37,10 +36,12 @@ public SignalLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - if (line.TrimStart().StartsWith(SignalLineStarter) == false) + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); + + if (cleanLine.StartsWith(SignalLineStarter) == false) return false; - var match = Regex.Match(line, m_signalRegex); + var match = Regex.Match(cleanLine, m_signalRegex); if (match.Success) { var factorStr = match.Groups[FactorGroup].Value; @@ -58,7 +59,7 @@ public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLin Minimum = double.Parse(match.Groups[MinGroup].Value, CultureInfo.InvariantCulture), Maximum = double.Parse(match.Groups[MaxGroup].Value, CultureInfo.InvariantCulture), Unit = match.Groups[UnitGroup].Value, - Receiver = match.Groups[ReceiverGroup].Value.Split(CommaSpaceSeparator, StringSplitOptions.RemoveEmptyEntries) // can be multiple receivers splitted by "," + Receiver = match.Groups[ReceiverGroup].Value.Split(ExtensionsAndHelpers.CommaSpaceSeparator, StringSplitOptions.RemoveEmptyEntries) // can be multiple receivers splitted by "," }; builder.AddSignal(sig); diff --git a/DbcParserLib/Parsers/SignalValueTypeLineParser.cs b/DbcParserLib/Parsers/SignalValueTypeLineParser.cs index f86c7d0..05ecd06 100644 --- a/DbcParserLib/Parsers/SignalValueTypeLineParser.cs +++ b/DbcParserLib/Parsers/SignalValueTypeLineParser.cs @@ -22,7 +22,7 @@ public SignalValueTypeLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(SignalValueTypeStarter) == false) return false; diff --git a/DbcParserLib/Parsers/UnknownLineParser.cs b/DbcParserLib/Parsers/UnknownLineParser.cs index 211bb43..66fa320 100644 --- a/DbcParserLib/Parsers/UnknownLineParser.cs +++ b/DbcParserLib/Parsers/UnknownLineParser.cs @@ -13,8 +13,7 @@ public UnknownLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - // Throw or log or add a specific entry in builder maybe? - m_observer.UnknownLine(); + m_observer.UnknownLine(line); return true; } } diff --git a/DbcParserLib/Parsers/ValueTableDefinitionLineParser.cs b/DbcParserLib/Parsers/ValueTableDefinitionLineParser.cs index b14df12..126b4ab 100644 --- a/DbcParserLib/Parsers/ValueTableDefinitionLineParser.cs +++ b/DbcParserLib/Parsers/ValueTableDefinitionLineParser.cs @@ -22,7 +22,7 @@ public ValueTableDefinitionLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(ValueTableDefinitionLineStarter) == false) { diff --git a/DbcParserLib/Parsers/ValueTableLineParser.cs b/DbcParserLib/Parsers/ValueTableLineParser.cs index 79b6819..e088ab6 100644 --- a/DbcParserLib/Parsers/ValueTableLineParser.cs +++ b/DbcParserLib/Parsers/ValueTableLineParser.cs @@ -26,7 +26,7 @@ public ValueTableLineParser(IParseFailureObserver observer) public bool TryParse(string line, IDbcBuilder builder, INextLineProvider nextLineProvider) { - var cleanLine = line.Trim(' '); + var cleanLine = line.ReplaceNewlinesWithSpace().Trim(); if (cleanLine.StartsWith(ValueTableLineStarter) == false) return false; diff --git a/DbcParserLib/PeekableTextReader.cs b/DbcParserLib/PeekableTextReader.cs new file mode 100644 index 0000000..5ec3920 --- /dev/null +++ b/DbcParserLib/PeekableTextReader.cs @@ -0,0 +1,49 @@ +using DbcParserLib.Observers; +using System.Collections.Generic; +using System.IO; + +namespace DbcParserLib +{ + internal class PeekableTextReader + { + private IParseFailureObserver m_observer; + private readonly TextReader m_underlying; + private readonly Queue m_bufferedLines; + private string m_virtualLineMemory; + + public PeekableTextReader(TextReader underlying, IParseFailureObserver observer) + { + m_underlying = underlying; + m_bufferedLines = new Queue(); + m_observer = observer; + } + + public string PeekLine() + { + string line = m_underlying.ReadLine(); + if (line == null) + return null; + m_bufferedLines.Enqueue(line); + return line; + } + + public void SetVirtualLine(string line) + { + m_virtualLineMemory = line; + } + + public string ReadLine() + { + if (m_virtualLineMemory != null) + { + var temp = m_virtualLineMemory; + m_virtualLineMemory = null; + return temp; + } + m_observer.CurrentLine++; + if (m_bufferedLines.Count > 0) + return m_bufferedLines.Dequeue(); + return m_underlying.ReadLine(); + } + } +}