Skip to content

Instantly share code, notes, and snippets.

@akarpov89
Created October 1, 2021 14:58
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akarpov89/03c53ea537451c7790341d7f697a89ec to your computer and use it in GitHub Desktop.
Save akarpov89/03c53ea537451c7790341d7f697a89ec to your computer and use it in GitHub Desktop.
Interpolated parsing technique
using System.Runtime.CompilerServices;
using static ParsingExtensions;
string input = "Name: Andrew; Age: 31";
string? name = null;
int age = 0;
if (input.TryParse($"Name: {Placeholder(ref name)}; Age: {Placeholder(ref age)}"))
{
Console.WriteLine($"{name} {age}");
}
else
{
Console.WriteLine("Does not match :(");
}
public static class ParsingExtensions
{
public static PlaceholderCell<T?> Placeholder<T>(ref T? arg) => new(ref arg);
public static bool TryParse(this string input, [InterpolatedStringHandlerArgument("input")] ref TryParseHandler handler)
{
return handler.Ok;
}
}
public readonly unsafe ref struct PlaceholderCell<T>
{
private readonly void* _ptr;
public PlaceholderCell(ref T? arg) => _ptr = Unsafe.AsPointer(ref arg);
public bool IsNull => _ptr is null;
public ref T Get() => ref Unsafe.AsRef<T>(_ptr);
public void Set(T arg) => Unsafe.Write(_ptr, arg);
}
[InterpolatedStringHandler]
public ref struct TryParseHandler
{
private ReadOnlySpan<char> _input;
private PlaceholderCell<string?> _substringPlaceholder;
public TryParseHandler(int literalLength, int formattedCount, ReadOnlySpan<char> input)
{
_input = input;
_substringPlaceholder = default;
Ok = true;
}
public bool Ok { get; private set; }
private bool Failed()
{
Ok = false;
return false;
}
public bool AppendLiteral(string literal)
{
if (!_substringPlaceholder.IsNull)
{
var index = _input.IndexOf(literal, StringComparison.Ordinal);
if (index < 1)
return Failed();
_substringPlaceholder.Set(_input.Slice(0, index).ToString());
_substringPlaceholder = default;
_input = _input.Slice(index + literal.Length);
return true;
}
if (_input.StartsWith(literal, StringComparison.Ordinal))
{
_input = _input.Slice(literal.Length);
return true;
}
return Failed();
}
public bool AppendFormatted(PlaceholderCell<string?> placeholder)
{
if (_input.Length == 0)
return Failed();
if (!_substringPlaceholder.IsNull)
return Failed();
_substringPlaceholder = placeholder;
return true;
}
public bool AppendFormatted(PlaceholderCell<int> placeholder)
{
if (_input.Length == 0)
return Failed();
var startPos = 0;
while (startPos < _input.Length && !char.IsDigit(_input[startPos]))
startPos++;
if (startPos >= _input.Length)
return Failed();
var endPos = startPos;
while (endPos < _input.Length - 1 && char.IsDigit(_input[endPos + 1]))
endPos++;
var numberSlice = _input.Slice(startPos, endPos - startPos + 1);
if (!int.TryParse(numberSlice, out var value))
return Failed();
placeholder.Set(value);
if (!_substringPlaceholder.IsNull)
{
_substringPlaceholder.Set(_input.Slice(0, startPos).ToString());
_substringPlaceholder = default;
}
_input = _input.Slice(endPos + 1);
return true;
}
}
@evilguest
Copy link

Impressive!

@VBAndCs
Copy link

VBAndCs commented Jan 29, 2022

I love it. I prefer a managed version of this interpolated parser, and I allowed to have a prefix b4 the match, and enhanced the code. So, here it is:

using System.Runtime.CompilerServices;

string input = "My Name: Andrew Smith  ; Age: 31 years; City: London.";

Placeholder<string> name = new();
Placeholder<int> age = new();
Placeholder<string> city = new();

input.TryParse($"Name: {name} Age: {age} City: {city}");
Console.WriteLine($"{name}, {age}, {city}");


public static class ParsingExtensions
{
    public static bool TryParse(this string input, [InterpolatedStringHandlerArgument("input")] TryParseHandler handler)
    {
        return handler.Result;
    }
}

public class Placeholder<T>
{
    T value;

    public T Value
    {
        get => value;
        set
        {
            Found = true;
            this.value = value;
        }
    }

    public bool Found = false;

    public override string ToString()
    {
        if (Found)
            return value?.ToString();

        return "Not Found";
    }
}



[InterpolatedStringHandler]
public class TryParseHandler
{
    string input;
    int strLength;
    int index = 0;

    public bool Result = true;

    public TryParseHandler(int literalLength, int formattedCount, string input)
    {
        this.input = input;
        strLength = input.Length;
    }

    private bool Failed()
    {
        Result = false;
        return false;
    }

    public bool AppendLiteral(string literal)
    {
        literal= literal.Trim();
        var i = input.IndexOf(literal, index);
        if (i < 0)
            return Failed();

        index = i + literal.Length;
        return true;
    }

    public bool AppendFormatted(Placeholder<string> placeholder)
    {
        if (index >= strLength)
            placeholder.Value = "";
        else
        {
            int start = index;
            while (index < strLength && (char.IsLetterOrDigit(input[index]) || input[index] == '_' || input[index] == ' '))
                index++;

            placeholder.Value = input.Substring(start, index - start).Trim();
        }

        return true;
    }

    public bool AppendFormatted(Placeholder<int> placeholder)
    {
        int start = index;
        while (start < strLength && input[start] == ' ')
            start++;

        if (start >= strLength)
            return Failed();

        index = start + 1;
        while (index < strLength && char.IsDigit(input[index]))
            index++;

        if (index - start == 0)
            return Failed();

        placeholder.Value = Convert.ToInt32(input.Substring(start, index - start));
        return true;
    }

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment