I'm making a little DFA compiler that renders directly to an assembly using System.Reflection.Emit
Note that below there's an FALIB define. This will always be defined when the compiler is used.
My IL code is supposed to implement a concrete version of this
#if FALIB
public
#endif
abstract partial class FARunner
{
protected abstract int MatchImpl(LexContext lc);
protected abstract FAMatch SearchImpl(LexContext lc);
public IEnumerable<FAMatch> Search(LexContext lc)
{
while(lc.Current!=LexContext.EndOfInput)
{
yield return SearchImpl(lc);
}
}
public int Match(LexContext lc)
{
return MatchImpl(lc);
}
public IEnumerable<FAMatch> Search(IEnumerable<char> @string)
{
foreach (var match in Search(LexContext.Create(@string)))
{
yield return match;
}
}
public IEnumerable<FAMatch> Search(TextReader reader)
{
foreach (var match in Search(LexContext.CreateFrom(reader)))
{
yield return match;
}
}
public int Match(IEnumerable<char> text)
{
return Match(LexContext.Create(text));
}
}
It just needs to fill in SearchImpl and MatchImpl with concrete methods, which I've done although they currently throw not-implemented.
The trouble I'm having is this - the MatchImpl method calls just fine.
The SearchImpl method - well it's weird. It not only doesn't get called, neither do any of the methods that delegate to it. I don't get an error, compiler thrown or runtime. It just silently fails when I try to call it.
The only thing I can think of is something about my return type being a struct. That's the only difference here that I can see - well that and the name.
What I have tried:
public static partial class FACompiler
{
public static FARunner Compile(this FA fa, IProgress<int> progress = null)
{
if (fa == null) throw new ArgumentNullException(nameof(fa));
fa = fa.ToDfa(progress);
var name = "FARunner" + fa.GetHashCode();
var asmName = new AssemblyName(name);
var asm = Thread.GetDomain().DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
ModuleBuilder mod = asm.DefineDynamicModule("M"+name);
TypeBuilder type = mod.DefineType(name, TypeAttributes.Public | TypeAttributes.Sealed, typeof(FARunner));
Type[] paramTypes = new Type[] { typeof(LexContext) };
Type searchReturnType = typeof(FAMatch);
MethodBuilder searchImpl = type.DefineMethod("SearchImpl", MethodAttributes.Public | MethodAttributes.ReuseSlot |
MethodAttributes.Virtual | MethodAttributes.HideBySig, searchReturnType, paramTypes);
ILGenerator searchGen = searchImpl.GetILGenerator();
searchGen.ThrowException(typeof(NotImplementedException));
MethodInfo searchImplBase = typeof(FARunner).GetMethod("SearchImpl",BindingFlags.NonPublic | BindingFlags.Instance);
type.DefineMethodOverride(searchImpl, searchImplBase);
Type matchReturnType = typeof(int);
MethodBuilder match = type.DefineMethod("MatchImpl", MethodAttributes.Public | MethodAttributes.ReuseSlot |
MethodAttributes.Virtual | MethodAttributes.HideBySig, matchReturnType, paramTypes);
ILGenerator matchGen = match.GetILGenerator();
matchGen.ThrowException(typeof(NotImplementedException));
MethodInfo matchBase = typeof(FARunner).GetMethod("MatchImpl", BindingFlags.NonPublic | BindingFlags.Instance);
type.DefineMethodOverride(match, matchBase);
Type newType = type.CreateType();
return (FARunner)Activator.CreateInstance(newType);
}
}
I also tried adding these lines to adjust the stack in case I needed it filled even in the event of a throw:
LocalBuilder famatch = searchGen.DeclareLocal(typeof(FAMatch));
searchGen.Emit(OpCodes.Ldloca_S, famatch);
searchGen.Emit(OpCodes.Initobj, typeof(FAMatch));
I get the same results.