Skip to content

Commit

Permalink
Add double and float parsers (#84)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastienros authored Feb 15, 2024
1 parent f2f5d54 commit c32c0c3
Show file tree
Hide file tree
Showing 4 changed files with 302 additions and 6 deletions.
2 changes: 1 addition & 1 deletion src/Parlot/Fluent/DecimalLiteral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public override bool Parse(ParseContext context, ref ParseResult<decimal> result

if (decimal.TryParse(sourceToParse, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
{
result.Set(start, end, value);
result.Set(start, end, value);
return true;
}
}
Expand Down
138 changes: 138 additions & 0 deletions src/Parlot/Fluent/DoubleLiteral.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using Parlot.Compilation;
using System;
using System.Globalization;
using System.Linq.Expressions;

namespace Parlot.Fluent
{
public sealed class DoubleLiteral : Parser<double>, ICompilable
{
private readonly NumberOptions _numberOptions;

public DoubleLiteral(NumberOptions numberOptions = NumberOptions.Default)
{
_numberOptions = numberOptions;
}

public override bool Parse(ParseContext context, ref ParseResult<double> result)
{
context.EnterParser(this);

var reset = context.Scanner.Cursor.Position;
var start = reset.Offset;

if ((_numberOptions & NumberOptions.AllowSign) == NumberOptions.AllowSign)
{
if (!context.Scanner.ReadChar('-'))
{
// If there is no '-' try to read a '+' but don't read both.
context.Scanner.ReadChar('+');
}
}

if (context.Scanner.ReadDecimal())
{
var end = context.Scanner.Cursor.Offset;
#if !SUPPORTS_SPAN_PARSE
var sourceToParse = context.Scanner.Buffer.Substring(start, end - start);
#else
var sourceToParse = context.Scanner.Buffer.AsSpan(start, end - start);
#endif

if (double.TryParse(sourceToParse, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
{
result.Set(start, end, value);
return true;
}
}

context.Scanner.Cursor.ResetPosition(reset);

return false;
}

public CompilationResult Compile(CompilationContext context)
{
var result = new CompilationResult();

var success = context.DeclareSuccessVariable(result, false);
var value = context.DeclareValueVariable(result, Expression.Default(typeof(double)));

// var start = context.Scanner.Cursor.Offset;
// var reset = context.Scanner.Cursor.Position;

var start = context.DeclareOffsetVariable(result);
var reset = context.DeclarePositionVariable(result);

if ((_numberOptions & NumberOptions.AllowSign) == NumberOptions.AllowSign)
{
// if (!context.Scanner.ReadChar('-'))
// {
// context.Scanner.ReadChar('+');
// }

result.Body.Add(
Expression.IfThen(
Expression.Not(context.ReadChar('-')),
context.ReadChar('+')
)
);
}

// if (context.Scanner.ReadDecimal())
// {
// var end = context.Scanner.Cursor.Offset;
// NETSTANDARD2_0 var sourceToParse = context.Scanner.Buffer.Substring(start, end - start);
// NETSTANDARD2_1 var sourceToParse = context.Scanner.Buffer.AsSpan(start, end - start);
// success = double.TryParse(sourceToParse, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
// }
//
// if (!success)
// {
// context.Scanner.Cursor.ResetPosition(begin);
// }
//

var end = Expression.Variable(typeof(int), $"end{context.NextNumber}");
#if NETSTANDARD2_0
var sourceToParse = Expression.Variable(typeof(string), $"sourceToParse{context.NextNumber}");
var sliceExpression = Expression.Assign(sourceToParse, Expression.Call(context.Buffer(), typeof(string).GetMethod("Substring", new[] { typeof(int), typeof(int) }), start, Expression.Subtract(end, start)));
var tryParseMethodInfo = typeof(double).GetMethod(nameof(double.TryParse), new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(double).MakeByRefType()});
#else
var sourceToParse = Expression.Variable(typeof(ReadOnlySpan<char>), $"sourceToParse{context.NextNumber}");
var sliceExpression = Expression.Assign(sourceToParse, Expression.Call(typeof(MemoryExtensions).GetMethod("AsSpan", new[] { typeof(string), typeof(int), typeof(int) }), context.Buffer(), start, Expression.Subtract(end, start)));
var tryParseMethodInfo = typeof(double).GetMethod(nameof(double.TryParse), new[] { typeof(ReadOnlySpan<char>), typeof(NumberStyles), typeof(IFormatProvider), typeof(double).MakeByRefType()});
#endif

// TODO: NETSTANDARD2_1 code path
var block =
Expression.IfThen(
context.ReadDecimal(),
Expression.Block(
new[] { end, sourceToParse },
Expression.Assign(end, context.Offset()),
sliceExpression,
Expression.Assign(success,
Expression.Call(
tryParseMethodInfo,
sourceToParse,
Expression.Constant(NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint),
Expression.Constant(CultureInfo.InvariantCulture),
value)
)
)
);

result.Body.Add(block);

result.Body.Add(
Expression.IfThen(
Expression.Not(success),
context.ResetPosition(reset)
)
);

return result;
}
}
}
138 changes: 138 additions & 0 deletions src/Parlot/Fluent/FloatLiteral.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using Parlot.Compilation;
using System;
using System.Globalization;
using System.Linq.Expressions;

namespace Parlot.Fluent
{
public sealed class FloatLiteral : Parser<float>, ICompilable
{
private readonly NumberOptions _numberOptions;

public FloatLiteral(NumberOptions numberOptions = NumberOptions.Default)
{
_numberOptions = numberOptions;
}

public override bool Parse(ParseContext context, ref ParseResult<float> result)
{
context.EnterParser(this);

var reset = context.Scanner.Cursor.Position;
var start = reset.Offset;

if ((_numberOptions & NumberOptions.AllowSign) == NumberOptions.AllowSign)
{
if (!context.Scanner.ReadChar('-'))
{
// If there is no '-' try to read a '+' but don't read both.
context.Scanner.ReadChar('+');
}
}

if (context.Scanner.ReadDecimal())
{
var end = context.Scanner.Cursor.Offset;
#if !SUPPORTS_SPAN_PARSE
var sourceToParse = context.Scanner.Buffer.Substring(start, end - start);
#else
var sourceToParse = context.Scanner.Buffer.AsSpan(start, end - start);
#endif

if (float.TryParse(sourceToParse, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
{
result.Set(start, end, value);
return true;
}
}

context.Scanner.Cursor.ResetPosition(reset);

return false;
}

public CompilationResult Compile(CompilationContext context)
{
var result = new CompilationResult();

var success = context.DeclareSuccessVariable(result, false);
var value = context.DeclareValueVariable(result, Expression.Default(typeof(float)));

// var start = context.Scanner.Cursor.Offset;
// var reset = context.Scanner.Cursor.Position;

var start = context.DeclareOffsetVariable(result);
var reset = context.DeclarePositionVariable(result);

if ((_numberOptions & NumberOptions.AllowSign) == NumberOptions.AllowSign)
{
// if (!context.Scanner.ReadChar('-'))
// {
// context.Scanner.ReadChar('+');
// }

result.Body.Add(
Expression.IfThen(
Expression.Not(context.ReadChar('-')),
context.ReadChar('+')
)
);
}

// if (context.Scanner.ReadDecimal())
// {
// var end = context.Scanner.Cursor.Offset;
// NETSTANDARD2_0 var sourceToParse = context.Scanner.Buffer.Substring(start, end - start);
// NETSTANDARD2_1 var sourceToParse = context.Scanner.Buffer.AsSpan(start, end - start);
// success = float.TryParse(sourceToParse, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var value))
// }
//
// if (!success)
// {
// context.Scanner.Cursor.ResetPosition(begin);
// }
//

var end = Expression.Variable(typeof(int), $"end{context.NextNumber}");
#if NETSTANDARD2_0
var sourceToParse = Expression.Variable(typeof(string), $"sourceToParse{context.NextNumber}");
var sliceExpression = Expression.Assign(sourceToParse, Expression.Call(context.Buffer(), typeof(string).GetMethod("Substring", new[] { typeof(int), typeof(int) }), start, Expression.Subtract(end, start)));
var tryParseMethodInfo = typeof(float).GetMethod(nameof(float.TryParse), new[] { typeof(string), typeof(NumberStyles), typeof(IFormatProvider), typeof(float).MakeByRefType()});
#else
var sourceToParse = Expression.Variable(typeof(ReadOnlySpan<char>), $"sourceToParse{context.NextNumber}");
var sliceExpression = Expression.Assign(sourceToParse, Expression.Call(typeof(MemoryExtensions).GetMethod("AsSpan", new[] { typeof(string), typeof(int), typeof(int) }), context.Buffer(), start, Expression.Subtract(end, start)));
var tryParseMethodInfo = typeof(float).GetMethod(nameof(float.TryParse), new[] { typeof(ReadOnlySpan<char>), typeof(NumberStyles), typeof(IFormatProvider), typeof(float).MakeByRefType()});
#endif

// TODO: NETSTANDARD2_1 code path
var block =
Expression.IfThen(
context.ReadDecimal(),
Expression.Block(
new[] { end, sourceToParse },
Expression.Assign(end, context.Offset()),
sliceExpression,
Expression.Assign(success,
Expression.Call(
tryParseMethodInfo,
sourceToParse,
Expression.Constant(NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint),
Expression.Constant(CultureInfo.InvariantCulture),
value)
)
)
);

result.Body.Add(block);

result.Body.Add(
Expression.IfThen(
Expression.Not(success),
context.ResetPosition(reset)
)
);

return result;
}
}
}
30 changes: 25 additions & 5 deletions src/Parlot/Fluent/Parsers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public static partial class Parsers

/// <summary>
/// Builds a parser that captures the output of another parser.
/// This is used to provide pattern matching capabilities, and optimized copmiled parsers that then don't need to materialize each parser result.
/// This is used to provide pattern matching capabilities, and optimized compiled parsers that then don't need to materialize each parser result.
/// </summary>
public static Parser<TextSpan> Capture<T>(Parser<T> parser) => new Capture<T>(parser);

Expand Down Expand Up @@ -114,13 +114,23 @@ public class LiteralBuilder
/// <summary>
/// Builds a parser that matches an integer.
/// </summary>
public Parser<long> Integer() => new IntegerLiteral();
public Parser<long> Integer(NumberOptions numberOptions = NumberOptions.Default) => new IntegerLiteral(numberOptions);

/// <summary>
/// Builds a parser that matches a floating point number.
/// Builds a parser that matches a floating point number represented as a <lang cref="decimal"/> value.
/// </summary>
public Parser<decimal> Decimal() => new DecimalLiteral();
public Parser<decimal> Decimal(NumberOptions numberOptions = NumberOptions.Default) => new DecimalLiteral(numberOptions);

/// <summary>
/// Builds a parser that matches a floating point number represented as a <lang cref="float"/> value.
/// </summary>
public Parser<float> Float(NumberOptions numberOptions = NumberOptions.Default) => new FloatLiteral(numberOptions);

/// <summary>
/// Builds a parser that matches a floating point number represented as a <lang cref="double"/> value.
/// </summary>
public Parser<double> Double(NumberOptions numberOptions = NumberOptions.Default) => new DoubleLiteral(numberOptions);

/// <summary>
/// Builds a parser that matches an quoted string that can be escaped.
/// </summary>
Expand Down Expand Up @@ -163,10 +173,20 @@ public class TermBuilder
public Parser<long> Integer(NumberOptions numberOptions = NumberOptions.Default) => Parsers.SkipWhiteSpace(new IntegerLiteral(numberOptions));

/// <summary>
/// Builds a parser that matches a floating point number.
/// Builds a parser that matches a floating point number represented as a <lang cref="decimal"/> value.
/// </summary>
public Parser<decimal> Decimal(NumberOptions numberOptions = NumberOptions.Default) => Parsers.SkipWhiteSpace(new DecimalLiteral(numberOptions));

/// <summary>
/// Builds a parser that matches a floating point number represented as a <lang cref="float"/> value.
/// </summary>
public Parser<float> Float(NumberOptions numberOptions = NumberOptions.Default) => Parsers.SkipWhiteSpace(new FloatLiteral(numberOptions));

/// <summary>
/// Builds a parser that matches a floating point number represented as a <lang cref="double"/> value.
/// </summary>
public Parser<double> Double(NumberOptions numberOptions = NumberOptions.Default) => Parsers.SkipWhiteSpace(new DoubleLiteral(numberOptions));

/// <summary>
/// Builds a parser that matches an quoted string that can be escaped.
/// </summary>
Expand Down

0 comments on commit c32c0c3

Please sign in to comment.