Created
October 1, 2021 14:58
-
-
Save akarpov89/03c53ea537451c7790341d7f697a89ec to your computer and use it in GitHub Desktop.
Interpolated parsing technique
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
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
Impressive!