Skip to content

Commit

Permalink
Less magic for tuple construction; more reflection caching
Browse files Browse the repository at this point in the history
  • Loading branch information
sblom committed Dec 16, 2023
1 parent c5ff34b commit bdbc208
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 66 deletions.
8 changes: 6 additions & 2 deletions RegExtract.Test/Usage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ public void CreateTreePlan()
var plan = ExtractionPlan<((int?, int?)?, char, string)?>.CreatePlan(regex);
object? result = plan.Extract(regex.Match("2-12 c: abcdefgji"));

regex = new Regex(@"(((\w)+) ?)+");
regex = new Regex(@"((\w)+ ?)+");
var plan2 = ExtractionPlan<List<List<char>>>.CreatePlan(regex);

result = plan2.Extract(regex.Match("The quick brown fox jumps over the lazy dog"));
Expand All @@ -521,7 +521,11 @@ public void CreateTreePlan()
[Fact]
public void deep_tuple_type_tree()
{
var plan = CreateAndLogPlan<(int, (int, int), int, int, int, int, (int, (List<int>, int)), int, int, int, int, int, int, int, int)>(@"(\d+) ((\d+) (\d+)) (\d+) (\d+) (\d+) (\d+) ((\d+) ((\d+ ?)+ (\d+))) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)");
var plan = CreateAndLogPlan<(int, (int, int), int, int, int, int, (int, (List<int>, int)), int, int, int, int, int, int, int, int)>(@"(\d+) \(((\d+) (\d+))\) (\d+) (\d+) (\d+) (\d+) ((\d+) \(((\d+ ?)+ (\d+))\)) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)");

var result = plan.Extract("1 (2 3) 4 5 6 7 8 (9 10 11 12 13) 14 15 16 17 18 19 20 21");

Assert.Equivalent((1, (2, 3), 4, 5, 6, 7, (8, (new List<int> { 9, 10, 11, 12 }, 13)), 14, 15, 16, 17, 18, 19, 20, 21), result);
}

