Construyendo árboles de expresiones personalizadas mientras se usan operadores en C #

.net c# expression-trees operators

Pregunta

Esta pregunta trata sobre la construcción de árboles de expresiones personalizadas en .NET usando los operadores que se encuentran en C # (o en cualquier otro idioma). Proporciono la pregunta junto con algunos de la información de fondo.


Para mi ensamblador de 64 bits de 2 fases administrado necesito soporte para expresiones. Por ejemplo, uno podría querer ensamblar:

mystring: DB 'hello, world'
          TIMES 64-$+mystring DB ' '

La expresión 64-$+mystring no debe ser una cadena, sino una expresión válida real con los beneficios de la sintaxis y la comprobación de tipos e IntelliSense en VS, algo así como:

64 - Reference.CurrentOffset + new Reference("mystring");

Esta expresión no se evalúa cuando se construye. En su lugar, se evalúa más adelante en el contexto de mi ensamblador (cuando determina las compensaciones de símbolos y demás). El .NET framework (desde .NET 3.5) proporciona soporte para los árboles de expresiones, y me parece que es ideal para este tipo de expresiones que se evalúan más adelante o en otro lugar.

Pero no sé cómo asegurarme de que puedo usar la sintaxis de C # (usando +, <<,%, etc.) para construir el árbol de expresiones. Quiero prevenir cosas como:

var expression = AssemblerExpression.Subtract(64,
    AssemblerExpression.Add(AssemblerExpression.CurrentOffset(),
        AssemblerExpression.Reference("mystring")))

¿Cómo haría usted para esto?


Nota: Necesito un árbol de expresiones para poder convertir la expresión en una representación de cadena personalizada aceptable y, al mismo tiempo, poder evaluarla en un punto en el tiempo que no sea su definición.


Una explicación de mi ejemplo: 64-$+mystring . El $ es el desplazamiento actual, por lo que es un número específico que se desconoce por adelantado (pero que se conoce en el momento de la evaluación). mystring es un símbolo que puede o no ser conocido en el momento de la evaluación (por ejemplo, cuando aún no se ha definido). Restar una constante C de un símbolo S es lo mismo que S + -C . Restar dos símbolos S0 y S1 ( S1 - S0 ) da la diferencia entera entre los valores de los dos símbolos.

Sin embargo, esta pregunta no es realmente acerca de cómo evaluar expresiones de ensamblador, sino más bien sobre cómo evaluar cualquier expresión que tenga clases personalizadas (para cosas como los símbolos y $ en el ejemplo) y cómo asegurarse de que pueda ser bastante -impreso utilizando algún visitante (manteniendo así el árbol). Y dado que .NET Framework tiene sus árboles de expresión y visitantes, sería bueno usarlos, si es posible.

Respuesta aceptada

No sé a qué se dirige exactamente, pero el siguiente es un enfoque incompleto que creo que funcionaría.

Nota I

  1. demuestre solo expresiones de referencia indexadas (por lo tanto, ignorando el direccionamiento indirecto a través de registros por ahora; podría agregar un RegisterInderectReference análogo a la clase SymbolicReference). Esto también se aplica a la característica sugerida de $ (compensación actual). Probablemente sería un registro seguro (?)
  2. no muestra explícitamente el unario / binaria operator- en el trabajo tampoco. Sin embargo, la mecánica es en gran medida la misma. No llegué a agregarlo porque no pude calcular la semántica de las expresiones de muestra en su pregunta
    (Yo creo que restar la dirección de una cadena conocida no es útil, por ejemplo)
  3. el enfoque no coloca límites (semánticos): puede compensar cualquier referencia a IRB derivada de ReferenceBase. En la práctica, es posible que solo desee permitir un nivel de indexación, y definir el operator+ directamente en SymbolicReference sería más apropiado.
  4. Ha sacrificado el estilo de codificación con fines de demostración (en general, no querrá Compile() repetidamente Compile() sus árboles de expresión, y la evaluación directa con .Compile()() ve fea y confusa. Se deja a la OP para integrarla en una moda mas legible

  5. La demostración del operador de conversión explícita es realmente fuera de tema. Me dejé llevar levemente (?)

  6. Puede observar el código ejecutándose en vivo en IdeOne.com

.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Linq;


namespace Assembler
{
    internal class State
    {
        public readonly IDictionary<string, ulong> SymbolTable = new Dictionary<string, ulong>();

        public void Clear() 
        {
            SymbolTable.Clear();
        }
    }

    internal interface IReference
    {
        ulong EvalAddress(State s); // evaluate reference to address
    }

    internal abstract class ReferenceBase : IReference
    {
        public static IndexedReference operator+(long directOffset, ReferenceBase baseRef) { return new IndexedReference(baseRef, directOffset); }
        public static IndexedReference operator+(ReferenceBase baseRef, long directOffset) { return new IndexedReference(baseRef, directOffset); }

        public abstract ulong EvalAddress(State s);
    }

    internal class SymbolicReference : ReferenceBase
    {
        public static explicit operator SymbolicReference(string symbol)    { return new SymbolicReference(symbol); }
        public SymbolicReference(string symbol) { _symbol = symbol; }

        private readonly string _symbol;

        public override ulong EvalAddress(State s) 
        {
            return s.SymbolTable[_symbol];
        }

        public override string ToString() { return string.Format("Sym({0})", _symbol); }
    }

    internal class IndexedReference : ReferenceBase
    {
        public IndexedReference(IReference baseRef, long directOffset) 
        {
            _baseRef = baseRef;
            _directOffset = directOffset;
        }

        private readonly IReference _baseRef;
        private readonly long _directOffset;

        public override ulong EvalAddress(State s) 
        {
            return (_directOffset<0)
                ? _baseRef.EvalAddress(s) - (ulong) Math.Abs(_directOffset)
                : _baseRef.EvalAddress(s) + (ulong) Math.Abs(_directOffset);
        }

        public override string ToString() { return string.Format("{0} + {1}", _directOffset, _baseRef); }
    }
}

namespace Program
{
    using Assembler;

    public static class Program
    {
        public static void Main(string[] args)
        {
            var myBaseRef1 = new SymbolicReference("mystring1");

            Expression<Func<IReference>> anyRefExpr = () => 64 + myBaseRef1;
            Console.WriteLine(anyRefExpr);

            var myBaseRef2 = (SymbolicReference) "mystring2"; // uses explicit conversion operator

            Expression<Func<IndexedReference>> indexedRefExpr = () => 64 + myBaseRef2;
            Console.WriteLine(indexedRefExpr);

            Console.WriteLine(Console.Out.NewLine + "=== show compiletime types of returned values:");
            Console.WriteLine("myBaseRef1     -> {0}", myBaseRef1);
            Console.WriteLine("myBaseRef2     -> {0}", myBaseRef2);
            Console.WriteLine("anyRefExpr     -> {0}", anyRefExpr.Compile().Method.ReturnType);
            Console.WriteLine("indexedRefExpr -> {0}", indexedRefExpr.Compile().Method.ReturnType);

            Console.WriteLine(Console.Out.NewLine + "=== show runtime types of returned values:");
            Console.WriteLine("myBaseRef1     -> {0}", myBaseRef1);
            Console.WriteLine("myBaseRef2     -> {0}", myBaseRef2);
            Console.WriteLine("anyRefExpr     -> {0}", anyRefExpr.Compile()());     // compile() returns Func<...>
            Console.WriteLine("indexedRefExpr -> {0}", indexedRefExpr.Compile()());

            Console.WriteLine(Console.Out.NewLine + "=== observe how you could add an evaluation model using some kind of symbol table:");
            var compilerState = new State();
            compilerState.SymbolTable.Add("mystring1", 0xdeadbeef); // raw addresses
            compilerState.SymbolTable.Add("mystring2", 0xfeedface);

            Console.WriteLine("myBaseRef1 evaluates to     0x{0:x8}", myBaseRef1.EvalAddress(compilerState));
            Console.WriteLine("myBaseRef2 evaluates to     0x{0:x8}", myBaseRef2.EvalAddress(compilerState));
            Console.WriteLine("anyRefExpr displays as      {0:x8}",   anyRefExpr.Compile()());
            Console.WriteLine("indexedRefExpr displays as  {0:x8}",   indexedRefExpr.Compile()());
            Console.WriteLine("anyRefExpr evaluates to     0x{0:x8}", anyRefExpr.Compile()().EvalAddress(compilerState));
            Console.WriteLine("indexedRefExpr evaluates to 0x{0:x8}", indexedRefExpr.Compile()().EvalAddress(compilerState));
        }
    }
}

Respuesta popular

C # admite la asignación de una expresión lambda a una Expression<TDelegate> , lo que hará que el compilador emita un código para crear un árbol de expresión que representa la expresión lambda, que luego puede manipular. P.ej:

Expression<Func<int, int, int>> times = (a, b) => a * b;

Podrías entonces tomar el árbol de expresiones generado y convertirlo en el árbol de sintaxis de tu ensamblador, pero esto no parece ser exactamente lo que estás buscando, y no creo que puedas aprovechar el C # compilador para hacer esto para la entrada arbitraria.

Probablemente vaya a tener que crear su propio analizador para su lenguaje ensamblador, ya que no creo que el compilador de C # haga lo que quiere en este caso.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow