diff --git a/src/Parlot/Fluent/Parser.cs b/src/Parlot/Fluent/Parser.cs index 53365ac7..359f8c0b 100644 --- a/src/Parlot/Fluent/Parser.cs +++ b/src/Parlot/Fluent/Parser.cs @@ -25,9 +25,42 @@ public abstract partial class Parser public Parser Then(U value) => new Then(this, value); /// - /// Builds a parser that converts the previous result. + /// Builds a parser that discards the previous result and returns the default value of type U. + /// For types that implement IConvertible, attempts type conversion. /// - public Parser Then() => new Then(this, x => (U?)Convert.ChangeType(x, typeof(U?), CultureInfo.CurrentCulture)); + public Parser Then() + { + // Check if U implements IConvertible at construction time for performance + var targetImplementsIConvertible = typeof(IConvertible).IsAssignableFrom(typeof(U)); + + if (targetImplementsIConvertible) + { + return new Then(this, x => + { + // If both T and U are IConvertible, try to convert + if (x is IConvertible) + { + try + { + return (U?)Convert.ChangeType(x, typeof(U), CultureInfo.CurrentCulture); + } + catch + { + // Fall back to default if conversion fails + return default(U); + } + } + + // For non-convertible types, return default + return default(U); + }); + } + else + { + // For types that don't implement IConvertible, just return default + return new Then(this, default(U)); + } + } /// /// Builds a parser that converts the previous result when it succeeds or returns a default value if it fails. diff --git a/test/Parlot.Tests/ThenIssueTests.cs b/test/Parlot.Tests/ThenIssueTests.cs new file mode 100644 index 00000000..b4167c18 --- /dev/null +++ b/test/Parlot.Tests/ThenIssueTests.cs @@ -0,0 +1,40 @@ +using Parlot.Fluent; +using Xunit; + +using static Parlot.Fluent.Parsers; + +namespace Parlot.Tests; + +#nullable enable + +public class ThenIssueTests +{ + [Theory] + [InlineData(""" + // A Comment + var a = 'test' + """)] + [InlineData(""" + var a = 'test' + """)] + public void ThenWithNonIConvertibleTypeShouldWork(string testString) + { + var commentParser = Terms.Text("//").And(AnyCharBefore(OneOf(Literals.Char('\n'), Literals.Char('\r')))); + var varKeyword = Terms.Text("var"); + var identifier = Terms.Identifier(); + var equal = Terms.Char('='); + var str = Terms.String(); + + var expressionParser = varKeyword.And(identifier).And(equal).And(str).Then(a => new AssignExpression(a.Item2.ToString()!, a.Item4.ToString()!)); + + var simpleParser = OneOf(commentParser.Then(), expressionParser); + + var success = simpleParser.TryParse(testString, out var result); + + Assert.True(success); + } + + public abstract record BaseExp; + + public record AssignExpression(string Name, string Value) : BaseExp; +}