Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions src/Parlot/Fluent/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,42 @@ public abstract partial class Parser<T>
public Parser<U> Then<U>(U value) => new Then<T, U>(this, value);

/// <summary>
/// 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.
/// </summary>
public Parser<U?> Then<U>() => new Then<T, U?>(this, x => (U?)Convert.ChangeType(x, typeof(U?), CultureInfo.CurrentCulture));
public Parser<U?> Then<U>()
{
// Check if U implements IConvertible at construction time for performance
var targetImplementsIConvertible = typeof(IConvertible).IsAssignableFrom(typeof(U));

if (targetImplementsIConvertible)
{
return new Then<T, U?>(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<T, U?>(this, default(U));
}
}

/// <summary>
/// Builds a parser that converts the previous result when it succeeds or returns a default value if it fails.
Expand Down
40 changes: 40 additions & 0 deletions test/Parlot.Tests/ThenIssueTests.cs
Original file line number Diff line number Diff line change
@@ -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<BaseExp?>(a => new AssignExpression(a.Item2.ToString()!, a.Item4.ToString()!));

var simpleParser = OneOf(commentParser.Then<BaseExp>(), expressionParser);

var success = simpleParser.TryParse(testString, out var result);

Assert.True(success);
}

public abstract record BaseExp;

public record AssignExpression(string Name, string Value) : BaseExp;
}