[Fact]
Expand Down
58 changes: 31 additions & 27 deletions RegExtract/ExtractionPlan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public class ExtractionPlan<T>: IFormattable
{
public ExtractionPlanNode Plan { get; protected set; }
RegexCaptureGroupTree? _tree;
Stack<Type> _typeStack = new();

protected ExtractionPlan()
{
Expand Down Expand Up @@ -148,7 +147,7 @@ ExtractionPlanNode BindPropertyPlan(RegexCaptureGroupNode tree, Type type, strin
return AssignTypesToTree(tree, type);
}

ExtractionPlanNode BindConstructorPlan(RegexCaptureGroupNode tree, Type type, int paramNum, int paramCount, Stack<RegexCaptureGroupNode>? stack)
ExtractionPlanNode BindConstructorPlan(RegexCaptureGroupNode tree, Type type, int paramNum, int paramCount)
{
if (IsNullable(type))
{
Expand All @@ -173,7 +172,7 @@ ExtractionPlanNode BindConstructorPlan(RegexCaptureGroupNode tree, Type type, in
{
try
{
type = GetTupleArgumentsList(type)[paramNum];
type = type.GetGenericArguments()[paramNum];
}
catch (IndexOutOfRangeException)
{
Expand All @@ -194,10 +193,31 @@ ExtractionPlanNode BindConstructorPlan(RegexCaptureGroupNode tree, Type type, in
}
}

return AssignTypesToTree(tree, type, stack);
return AssignTypesToTree(tree, type);
}

private ExtractionPlanNode AssignTypesToTree(RegexCaptureGroupNode tree, Type type, Stack<RegexCaptureGroupNode>? stack = null)
ExtractionPlanNode BindTupleConstructorPlan(string name, IEnumerable<RegexCaptureGroupNode> nodes, Type tupleType)
{
var typeArgs = IsNullable(tupleType) ? tupleType.GetGenericArguments().Single().GetGenericArguments() : tupleType.GetGenericArguments();

List<ExtractionPlanNode> groups = new();

foreach (var (node, type, idx) in nodes.Zip(typeArgs, (n, t) => (n,t)).Select(((x,i) => (x.n, x.t, i))))
{
if (idx < 7)
{
groups.Add(BindConstructorPlan(node, tupleType, idx, typeArgs.Length));
}
else
{
groups.Add(BindTupleConstructorPlan(name, nodes.Skip(7), type));
}
}

return ExtractionPlanNode.Bind(name, tupleType, groups.ToArray(), new ExtractionPlanNode[0]);
}

private ExtractionPlanNode AssignTypesToTree(RegexCaptureGroupNode tree, Type type)
{
var unwrappedType = IsNullable(type) ? type.GetGenericArguments().Single() : type;

Expand All @@ -206,31 +226,15 @@ private ExtractionPlanNode AssignTypesToTree(RegexCaptureGroupNode tree, Type ty

if (IsDirectlyConstructable(type))
{
if (tree.children.Any())
if (tree.children.Count() == 1)
{
return new VirtualUnaryTupleNode(tree.name, type, new[] { AssignTypesToTree(tree.children.Single(), type) }, new ExtractionPlanNode[0]);
}
return ExtractionPlanNode.BindLeaf(tree.name, type, groups.ToArray(), namedgroups.ToArray());
}
else if (IsTuple(unwrappedType) && GetTupleArgumentsList(unwrappedType).Count() > tree.children.Where(child => int.TryParse(child.name, out var _)).Count())
else if (IsTuple(unwrappedType))
{
stack = new Stack<RegexCaptureGroupNode>(tree.children.Reverse());

while (stack.Any())
{
//var tupleParamCount = IsTuple(type) ? type.
RegexCaptureGroupNode node = stack.Pop();

if (int.TryParse(node.name, out var num))
{
var plan = BindConstructorPlan(node, type, groups.Count, GetTupleArgumentsList(type).Count(), stack);
groups.Add(plan);
}
else
{
namedgroups.Add(BindPropertyPlan(node, type, node.name));
}
}
return BindTupleConstructorPlan(tree.name, tree.children, type);
}
else if (IsInitializableCollection(type))
{
Expand All @@ -243,12 +247,12 @@ private ExtractionPlanNode AssignTypesToTree(RegexCaptureGroupNode tree, Type ty

if (typeParams.Length < 2 && !IsInitializableCollection(typeParams.FirstOrDefault()))
{
return ExtractionPlanNode.Bind(tree.name, type, new[] { BindConstructorPlan(tree, type, 0, 1, stack) }, new ExtractionPlanNode[0]);
return ExtractionPlanNode.Bind(tree.name, type, new[] { BindConstructorPlan(tree, type, 0, 1) }, new ExtractionPlanNode[0]);
}

foreach (var node in tree.children)
{
var plan = BindConstructorPlan(node, type, groups.Count, tree.NumberedGroups.Count(), stack);
var plan = BindConstructorPlan(node, type, groups.Count, tree.NumberedGroups.Count());
groups.Add(plan);
}
// TODO: assert that there are no named groups
Expand All @@ -259,7 +263,7 @@ private ExtractionPlanNode AssignTypesToTree(RegexCaptureGroupNode tree, Type ty
{
if (int.TryParse(node.name, out var num))
{
var plan = BindConstructorPlan(node, type, groups.Count, tree.NumberedGroups.Count(), stack);
var plan = BindConstructorPlan(node, type, groups.Count, tree.NumberedGroups.Count());
groups.Add(plan);
}
else
Expand Down
76 changes: 39 additions & 37 deletions RegExtract/ExtractionPlanNodeTypes.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
Expand Down Expand Up @@ -36,6 +37,7 @@ internal record CollectionInitializerNode(string groupName, Type type, Extractio
{
var genericArgs = type.GetGenericArguments();

// TODO: Create a pre-sized collection
var vals = Activator.CreateInstance(type);
var addMethod = type.GetMethod("Add");

Expand Down Expand Up @@ -73,31 +75,12 @@ internal record CollectionInitializerNode(string groupName, Type type, Extractio
internal record ConstructTupleNode(string groupName, Type type, ExtractionPlanNode[] constructorParams, ExtractionPlanNode[] propertySetters) :
ExtractionPlanNode(groupName, type, constructorParams, propertySetters)
{
private object CreateGenericTuple(Type tupleType, IEnumerable<object?> vals)
{
var typeArgs = tupleType.GetGenericArguments();
var constructor = tupleType.GetConstructor(tupleType.GetGenericArguments());

if (typeArgs.Count() < 8 && vals.Count() != typeArgs.Count())
throw new ArgumentException($"Number of capture groups doesn't match tuple arity.");

if (typeArgs.Count() <= 7)
{
return constructor.Invoke(vals.ToArray());
}
else
{
return constructor.Invoke(vals.Take(7)
.Concat(new[] { CreateGenericTuple(typeArgs[7], vals.Skip(7)) })
.ToArray());
}
}

internal override object? Construct(Match match, Type type, (string Value, int Index, int Length) range)
{
type = IsNullable(type) ? type.GetGenericArguments().Single() : type;
var constructor = type.GetConstructor(type.GetGenericArguments());

return CreateGenericTuple(type, constructorParams.Select(i => i.Execute(match, range.Index, range.Length)));
return constructor.Invoke(constructorParams.Select(i => i.Execute(match, range.Index, range.Length)).ToArray());
}

internal override void Validate()
Expand All @@ -112,17 +95,20 @@ internal override void Validate()
internal record ConstructorNode(string groupName, Type type, ExtractionPlanNode[] constructorParams, ExtractionPlanNode[] propertySetters) :
ExtractionPlanNode(groupName, type, constructorParams, propertySetters)
{
ConstructorInfo? _constructor = null;

ConstructorInfo constructor
{
get
{
return _constructor ?? (_constructor = type.GetConstructors().Where(cons => cons.GetParameters().Length == constructorParams.Length).Single());
}
}

internal override object? Construct(Match match, Type type, (string Value, int Index, int Length) range)
{
type = IsNullable(type) ? type.GetGenericArguments().Single() : type;

var constructors = type.GetConstructors()
.Where(cons => cons.GetParameters().Length == constructorParams.Length);

var constructor = constructors.Single();

var paramTypes = constructor.GetParameters().Select(x => x.ParameterType);

return constructor.Invoke(constructorParams.Select(i => i.Execute(match, range.Index, range.Length)).ToArray());
}

Expand Down Expand Up @@ -153,12 +139,20 @@ internal record EnumParseNode(string groupName, Type type, ExtractionPlanNode[]
internal record StringConstructorNode(string groupName, Type type, ExtractionPlanNode[] constructorParams, ExtractionPlanNode[] propertySetters) :
ExtractionPlanNode(groupName, type, constructorParams, propertySetters)
{
internal override object? Construct(Match match, Type type, (string Value, int Index, int Length) range)
ConstructorInfo? _constructor = null;

ConstructorInfo constructor
{
type = IsNullable(type) ? type.GetGenericArguments().Single() : type;
get
{
return _constructor ?? (_constructor = (IsNullable(type) ? type.GetGenericArguments().Single() : type).GetConstructor(new[] { typeof(string) }));
}
}

var constructor = type.GetConstructor(new[] { typeof(string) });

internal override object? Construct(Match match, Type type, (string Value, int Index, int Length) range)
{
Debug.Assert(type == this.type);
return constructor.Invoke(new[] { range.Value });
}

Expand All @@ -178,16 +172,24 @@ internal override void Validate()
internal record StaticParseMethodNode(string groupName, Type type, ExtractionPlanNode[] constructorParams, ExtractionPlanNode[] propertySetters) :
ExtractionPlanNode(groupName, type, constructorParams, propertySetters)
{
internal override object? Construct(Match match, Type type, (string Value, int Index, int Length) range)
{
type = IsInitializableCollection(type) ? type.GetGenericArguments().Single() : type;
type = IsNullable(type) ? type.GetGenericArguments().Single() : type;
MethodInfo? _parse = null;

var parse = type.GetMethod("Parse",
MethodInfo parse
{
get
{
return _parse ?? (_parse = (IsNullable(type) ? type.GetGenericArguments().Single() : type)
.GetMethod("Parse",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(string) },
null);
null));
}
}

internal override object? Construct(Match match, Type type, (string Value, int Index, int Length) range)
{
type = IsNullable(type) ? type.GetGenericArguments().Single() : type;

return parse.Invoke(null, new object[] { range.Value });
}
Expand Down

0 comments on commit bdbc208

Please sign in to comment.