Konstruieren von benutzerdefinierten Ausdrucksbäumen unter Verwendung von Operatoren in C #

.net c# expression-trees operators

Frage

Bei dieser Frage geht es darum, benutzerdefinierte Ausdrucksbäume in .NET zu erstellen, indem die in C # (oder einer anderen Sprache) gefundenen Operatoren verwendet werden. Ich stelle die Frage zusammen mit einigen Hintergrundinformationen.


Für meinen verwalteten 2-Phasen 64-Bit Assembler benötige ich Unterstützung für Ausdrücke. Zum Beispiel könnte man Folgendes zusammenstellen:

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

Der Ausdruck 64-$+mystring darf kein String, sondern ein tatsächlich gültiger Ausdruck mit den Vorteilen der Syntax- und Typprüfung und IntelliSense in VS sein.

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

Dieser Ausdruck wird nicht ausgewertet, wenn er erstellt wird. Stattdessen wird es später im Kontext meines Assemblers ausgewertet (wenn es die Symboloffsets und dergleichen bestimmt). Das .NET-Framework (seit .NET 3.5) bietet Unterstützung für Expressions-Bäume, und es scheint mir, dass es ideal für diese Art von Ausdrücken ist, die später oder woanders ausgewertet werden.

Aber ich weiß nicht, wie ich sicherstellen kann, dass ich die C # -Syntax verwenden kann (mit +, <<,%, usw.), um den Ausdrucksbaum aufzubauen. Ich möchte Dinge verhindern wie:

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

Wie würdest du das machen?


Hinweis: Ich brauche einen Ausdrucksbaum, um den Ausdruck in eine akzeptable benutzerdefinierte Zeichenfolgendarstellung konvertieren zu können und gleichzeitig in der Lage zu sein, ihn zu einem anderen Zeitpunkt als seiner Definition auszuwerten.


Eine Erklärung meines Beispiels: 64-$+mystring . Der $ ist der aktuelle Offset, also ist es eine bestimmte Zahl, die im Voraus unbekannt ist (aber zur Evaluierungszeit bekannt ist). Der mystring ist ein Symbol, das zur Evaluierungszeit bekannt sein kann oder nicht (z. B. wenn es noch nicht definiert wurde). Das Subtrahieren einer Konstante C von einem Symbol S ist dasselbe wie S + -C . Das Subtrahieren von zwei Symbolen S0 und S1 ( S1 - S0 ) ergibt den ganzzahligen Unterschied zwischen den zwei Symbolwerten.

Bei dieser Frage geht es jedoch nicht wirklich darum, wie man Assemblerausdrücke auswertet, sondern darum, wie man einen Ausdruck bewertet, der benutzerdefinierte Klassen enthält (für Dinge wie die Symbole und $ im Beispiel) und wie man trotzdem sicherstellen kann, dass es hübsch ist mit einem Besucher drucken (also den Baum behalten). Und da das .NET-Framework seine Expression-Bäume und Besucher hat, wäre es schön, wenn möglich diese zu verwenden.

Akzeptierte Antwort

Ich weiß nicht, was genau Sie anstreben, aber das Folgende ist ein skizzenhafter Ansatz, der meiner Meinung nach funktionieren würde.

Hinweis ich

  1. demonstrieren Sie nur indizierte Referenzausdrücke (ignorieren Sie daher die indirekte Adressierung über Register für jetzt; Sie könnten eine RegisterInderectReference analog zur SymbolicReference-Klasse hinzufügen). Dies gilt auch für die von Ihnen empfohlene $ (current offset) -Funktion. Es wäre wahrscheinlich sicher ein Register (?)
  2. zeigt den unären / binären operator- bei der Arbeit nicht explizit an. Die Mechanik ist jedoch weitgehend gleich. Ich hörte kurz auf, es hinzuzufügen, weil ich die Semantik der Beispielausdrücke in Ihrer Frage nicht ausarbeiten konnte
    (Ich denke, dass das Subtrahieren der Adresse einer bekannten Zeichenfolge zum Beispiel nicht sinnvoll ist)
  3. Der Ansatz legt keine (semantischen) Grenzen fest: Sie können jeden von ReferenceBase abgeleiteten IReference kompensieren. In der Praxis möchten Sie möglicherweise nur eine Indizierungsebene zulassen, und es wäre angemessener, den operator+ direkt auf SymbolicReference zu definieren.
  4. (Im Allgemeinen hat Codierstil für Demozwecke geopfert, werden Sie nicht wollen , um wiederholt Compile() Ihre Ausdrucksbäume und direkte Auswertung mit .Compile()() sieht hässlich und verwirrend. Es ist links nach OP bis es integriert in eine lesbarere Mode

  5. Die Demonstration des expliziten Konvertierungsoperators ist wirklich nicht Thema. Ich wurde leicht weggetragen (?)

  6. Sie können den Code live auf IdeOne.com beobachten

.

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));
        }
    }
}

Beliebte Antwort

C # unterstützt die Zuordnung eines Lambda-Ausdrucks zu einem Expression<TDelegate> , wodurch der Compiler Code Expression<TDelegate> , um einen Ausdrucksbaum zu erstellen, der den Lambda-Ausdruck darstellt, den Sie dann bearbeiten können. Z.B:

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

Sie könnten dann möglicherweise die generierte Ausdrucksbaumstruktur übernehmen und sie in den Syntaxbaum Ihres Assemblers konvertieren, aber das scheint nicht ganz das zu sein, wonach Sie suchen, und ich glaube nicht, dass Sie das nutzen können C # -Compiler, um dies für beliebige Eingabe zu tun.

Wahrscheinlich werden Sie am Ende einen eigenen Parser für Ihre Assemblersprache erstellen müssen, da ich nicht glaube, dass der C # -Compiler in diesem Fall das tun wird, was Sie wollen.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow