Costruzione di alberi di espressioni personalizzate durante l'utilizzo di operatori in C #

.net c# expression-trees operators

Domanda

Questa domanda riguarda la costruzione di alberi di espressioni personalizzate in .NET utilizzando gli operatori trovati in C # (o in qualsiasi altra lingua). Fornisco la domanda insieme ad alcune informazioni di base.


Per il mio assemblatore a 64 fasi gestito a 2 fasi ho bisogno del supporto per le espressioni. Ad esempio, si potrebbe voler assemblare:

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

L'espressione 64-$+mystring non deve essere una stringa ma un'espressione valida reale con i vantaggi della sintassi e del controllo del tipo e IntelliSense in VS, qualcosa sulla falsariga di:

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

Questa espressione non viene valutata quando è costruita. Invece, viene valutato successivamente nel contesto del mio assemblatore (quando determina gli offset del simbolo e così via). Il framework .NET (da .NET 3.5) fornisce supporto per gli alberi di espressioni e mi sembra che sia l'ideale per questo tipo di espressioni che vengono valutate successivamente o altrove.

Ma non so come assicurare che io possa usare la sintassi C # (usando +, <<,%, ecc.) Per costruire l'albero delle espressioni. Voglio evitare cose come:

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

Come andresti su questo?


Nota: ho bisogno di un albero di espressioni per essere in grado di convertire l'espressione in una rappresentazione di stringa personalizzata accettabile e allo stesso tempo essere in grado di valutarlo in un momento diverso dalla sua definizione.


Una spiegazione del mio esempio: 64-$+mystring . $ È l'offset corrente, quindi è un numero specifico che è sconosciuto in anticipo (ma noto al momento della valutazione). Il mystring è un simbolo che può o non può essere conosciuto al momento della valutazione (ad esempio quando non è stato ancora definito). Sottraendo una costante C da un simbolo S è uguale a S + -C . Sottraendo due simboli S0 e S1 ( S1 - S0 ) si ottiene la differenza tra i valori dei due simboli.

Tuttavia, questa domanda non riguarda in realtà come valutare le espressioni assembler, ma più come valutare qualsiasi espressione che abbia classi personalizzate in esse (per cose come i simboli e $ nell'esempio) e come assicurarsi che possa essere carina -stampato utilizzando un visitatore (mantenendo così l'albero). E poiché il framework .NET ha le sue espressioni alberi e visitatori, sarebbe bello utilizzarli, se possibile.

Risposta accettata

Non so esattamente cosa tu stia puntando, ma il seguente è un approccio approssimativo che penso possa funzionare.

Nota I

  1. dimostrare solo espressioni di riferimento indicizzate (ignorando quindi l'indirizzamento indiretto tramite registri per ora; è possibile aggiungere un RegisterInderectReference analogo alla classe SymbolicReference). Ciò vale anche per la funzione suggerita $ (offset corrente). Probabilmente sarebbe sicuro un registro (?)
  2. non mostra esplicitamente il unario / binario operator- sul posto di lavoro sia. Tuttavia, i meccanismi sono in gran parte gli stessi. Ho smesso di aggiungerlo perché non riuscivo a capire la semantica delle espressioni di esempio nella tua domanda
    (Io penserei che sottrarre l'indirizzo di una stringa conosciuta non sia utile, per esempio)
  3. l'approccio non pone limiti (semantici): è possibile compensare qualsiasi IReference derivato da ReferenceBase. In pratica, potresti voler solo consentire un livello di indicizzazione e definire l' operator+ direttamente su SymbolicReference sarebbe più appropriato.
  4. Ha sacrificato lo stile di codifica per scopi dimostrativi (in generale, non si desidera Compile() ripetutamente Compile() gli alberi delle espressioni e la valutazione diretta con .Compile()() sembra brutta e confusa. Viene lasciata .Compile()() per integrarla in una maniera più leggibile

  5. La dimostrazione dell'operatore di conversione esplicita è davvero fuori tema. Mi sono lasciato trasportare (?)

  6. È possibile osservare il codice in esecuzione su 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));
        }
    }
}

Risposta popolare

C # supporta l'assegnazione di un'espressione lambda a Expression<TDelegate> , che farà sì che il compilatore emetta il codice per creare un albero di espressioni che rappresenta l'espressione lambda, che è quindi possibile manipolare. Per esempio:

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

Potresti quindi prendere la struttura dell'espressione generata e convertirla nell'albero di sintassi del tuo assemblatore, ma questo non sembra essere esattamente quello che stai cercando, e non penso che sarai in grado di sfruttare il Compilatore C # per fare questo per l'input arbitrario.

Probabilmente finirai per dover costruire il tuo parser per il tuo linguaggio assembly, poiché non credo che il compilatore C # farà ciò che vuoi in questo caso.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché